diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 8610a95a5..5f5835071 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -50,6 +50,9 @@ ### Fixed - Fixed the multi-select (checkbox) `ask` picker showing no visible cursor on themes where `accent` fg is close to the terminal foreground; the focused option now renders as a full-width `selectedBg` highlight band spanning label and wrapped description rows, matching the Ctrl+R history overlay ([#4157](https://github.com/can1357/oh-my-pi/issues/4157)). +### Fixed + +- Fixed approved plan execution prompts embedding the full plan instead of requiring the executor to read the durable `local://-plan.md` file. ([#4164](https://github.com/can1357/oh-my-pi/issues/4164)) ## [16.2.12] - 2026-07-01 diff --git a/packages/coding-agent/src/plan-mode/approved-plan-prompt.test.ts b/packages/coding-agent/src/plan-mode/approved-plan-prompt.test.ts new file mode 100644 index 000000000..1d54446a0 --- /dev/null +++ b/packages/coding-agent/src/plan-mode/approved-plan-prompt.test.ts @@ -0,0 +1,34 @@ +import { describe, expect, it } from "bun:test"; +import { prompt } from "@oh-my-pi/pi-utils"; +import planModeApprovedPrompt from "../prompts/system/plan-mode-approved.md" with { type: "text" }; +import planModeCompactInstructionsPrompt from "../prompts/system/plan-mode-compact-instructions.md" with { + type: "text", +}; +import planModeReferencePrompt from "../prompts/system/plan-mode-reference.md" with { type: "text" }; + +const PLAN_FILE_PATH = "local://durable-plan.md"; +const PLAN_SENTINEL = "SENTINEL_HEADROOM_COMPRESSED_PLAN_CONTENT"; + +describe("approved plan execution prompts", () => { + it("loads the approved plan from the durable local file instead of embedding content", () => { + const approved = prompt.render(planModeApprovedPrompt, { + planContent: PLAN_SENTINEL, + planFilePath: PLAN_FILE_PATH, + contextPreserved: false, + }); + const reference = prompt.render(planModeReferencePrompt, { + planContent: PLAN_SENTINEL, + planFilePath: PLAN_FILE_PATH, + }); + const compact = prompt.render(planModeCompactInstructionsPrompt, { + planFilePath: PLAN_FILE_PATH, + }); + + for (const rendered of [approved, reference, compact]) { + expect(rendered).toContain(PLAN_FILE_PATH); + expect(rendered).not.toContain(PLAN_SENTINEL); + } + expect(approved).toContain("MUST read `local://durable-plan.md`"); + expect(reference).toContain("MUST read `local://durable-plan.md`"); + }); +}); diff --git a/packages/coding-agent/src/prompts/system/plan-mode-approved.md b/packages/coding-agent/src/prompts/system/plan-mode-approved.md index 7b8b0df5f..96b7f9347 100644 --- a/packages/coding-agent/src/prompts/system/plan-mode-approved.md +++ b/packages/coding-agent/src/prompts/system/plan-mode-approved.md @@ -1,25 +1,22 @@ Plan approved. {{#if contextPreserved}} -- Context preserved. Use conversation history when useful; this plan is the source of truth if it conflicts with earlier exploration. +- Context preserved. Use conversation history when useful; the plan file is the source of truth if it conflicts with earlier exploration. {{/if}} -You MUST execute this plan step by step. You have full tool access. +You MUST read `{{planFilePath}}` before executing. +The file content is the authoritative plan; visible/compressed context is secondary. +Read failure? Report the exact path and error instead of guessing. +After reading, you MUST execute the plan step by step with full tool access. You MUST verify each step before proceeding to the next. {{#has tools "todo"}} -Before execution, initialize todo tracking with `todo`. +After reading the plan, initialize todo tracking with `todo`. After each completed step, immediately update `todo`. If `todo` fails, fix the payload and retry before continuing. {{/has}} -The plan path is for subagent handoff only. You already have the plan; NEVER read it. -The full plan is injected below. You MUST execute it now: - - -{{planContent}} - - +NEVER stop because inline plan content is compressed, expired, or unrecoverable. Read `{{planFilePath}}`. You MUST keep going until complete. This matters. diff --git a/packages/coding-agent/src/prompts/system/plan-mode-compact-instructions.md b/packages/coding-agent/src/prompts/system/plan-mode-compact-instructions.md index 1bc8d9a33..02be72a89 100644 --- a/packages/coding-agent/src/prompts/system/plan-mode-compact-instructions.md +++ b/packages/coding-agent/src/prompts/system/plan-mode-compact-instructions.md @@ -12,5 +12,6 @@ You MUST drop: - Restated context already present in the plan file. {{#if planFilePath}} -The approved plan file is at `{{planFilePath}}`; it is the authoritative source of truth and need not be re-summarized in detail. +The approved plan file is at `{{planFilePath}}`; it is the authoritative source of truth. +You MUST preserve this durable path and the fact that the executor must read it directly after compaction. {{/if}} diff --git a/packages/coding-agent/src/prompts/system/plan-mode-reference.md b/packages/coding-agent/src/prompts/system/plan-mode-reference.md index 8709a4942..410a707b5 100644 --- a/packages/coding-agent/src/prompts/system/plan-mode-reference.md +++ b/packages/coding-agent/src/prompts/system/plan-mode-reference.md @@ -1,11 +1,10 @@ ## Existing Plan - -{{planContent}} - +The approved plan file is at `{{planFilePath}}`. If this plan is relevant to current work and not complete, you MUST continue executing it. +If you do not have the current plan content in visible context, you MUST read `{{planFilePath}}`. If the plan is stale or unrelated, you MUST ignore it. -The plan path is for subagent handoff only. You already have the plan; NEVER read it. +NEVER stop because inline plan content is compressed, expired, or unrecoverable. Read the file. diff --git a/packages/coding-agent/src/session/agent-session.ts b/packages/coding-agent/src/session/agent-session.ts index b1b1d44f0..9ac3d7083 100644 --- a/packages/coding-agent/src/session/agent-session.ts +++ b/packages/coding-agent/src/session/agent-session.ts @@ -6666,9 +6666,8 @@ export class AgentSession { const planFilePath = this.#planReferencePath; const resolvedPlanPath = resolveLocalUrlToPath(planFilePath, this.#localProtocolOptions()); - let planContent: string; try { - planContent = await Bun.file(resolvedPlanPath).text(); + await fs.promises.access(resolvedPlanPath, fs.constants.R_OK); } catch (error) { if (isEnoent(error)) { return null; @@ -6678,7 +6677,6 @@ export class AgentSession { const content = prompt.render(planModeReferencePrompt, { planFilePath, - planContent, }); this.#planReferenceSent = true;