Skip to content

<objective> Build the ACP v1 stdio adapter over the shared dispatcher with the six tools on a documented realm/tool Realm extension method.

Purpose: Deliver the ACP half of TOOL-05 per P5-D3 and P5-D10, in files owned only by this plan, holding no tool logic. Output: Agent module plus its test file. </objective>

<execution_context> @~/.claude/gsd-core/workflows/execute-plan.md @~/.claude/gsd-core/templates/summary.md </execution_context>

<context> @.planning/PROJECT.md @.planning/ROADMAP.md @.planning/phases/05-omp-plugin-mcp-and-acp-surfaces/05-CONTEXT.md @docs/integration/acp.md </context>

<tasks>

<task type="tdd"> <name>Task 1: v1 initialize over stdio with the batch rule (P5-D3, P5-D10)</name> <files>src/acp/agent.ts, test/acp/agent.test.ts</files> <read_first> - docs/integration/acp.md Q1 (protocolVersion 1 with clientInfo and agentInfo; highest-supported-at-or-below negotiation; client closes when it cannot support the selection) - docs/integration/acp.md Q2 (stdio framing; lifecycle messages never batched) - src/core/errors.ts lines 6-30 (ERROR_CODES; no code added) </read_first> <behavior> - Test (TOOL-05): a v1 initialize request gets a protocolVersion 1 reply carrying agent info, framed as one newline-terminated JSON message with diagnostics on stderr only - Test (TOOL-05): initialize, session/new, and session/prompt equivalents are never packed into one batch line - Test (TOOL-05): closing stdin shuts the adapter down without hanging - Test titles cite TOOL-05 </behavior> <action>Write the failing tests first per behavior, then implement the v1 session surface in src/acp/agent.ts per P5-D10: parse newline-delimited JSON-RPC on stdin, answer initialize with protocolVersion 1 and agent info per acp.md Q1, keep every lifecycle-sensitive message on its own line per the Q2 batch rule, write diagnostics to stderr only, and stop on stdin close. Use only node stream handling plus JSON; add no npm dependency. Open the module with a header comment naming P5-D3 and P5-D10 and marking the module as a Realm ACP agent, not a standard-ACP implementation. (Implements the P5-D3 transport half and the P5-D10 negotiation half.)</action> <acceptance_criteria> - test/acp/agent.test.ts asserts the v1 initialize reply, the never-batched lifecycle lines, stderr-only diagnostics, and close-on-stdin-end - package.json gains no dependency </acceptance_criteria> <verify> <automated>npx tsc -p tsconfig.json && node --test dist/test/acp/agent.test.js</automated> </verify> <done>v1 initialize with batch rule green.</done> </task>

