design phase 9 w14 plan todo - sinrimin/harness-nexus GitHub Wiki
Phase 9 W14 โ Plan / todo panel + subagent & permission verification
Status: SHIPPED 2026-09-17 (branch
feat/p9-w14-plan-todo, daemon0.19.0-p9w14). Ground truth:research-phase-9-w14-w15-plan-commands.md(adapter-source-verified: claude wrapper 0.78.0, codex-acp 0.16.0, opencode 1.18.x, dsh-acp 0.1.2-rc.1, ACP SDK 1.4.0).
1. Problem
The ACP plan session update carries the agent's todo/task list as a
full-replace snapshot. claude-agent-acp (since its task-list migration) emits
TodoWrite and TaskCreate/TaskUpdate/TaskList exclusively as plan updates
โ the tool calls are suppressed โ so the portal chat shows NOTHING while the
agent works through a todo list. codex-acp emits the same plan update from
its update_plan tool. Our daemon maps both to the raw fallback and the
web fold drops them.
Second half of the wave (verification, not new surface): confirm the
subagent path (claude's Task tool card โ the ONE subagent surface that
still rides an ordinary tool call) and the user confirmation path
(session/request_permission โ permission card โ respond) behave correctly
end-to-end on the rig, across the targets that support them.
2. Surface
2.1 Wire (packages/shared/src/realtime.ts)
Additive stream kind on chatStreamEventSchema:
export const planEntryStatusSchema = z.enum(['pending', 'in_progress', 'completed']);
export const planEntrySchema = z.object({
content: z.string().min(1).max(512),
status: planEntryStatusSchema,
priority: z.enum(['high', 'medium', 'low']).optional(),
});
// chatStreamEventSchema +=
z.object({ kind: z.literal('plan'), entries: z.array(planEntrySchema).max(128) });
Full REPLACE semantics (ACP's own contract: every update carries the complete
list). An empty entries array is legal (= plan cleared) and the UI hides on
it. Because it rides the ordinary stream it flows through the history ring
and session/load replay capture for free โ a resync converges to the last
snapshot.
2.2 Daemon (packages/cli/src/daemon/chat.ts)
One new arm in mapAcpUpdate (stateless โ no merge needed, unlike
session_config): case 'plan' โ validate/clamp entries (โค128, content
โค512 chars, drop malformed rows) โ {kind:'plan', entries}. Same mapping
covers the live path, the load-capture path, and (nothing to do) the dsh
transcript path โ dsh never emits plans.
DAEMON_VERSION โ 0.19.0-p9w14. No new capability (the update arrives
unasked), no /ctl method, no storage.
2.3 Web
apps/web/src/realtime.ts: mirror types (PlanEntry,planarm).fold.ts:FoldState.plan: PlanEntry[](last-wins inapplyEvent; fresh increateFoldState; history rebuild converges through replay).components/chat/todo-panel.tsxโ the portal-reference TodoPanel, restyled to Signal: a single card rendered ABOVE the composer inside the existingmax-w-3xlcolumn. Hidden whenentries.length === 0. Collapsed by default; header =ListTodoIcon+ title + progress summary (ๅทฒๅฎๆ N ยท ่ฟ่กไธญ N ยท ๅพ ๅ N, zero-count segments omitted) + chevron; expanded = one row per entry โ status glyph + single-line ellipsis text (titlecarries the full content), max-height with internal scroll. Status colors follow the established state encoding (--okcompleted,--warnin-progress, muted dashed pending);--signalstays reserved for the live-turn indicator.- The W6
TodoCard(registry keyTodoWrite) STAYS โ any adapter that still surfaces a todo tool as a tool call keeps its inline checklist card.
2.4 Out of scope
- Draft subagent sessions (ACP PR #1992,
subagent_spawned+ child-stream rerouting behind a client capability): NOT advertised โ unstable draft, and our single-channel fold would fold child output into the main transcript. Subagents remain theTasktool card. Revisit when the SDK ships it stable. session_info_update(claude goal_meta),compaction_*, unstableplan_update/plan_removedโ stayraw.- Editing/creating plan entries from the UI (no ACP request exists).
3. Verification scope (the wave's second deliverable)
Rig E2E on the live machine (docs/dev/test-rig.md, git-ignored):
- claude-code todo: a multi-step prompt โ TodoPanel appears above the composer, entries flip pending โ in_progress (activeForm text) โ completed; collapse/expand works; a resumed session replays to the final snapshot (panel present on open).
- claude-code subagent: a prompt that spawns a subagent (
Tasktool) โ TaskCard renders with the prompt body, completes with the result summary; subagent output does NOT leak into the main message stream. - Permissions: claude-code (a Bash command needing approval) and opencode (an edit in default permission mode) โ permission card renders with the adapter's own option set โ allow โ tool proceeds; reject path settles the card. dsh documents no requests (trusted-client design โ unchanged finding).
- codex plan: a planning prompt triggers codex's
update_plantool โ the same panel renders.
4. Tests
packages/cli/test/chat.test.ts:mapAcpUpdateplan cases (happy path, content clamp, malformed-row drop, empty array passes as cleared) + a fixture round-trip (newshow-planprompt arm in the fixture agent: pending snapshot โ progress snapshot โ completion snapshot + message + end_turn).- Fold behavior is covered by the daemon wire contract + manual rig pass
(the web has no component test harness; the fold's plan arm mirrors
session_config's state-only merge).
5. Rig results (2026-09-17, daemon 0.19.0-p9w14)
- codex plan E2E โ โ the
update_plantool fires on a "make a plan then execute" prompt;plansnapshots flow end-to-end and the web TodoPanel renders, converges live, and settles atๅทฒๅฎๆ 3 3/3(zero-count segments omitted). Browser-verified collapsed + expanded. - claude task-list lane DORMANT today โ CLI 2.1.263 headless sessions
expose NEITHER TodoWrite NOR TaskCreate/TaskUpdate to the model (verified
by having the model list its tools verbatim: Agent/Bash/Cron*/Edit/
EnterPlanMode/โฆ only). The wrapper's TodoWrite/Task*โplan conversion
therefore has no producer on this rig right now; the wire + panel are
ready for when the CLI ships task tools to SDK sessions. The subagent
tool is
Agent(ex-Task) and still rides an ordinary tool_call โ the existing TaskCard.CORRECTED 9 W14.1 (see
phase-9-w14.1-claude-ground-truth.md): right symptom, wrong cause โ portal sessions run the SDK-BUNDLED CLI 2.1.270 (not the native 2.1.263), and โฅ2.1.233 ships the Task tools disabled by default. The daemon's claude adapter now setsCLAUDE_CODE_ENABLE_TODO_TOOLS=1; TaskCreate fires and plan snapshots flow E2E. The lane is LIVE. - Permissions โ โ claude-code (options allow-once / allow-with-updates / reject โ allow-once โ tool proceeds โ clean turn end) and opencode (once / always / reject โ allow_once โ clean end). The browser permission card (codex, its own three-option set) approved and the turn completed.
- Side capture:
available_commands_updateobserved live from claude (incl.deep-research) and codex (reviewwithinput.hint) โ the W15 input.