Plan mode: xd://propose ignores slug plan files and only finds local://PLAN.md #37
Labels
No labels
Agent
Ignore
Agent
In Progress
Agent
Planned
Agent
Planning
Agent
Ready
Agent
Triage
Compat
Breaking
Kind
Bug
Kind
Documentation
Kind
Enhancement
Kind
Feature
Kind
Security
Kind
Testing
Priority
Critical
Priority
High
Priority
Low
Priority
Medium
Reviewed
Confirmed
Reviewed
Duplicate
Reviewed
Invalid
Reviewed
Won't Fix
Status
Abandoned
Status
Blocked
Status
Need More Info
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
lucas/omp-deck#37
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Implementation: #43
Problem
In a deck plan-mode session, the agent wrote its plan to
local://implement-review-replies-plan.md(as the plan-mode prompt requires) and wroteimplement-review-repliesplus a title line toxd://propose. Propose failed withPlan file not found at local://PLAN.md. Write the finalized plan to local://PLAN.md before requesting approval.Writing an identical copy tolocal://PLAN.mdmade propose succeed. The prompt mandateslocal://<slug>-plan.md; the deck behaves as if the path is fixed atlocal://PLAN.md.Root cause (verified in code):
node_modules/@oh-my-pi/pi-coding-agent/src/tools/resolve.ts,dispatchResolutionDevice) passes the entire trimmed propose body, including the title line, to the plan proposal handler.resolveApprovedPlan(plan-mode/approved-plan.ts) derives the slug vianormalizePlanTitle, which throws on/,\or..— so a title such as one mentioning.omp/commands/implement.mddrops the slug entirely. A multi-line body without those characters gets whitespace collapsed into one long bogus slug.listPlanFiles(a scan of the session-local dir for*plan.md, newest first) and the state path. The TUI, ACP and prewalk handlers passlistPlanFiles; the deck'sPlanModeBridge.#handlePlanPropose(apps/server/src/bridge/plan-mode-bridge.ts) does not, so the only candidate left is the state pathlocal://PLAN.md.local://PLAN.md(apps/web/src/lib/inbox-prompt.ts, protocol comments), contradicting the SDK's slug convention.Plan
Approach
apps/server/src/bridge/plan-mode-bridge.ts:import { listPlanFiles } from "@oh-my-pi/pi-coding-agent/plan-mode/plan-files";(the package exports./plan-mode/*). No deck equivalent exists; reuse the SDK helper.#handlePlanPropose, add to theresolveApprovedPlan({...})input:listPlanFiles: () => listPlanFiles({ localProtocolOptions: { getArtifactsDir: this.getArtifactsDir, getSessionId: this.getSessionId } }). These are the same callbacks#readPlanFilealready feeds toresolveLocalUrlToPath, so the scan and the read resolve the samelocal://root.listPlanFilesreturns[]when the dir is missing; no extra handling.PLAN_FILE_URLas the plan-mode state default (the SDK requires a state path), but rewrite its doc comment (lines ~67–69) to: it is only the state-path fallback; agents writelocal://<slug>-plan.md, found via the slug or thelistPlanFilesscan.local://PLAN.mdas an example write target and the#handlePlanProposedoc comment to mention the scan step (slug → state path → scanned plan files).apps/web/src/lib/inbox-prompt.tslines ~83 and ~109: replace(local://PLAN.md). Do not impose your own layout — plan mode owns the format.with(local://<slug>-plan.md, slug written to xd://propose). Do not impose your own layout — plan mode owns the format.in both places.packages/protocol/src/index.ts: line ~792 comment →Plan-mode state path (\local://PLAN.md` default); the agent's actual plan lives at `local://-plan.md`.; line ~804 →`local://` path of the plan file the proposal resolved to (normally `local://-plan.md`).; lines ~1013 and ~1029: replacelocal://PLAN.mdwiththe proposed plan file (`planFilePath`)and drop "before the rename" (no rename happens since SDK v18.10.1 — confirm against#handleDetachedResponse/approve path in the bridge, which writeseditedContenttopending.planFilePath`).apps/web/src/lib/store.ts?? "local://PLAN.md"fallback and the migration comment as-is (they describe the state default, which is still correct).Critical files & anchors
apps/server/src/bridge/plan-mode-bridge.ts—#handlePlanPropose(resolveApprovedPlancall ~line 578),PLAN_FILE_URL~line 70.node_modules/@oh-my-pi/pi-coding-agent/src/plan-mode/plan-files.ts—listPlanFilessignature({ localProtocolOptions }) => Promise<string[]>.apps/server/src/bridge/plan-mode-bridge.test.ts— harnessplanFileForSlug,invokePropose, test "approve happy path" ~line 257.Verification
apps/server/src/bridge/plan-mode-bridge.test.tsnext to the happy-path test:enter(), write# Foo\n\nBody.\ntoharness.planFileForSlug("foo"), invoke propose with body"foo\nImplement review replies in .omp/commands/implement.md"; expect theplan_proposedframe'splanFilePathto belocal://foo-plan.mdandplanContentto match/Body/. Thenrespond(..., { action: "reject" })to settle. Before the fix this rejects withPlan file not found at local://PLAN.md.[])./workspace/omp-deck:bun test apps/server/src/bridge/plan-mode-bridge.test.ts, then the server typecheck used by the repo (bun run typecheckif defined in rootpackage.json; otherwisebunx tsc --noEmit -p apps/server).local://x-plan.mdand proposex+ a title line containing a/; the approval card opens showinglocal://x-plan.md.Assumptions & contingencies
@oh-my-pi/pi-coding-agent/plan-mode/plan-filesimport fails to resolve at runtime/typecheck, importlistPlanFilesthe same wayapproved-planis imported; if still unavailable, implement an identical local helper in the bridge (readdir ofresolveLocalUrlToPath("local://", …)'s root,*plan.md, sort by mtime desc).