Skip to content

test(acp): drive ACP tests through the wire protocol - #52307

Merged
nexxeln merged 2 commits into
v2from
acp-wire-tests
Sep 30, 2026
Merged

nexxeln merged 2 commits into
v2from
acp-wire-tests

Conversation

@nexxeln

@nexxeln nexxeln commented Sep 30, 2026 •

Copy link
Copy Markdown
Member

ACP Effect port tracking: #35457 | Refs #37229 | Refs #41660 | Refs #49591 | Refs #51716

Why the change

The ACP tests called internals (ACPService.make, streamTurn, replayMessages, syncEditedFiles) that the upcoming Effect port deletes, so this PR moves every behavioral test onto the ACP wire and lets the port rewrite internals without touching tests.

Special things to note

  • Tests only; there are no production changes. Tests check what an ACP client observes (JSON-RPC results and error codes, session/update, permission and fs/write_text_file requests, $/cancel_request) plus typed facts from the fake server. They do not pin exact internal request sequences.
  • Four regression tests are test.todo and fail today for the intended reason under bun test --todo: compact is missing from the advertised commands, usage covers only the last step, native edit/write/patch updates have no locations, and opencode acp keeps running after its private server dies. The last one needs pgrep, so it is skipped on Windows.
  • Current behavior worth knowing: if a cancel arrives while the prompt's admission request is still in flight, the agent sends two interrupts. The active-turn cancel tests wait for admission before cancelling and assert exactly one interrupt.

Change outline

One harness, wire-fixture.ts, replaces service-fixture.ts and sse-fixture.ts. The agent is built in one place (ACP.connect), which is the only line the port needs to change.

flowchart LR
  C["SDK client()<br/>records updates, permissions,<br/>writes, child updates"] <-- "in-memory NDJSON<br/>(raw tap records every agent message)" --> A["ACP.connect(...)"]
  A --> P["@opencode/client/promise"]
  P -- "HTTP + SSE" --> S["fake Bun.serve routes<br/>parse bodies with Schema (400 if malformed)"]
  S --> F["typed facts<br/>submissions, selections, interrupts,<br/>replies, mcp, catalogReads"]
  S --> St["mutable state<br/>catalog, sessions, messages"]
Loading

Hooks return the events to stream, and the server gives each event its id and sequence number when sending it:

startSession({
  onPrompt: ({ sessionID, id, text }) => turn(sessionID, id, textDelta(sessionID, "msg", "hello")),
  onInterrupt: ({ sessionID }) => [interrupted(sessionID)],
  permission: (request, signal) => ({ outcome: { outcome: "selected", optionId: "once" } }),
})

What each file covers now:

 packages/cli/test/acp/
+├── wire-fixture.ts          # harness: SDK client ↔ ACP.connect ↔ fake server
+├── select-options.ts        # config option helpers shared with subprocess tests
+├── session.test.ts          # initialize, new/load/resume/fork/list/close/delete, MCP
+├── catalog.test.ts          # shared catalog, refresh on *.updated, reload before rejecting, retries
+├── prompt.test.ts           # routing, synthetic context, usage, stop reasons, errors, retry _meta, cancellation
+├── turn.test.ts             # isolation, streaming order, children, tools, forms
+├── replay.test.ts           # replay order and resilience
+├── permission.test.ts       # permission flow, edit previews, fs sync
-├── service*.test.ts, event*.test.ts, permission-behavior.test.ts
-├── service-fixture.ts, sse-fixture.ts
 ├── lifecycle.subprocess.test.ts  # + #51716 todo
 └── (pure-module tests unchanged)

New characterization tests:

cancel before admission, mid-turn session/cancel, $/cancel_request on the prompt
session/close with an active turn: the turn settles first, then the session detaches
a second concurrent prompt is rejected
config_option_update / available_commands_update on model.updated, agent.updated, command.updated
reasoning ordinals match between live streaming and replay
nested and background child ext updates
opencode/retry _meta is set, cleared, and reported on cancel
stop reasons (end_turn, max_tokens, refusal, cancelled), auth required, internal errors

@nexxeln
nexxeln merged commit 7878744 into v2 Sep 30, 2026
10 checks passed
@nexxeln
nexxeln deleted the acp-wire-tests branch September 30, 2026 16:39
Ichinose-Kazuki pushed a commit to Ichinose-Kazuki/opencode that referenced this pull request Oct 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant