Implement Plan button

This commit is contained in:
Colin McDonnell
2025-12-30 20:14:49 -08:00
parent 573c473dc1
commit ad1f51d704
5 changed files with 138 additions and 15 deletions
+51 -7
View File
@@ -98415,7 +98415,11 @@ var addTools = (ctx, server, tools) => {
// mcp/comment.ts
var LEAPING_INTO_ACTION_PREFIX = "Leaping into action";
async function buildCommentFooter(payload, octokit) {
async function buildCommentFooter({
payload,
octokit,
customParts
}) {
const repoContext = parseRepoContext();
const runId = process.env.GITHUB_RUN_ID;
const agentName = payload.agent;
@@ -98432,7 +98436,7 @@ async function buildCommentFooter(payload, octokit) {
} catch {
}
}
return buildPullfrogFooter({
const footerParams = {
triggeredBy: true,
agent: {
displayName: agentInfo?.displayName || "Unknown agent",
@@ -98444,11 +98448,19 @@ async function buildCommentFooter(payload, octokit) {
runId,
...workflowRunHtmlUrl ? { htmlUrl: workflowRunHtmlUrl } : {}
} : void 0
});
};
if (customParts && customParts.length > 0) {
return buildPullfrogFooter({ ...footerParams, customParts });
}
return buildPullfrogFooter(footerParams);
}
function buildImplementPlanLink(owner, repo, issueNumber, commentId) {
const apiUrl = process.env.API_URL || "https://pullfrog.com";
return `[Implement plan \u2794](${apiUrl}/trigger/${owner}/${repo}/${issueNumber}?action=implement&comment_id=${commentId})`;
}
async function addFooter(body, payload, octokit) {
const bodyWithoutFooter = stripExistingFooter(body);
const footer = await buildCommentFooter(payload, octokit);
const footer = await buildCommentFooter({ payload, octokit });
return `${bodyWithoutFooter}${footer}`;
}
var Comment = type({
@@ -98532,9 +98544,18 @@ var ReportProgress = type({
body: type.string.describe("the progress update content to share")
});
async function reportProgress(ctx, { body }) {
const bodyWithFooter = await addFooter(body, ctx.payload, ctx.octokit);
const existingCommentId = getProgressCommentId();
const issueNumber = ctx.toolState.prNumber ?? ctx.toolState.issueNumber ?? ctx.payload.event.issue_number;
const isPlanMode = ctx.toolState.selectedMode === "Plan";
if (existingCommentId) {
const customParts = isPlanMode && issueNumber !== void 0 ? [buildImplementPlanLink(ctx.owner, ctx.name, issueNumber, existingCommentId)] : void 0;
const bodyWithoutFooter = stripExistingFooter(body);
const footer = await buildCommentFooter({
payload: ctx.payload,
octokit: ctx.octokit,
customParts
});
const bodyWithFooter = `${bodyWithoutFooter}${footer}`;
const result2 = await ctx.octokit.rest.issues.updateComment({
owner: ctx.owner,
repo: ctx.name,
@@ -98549,18 +98570,40 @@ async function reportProgress(ctx, { body }) {
action: "updated"
};
}
const issueNumber = ctx.toolState.prNumber ?? ctx.toolState.issueNumber ?? ctx.payload.event.issue_number;
if (issueNumber === void 0) {
return void 0;
}
const initialBody = await addFooter(body, ctx.payload, ctx.octokit);
const result = await ctx.octokit.rest.issues.createComment({
owner: ctx.owner,
repo: ctx.name,
issue_number: issueNumber,
body: bodyWithFooter
body: initialBody
});
setProgressCommentId(result.data.id);
progressCommentWasUpdated = true;
if (isPlanMode) {
const customParts = [buildImplementPlanLink(ctx.owner, ctx.name, issueNumber, result.data.id)];
const bodyWithoutFooter = stripExistingFooter(body);
const footer = await buildCommentFooter({
payload: ctx.payload,
octokit: ctx.octokit,
customParts
});
const bodyWithPlanLink = `${bodyWithoutFooter}${footer}`;
const updateResult = await ctx.octokit.rest.issues.updateComment({
owner: ctx.owner,
repo: ctx.name,
comment_id: result.data.id,
body: bodyWithPlanLink
});
return {
commentId: updateResult.data.id,
url: updateResult.data.html_url,
body: updateResult.data.body || "",
action: "created"
};
}
return {
commentId: result.data.id,
url: result.data.html_url,
@@ -124718,6 +124761,7 @@ function SelectModeTool(ctx) {
availableModes: ctx.modes.map((m) => ({ name: m.name, description: m.description }))
};
}
ctx.toolState.selectedMode = selectedMode.name;
return {
modeName: selectedMode.name,
description: selectedMode.description,
+8
View File
@@ -212,6 +212,13 @@ interface FixReviewEvent extends BasePayloadEvent {
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";
}
@@ -231,6 +238,7 @@ export type PayloadEvent =
| CheckSuiteCompletedEvent
| WorkflowDispatchEvent
| FixReviewEvent
| ImplementPlanEvent
| UnknownEvent;
export interface DispatchOptions {
+1
View File
@@ -337,6 +337,7 @@ export interface DependencyInstallationState {
export interface ToolState {
prNumber?: number;
issueNumber?: number;
selectedMode?: string;
review?: {
id: number; // REST API database ID (for fix URLs)
nodeId: string; // GraphQL node ID (for mutations)
+75 -8
View File
@@ -15,7 +15,17 @@ import { execute, tool } from "./shared.ts";
*/
export const LEAPING_INTO_ACTION_PREFIX = "Leaping into action";
async function buildCommentFooter(payload: Payload, octokit?: Octokit): Promise<string> {
interface BuildCommentFooterParams {
payload: Payload;
octokit?: Octokit | undefined;
customParts?: string[] | undefined;
}
async function buildCommentFooter({
payload,
octokit,
customParts,
}: BuildCommentFooterParams): Promise<string> {
const repoContext = parseRepoContext();
const runId = process.env.GITHUB_RUN_ID;
@@ -38,7 +48,7 @@ async function buildCommentFooter(payload: Payload, octokit?: Octokit): Promise<
}
}
return buildPullfrogFooter({
const footerParams = {
triggeredBy: true,
agent: {
displayName: agentInfo?.displayName || "Unknown agent",
@@ -52,12 +62,27 @@ async function buildCommentFooter(payload: Payload, octokit?: Octokit): Promise<
...(workflowRunHtmlUrl ? { htmlUrl: workflowRunHtmlUrl } : {}),
}
: undefined,
});
};
if (customParts && customParts.length > 0) {
return buildPullfrogFooter({ ...footerParams, customParts });
}
return buildPullfrogFooter(footerParams);
}
function buildImplementPlanLink(
owner: string,
repo: string,
issueNumber: number,
commentId: number
): string {
const apiUrl = process.env.API_URL || "https://pullfrog.com";
return `[Implement plan ➔](${apiUrl}/trigger/${owner}/${repo}/${issueNumber}?action=implement&comment_id=${commentId})`;
}
async function addFooter(body: string, payload: Payload, octokit?: Octokit): Promise<string> {
const bodyWithoutFooter = stripExistingFooter(body);
const footer = await buildCommentFooter(payload, octokit);
const footer = await buildCommentFooter({ payload, octokit });
return `${bodyWithoutFooter}${footer}`;
}
@@ -181,11 +206,26 @@ export async function reportProgress(
}
| undefined
> {
const bodyWithFooter = await addFooter(body, ctx.payload, ctx.octokit);
const existingCommentId = getProgressCommentId();
const issueNumber =
ctx.toolState.prNumber ?? ctx.toolState.issueNumber ?? ctx.payload.event.issue_number;
const isPlanMode = ctx.toolState.selectedMode === "Plan";
// if we already have a progress comment, update it
if (existingCommentId) {
const customParts =
isPlanMode && issueNumber !== undefined
? [buildImplementPlanLink(ctx.owner, ctx.name, issueNumber, existingCommentId)]
: undefined;
const bodyWithoutFooter = stripExistingFooter(body);
const footer = await buildCommentFooter({
payload: ctx.payload,
octokit: ctx.octokit,
customParts,
});
const bodyWithFooter = `${bodyWithoutFooter}${footer}`;
const result = await ctx.octokit.rest.issues.updateComment({
owner: ctx.owner,
repo: ctx.name,
@@ -205,24 +245,51 @@ export async function reportProgress(
// no existing comment - create one
// use fallback chain: dynamically set context > event payload
const issueNumber =
ctx.toolState.prNumber ?? ctx.toolState.issueNumber ?? ctx.payload.event.issue_number;
if (issueNumber === undefined) {
// cannot create comment without issue_number (e.g., workflow_dispatch events)
return undefined;
}
// for new comments, we need to create first, then update with Plan link if in Plan mode
const initialBody = await addFooter(body, ctx.payload, ctx.octokit);
const result = await ctx.octokit.rest.issues.createComment({
owner: ctx.owner,
repo: ctx.name,
issue_number: issueNumber,
body: bodyWithFooter,
body: initialBody,
});
// store the comment ID for future updates
setProgressCommentId(result.data.id);
progressCommentWasUpdated = true;
// if Plan mode, update the comment to add the "Implement plan" link
if (isPlanMode) {
const customParts = [buildImplementPlanLink(ctx.owner, ctx.name, issueNumber, result.data.id)];
const bodyWithoutFooter = stripExistingFooter(body);
const footer = await buildCommentFooter({
payload: ctx.payload,
octokit: ctx.octokit,
customParts,
});
const bodyWithPlanLink = `${bodyWithoutFooter}${footer}`;
const updateResult = await ctx.octokit.rest.issues.updateComment({
owner: ctx.owner,
repo: ctx.name,
comment_id: result.data.id,
body: bodyWithPlanLink,
});
return {
commentId: updateResult.data.id,
url: updateResult.data.html_url,
body: updateResult.data.body || "",
action: "created",
};
}
return {
commentId: result.data.id,
url: result.data.html_url,
+3
View File
@@ -25,6 +25,9 @@ export function SelectModeTool(ctx: ToolContext) {
};
}
// store selected mode in toolState for use by other tools (e.g., report_progress)
ctx.toolState.selectedMode = selectedMode.name;
return {
modeName: selectedMode.name,
description: selectedMode.description,