97dce099c1
* Granular tool permissions * Fix build * Start on UI * Fixes * Fmt * Go ham on UI * Update migrations * Considate wiki files * Clean up * More tweaks. Docs. * Consolidate collab and noncollab * Fix build * Restrict for non-collaborators
316 lines
8.4 KiB
TypeScript
316 lines
8.4 KiB
TypeScript
/**
|
|
* ⚠️ NO IMPORTS except modes.ts - this file is imported by Next.js and must avoid pulling in backend code.
|
|
* All shared constants, types, and data used by both the Next.js app and the action runtime live here.
|
|
* Other files in action/ re-export from this file for backward compatibility.
|
|
*/
|
|
|
|
import { type } from "arktype";
|
|
import type { Mode } from "./modes.ts";
|
|
|
|
// mcp name constant
|
|
export const ghPullfrogMcpName = "gh_pullfrog";
|
|
|
|
export interface AgentManifest {
|
|
displayName: string;
|
|
apiKeyNames: string[];
|
|
url: string;
|
|
}
|
|
|
|
// agent manifest - static metadata about available agents
|
|
export const agentsManifest = {
|
|
claude: {
|
|
displayName: "Claude Code",
|
|
apiKeyNames: ["ANTHROPIC_API_KEY"],
|
|
url: "https://claude.com/claude-code",
|
|
},
|
|
codex: {
|
|
displayName: "Codex CLI",
|
|
apiKeyNames: ["OPENAI_API_KEY"],
|
|
url: "https://platform.openai.com/docs/guides/codex",
|
|
},
|
|
cursor: {
|
|
displayName: "Cursor CLI",
|
|
apiKeyNames: ["CURSOR_API_KEY"],
|
|
url: "https://cursor.com/",
|
|
},
|
|
gemini: {
|
|
displayName: "Gemini CLI",
|
|
apiKeyNames: ["GOOGLE_API_KEY", "GEMINI_API_KEY"],
|
|
url: "https://ai.google.dev/gemini-api/docs",
|
|
},
|
|
opencode: {
|
|
displayName: "OpenCode",
|
|
apiKeyNames: [], // empty array means OpenCode accepts any API_KEY from environment
|
|
url: "https://opencode.ai",
|
|
},
|
|
} as const satisfies Record<string, AgentManifest>;
|
|
|
|
// agent name type - union of agent slugs
|
|
export type AgentName = keyof typeof agentsManifest;
|
|
export const AgentName = type.enumerated(...Object.keys(agentsManifest));
|
|
|
|
export type AgentApiKeyName = (typeof agentsManifest)[AgentName]["apiKeyNames"][number];
|
|
|
|
// effort level type - controls model selection and thinking level
|
|
// mini = fast/minimal, auto = balanced/default, max = maximum capability
|
|
export const Effort = type.enumerated("mini", "auto", "max");
|
|
export type Effort = typeof Effort.infer;
|
|
|
|
// tool permission types shared with server dispatch
|
|
export type ToolPermission = "disabled" | "enabled";
|
|
export type BashPermission = "disabled" | "restricted" | "enabled";
|
|
|
|
// permission level for the author who triggered the event
|
|
// matches GitHub's permission levels: admin > write > maintain > triage > read > none
|
|
export type AuthorPermission = "admin" | "maintain" | "write" | "triage" | "read" | "none";
|
|
|
|
// base interface for common payload event fields
|
|
interface BasePayloadEvent {
|
|
issue_number?: number;
|
|
is_pr?: boolean;
|
|
branch?: string;
|
|
pr_title?: string;
|
|
pr_body?: string | null;
|
|
issue_title?: string;
|
|
issue_body?: string | null;
|
|
comment_id?: number;
|
|
comment_body?: string;
|
|
review_id?: number;
|
|
review_body?: string | null;
|
|
review_state?: string;
|
|
review_comments?: any[];
|
|
context?: any;
|
|
thread?: any;
|
|
pull_request?: any;
|
|
check_suite?: {
|
|
id: number;
|
|
head_sha: string;
|
|
head_branch: string | null;
|
|
status: string | null;
|
|
conclusion: string | null;
|
|
url: string;
|
|
};
|
|
comment_ids?: number[] | "all";
|
|
/** permission level of the user who triggered this event */
|
|
authorPermission?: AuthorPermission;
|
|
[key: string]: any;
|
|
}
|
|
|
|
interface PullRequestOpenedEvent extends BasePayloadEvent {
|
|
trigger: "pull_request_opened";
|
|
issue_number: number;
|
|
is_pr: true;
|
|
pr_title: string;
|
|
pr_body: string | null;
|
|
branch: string;
|
|
}
|
|
|
|
interface PullRequestReadyForReviewEvent extends BasePayloadEvent {
|
|
trigger: "pull_request_ready_for_review";
|
|
issue_number: number;
|
|
is_pr: true;
|
|
pr_title: string;
|
|
pr_body: string | null;
|
|
branch: string;
|
|
}
|
|
|
|
interface PullRequestReviewRequestedEvent extends BasePayloadEvent {
|
|
trigger: "pull_request_review_requested";
|
|
issue_number: number;
|
|
is_pr: true;
|
|
pr_title: string;
|
|
pr_body: string | null;
|
|
branch: string;
|
|
}
|
|
|
|
interface PullRequestReviewSubmittedEvent extends BasePayloadEvent {
|
|
trigger: "pull_request_review_submitted";
|
|
issue_number: number;
|
|
is_pr: true;
|
|
review_id: number;
|
|
review_body: string | null;
|
|
review_state: string;
|
|
review_comments: any[];
|
|
context: any;
|
|
branch: string;
|
|
}
|
|
|
|
interface PullRequestReviewCommentCreatedEvent extends BasePayloadEvent {
|
|
trigger: "pull_request_review_comment_created";
|
|
issue_number: number;
|
|
is_pr: true;
|
|
pr_title: string;
|
|
comment_id: number;
|
|
comment_body: string;
|
|
thread?: any;
|
|
branch: string;
|
|
}
|
|
|
|
interface IssuesOpenedEvent extends BasePayloadEvent {
|
|
trigger: "issues_opened";
|
|
issue_number: number;
|
|
issue_title: string;
|
|
issue_body: string | null;
|
|
}
|
|
|
|
interface IssuesAssignedEvent extends BasePayloadEvent {
|
|
trigger: "issues_assigned";
|
|
issue_number: number;
|
|
issue_title: string;
|
|
issue_body: string | null;
|
|
}
|
|
|
|
interface IssuesLabeledEvent extends BasePayloadEvent {
|
|
trigger: "issues_labeled";
|
|
issue_number: number;
|
|
issue_title: string;
|
|
issue_body: string | null;
|
|
}
|
|
|
|
interface IssueCommentCreatedEvent extends BasePayloadEvent {
|
|
trigger: "issue_comment_created";
|
|
comment_id: number;
|
|
comment_body: string;
|
|
issue_number: number;
|
|
// PR-specific fields (only present when is_pr is true)
|
|
is_pr?: true;
|
|
branch?: string;
|
|
pr_title?: string;
|
|
pr_body?: string | null;
|
|
}
|
|
|
|
interface CheckSuiteCompletedEvent extends BasePayloadEvent {
|
|
trigger: "check_suite_completed";
|
|
issue_number: number;
|
|
is_pr: true;
|
|
pr_title: string;
|
|
pr_body: string | null;
|
|
pull_request: any;
|
|
branch: string;
|
|
check_suite: {
|
|
id: number;
|
|
head_sha: string;
|
|
head_branch: string | null;
|
|
status: string | null;
|
|
conclusion: string | null;
|
|
url: string;
|
|
};
|
|
}
|
|
|
|
interface WorkflowDispatchEvent extends BasePayloadEvent {
|
|
trigger: "workflow_dispatch";
|
|
}
|
|
|
|
/** simplified review comment data for payload */
|
|
export interface ReviewCommentData {
|
|
id: number;
|
|
body: string;
|
|
path: string;
|
|
line: number | null;
|
|
user: string | null;
|
|
html_url: string;
|
|
in_reply_to_id: number | null;
|
|
}
|
|
|
|
interface FixReviewEvent extends BasePayloadEvent {
|
|
trigger: "fix_review";
|
|
issue_number: number;
|
|
is_pr: true;
|
|
review_id: number;
|
|
/** username of the person who triggered this action */
|
|
triggerer: string;
|
|
/** "all" to fix all comments, or specific comment IDs to fix */
|
|
comment_ids: number[] | "all";
|
|
/** comments the triggerer approved (via thumbs up) - these should be addressed */
|
|
approved_comments: ReviewCommentData[];
|
|
/** other comments in the review - for context only, do not address unless asked */
|
|
unapproved_comments: ReviewCommentData[];
|
|
}
|
|
|
|
interface ImplementPlanEvent extends BasePayloadEvent {
|
|
trigger: "implement_plan";
|
|
issue_number: number;
|
|
plan_comment_id: number;
|
|
plan_content: string;
|
|
}
|
|
|
|
interface UnknownEvent extends BasePayloadEvent {
|
|
trigger: "unknown";
|
|
}
|
|
|
|
// discriminated union for payload event based on trigger
|
|
// note: all events use issue_number for consistency (PRs are issues in GitHub's API)
|
|
export type PayloadEvent =
|
|
| PullRequestOpenedEvent
|
|
| PullRequestReadyForReviewEvent
|
|
| PullRequestReviewRequestedEvent
|
|
| PullRequestReviewSubmittedEvent
|
|
| PullRequestReviewCommentCreatedEvent
|
|
| IssuesOpenedEvent
|
|
| IssuesAssignedEvent
|
|
| IssuesLabeledEvent
|
|
| IssueCommentCreatedEvent
|
|
| CheckSuiteCompletedEvent
|
|
| WorkflowDispatchEvent
|
|
| FixReviewEvent
|
|
| ImplementPlanEvent
|
|
| UnknownEvent;
|
|
|
|
export interface DispatchOptions {
|
|
/**
|
|
* When true, disables progress comment (no "leaping into action" comment, no report_progress tool)
|
|
*/
|
|
readonly disableProgressComment?: true;
|
|
|
|
/**
|
|
* Granular tool permissions set server-side for dispatch workflows.
|
|
*/
|
|
readonly web?: ToolPermission;
|
|
readonly search?: ToolPermission;
|
|
readonly write?: ToolPermission;
|
|
readonly bash?: BashPermission;
|
|
}
|
|
|
|
export type MutableDispatchOptions = {
|
|
-readonly [K in keyof DispatchOptions]: DispatchOptions[K];
|
|
};
|
|
|
|
// payload type for agent execution
|
|
export interface Payload extends DispatchOptions {
|
|
"~pullfrog": true;
|
|
|
|
/**
|
|
* Agent slug identifier (e.g., "claude", "codex", "gemini")
|
|
*/
|
|
readonly agent: AgentName | null;
|
|
|
|
/**
|
|
* The prompt/instructions for the agent to execute
|
|
*/
|
|
readonly prompt: string;
|
|
|
|
/**
|
|
* Event data from webhook payload.
|
|
* Discriminated union based on trigger field.
|
|
*/
|
|
readonly event: PayloadEvent;
|
|
|
|
/**
|
|
* Execution mode configuration
|
|
*/
|
|
modes: readonly Mode[];
|
|
|
|
/**
|
|
* Effort level for model selection (mini, auto, max)
|
|
* Defaults to "auto" if not specified
|
|
*/
|
|
readonly effort?: Effort;
|
|
|
|
/**
|
|
* Optional IDs of the issue, PR, or comment that the agent is working on
|
|
*/
|
|
readonly comment_id?: number | null;
|
|
readonly issue_id?: number | null;
|
|
readonly pr_id?: number | null;
|
|
}
|