fix(agent): preserved approved plan path
Approved plan execution now requires reading the durable local plan file instead of embedding the plan body in synthetic execution prompts. This keeps execution recoverable when Headroom-compressed inline content expires. Fixes #4164
This commit is contained in:
@@ -2,6 +2,10 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed approved plan execution prompts embedding the full plan instead of requiring the executor to read the durable `local://<slug>-plan.md` file. ([#4164](https://github.com/can1357/oh-my-pi/issues/4164))
|
||||
|
||||
## [16.2.12] - 2026-07-01
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
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`");
|
||||
});
|
||||
});
|
||||
@@ -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}}
|
||||
|
||||
<instruction>
|
||||
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.
|
||||
</instruction>
|
||||
|
||||
The full plan is injected below. You MUST execute it now:
|
||||
|
||||
<plan path="{{planFilePath}}">
|
||||
{{planContent}}
|
||||
</plan>
|
||||
|
||||
<critical>
|
||||
NEVER stop because inline plan content is compressed, expired, or unrecoverable. Read `{{planFilePath}}`.
|
||||
You MUST keep going until complete. This matters.
|
||||
</critical>
|
||||
|
||||
@@ -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}}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
## Existing Plan
|
||||
|
||||
<plan path="{{planFilePath}}">
|
||||
{{planContent}}
|
||||
</plan>
|
||||
The approved plan file is at `{{planFilePath}}`.
|
||||
|
||||
<instruction>
|
||||
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.
|
||||
</instruction>
|
||||
|
||||
@@ -6523,9 +6523,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;
|
||||
@@ -6535,7 +6534,6 @@ export class AgentSession {
|
||||
|
||||
const content = prompt.render(planModeReferencePrompt, {
|
||||
planFilePath,
|
||||
planContent,
|
||||
});
|
||||
|
||||
this.#planReferenceSent = true;
|
||||
|
||||
Reference in New Issue
Block a user