<task type="tdd"> <name>Task 2: realm/tool extension carrying the six tools (P5-D10, P5-D11)</name> <files>src/acp/agent.ts, test/acp/agent.test.ts</files> <read_first> - src/acp/agent.ts (from Task 1 of this plan; the framing the extension rides on) - docs/integration/acp.md (from plan 05-02; the recorded realm/tool extension text) - src/tools/dispatch.ts lines 109-148 (call and the six ToolCall branches to route to) - src/contract/validate.ts lines 62-76 (ToolCall map shape carried unchanged) - test/support/fake-tools.ts lines 153-200 (makeHarness dispatcher the test drives) </read_first> <behavior> - Test (TOOL-05): a realm/tool request carrying each of the six ToolCall maps routes to ToolDispatcher.call with the map unchanged (asserted on the dispatcher's recorded call order from the harness) - Test (TOOL-05): a dispatcher refusal surfaces the catalogue code in the realm/tool reply without adapter-added logic - Test (TOOL-05): a request naming any other tool method is rejected without reaching the dispatcher - Test titles cite TOOL-05 </behavior> <action>Write the failing tests first per behavior, then implement the realm/tool extension in src/acp/agent.ts per P5-D10 and P5-D11: accept only the documented realm/tool method, extract the embedded ToolCall map unchanged, return ToolDispatcher.call's ToolOutcome inside the reply, and map a refusal to its catalogue code with no added decision; reject unknown methods before any dispatch. The module imports the dispatcher type only and calls call, nothing deeper. (Implements the P5-D11 routing half for ACP.)</action> <acceptance_criteria> - test/acp/agent.test.ts asserts unchanged ToolCall routing for all six tools, code-faithful refusals, and unknown-method rejection with no dispatch - src/acp/agent.ts calls no dispatcher member except call </acceptance_criteria> <verify> <automated>npx tsc -p tsconfig.json && node --test dist/test/acp/agent.test.js</automated> </verify> <done>Extension routing green for all six tools with refusals faithful.</done> </task>

<task type="tdd"> <name>Task 3: Session lifecycle drive with dispatcher-equal outcomes (TOOL-05)</name> <files>src/acp/agent.ts, test/acp/agent.test.ts</files> <read_first> - src/acp/agent.ts (from Tasks 1-2 of this plan; the adapter the drive talks to) - test/support/fake-tools.ts lines 21-35 (ADVISORY and REQUIRED policies the two-mode drive uses) - src/core/status.ts lines 38-54 (displayStatus the status outcome carries) </read_first> <behavior> - Test (TOOL-05): initialize then realm/tool status then realm/tool write complete in order over in-memory stdio against the harness dispatcher - Test (TOOL-05): the status outcome carries the shared displayStatus label, not an adapter-invented label - Test titles cite TOOL-05 </behavior> <action>Write the failing tests first per behavior, then add the lifecycle drive to test/acp/agent.test.ts: speak to the adapter over in-memory stdin/stdout streams as an ACP client would (initialize first, then realm/tool calls, one message per line), and assert the outcomes equal the harness dispatcher's own ToolOutcome values. Cover status (label proves the shared mapping) and one refusal case in required mode. The adapter module gains no new behavior in this task; only the test drives it end to end. (Proves the ACP path reaches the same dispatcher with equal results.)</action> <acceptance_criteria> - The drive test completes initialize plus two realm/tool calls in order with no real process - Outcome payloads equal the harness dispatcher's own outcomes </acceptance_criteria> <verify> <automated>npx tsc -p tsconfig.json && node --test dist/test/acp/agent.test.js</automated> </verify> <done>Lifecycle drive green with dispatcher-equal outcomes.</done> </task>

</tasks>

<threat_model>

Trust Boundaries ​

BoundaryDescription
stdin bytes -> ACP parserUntrusted client bytes cross into the adapter; malformed lines and unknown methods are rejected before any dispatch
extension params -> dispatcherThe embedded ToolCall map crosses into ToolDispatcher.call unchanged; schema validation still runs first inside the pipeline

STRIDE Threat Register ​

Threat IDCategoryComponentSeverityDispositionMitigation Plan
T-05-07T (Tampering)src/acp/agent.tshighmitigateOnly realm/tool reaches the dispatcher and the map crosses unchanged, so no client input can skip schema, gate, lock, or audit stages (P5-D11); the routing test proves the map is unchanged and unknown methods never dispatch
T-05-08R (Repudiation)src/acp/agent.tslowacceptLifecycle messages are never batched per the Q2 rule, so initialize and session messages keep their own audit-ordered replies; rationale: ordering is asserted by the drive test, and content audit still lives in the dispatcher
</threat_model>

<verification> npx tsc -p tsconfig.json && node --test dist/test/acp/agent.test.js; grep test/acp/agent.test.ts for TOOL-05 in test titles. </verification>

<success_criteria> v1 initialize with batch rule, realm/tool routing for all six tools with faithful refusals, and a lifecycle drive with dispatcher-equal outcomes all green with no tool logic in the surface. </success_criteria>

Artifacts this phase produces (contribution of this plan) ​

  • src/acp/agent.ts (new): ACP v1 stdio agent with the realm/tool Realm extension, routing all six tools to ToolDispatcher.call (P5-D3, P5-D10, P5-D11).
  • test/acp/agent.test.ts (new): TOOL-05 initialize, batch-rule, routing, refusal, and lifecycle-drive cases.

<output> Create .planning/phases/05-omp-plugin-mcp-and-acp-surfaces/05-04-SUMMARY.md when done </output>

AGPL-3.0-only. Copyright (C) 2026 Spectrum Web Co/swcstudiospace.