research phase 9 w14 w15 plan commands - sinrimin/harness-nexus GitHub Wiki
Status: verified 2026-09-17 against adapter SOURCES (the
/tmp/acp-researchcopies) +@agentclientprotocol/[email protected]type declarations (npm tarball). Ground truth for W14 (plan / todo display, subagent & permission verification) and W15 (slash commands).
The portal chat currently drops two ACP session-update kinds on the floor:
plan and available_commands_update both fall into the daemon's raw
fallback (mapAcpUpdate default arm) and the web fold ignores every raw
event except hnx/prompt-error. Result: Claude Code's todo list is COMPLETELY
INVISIBLE (the wrapper no longer emits TodoWrite as a tool call), and no agent
surfaces its slash commands. This doc verifies what each adapter actually
emits before W14/W15 build the display.
// session/update β full REPLACE snapshot; the client replaces the whole plan.
{ sessionUpdate: "plan", entries: PlanEntry[] }
PlanEntry = {
content: string // human-readable task description
priority: "high" | "medium" | "low"
status: "pending" | "in_progress" | "completed"
_meta?: ...
}
// Doc note (verbatim): "When updating a plan, the agent must send a complete
// list of all entries with their current status."
// session/update β the agent's slash-command catalog.
{ sessionUpdate: "available_commands_update", availableCommands: AvailableCommand[] }
AvailableCommand = {
name: string // NO leading slash (e.g. "init"); MCP ones "mcp:foo"
description: string
input?: { hint: string } | null // unstructured: "text typed after the name"
}There is NO dedicated command-invocation RPC β a command runs as an ordinary
session/prompt whose text is /name rest-of-line (the
UnstructuredCommandInput doc: "All text that was typed after the command
name is provided as input"). The full 1.4.0 sessionUpdate union (for
completeness): user_message_chunk | agent_message_chunk | agent_thought_chunk | tool_call | tool_call_update | plan | plan_update* | plan_removed* | available_commands_update | current_mode_update | config_option_update | session_info_update | usage_update | compaction_update | compaction_summary_chunk (* = UNSTABLE draft kinds β
no shipped adapter uses them; they keep falling to raw).
| surface | claude-code (@agentclientprotocol/claude-agent-acp 0.78.0) |
codex (codex-acp 0.16.0) |
opencode (1.18.x) | dsh (dsh-acp 0.1.2-rc.1) |
|---|---|---|---|---|
plan entries |
β
TodoWrite β plan (tools.ts planEntries) AND TaskCreate/TaskUpdate/TaskList β plan (taskStateToPlanEntries, accumulated per session) |
β
update_plan tool β PlanEntry[] (thread.rs update_plan) |
β (no plan emission anywhere in src/acp/) |
β |
available_commands_update |
β
after session/new, load, resume (setTimeout after the response; "after replay so it doesn't interleave with history"); MCP commands renamed mcp:<name>; terminal-bound + a static unsupported list (clear, cost, todos, login, β¦) filtered OUT |
β
on thread start (+200ms): review, review-branch, review-commit, init, compact, logout; review ones carry input.hint
|
β
on session ops: from Command.Info (includes command/*.md custom commands β i.e. platform-deployed commands surface here); {name, description} only, NO input
|
β (emits only message/thought/tool/config_option/usage updates) |
| command invocation | prompt /name args (CLI parses slash) |
prompt /name args ("User prompts, including slash commands like /init, /review, /compact") |
prompt /name args β detectSlashCommand matches availableCommands by name, routes to its internal session.command
|
n/a (no commands) |
| subagents |
Task (the subagent-spawner tool) still emits an ordinary tool_call β the portal's TaskCard. TodoWrite + TaskCreate/TaskUpdate/TaskList/TaskGet are the ones SUPPRESSED in favor of plan snapshots (isTaskTool/shouldEmitToolCall, acp-agent.ts:9232) |
n/a | n/a | n/a |
| permissions | β
session/request_permission
|
β | β | β never (trusted-programmatic-clients design; W9 research) |
acp-agent.ts streaming switch: chunk.name === "TodoWrite" with
Array.isArray(chunk.input?.todos) produces a plan update INSTEAD of a
tool_call; isTaskTool(name) (TaskCreate/TaskUpdate/TaskList/TaskGet β the
SDK-era task list, NOT the subagent Task tool) is suppressed the same way,
with the plan snapshot emitted at tool_result time. Consequences:
- The W6
TodoCard(registry keyTodoWrite) is DEAD for claude-code β the tool call never arrives. The portal's todo display must ride theplanupdate. (The card stays as a fallback for any adapter that still emits TodoWrite as a tool call.) -
in_progressentries carry the agent'sactiveFormtext (e.g. "Analyzing dependenciesβ¦") instead of the static content β display verbatim. - During
session/loadreplay the same mapping runs, so history replay naturally ends at the final plan snapshot β a last-wins panel converges without extra plumbing.
claude-agent-acp carries a draft implementation of ACP PR #1992
(acp-subagents.ts / native-subagents.ts): when the CLIENT advertises
capabilities.subagents, it emits subagent_spawned / subagent_state_update
and REWRITES child updates to the child's own sessionId. Our daemon does not
advertise it (initialize sends clientCapabilities: {}) and W14 will keep it
that way: the draft is unstable, and our single-channel fold has no concept of
a second session stream β routed child output would corrupt the main
transcript. Subagents keep surfacing as Task tool cards (verified path
above); W14's subagent work is rig VERIFICATION of that path, not the draft.
-
TodoPanel (
components/stream/TodoPanel.tsx): a strip ABOVE the input area; empty list renders nothing; collapsed by default (header: icon + title + progress summary + chevron); expanded = per-entry status glyph + single-line ellipsis, capped + internal scroll. Data = last-wins plan snapshot (useAcpConnectioncase 'plan'βsetPlan(entries)); replay converges ("δΌθ―ζεΌε³ζ’ε€ι’ζΏ"). -
Commands (
commands.ts+ChatInput.tsx): palette opens when the draft starts with/(not while a turn runs); first word after/is the filter (name OR description substring); β/β cycle, Esc closes, Enter on a BARE command selects + fills/name, Enter WITH args sends as a normal prompt. The portal ALSO ships a hardcoded fallback table β we deliberately DON'T (data-driven honesty: an agent that never pushed commands shows no palette; same rule as dsh's absent selectors).
- plan: β€128 entries kept,
contentclamped to 512 chars, unknownstatusdropped entry-wise,prioritypassed through when present (display may ignore). - commands: β€64 rows,
nameβ€128,descriptionclamped 512,input.hintclamped 256. Names ride VERBATIM (the web adds the/); MCPmcp:*names keep their prefix.
- W14 renders ONE todo/plan panel fed by the
plansnapshot β no per-update transcript rows (full-replace semantics make rows a lie about the model). - opencode gets NO plan panel (honest absence); its todo tool (if a model uses one) surfaces as an ordinary tool card.
- dsh shows neither panel nor palette β no fallbacks.
-
session_info_update(claude goal_meta) andcompaction_*stayrawβ out of scope for both waves.