From 9c99bcbbac7a7877e0390b7b3f91e28379bf2a5f Mon Sep 17 00:00:00 2001 From: Anna Bocharova Date: Tue, 10 Mar 2026 20:34:54 +0000 Subject: [PATCH] feat: Improving the plan revisions (#465) * feat(plans): Suggested plan for plan revisions. * fix: add planCommentId to reduce GitHub API calls. * Revert "fix: add planCommentId to reduce GitHub API calls." This reverts commit ef9c24811fa291b12ac3601cc4cd3edb7c9a0fca. * Improving plan revision: the implementation draft. * fix schema composition order. * fix: reusing existing retry helper (action) for reportPlanCommentToRun. * mv: updatePlanCommentId. * fix: higher severity for logging error. * fix: add error handling when calling findExistingPlanCommentIdForIssue. * feat: improving the revisit plan request detection by adding PLAN_REVISION_VERBS. * Updating the plan with alternative non-determenistic solution. * add more verbs to PLAN_REVISION_VERBS. * fix: supply the previous plan in the event context as previousPlanBody, updating Plan mode instructions. * fix: adjusting the way PLAN_REVISION_VERBS are used in sentences. * fix: using GraphQL approach with NodeId to find commentId in findExistingPlanCommentIdForIssue. * fix: use double word boundaries (both sides). * fix condition in findExistingPlanCommentIdForIssue. * fix: rm unused args from findExistingPlanCommentIdForIssue. * bump the action version. * fix(plan): rm everything related to approach A. * fix(plan): No limit for progress comments. * feat(plan): the new plan. * Revert: changed to webhook (no longer involved). * feat: mv plan comment lookup into a new API endpoint. * Revert: changes to select_mode tool. * FEAT: The new implementation. * fix arktype issues. * fix plan diagram. * fix(selectMode): e2e type constraints for fetchExistingPlanComment. * Revert "fix(selectMode): e2e type constraints for fetchExistingPlanComment." This reverts commit 53f3b6650a9928e3080700faa9eead0052e94333. * fix(selectMode): type constraints (copy) for fetchExistingPlanComment. * feat: improving isHttpError helper and reusing it consistently instead of casting. * address review: remove unconditional retry, add plan comment warning, dedupe type, remove dead guard, fix GraphQL types * Fix: tightening the PlanEdit guidance. * fix(select_mode): Providing the agent with existingPlanCommentId as well. * fix(instructions): Adjusting the primary guidance to prefer Plan mode for issue-related ambiguous requests. * fix(wiki): updating the delegation docs according to the current instructions. * fix(instructions): rm implication to call for issue details. * fix(select_mode): tweak for PlanEdit. * fix(select_mode): more tweaks to PlanEdit. * fix(select_mode): tweaks for the order of instructions and context. * revert: to the state of 5c400efce1f1fec0a0855eeacad2bc3b721fd1bf. * fix(select_mode): Correcting the guideline. * rm the plan from the branch (impletemented). --------- Co-authored-by: pullfrog[bot] <226033991+pullfrog[bot]@users.noreply.github.com> --- entry | 151 ++++++++++++++++++++++++++++++++++++++++++---- mcp/comment.ts | 114 +++++++++++++++++++++++++++++++--- mcp/selectMode.ts | 68 +++++++++++++++++++-- mcp/server.ts | 3 + package.json | 2 +- post | 12 +++- 6 files changed, 319 insertions(+), 31 deletions(-) diff --git a/entry b/entry index fe82eb7..b812e8a 100755 --- a/entry +++ b/entry @@ -145074,6 +145074,33 @@ function stripExistingFooter(body) { } // mcp/comment.ts +async function updatePlanCommentId(ctx, planCommentNodeId) { + if (ctx.runId === void 0 || !ctx.apiToken) return; + try { + await retry( + async () => { + const response = await apiFetch({ + path: `/api/workflow-run/${ctx.runId}`, + method: "PATCH", + headers: { + authorization: `Bearer ${ctx.apiToken}`, + "content-type": "application/json" + }, + body: JSON.stringify({ planCommentNodeId }), + signal: AbortSignal.timeout(1e4) + }); + if (!response.ok) throw new Error(`PATCH workflow-run: ${response.status}`); + }, + { + maxAttempts: 3, + delayMs: 2e3, + label: "updatePlanCommentId" + } + ); + } catch (error49) { + log.warning(`updatePlanCommentId exhausted retries: ${error49}`); + } +} async function buildCommentFooter({ agent: agent2, octokit, @@ -145117,14 +145144,17 @@ async function addFooter(ctx, body) { } var Comment = type({ issueNumber: type.number.describe("the issue number to comment on"), - body: type.string.describe("the comment body content") + body: type.string.describe("the comment body content"), + type: type.enumerated("Plan", "Comment").describe( + "Plan: record this comment as the plan for this run (use report_progress for progress/plan updates on the current run). Comment: regular comment (default)." + ).optional() }); function CreateCommentTool(ctx) { return tool({ name: "create_issue_comment", - description: "Create a comment on a GitHub issue. NOTE: Do NOT use this for progress updates or status summaries - use report_progress instead, which updates the existing progress comment.", + description: "Create a comment on a GitHub issue. For progress/plan updates on the current run use report_progress instead. Use type: 'Plan' only when creating a standalone plan comment to record as this run's plan.", parameters: Comment, - execute: execute(async ({ issueNumber, body }) => { + execute: execute(async ({ issueNumber, body, type: commentType }) => { const bodyWithFooter = await addFooter(ctx, body); const result = await ctx.octokit.rest.issues.createComment({ owner: ctx.repo.owner, @@ -145132,6 +145162,9 @@ function CreateCommentTool(ctx) { issue_number: issueNumber, body: bodyWithFooter }); + if (commentType === "Plan" && result.data.node_id) { + await updatePlanCommentId(ctx, result.data.node_id); + } return { success: true, commentId: result.data.id, @@ -145169,16 +145202,50 @@ function EditCommentTool(ctx) { }); } var ReportProgress = type({ - body: type.string.describe("the progress update content to share") + body: type.string.describe("the progress update content to share"), + "target_plan_comment?": type("boolean").describe( + "when true, update the existing plan comment (from select_mode lookup) instead of the progress comment; use when editing an existing plan" + ) }); -async function reportProgress(ctx, { body }) { +async function reportProgress(ctx, params) { + const { body, target_plan_comment } = params; ctx.toolState.lastProgressBody = body; if (ctx.payload.event.silent) { return { body, action: "skipped" }; } - const existingCommentId = ctx.toolState.progressCommentId; const issueNumber = ctx.payload.event.issue_number ?? ctx.toolState.issueNumber; const isPlanMode = ctx.toolState.selectedMode === "Plan"; + if (target_plan_comment === true && ctx.toolState.existingPlanCommentId === void 0) { + log.warning("target_plan_comment requested but no existingPlanCommentId in tool state"); + } + if (target_plan_comment === true && ctx.toolState.existingPlanCommentId !== void 0) { + const commentId = ctx.toolState.existingPlanCommentId; + const customParts = isPlanMode && issueNumber !== void 0 ? [buildImplementPlanLink(ctx.repo.owner, ctx.repo.name, issueNumber, commentId)] : void 0; + const bodyWithoutFooter = stripExistingFooter(body); + const footer = await buildCommentFooter({ + agent: ctx.agent, + octokit: ctx.octokit, + customParts + }); + const bodyWithFooter = `${bodyWithoutFooter}${footer}`; + const result2 = await ctx.octokit.rest.issues.updateComment({ + owner: ctx.repo.owner, + repo: ctx.repo.name, + comment_id: commentId, + body: bodyWithFooter + }); + ctx.toolState.wasUpdated = true; + if (isPlanMode && result2.data.node_id) { + await updatePlanCommentId(ctx, result2.data.node_id); + } + return { + commentId: result2.data.id, + url: result2.data.html_url, + body: result2.data.body || "", + action: "updated" + }; + } + const existingCommentId = ctx.toolState.progressCommentId; if (existingCommentId) { const customParts = isPlanMode && issueNumber !== void 0 ? [buildImplementPlanLink(ctx.repo.owner, ctx.repo.name, issueNumber, existingCommentId)] : void 0; const bodyWithoutFooter = stripExistingFooter(body); @@ -145195,6 +145262,9 @@ async function reportProgress(ctx, { body }) { body: bodyWithFooter }); ctx.toolState.wasUpdated = true; + if (isPlanMode && result2.data.node_id) { + await updatePlanCommentId(ctx, result2.data.node_id); + } return { commentId: result2.data.id, url: result2.data.html_url, @@ -145234,6 +145304,9 @@ async function reportProgress(ctx, { body }) { comment_id: result.data.id, body: bodyWithPlanLink }); + if (updateResult.data.node_id) { + await updatePlanCommentId(ctx, updateResult.data.node_id); + } return { commentId: updateResult.data.id, url: updateResult.data.html_url, @@ -145253,8 +145326,12 @@ function ReportProgressTool(ctx) { name: "report_progress", description: "Share progress on the associated GitHub issue/PR. Call this to post updates as you work. The first call creates a comment, subsequent calls update it. Use this throughout your work to keep stakeholders informed.", parameters: ReportProgress, - execute: execute(async ({ body }) => { - const result = await reportProgress(ctx, { body }); + execute: execute(async (params) => { + const reportParams = { body: params.body }; + if (params.target_plan_comment !== void 0) { + reportParams.target_plan_comment = params.target_plan_comment; + } + const result = await reportProgress(ctx, reportParams); if (result.action === "skipped") { return { success: true, @@ -147693,6 +147770,9 @@ function ResolveReviewThreadTool(ctx) { var SelectModeParams = type({ mode: type.string.describe( "the name of the mode to select (e.g., 'Build', 'Plan', 'Review', 'IncrementalReview', 'Fix', 'AddressReviews', 'Task', 'ResolveConflicts')" + ), + "issue_number?": type("number").describe( + "optional issue number; when provided with Plan mode, used to look up an existing plan comment for this issue (edit vs create)" ) }); function resolveMode(modes2, modeName) { @@ -147845,6 +147925,21 @@ Use max effort for thorough reviews.`, ### Effort +Use mini or auto effort.`, + PlanEdit: `### Checklist (editing existing plan) + +An existing plan comment was found for this issue. Update that comment with the revised plan \u2014 do not create a new plan comment. + +1. Use \`previousPlanBody\` from this response as the plan to revise; do not call \`get_issue\` or \`get_issue_comments\`. +2. When delegating, the subagent prompt must contain: + - the current plan (\`previousPlanBody\`) and the user's revision request + - relevant codebase context (file paths, architecture notes from AGENTS.md) + - instructions to produce a structured plan with clear milestones and to return the full plan via \`${ghPullfrogMcpName}/set_output\` as markdown (do not create plan files or save to disk) +3. After the subagent completes, call \`${ghPullfrogMcpName}/report_progress\` with the full plan text and \`{ target_plan_comment: true }\` so the revised plan updates the existing plan comment (not the progress comment). +4. Then post a short note to the progress comment (e.g. "Plan has been updated in the comment above.") via \`${ghPullfrogMcpName}/report_progress\` so it is not left as "Leaping...". + +### Effort + Use mini or auto effort.`, Fix: `### Checklist @@ -147884,14 +147979,29 @@ Use auto effort.`, - if the task involved labeling, commenting, or other GitHub operations, perform those directly 5. Use mini effort for simple research tasks, auto for typical tasks, max for complex multi-file changes.` }; -function buildOrchestratorGuidance(mode) { - const guidance = modeGuidance[mode.name] ?? ""; +function buildOrchestratorGuidance(mode, overrideGuidance) { + const guidance = overrideGuidance ?? modeGuidance[mode.name] ?? ""; return { modeName: mode.name, description: mode.description, orchestratorGuidance: guidance }; } +async function fetchExistingPlanComment(ctx, issueNumber) { + if (!ctx.apiToken) return null; + try { + const response = await apiFetch({ + path: `/api/repo/${ctx.repo.owner}/${ctx.repo.name}/issue/${issueNumber}/plan-comment`, + method: "GET", + headers: { authorization: `Bearer ${ctx.apiToken}` }, + signal: AbortSignal.timeout(1e4) + }); + const data = await response.json(); + return response.ok && "commentId" in data ? data : null; + } catch { + return null; + } +} function SelectModeTool(ctx) { return tool({ name: "select_mode", @@ -147903,11 +148013,12 @@ function SelectModeTool(ctx) { error: `mode already selected: "${ctx.toolState.selectedMode}". mode selection is final and cannot be changed. complete your current workflow within this mode.` }; } - const selectedMode = resolveMode(ctx.modes, params.mode); + const modeName = params.mode; + const selectedMode = resolveMode(ctx.modes, modeName); if (!selectedMode) { const availableModes = ctx.modes.map((m) => m.name).join(", "); return { - error: `mode "${params.mode}" not found. available modes: ${availableModes}`, + error: `mode "${modeName}" not found. available modes: ${availableModes}`, availableModes: ctx.modes.map((m) => ({ name: m.name, description: m.description @@ -147915,6 +148026,20 @@ function SelectModeTool(ctx) { }; } ctx.toolState.selectedMode = selectedMode.name; + if (selectedMode.name === "Plan") { + const issueNumber = params.issue_number ?? ctx.payload.event.issue_number; + if (issueNumber !== void 0) { + const existing = await fetchExistingPlanComment(ctx, issueNumber); + if (existing !== null) { + ctx.toolState.existingPlanCommentId = existing.commentId; + ctx.toolState.previousPlanBody = existing.body; + return { + ...buildOrchestratorGuidance(selectedMode, modeGuidance.PlanEdit), + previousPlanBody: existing.body + }; + } + } + } return buildOrchestratorGuidance(selectedMode); }) }); @@ -148723,7 +148848,7 @@ import { join as join12 } from "node:path"; // package.json var package_default = { name: "@pullfrog/pullfrog", - version: "0.0.177", + version: "0.0.178", type: "module", files: [ "index.js", diff --git a/mcp/comment.ts b/mcp/comment.ts index c0458a8..eac0fe0 100644 --- a/mcp/comment.ts +++ b/mcp/comment.ts @@ -1,11 +1,43 @@ import { type } from "arktype"; import type { Agent } from "../agents/index.ts"; +import { apiFetch } from "../utils/apiFetch.ts"; import { getApiUrl } from "../utils/apiUrl.ts"; import { buildPullfrogFooter, stripExistingFooter } from "../utils/buildPullfrogFooter.ts"; +import { log } from "../utils/cli.ts"; import { type OctokitWithPlugins, parseRepoContext } from "../utils/github.ts"; +import { retry } from "../utils/retry.ts"; import type { ToolContext } from "./server.ts"; import { execute, tool } from "./shared.ts"; +/** PATCH workflow-run with plan comment node_id so plan revisions can update that comment in place. */ +async function updatePlanCommentId(ctx: ToolContext, planCommentNodeId: string): Promise { + if (ctx.runId === undefined || !ctx.apiToken) return; + try { + await retry( + async () => { + const response = await apiFetch({ + path: `/api/workflow-run/${ctx.runId}`, + method: "PATCH", + headers: { + authorization: `Bearer ${ctx.apiToken}`, + "content-type": "application/json", + }, + body: JSON.stringify({ planCommentNodeId }), + signal: AbortSignal.timeout(10_000), + }); + if (!response.ok) throw new Error(`PATCH workflow-run: ${response.status}`); + }, + { + maxAttempts: 3, + delayMs: 2000, + label: "updatePlanCommentId", + } + ); + } catch (error) { + log.warning(`updatePlanCommentId exhausted retries: ${error}`); + } +} + /** * The prefix text for the initial "leaping into action" comment. * This is used to identify if a comment is still in its initial state @@ -86,15 +118,21 @@ export async function addFooter(ctx: AddFooterCtx, body: string): Promise { + execute: execute(async ({ issueNumber, body, type: commentType }) => { const bodyWithFooter = await addFooter(ctx, body); const result = await ctx.octokit.rest.issues.createComment({ @@ -104,6 +142,10 @@ export function CreateCommentTool(ctx: ToolContext) { body: bodyWithFooter, }); + if (commentType === "Plan" && result.data.node_id) { + await updatePlanCommentId(ctx, result.data.node_id); + } + return { success: true, commentId: result.data.id, @@ -147,6 +189,9 @@ export function EditCommentTool(ctx: ToolContext) { export const ReportProgress = type({ body: type.string.describe("the progress update content to share"), + "target_plan_comment?": type("boolean").describe( + "when true, update the existing plan comment (from select_mode lookup) instead of the progress comment; use when editing an existing plan" + ), }); /** @@ -161,13 +206,14 @@ export const ReportProgress = type({ */ export async function reportProgress( ctx: ToolContext, - { body }: { body: string } + params: { body: string; target_plan_comment?: boolean } ): Promise<{ commentId?: number; url?: string; body: string; action: "created" | "updated" | "skipped"; }> { + const { body, target_plan_comment } = params; // always track the body for job summary ctx.toolState.lastProgressBody = body; @@ -177,14 +223,50 @@ export async function reportProgress( return { body, action: "skipped" }; } - const existingCommentId = ctx.toolState.progressCommentId; - // Explicit issue_number from the payload wins here - // This is especially important for manually triggered workflows from trigger links. - // Without this, the created "Implement plan" would be created for the wrong issue - // if the run happens to research other issues (and thus overwrite the toolState.issueNumber). const issueNumber = ctx.payload.event.issue_number ?? ctx.toolState.issueNumber; const isPlanMode = ctx.toolState.selectedMode === "Plan"; + // when editing existing plan: update the plan comment from tool state (set by select_mode) + if (target_plan_comment === true && ctx.toolState.existingPlanCommentId === undefined) { + log.warning("target_plan_comment requested but no existingPlanCommentId in tool state"); + } + if (target_plan_comment === true && ctx.toolState.existingPlanCommentId !== undefined) { + const commentId = ctx.toolState.existingPlanCommentId; + const customParts = + isPlanMode && issueNumber !== undefined + ? [buildImplementPlanLink(ctx.repo.owner, ctx.repo.name, issueNumber, commentId)] + : undefined; + const bodyWithoutFooter = stripExistingFooter(body); + const footer = await buildCommentFooter({ + agent: ctx.agent, + octokit: ctx.octokit, + customParts, + }); + const bodyWithFooter = `${bodyWithoutFooter}${footer}`; + + const result = await ctx.octokit.rest.issues.updateComment({ + owner: ctx.repo.owner, + repo: ctx.repo.name, + comment_id: commentId, + body: bodyWithFooter, + }); + + ctx.toolState.wasUpdated = true; + + if (isPlanMode && result.data.node_id) { + await updatePlanCommentId(ctx, result.data.node_id); + } + + return { + commentId: result.data.id, + url: result.data.html_url, + body: result.data.body || "", + action: "updated", + }; + } + + const existingCommentId = ctx.toolState.progressCommentId; + // if we already have a progress comment, update it if (existingCommentId) { const customParts = @@ -209,6 +291,10 @@ export async function reportProgress( ctx.toolState.wasUpdated = true; + if (isPlanMode && result.data.node_id) { + await updatePlanCommentId(ctx, result.data.node_id); + } + return { commentId: result.data.id, url: result.data.html_url, @@ -264,6 +350,10 @@ export async function reportProgress( body: bodyWithPlanLink, }); + if (updateResult.data.node_id) { + await updatePlanCommentId(ctx, updateResult.data.node_id); + } + return { commentId: updateResult.data.id, url: updateResult.data.html_url, @@ -286,8 +376,12 @@ export function ReportProgressTool(ctx: ToolContext) { description: "Share progress on the associated GitHub issue/PR. Call this to post updates as you work. The first call creates a comment, subsequent calls update it. Use this throughout your work to keep stakeholders informed.", parameters: ReportProgress, - execute: execute(async ({ body }) => { - const result = await reportProgress(ctx, { body }); + execute: execute(async (params) => { + const reportParams: { body: string; target_plan_comment?: boolean } = { body: params.body }; + if (params.target_plan_comment !== undefined) { + reportParams.target_plan_comment = params.target_plan_comment; + } + const result = await reportProgress(ctx, reportParams); if (result.action === "skipped") { // no-op: no comment target, but progress is still tracked for job summary diff --git a/mcp/selectMode.ts b/mcp/selectMode.ts index 89e71ec..28ee91b 100644 --- a/mcp/selectMode.ts +++ b/mcp/selectMode.ts @@ -1,6 +1,7 @@ import { type } from "arktype"; import { ghPullfrogMcpName } from "../external.ts"; import type { Mode } from "../modes.ts"; +import { apiFetch } from "../utils/apiFetch.ts"; import type { ToolContext } from "./server.ts"; import { execute, tool } from "./shared.ts"; @@ -8,6 +9,9 @@ export const SelectModeParams = type({ mode: type.string.describe( "the name of the mode to select (e.g., 'Build', 'Plan', 'Review', 'IncrementalReview', 'Fix', 'AddressReviews', 'Task', 'ResolveConflicts')" ), + "issue_number?": type("number").describe( + "optional issue number; when provided with Plan mode, used to look up an existing plan comment for this issue (edit vs create)" + ), }); function resolveMode(modes: Mode[], modeName: string): Mode | null { @@ -166,6 +170,22 @@ Use max effort for thorough reviews.`, ### Effort +Use mini or auto effort.`, + + PlanEdit: `### Checklist (editing existing plan) + +An existing plan comment was found for this issue. Update that comment with the revised plan — do not create a new plan comment. + +1. Use \`previousPlanBody\` from this response as the plan to revise; do not call \`get_issue\` or \`get_issue_comments\`. +2. When delegating, the subagent prompt must contain: + - the current plan (\`previousPlanBody\`) and the user's revision request + - relevant codebase context (file paths, architecture notes from AGENTS.md) + - instructions to produce a structured plan with clear milestones and to return the full plan via \`${ghPullfrogMcpName}/set_output\` as markdown (do not create plan files or save to disk) +3. After the subagent completes, call \`${ghPullfrogMcpName}/report_progress\` with the full plan text and \`{ target_plan_comment: true }\` so the revised plan updates the existing plan comment (not the progress comment). +4. Then post a short note to the progress comment (e.g. "Plan has been updated in the comment above.") via \`${ghPullfrogMcpName}/report_progress\` so it is not left as "Leaping...". + +### Effort + Use mini or auto effort.`, Fix: `### Checklist @@ -214,8 +234,8 @@ type OrchestratorGuidance = { orchestratorGuidance: string; }; -function buildOrchestratorGuidance(mode: Mode): OrchestratorGuidance { - const guidance = modeGuidance[mode.name] ?? ""; +function buildOrchestratorGuidance(mode: Mode, overrideGuidance?: string): OrchestratorGuidance { + const guidance = overrideGuidance ?? modeGuidance[mode.name] ?? ""; return { modeName: mode.name, description: mode.description, @@ -223,6 +243,28 @@ function buildOrchestratorGuidance(mode: Mode): OrchestratorGuidance { }; } +// matches the API response for /repo/[owner]/[repo]/issue/[issueNumber]/plan-comment +export type PlanCommentResponsePayload = { error: string } | { commentId: number; body: string }; + +async function fetchExistingPlanComment( + ctx: ToolContext, + issueNumber: number +): Promise | null> { + if (!ctx.apiToken) return null; + try { + const response = await apiFetch({ + path: `/api/repo/${ctx.repo.owner}/${ctx.repo.name}/issue/${issueNumber}/plan-comment`, + method: "GET", + headers: { authorization: `Bearer ${ctx.apiToken}` }, + signal: AbortSignal.timeout(10_000), + }); + const data = (await response.json()) as PlanCommentResponsePayload; + return response.ok && "commentId" in data ? data : null; + } catch { + return null; + } +} + export function SelectModeTool(ctx: ToolContext) { return tool({ name: "select_mode", @@ -236,12 +278,14 @@ export function SelectModeTool(ctx: ToolContext) { }; } - const selectedMode = resolveMode(ctx.modes, params.mode); + const modeName = params.mode; + + const selectedMode = resolveMode(ctx.modes, modeName); if (!selectedMode) { const availableModes = ctx.modes.map((m) => m.name).join(", "); return { - error: `mode "${params.mode}" not found. available modes: ${availableModes}`, + error: `mode "${modeName}" not found. available modes: ${availableModes}`, availableModes: ctx.modes.map((m) => ({ name: m.name, description: m.description, @@ -250,6 +294,22 @@ export function SelectModeTool(ctx: ToolContext) { } ctx.toolState.selectedMode = selectedMode.name; + + if (selectedMode.name === "Plan") { + const issueNumber = params.issue_number ?? ctx.payload.event.issue_number; + if (issueNumber !== undefined) { + const existing = await fetchExistingPlanComment(ctx, issueNumber); + if (existing !== null) { + ctx.toolState.existingPlanCommentId = existing.commentId; + ctx.toolState.previousPlanBody = existing.body; + return { + ...buildOrchestratorGuidance(selectedMode, modeGuidance.PlanEdit), + previousPlanBody: existing.body, + }; + } + } + } + return buildOrchestratorGuidance(selectedMode); }), }); diff --git a/mcp/server.ts b/mcp/server.ts index cf66ad9..b2e9926 100644 --- a/mcp/server.ts +++ b/mcp/server.ts @@ -64,6 +64,9 @@ export interface ToolState { progressCommentId: number | null | undefined; lastProgressBody?: string; wasUpdated?: boolean; + // set by select_mode when Plan + issue_number and plan-comment API returns existing plan (for report_progress target_plan_comment) + existingPlanCommentId?: number; + previousPlanBody?: string; output?: string; usageEntries: AgentUsage[]; } diff --git a/package.json b/package.json index 43932c8..2eeeaa0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@pullfrog/pullfrog", - "version": "0.0.177", + "version": "0.0.178", "type": "module", "files": [ "index.js", diff --git a/post b/post index 95f1883..e717562 100755 --- a/post +++ b/post @@ -41258,14 +41258,20 @@ function createOctokit(token) { var LEAPING_INTO_ACTION_PREFIX = "Leaping into action"; var Comment = type({ issueNumber: type.number.describe("the issue number to comment on"), - body: type.string.describe("the comment body content") + body: type.string.describe("the comment body content"), + type: type.enumerated("Plan", "Comment").describe( + "Plan: record this comment as the plan for this run (use report_progress for progress/plan updates on the current run). Comment: regular comment (default)." + ).optional() }); var EditComment = type({ commentId: type.number.describe("the ID of the comment to edit"), body: type.string.describe("the new comment body content") }); var ReportProgress = type({ - body: type.string.describe("the progress update content to share") + body: type.string.describe("the progress update content to share"), + "target_plan_comment?": type("boolean").describe( + "when true, update the existing plan comment (from select_mode lookup) instead of the progress comment; use when editing an existing plan" + ) }); var ReplyToReviewComment = type({ pull_number: type.number.describe("the pull request number"), @@ -41312,7 +41318,7 @@ var Effort = type.enumerated("mini", "auto", "max"); // package.json var package_default = { name: "@pullfrog/pullfrog", - version: "0.0.177", + version: "0.0.178", type: "module", files: [ "index.js",