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>
This commit is contained in:
Anna Bocharova
2026-03-10 20:34:54 +00:00
committed by pullfrog[bot]
parent 6c9747585f
commit 9c99bcbbac
6 changed files with 319 additions and 31 deletions
+138 -13
View File
@@ -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",
+104 -10
View File
@@ -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<void> {
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<string
export const Comment = type({
issueNumber: type.number.describe("the issue number to comment on"),
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(),
});
export function CreateCommentTool(ctx: ToolContext) {
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.",
"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({
@@ -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
+64 -4
View File
@@ -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<Extract<PlanCommentResponsePayload, { commentId: number }> | 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);
}),
});
+3
View File
@@ -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[];
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@pullfrog/pullfrog",
"version": "0.0.177",
"version": "0.0.178",
"type": "module",
"files": [
"index.js",
+9 -3
View File
@@ -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",