From 0d4d6e63055c888825f2d647a9ae54a4b761baa9 Mon Sep 17 00:00:00 2001 From: Edwar Diaz Date: Sat, 8 Aug 2026 17:39:51 +0000 Subject: [PATCH 1/3] docs(acp): spec, architecture, requirements and roadmap for the ACP migration --- .agents/skills/acp-client/SKILL.md | 116 +++++++ docs/specs/acp/01-research.md | 230 +++++++++++++ docs/specs/acp/02-architecture.md | 183 +++++++++++ docs/specs/acp/03-requirements.md | 301 ++++++++++++++++++ docs/specs/acp/04-roadmap.md | 121 +++++++ docs/specs/acp/README.md | 40 +++ .../0001-adopt-acp-v1-stable-v2-flagged.md | 30 ++ .../specs/acp/adr/0002-backend-as-acp-host.md | 27 ++ .../0003-websocket-jsonrpc-ui-transport.md | 29 ++ ...0004-history-acp-first-with-local-cache.md | 31 ++ .../0005-agent-catalog-from-acp-registry.md | 28 ++ .../0006-local-models-via-own-acp-agent.md | 30 ++ .../adr/0007-credentials-and-permissions.md | 32 ++ ...supersede-feat-acp-provider-abstraction.md | 34 ++ 14 files changed, 1232 insertions(+) create mode 100644 .agents/skills/acp-client/SKILL.md create mode 100644 docs/specs/acp/01-research.md create mode 100644 docs/specs/acp/02-architecture.md create mode 100644 docs/specs/acp/03-requirements.md create mode 100644 docs/specs/acp/04-roadmap.md create mode 100644 docs/specs/acp/README.md create mode 100644 docs/specs/acp/adr/0001-adopt-acp-v1-stable-v2-flagged.md create mode 100644 docs/specs/acp/adr/0002-backend-as-acp-host.md create mode 100644 docs/specs/acp/adr/0003-websocket-jsonrpc-ui-transport.md create mode 100644 docs/specs/acp/adr/0004-history-acp-first-with-local-cache.md create mode 100644 docs/specs/acp/adr/0005-agent-catalog-from-acp-registry.md create mode 100644 docs/specs/acp/adr/0006-local-models-via-own-acp-agent.md create mode 100644 docs/specs/acp/adr/0007-credentials-and-permissions.md create mode 100644 docs/specs/acp/adr/0008-supersede-feat-acp-provider-abstraction.md diff --git a/.agents/skills/acp-client/SKILL.md b/.agents/skills/acp-client/SKILL.md new file mode 100644 index 0000000..ae2ee1f --- /dev/null +++ b/.agents/skills/acp-client/SKILL.md @@ -0,0 +1,116 @@ +--- +name: acp-client +description: How to talk to Agent Client Protocol (ACP) agents from Node/TypeScript — SDK usage, version differences, capability gating, slash commands, images, permissions, history replay, and how to probe a real agent. Use when working on the DevMentorAI ACP host (apps/backend/src/acp) or debugging an agent integration. +--- + +# ACP client integration (DevMentorAI) + +Spec: · design docs: `docs/specs/acp/` (read `01-research.md` +before changing protocol code). ACP here means *Agent Client Protocol*, not IBM's "Agent +Communication Protocol". + +## Ground rules + +- **v1 is the production target.** `@agentclientprotocol/sdk` main entrypoint = v1 + (`PROTOCOL_VERSION === 1`); v2 is draft behind `@agentclientprotocol/sdk/experimental/v2` + and gated by the `ACP_V2` flag. Version-specific shapes live only in `acp/normalize/*`. +- **Capability-first.** Never call a method or send a content type the agent did not + advertise in `initialize` / `promptCapabilities`. Unsupported methods come back as + JSON-RPC `-32601`, which means *unsupported*, not *broken*. +- **v1 `session/prompt` blocks for the whole turn** and its response carries `stopReason`. + The host synthesises `running`/`idle` state from that. (v2 uses `state_update` instead.) +- Updates can still arrive **after** `session/cancel` — keep accepting them. +- Only `apps/backend/src/acp/**` may import the ACP SDK. + +## Minimal client over stdio + +```ts +import { spawn } from 'node:child_process'; +import { Readable, Writable } from 'node:stream'; +import * as acp from '@agentclientprotocol/sdk'; + +const child = spawn(cmd, args, { stdio: ['pipe', 'pipe', 'pipe'], cwd, env }); +const stream = acp.ndJsonStream( + Writable.toWeb(child.stdin) as WritableStream, + Readable.toWeb(child.stdout) as ReadableStream, +); + +const app = acp.client({ name: 'devmentorai', version: '…' }) + .sessionUpdate(async (params) => { /* normalise → AcpEvent */ }) + .requestPermission(async (params) => ({ + outcome: { outcome: 'selected', optionId: chosenOptionId }, + })); + +const conn = await app.connect(stream); +const init = await conn.initialize({ protocolVersion: acp.PROTOCOL_VERSION, clientCapabilities: {} }); +const { sessionId } = await conn.newSession({ cwd, mcpServers: [] }); +const { stopReason } = await conn.prompt({ sessionId, prompt: [{ type: 'text', text: 'hi' }] }); +``` + +`child.stderr` is diagnostics only — keep a bounded ring buffer and never parse it. + +## Launching the agents we support + +Launch specs are data, from `https://cdn.agentclientprotocol.com/registry/v1/latest/registry.json` +(`distribution.npx` = `{ package, args, env }`; `distribution.binary.` = +`{ archive, cmd, args, sha256 }`). Handy ones: + +| Agent | Command | +| --- | --- | +| GitHub Copilot | `npx @github/copilot --acp --stdio` (or `--acp --port N` for TCP) | +| Gemini CLI | `npx @google/gemini-cli --acp` | +| Claude | `npx @agentclientprotocol/claude-agent-acp` | +| Codex | `npx @agentclientprotocol/codex-acp` | +| Devin CLI | `devin acp` | +| OpenCode / Cursor / goose | `opencode acp` / `cursor-agent acp` / `goose acp` | + +LM Studio, Ollama and other OpenAI-compatible endpoints are **not** ACP; they are served by +our own agent, `apps/acp-openai-agent`. + +## Slash commands + +Push-only: handle `session/update` with `sessionUpdate === 'available_commands_update'` and +treat each notification as a **complete replacement**. There is no request to fetch them. +Invoke a command by sending its text as an ordinary prompt: `[{ type: 'text', text: '/usage' }]`. +Commands that need an interactive TUI are not advertised and would be forwarded to the model +as plain text. + +## Content blocks + +```ts +{ type: 'text', text } // baseline +{ type: 'image', mimeType: 'image/png', data: base64 } // needs promptCapabilities.image +{ type: 'resource', resource: { uri, mimeType, text } } // needs promptCapabilities.embeddedContext +{ type: 'resource_link', uri: 'file:///abs/path', name } // baseline +``` + +Page/selection context goes in `resource` blocks, not concatenated into the text. + +## History + +`session/list` for listing, replay via v1 `session/load` (needs `agentCapabilities.loadSession`) +or v2 `session/resume` + `replayFrom: { type: 'start' }`. The agent replays the conversation as +`session/update` notifications; reconcile by `messageId` / `toolCallId` to avoid duplicates. +Support varies per agent — Copilot CLI has `loadSession: true`, many agents have nothing. + +## Probing a real agent (debugging) + +Use the SDK's own example agent for credential-free plumbing tests: + +``` +node node_modules/@agentclientprotocol/sdk/dist/examples/agent.js +``` + +For a real agent, log every frame verbatim before assuming anything. Measured behaviour: + +- Copilot CLI: v1, `loadSession: true`, `promptCapabilities.image: true`, `audio: false`, + `embeddedContext: true`, auth method `copilot-login`. Unauthenticated, `session/new` fails + with `{ code: -32000, message: 'Authentication required' }` → run `copilot login` once. +- SDK example agent: v1, `loadSession: false`, `-32601` for `session/list`/`load`/`resume`, + `session/cancel` → `{ stopReason: 'cancelled' }` with trailing chunks after the cancel. + +## Testing + +Integration tests run against a **fixture agent** built with the SDK's `agent()` side, scripted +to emit specific update variants, request permissions, stall, or crash. Do not mock our own +event shapes — mock at the protocol boundary. diff --git a/docs/specs/acp/01-research.md b/docs/specs/acp/01-research.md new file mode 100644 index 0000000..b2a12f1 --- /dev/null +++ b/docs/specs/acp/01-research.md @@ -0,0 +1,230 @@ +# 01 — Research: ACP protocol and agent ecosystem + +Status: complete for the decisions in [adr/](./adr/). Sources are linked inline; a full +local mirror of the ACP docs (v1 + v2 markdown) was used while writing this. + +## 1. What ACP is + +- JSON-RPC 2.0, UTF-8, **newline-delimited JSON** messages. +- Transports: **stdio** (the Client launches the Agent as a subprocess — the recommended + and universally supported one) and TCP/HTTP variants offered by individual agents. + Streamable HTTP is still a draft proposal in the spec. + +- Content model is MCP's `ContentBlock`, so `text`, `image`, `audio`, `resource` + (embedded) and `resource_link` blocks flow unchanged between MCP tools and ACP. +- Markdown is the default rendering format for user-facing text. + +### 1.1 Method surface (what we will implement as a Client) + +Agent methods we call: `initialize`, `authenticate` (v1) / `auth/login` + `auth/logout` (v2), +`session/new`, `session/load` (v1) / `session/resume` (v2), `session/list`, `session/close`, +`session/prompt`, `session/set_config_option`, `session/delete`, and the +`session/cancel` notification. + +Client methods we must implement (agent → us): `session/update` (notification), +`session/request_permission`, plus optional `fs/read_text_file`, `fs/write_text_file`, +`terminal/*` in v1 and `elicitation/create` in v2. + +`session/update` variants carry everything the UI renders: + +| Variant | Renders as | +| --- | --- | +| `user_message` / `user_message_chunk` | the echoed user turn (source of truth for `messageId`) | +| `agent_message` / `agent_message_chunk` | streamed assistant text | +| `agent_thought` / `agent_thought_chunk` | collapsible reasoning | +| `tool_call` (v1) / `tool_call_update` (upsert) | tool cards: `kind` (read/edit/delete/move/search/execute/think/fetch/other), `status` (pending/in_progress/completed/failed/cancelled), `content`, `locations`, `rawInput`/`rawOutput` | +| `tool_call_content_chunk` (v2) | appended tool output | +| `terminal_update` / `terminal_output_chunk` (v2) | display-only terminal with base64 byte chunks and exit status | +| `plan` (v1) / `plan_update` (v2) | agent plan / todo list with per-entry status + priority | +| `available_commands_update` | **slash commands**: `name`, `description`, optional `input.hint` | +| `config_option_update` | model / mode / reasoning selectors (complete state each time) | +| `session_info_update` | session title + metadata (auto-generated titles) | +| `usage_update` | context tokens used/size and cumulative cost | + +### 1.2 Prompt lifecycle + +- v1: `session/prompt` **blocks for the whole turn** and its response carries `stopReason` + (`end_turn`, `max_tokens`, `max_turn_requests`, `refusal`, `cancelled`). +- v2: `session/prompt` returns `{}` as soon as the prompt is *accepted*; the turn is + reported by `state_update` notifications (`running` / `idle` / `requires_action`) and the + stop reason arrives on the idle `state_update`. +- Cancellation is the `session/cancel` notification in both versions. The Client should + pre-mark unfinished tool calls as `cancelled` and must answer pending permission + requests with the `cancelled` outcome. + +### 1.3 Slash commands (a first-class protocol feature) + +Agents advertise commands with the `available_commands_update` notification (push only, +there is no request to fetch them), re-sent as a **complete replacement** whenever the +set changes. Commands are invoked by sending the literal text `"/name args"` as a normal +text content block in `session/prompt` — no special method. Copilot CLI documents exactly +this behaviour and lists `/compact`, `/context`, `/usage`, `/env`, `/model`, `/mcp`, +`/plan`, `/review`, `/research`, `/session`, `/rename`, plus one command per enabled skill; +commands that need an interactive TUI (`/diff`, `/login`, `/theme`, …) are *not* advertised +and would be forwarded to the model as plain text. + + +### 1.4 Images and other content + +- Prompt content beyond `text` and `resource_link` is capability-gated by the agent's + `promptCapabilities`: `image`, `audio`, `embeddedContext`. +- Images are `{ type: "image", mimeType, data: , uri? }` — base64 in the JSON-RPC + message, so message size (and our 50 MB body limit / image pipeline) matters. +- Page context, file context and @-mentions should be sent as embedded + `resource` blocks (`{ uri, mimeType, text }`), which is what the spec recommends for + context the agent cannot read itself — this is the natural home for DevMentorAI's + browser-context payloads. +- A Client **MUST NOT** send a content type the agent did not advertise, so every content + path needs a documented fallback (see R-030..R-033). + +### 1.5 History + +- `session/list` returns `SessionInfo[]` (`sessionId`, `cwd`, `title`, `updatedAt`, `_meta`) + with opaque cursor pagination — but only for agents that keep sessions. +- Replay: v1 `session/load` (gated by the `loadSession` capability) or v2 + `session/resume` with `replayFrom: { type: "start" }`; the agent replays the whole + conversation as `session/update` notifications before answering the request. +- Consequence: history *can* come from the agent, but support is per-agent and optional, + and each agent has its own session store. A local index/cache is still needed for + cross-agent listing and for offline rendering (see ADR-0004). + +### 1.6 Configuration, modes, models + +v2 removed the dedicated modes API; modes, **model selection**, and reasoning/thinking +level are all `ConfigOption`s (`select` or `boolean`, with `category` hints `mode`, +`model`, `model_config`, `thought_level`). The Client sets them with +`session/set_config_option` and always receives the *complete* option state back. This +replaces DevMentorAI's bespoke model catalog and reasoning-effort plumbing. + +### 1.7 Authentication + +`initialize` returns `authMethods`; a non-empty list means the agent implements +`authenticate` (v1) / `auth/login` + `auth/logout` (v2). Agents may also reject work with +an `auth_required` error at any time. The ACP registry only lists agents that return valid +`authMethods`. Some agents additionally accept credentials via environment variables +(e.g. Copilot CLI BYOK via `COPILOT_PROVIDER_*`, Devin CLI via `WINDSURF_API_KEY` or +`devin auth login`). + +## 2. Protocol version reality check (drives ADR-0001) + +- The published **stable** schema is **v1**. v2 is labelled *draft*: the docs say to gate + it behind version negotiation and feature flags until it stabilises, and to keep serving + v1 peers. +- The official TypeScript SDK `@agentclientprotocol/sdk@1.3.0` exports + `PROTOCOL_VERSION = 1` from its main entrypoint; v2 lives behind + `@agentclientprotocol/sdk/experimental/v2` and its own docs warn the wire format may + change incompatibly in any release. +- Therefore: implement **v1 first**, negotiate per connection, and keep the v2 surface + behind a flag with a normalisation layer that both versions map onto. + +### 2.1 Spike evidence (measured, not assumed) + +A throwaway ACP Client (SDK `client()` + `ndJsonStream` over a spawned subprocess) was run +against two agents: + +**SDK example agent** (`dist/examples/agent.js`, no credentials): negotiated +`protocolVersion: 1`, `agentCapabilities: { loadSession: false }`. Observed update order: +`agent_message_chunk` → `tool_call` → `tool_call_update`. `session/request_permission` +arrived twice and was answered `{ outcome: { outcome: "selected", optionId: "allow" } }`. +`session/list`, `session/load` and `session/resume` all returned JSON-RPC `-32601` +(*Method not found*) — i.e. **unsupported methods are ordinary JSON-RPC errors**, so the +host must key behaviour off advertised capabilities rather than trying and catching. +`session/cancel` mid-turn produced `{ "stopReason": "cancelled" }` on the *prompt response* +(v1 semantics) and further `agent_message_chunk` notifications arrived **after** the cancel +was sent — the client must keep accepting updates post-cancel. + +**Copilot CLI** (`copilot --acp --stdio`, binary resolved from the existing pnpm install): +negotiated `protocolVersion: 1` with `loadSession: true`, +`promptCapabilities: { image: true, audio: false, embeddedContext: true }`, MCP HTTP/SSE +support, a session-list capability, and one auth method +`{ id: "copilot-login", description: "Run \`copilot login\` in the terminal" }`. +Without credentials, `session/new` failed with +`{ "code": -32000, "message": "Authentication required" }`, so nothing past the handshake +could be exercised. This answers **Q1** (Copilot = v1 today, images and embedded context +supported, history replay available) and confirms the auth flow is out-of-band: the user +runs `copilot login` once, and our UI must surface that as an actionable error. + +## 3. Agent matrix + +Machine-readable catalog: `https://cdn.agentclientprotocol.com/registry/v1/latest/registry.json` +(38 agents at time of writing, schema documented in the registry repo). Each entry has +`id`, `name`, `version`, `description`, `repository`, `icon`, and a `distribution` block — +either `npx` (`package`, `args`, `env`) or `binary` (per-platform `archive`, `cmd`, `args`, +`sha256`). This is exactly the metadata our agent catalog needs, including checksums. + +Agents relevant to the user's request: + +| Agent | Launch | Notes | +| --- | --- | --- | +| GitHub Copilot CLI | `npx @github/copilot --acp [--stdio\|--port N]` | Public preview since 2026-01-28. Registry entry `github-copilot-cli`. Tool filtering / reasoning effort are **server-start flags**, not per-session — a behavioural regression vs today's SDK (see R-021). BYOK via `COPILOT_PROVIDER_*` can run without GitHub login. | +| Claude (Claude Code / Agent SDK) | `npx @agentclientprotocol/claude-agent-acp` | Official adapter; the old `@zed-industries/claude-agent-acp` is deprecated. | +| Gemini CLI | `npx @google/gemini-cli --acp` | Native flag. | +| Codex CLI | `npx @agentclientprotocol/codex-acp` | Adapter. | +| OpenCode | `opencode acp` (binary, per-platform archive + sha256) | Native. | +| Cursor | `cursor-agent acp` (binary archive) | Native. | +| Devin CLI | `devin acp` (binary archive) | Native, stdio only. Advertises its full slash-command set over ACP; credentials from `devin auth login` / `WINDSURF_API_KEY` / the ACP `authenticate` request. | +| goose, Kimi, Qwen, Factory Droid, Qoder, Kiro, Amp, Cline, Junie, … | registry | Free with the same catalog mechanism — no per-agent code. | +| **LM Studio / Ollama / any OpenAI-compatible endpoint** | **no native ACP** | Confirmed: LM Studio exposes an OpenAI-compatible + MCP API, not ACP. Options: a third-party bridge (`acp-bridge`) or ship our own tiny ACP *agent* that fronts an OpenAI-compatible endpoint. See ADR-0006. | + +Consequence: supporting "as many agents as Devin Desktop" needs **zero per-agent code** for +registry agents — only a catalog, a launcher, and a strictly capability-driven UI. + +## 4. What the existing `feat/acp` branch actually is + +Audited `origin/feat/acp` (37 commits, 78 files, +5852/−923): despite the name it contains +**no ACP** — no `@agentclientprotocol/sdk`, no JSON-RPC framing, no ACP types. It is a +*multi-provider* abstraction: `llm-provider.service.ts` plus `providers/*` adapters +(Copilot wrapper, `cli-command.provider.ts` spawning CLIs and scraping stdout with +synthetic Copilot-shaped events, `openai-compatible.provider.ts` for Ollama/LM +Studio/OpenRouter/Groq), an encrypted credential store, `routes/providers.ts`, and +extension UI for provider-grouped models, provider badges and recovery states. Its +abstraction boundary is still typed in Copilot SDK event shapes and each adapter +re-implements its own history. + +Keep from it: the encrypted credential store, the OpenAI-compatible client (as the guts of +our own ACP agent for local models), the provider-grouped model UI patterns and +availability/recovery UX. Discard: the CLI stdout-scraping adapters, the Copilot-shaped +event interface, and per-adapter history reconstruction. + +## 5. Current DevMentorAI coupling (what the refactor has to move) + +- `apps/backend/src/services/copilot.service.ts` (~1.1k lines): SDK client, session map, + send/stream, retries, attachments, permissions auto-approved (`approveAll`), mock mode. +- `apps/backend/src/routes/chat.ts` (~710 lines): SDK event → SSE translation + (`message_delta`, `message_complete`, `tool_start`, `tool_complete`, `error`, `done`), + 120 s turn / 30 s idle timeouts. +- `routes/sessions.ts`, `account.ts`, `models.ts`, `health.ts`, `tools.ts`: Copilot-specific + lifecycle, auth/quota, model catalog, health and DevOps tools. +- SQLite (`~/.devmentorai/devmentorai.db`, raw `better-sqlite3`): `sessions`, `messages`, + `session_contexts` — our own history, independent of the agent. +- Extension: `services/api-client.ts` (fetch + SSE parsing, no WebSocket), `hooks/useChat.ts`, + `ChatView.tsx`, `MessageBubble.tsx`, quick actions in `background.ts`, writing assistant. + No slash-command, permission, plan or diff UI exists today. + +## 6. Findings that change the product, not just the code + +1. **Permissions become real.** Today every tool call is auto-approved. ACP agents expect a + Client that can ask the user; a permission UI is mandatory, and auto-approve must + become an explicit, per-agent user setting. +2. **A workspace directory becomes mandatory.** `session/new` requires an absolute `cwd` + and all protocol paths are absolute. A browser extension has no project directory, so + DevMentorAI must own a configurable workspace root and treat it as a security boundary. +3. **Agents must be installed/launchable locally.** The backend must spawn them (npx or + downloaded binary). Copilot no longer arrives "for free" with the SDK dependency; it is + `npx @github/copilot --acp` plus its own auth. +4. **The UI must be capability-driven.** Models, modes, reasoning, slash commands, images + and history availability all vary per agent and per session, pushed at runtime. +5. **Our message store stops being the source of truth** for agents that support replay, + but cannot be deleted outright (cross-agent listing, offline, agents without replay). + +## 7. Open questions + +- ~~**Q1** Does the installed Copilot CLI negotiate ACP v1 or v2, and what + `promptCapabilities` does it advertise?~~ **Answered by the spike (§2.1):** v1, + `loadSession: true`, `image: true`, `audio: false`, `embeddedContext: true`. +- **Q2** Which of the *other* target agents support `session/load`/`session/resume` replay + in practice? Copilot does; the SDK example does not. Determines how much of our local + history we can retire (R-040). Measured per agent during Phase 5. +- **Q3** Do we bundle any agent (e.g. pin `@github/copilot`) or always resolve at runtime + from the registry with a user-visible install step? +- **Q4** Native messaging host: keep it as-is, or route it through the same ACP host? diff --git a/docs/specs/acp/02-architecture.md b/docs/specs/acp/02-architecture.md new file mode 100644 index 0000000..927bd84 --- /dev/null +++ b/docs/specs/acp/02-architecture.md @@ -0,0 +1,183 @@ +# 02 — Target architecture + +## 1. Shape of the system + +``` +┌──────────────────────────── browser ────────────────────────────┐ +│ extension (WXT) │ +│ side panel chat · quick actions · writing assistant │ +│ slash-command palette · tool-call cards · permission prompts │ +│ plan view · config selectors (model/mode/reasoning) │ +└───────────────▲─────────────────────────────────────────────────┘ + │ WebSocket, JSON-RPC 2.0 (bidirectional) + │ ui/* requests · session/update fan-out +┌───────────────▼─────────────────────────────────────────────────┐ +│ backend (Fastify) ── the ACP HOST ── │ +│ ws gateway ──► session router ──► AcpConnection (1 per agent) │ +│ │ │ │ +│ │ └─ @agentclientprotocol/sdk +│ │ client() + ndJsonStream +│ ├─ agent catalog (registry.json + custom) │ +│ ├─ agent launcher (spawn npx / binary / TCP)│ +│ ├─ credential store (encrypted, env-only) │ +│ ├─ workspace manager (cwd policy) │ +│ └─ session index + display cache (SQLite) │ +└───────────────▲─────────────────────────────────────────────────┘ + │ stdio NDJSON (one subprocess per agent) + ┌────────────┴───────────┬──────────────┬─────────────────┐ + │ copilot --acp --stdio │ gemini --acp │ devin acp … │ + └────────────────────────┴──────────────┴─────────────────┘ + │ + apps/acp-openai-agent (ours) + ACP agent fronting LM Studio / Ollama / + any OpenAI-compatible endpoint +``` + +Key property: **the backend is an ACP Client and nothing else.** There is no +provider abstraction, no per-vendor adapter, and no Copilot-specific code path. Adding an +agent is a catalog entry (or a user-supplied command line), never new code. + +Rationale for each boundary: [ADR-0002](./adr/0002-backend-as-acp-host.md) (browser cannot +spawn processes), [ADR-0003](./adr/0003-websocket-jsonrpc-ui-transport.md) (SSE cannot carry +agent→UI *requests* such as permissions), [ADR-0006](./adr/0006-local-models-via-own-acp-agent.md). + +## 2. Backend modules + +`apps/backend/src/acp/` + +| Module | Responsibility | +| --- | --- | +| `catalog/agent-catalog.ts` | Built-in curated agents + cached ACP registry (`registry.json`) + user-defined custom agents. Resolves a platform-specific `LaunchSpec { kind: npx\|binary\|command\|tcp, cmd, args, env, sha256? }`. | +| `catalog/agent-installer.ts` | Optional download/extract of binary distributions into `~/.devmentorai/agents///`, sha256-verified; npx agents resolve lazily. | +| `launcher.ts` | Spawns the agent, wires `stdin`/`stdout` into `ndJsonStream`, keeps `stderr` in a bounded ring buffer for diagnostics, owns process lifecycle (exit, crash, kill on idle timeout, graceful shutdown). | +| `connection.ts` | One `AcpConnection` per running agent: `initialize` + version negotiation, capability record, `authenticate`/`auth/login`, and all Client-side handlers (`sessionUpdate`, `requestPermission`, `elicitation/create` when v2). | +| `normalize/v1.ts`, `normalize/v2.ts` | Map version-specific wire shapes onto one internal `AcpEvent` union (see §4). All version differences are confined here. | +| `session-manager.ts` | DevMentorAI session ⇄ `{ agentId, acpSessionId, cwd, protocolVersion, capabilities, configOptions }`. `session/new`, replay/resume, `close`, `cancel`, `set_config_option`, `prompt`. | +| `permissions.ts` | Correlates `session/request_permission` with a UI prompt; applies per-agent policy (`ask` / `allow_always` remembered choices / `deny`); auto-answers `cancelled` on cancellation. | +| `content.ts` | Builds `ContentBlock[]` for prompts from user text, images, page context and selections — strictly gated by `promptCapabilities`, with documented degradation. | +| `workspace.ts` | Resolves and validates the absolute `cwd` for a session; enforces the workspace root as a boundary. | +| `credentials.ts` | Encrypted at-rest store (`~/.devmentorai/credentials`, `0600`); values are only ever injected into an agent process `env`, never returned over HTTP/WS. | +| `errors.ts` | JSON-RPC / process failures → typed `AcpError` (§5). | + +`apps/backend/src/acp/` is the only place allowed to import `@agentclientprotocol/sdk`. + +## 3. UI transport (extension ⇄ backend) + +WebSocket at `/acp` carrying JSON-RPC 2.0 both ways. + +UI → backend (requests): `ui/agents.list`, `ui/agents.install`, `ui/agents.auth`, +`ui/sessions.list`, `ui/sessions.create`, `ui/sessions.resume`, `ui/sessions.close`, +`ui/prompt`, `ui/cancel`, `ui/setConfigOption`, `ui/permission.respond`, +`ui/elicitation.respond`. + +Backend → UI (notifications): `ui/event` wrapping the normalised `AcpEvent`s, plus +`ui/agentStatus` (process up/down/crashed, auth state) and `ui/error`. + +Backend → UI (requests, answered by the UI): `ui/permission.request`, +`ui/elicitation.create`. These are why the transport must be bidirectional: SSE cannot +carry them, and a turn *blocks* on the answer. + +Reconnection: the UI resubscribes per session and requests a replay of the current turn +from the backend's in-memory turn buffer, so a panel close/reopen mid-turn does not lose +content. REST endpoints are kept only for non-streaming utilities (health, images, +catalog) — see [ADR-0003](./adr/0003-websocket-jsonrpc-ui-transport.md). + +## 4. Internal event model (`packages/shared`) + +One union, version-agnostic, upsert-shaped — the UI reduces it into a session view model: + +```ts +type AcpEvent = + | { type: 'message'; role: 'user' | 'assistant' | 'thought'; messageId: string; + content: ContentBlock[]; mode: 'replace' | 'append' } + | { type: 'tool_call'; toolCallId: string; title?: string; kind?: ToolKind; + status?: ToolCallStatus; content?: ToolCallContent[]; + locations?: ToolCallLocation[]; raw?: { input?: unknown; output?: unknown }; + mode: 'replace' | 'append' } + | { type: 'terminal'; terminalId: string; command?: string; cwd?: string; + output?: { data: string; mode: 'snapshot' | 'append' }; + exitStatus?: { exitCode?: number | null; signal?: string | null } } + | { type: 'plan'; planId?: string; entries: PlanEntry[] } + | { type: 'commands'; commands: AvailableCommand[] } // complete replacement + | { type: 'config'; options: ConfigOption[] } // complete state + | { type: 'session_info'; title?: string | null; updatedAt?: string | null } + | { type: 'usage'; used: number; size: number; cost?: { amount: number; currency: string } } + | { type: 'state'; state: 'running' | 'idle' | 'requires_action'; + stopReason?: StopReason } + | { type: 'error'; error: AcpErrorPayload }; +``` + +Normalisation rules that hide the v1/v2 split: + +- v1 `tool_call` and `tool_call_update` both become `tool_call` with `mode: 'replace'`; + v2 `tool_call_content_chunk` becomes `mode: 'append'`. +- v1 has no `state_update`: the host **synthesises** `state: running` when it sends + `session/prompt` and `state: idle` with the `stopReason` from the prompt *response*. + In v2 the notifications are passed through and the prompt response is ignored. +- v1 `plan` → `plan` with no `planId`; v2 `plan_update` keeps it. +- Chunk variants (`*_message_chunk`) become `message` with `mode: 'append'`; whole-message + updates use `mode: 'replace'`, keyed by `messageId` (chunk-then-replace ordering per spec). +- Unknown `sessionUpdate` variants, unknown content types, unknown `kind`/`status` values + and `_`-prefixed extensions are **preserved and rendered generically**, never dropped. + +## 5. Error taxonomy + +| `AcpError.code` | Cause | UI treatment | +| --- | --- | --- | +| `agent_not_installed` | catalog entry has no resolvable binary/npx | "Install" action | +| `agent_launch_failed` | spawn/ENOENT/non-zero exit at startup | show last stderr lines + retry | +| `agent_crashed` | process exited mid-session | mark session degraded, offer resume | +| `protocol_version_unsupported` | agent negotiated a version we don't support | block with explanation | +| `auth_required` | `authMethods` present and unauthenticated, or `-32000`-class auth error | actionable instructions (e.g. run `copilot login`) or `auth/login` button | +| `capability_unsupported` | UI asked for something not advertised (image, replay, config) | disable control + tooltip; never send it anyway | +| `permission_denied` | user rejected | tool card shows rejected, turn continues | +| `cancelled` | user cancelled | not an error state in the UI | +| `agent_error` | any other JSON-RPC error from the agent | surfaced verbatim (code + message) with a copy action | + +Rules: JSON-RPC `-32601` means *unsupported*, not *broken* (spike §2.1) — capability checks +come first, errors are the backstop. Errors are persisted with the turn so a reopened +session shows why it stopped. + +## 6. Data model + +`sessions` gains: `agent_id`, `acp_session_id`, `cwd`, `protocol_version`, +`capabilities_json`, `config_options_json`, `title_source` (`agent` | `local`), +`replay_supported`. `model`/`reasoning_effort`/`custom_agent` are superseded by +`config_options_json` and removed after the migration window. + +New `agents` table: installed/known agents with `id`, `source` (`builtin` | `registry` | `custom`), +`version`, `launch_json`, `auth_state`, `last_error`. + +`messages` and `session_contexts` are retained as a **display cache** (fast paint, offline, +and the only history for agents without replay), explicitly not the source of truth. Cache +entries are keyed by `messageId`/`toolCallId` so a replay reconciles rather than duplicates. +See [ADR-0004](./adr/0004-history-acp-first-with-local-cache.md). + +## 7. Content mapping (prompts) + +| DevMentorAI input | ACP block | Requires | Degradation | +| --- | --- | --- | --- | +| chat text | `text` | baseline | — | +| slash command | `text` = `"/name args"` | advertised command | send as plain text with a warning | +| screenshot / pasted image | `image` (base64, after the existing sharp pipeline) | `promptCapabilities.image` | drop the image, insert a note, warn in UI | +| page context / selection / DOM extract | `resource` (`uri: devmentor://tab/…`, `mimeType`, `text`) | `promptCapabilities.embeddedContext` | inline as fenced text in the `text` block | +| referenced file in the workspace | `resource_link` | baseline | — | +| audio | `audio` | `promptCapabilities.audio` | unsupported today | + +## 8. Security model + +- Agents are local subprocesses with the user's privileges. The workspace root is the + declared `cwd` boundary; the UI always shows which directory a session runs in. +- Permissions are user decisions by default. Auto-approve is an explicit per-agent opt-in + and is surfaced in the session header — no silent `approveAll` as today. +- Credentials: prefer the agent's own out-of-band login (`copilot login`, `devin auth login`) + or ACP `auth/login`. Where an agent needs env keys, they live in the encrypted store, + are injected into that agent's process env only, and are never echoed by any API. +- The WS gateway stays bound to loopback and validates the extension origin. +- Agent stderr is captured for diagnostics but treated as untrusted text in the UI. + +## 9. What gets deleted + +`copilot.service.ts`, the Copilot SDK dependencies, the SSE chat translation layer, the +bespoke model catalog + reasoning-effort plumbing, Copilot auth/quota routes, and mock mode +(replaced by a test-only ACP agent fixture that speaks the real protocol). diff --git a/docs/specs/acp/03-requirements.md b/docs/specs/acp/03-requirements.md new file mode 100644 index 0000000..9da242c --- /dev/null +++ b/docs/specs/acp/03-requirements.md @@ -0,0 +1,301 @@ +# 03 — Requirements and acceptance criteria + +Format: each requirement has an ID, a statement, acceptance criteria (AC) in +Given/When/Then, and a verification method — `unit`, `integration` (backend against a +real ACP agent fixture), `contract` (shared types / schema), `e2e` (extension via +Playwright), or `manual` (recorded). + +Test doubles: integration tests run against a **fixture agent** built with the ACP SDK's +agent side (`agent()`), which speaks the real protocol and can be scripted to emit any +update variant, request permissions, fail, hang, crash, or advertise arbitrary +capabilities. No hand-written mock of our own event shapes is acceptable. + +Priority: **P0** = MVP (Phases 1–4), **P1** = multi-agent completeness (Phases 5–7), +**P2** = later. + +--- + +## A. Protocol layer + +**R-001 (P0)** The backend acts as an ACP Client using `@agentclientprotocol/sdk`, over +NDJSON on the agent's stdio. +- AC1 Given a launch spec, when a session is created, then `initialize` → `session/new` → + `session/prompt` complete and updates stream back. `integration` +- AC2 `@agentclientprotocol/sdk` is imported only from `apps/backend/src/acp/**`. `unit` (lint rule / import test) + +**R-002 (P0)** Protocol version is negotiated per connection: v1 is supported and used by +default; v2 is only used when the agent negotiates it and the `ACP_V2` flag is enabled. +- AC1 Given an agent answering `protocolVersion: 1`, then the v1 surface is used. `integration` +- AC2 Given an agent answering an unsupported version, then the connection fails with + `protocol_version_unsupported` and no session is created. `integration` +- AC3 The negotiated version is recorded on the session and visible in diagnostics. `integration` + +**R-003 (P0)** Both versions are normalised into the single `AcpEvent` union; no +version-specific shape escapes `acp/normalize/*`. +- AC1 For each documented v1 `sessionUpdate` variant, a golden-file test maps wire JSON → + `AcpEvent`. `unit` +- AC2 Same for the v2 variants under the flag. `unit` +- AC3 An unknown `sessionUpdate` variant, unknown content type, unknown tool `kind`/`status` + and an `_`-prefixed field are preserved as generic events, not dropped or thrown on. `unit` + +**R-004 (P0)** In v1 the host synthesises turn state: `running` on prompt dispatch, `idle` +with the response's `stopReason` on completion. +- AC1 A completed turn yields exactly one `state: idle` with `stopReason: end_turn`. `integration` +- AC2 `stopReason` values `max_tokens`, `refusal`, `cancelled` propagate unchanged. `integration` + +**R-005 (P0)** Capability-first: the host never calls a method or sends content the agent +did not advertise. +- AC1 Given `loadSession: false`, then no `session/load` is ever sent and the UI exposes no + replay action. `integration` +- AC2 Given `promptCapabilities.image: false`, then no `image` block is sent (R-031). `integration` +- AC3 A `-32601` from an agent is reported as `capability_unsupported`, not a crash. `integration` + +**R-006 (P0)** Cancellation: `session/cancel` is sent, updates arriving after it are still +accepted, unfinished tool calls are marked `cancelled`, and pending permission requests are +answered with the `cancelled` outcome. +- AC1 Given a turn cancelled mid-tool-call, then the turn ends with `stopReason: cancelled` + and no unhandled promise/`ECONNRESET` appears in logs. `integration` +- AC2 Post-cancel `agent_message_chunk`s are appended without error. `integration` + +**R-007 (P0)** One agent process per agent instance; the host multiplexes sessions over it +and owns its lifecycle (spawn, stderr ring buffer, exit detection, graceful shutdown). +- AC1 Two concurrent sessions on one agent interleave without cross-talk (updates land on + the right session). `integration` +- AC2 Killing the agent process surfaces `agent_crashed` on every affected session within 1 s + and does not take the backend down. `integration` +- AC3 Backend shutdown terminates all agent processes; no orphans remain. `integration` + +**R-008 (P1)** TCP transport is supported for agents that offer it (e.g. +`copilot --acp --port N`). +- AC1 A session over TCP behaves identically to stdio for prompt/stream/cancel. `integration` + +--- + +## B. Agent catalog, install, auth + +**R-010 (P0)** An agent catalog exposes built-in curated agents, the ACP registry +(`registry.json`, cached with a TTL and an offline fallback), and user-defined custom +agents (`cmd` + `args` + `env`). +- AC1 `ui/agents.list` returns id, name, description, icon, version, source, install state, + auth state and platform availability. `integration` +- AC2 With the network unavailable, the cached/built-in catalog is still returned. `unit` +- AC3 A custom agent defined by the user can be launched and used end-to-end. `integration` + +**R-011 (P1)** Binary distributions can be installed on demand into +`~/.devmentorai/agents///`, verified against the registry `sha256`. +- AC1 Given a checksum mismatch, then installation fails, nothing is executed, and the + error is `agent_launch_failed` with the mismatch detail. `unit` +- AC2 Install progress is streamed to the UI. `e2e` + +**R-012 (P0)** Launch specs are resolved per platform (`linux-x86_64`, `darwin-aarch64`, +`windows-x86_64`, …); unsupported platforms are reported, never spawned blindly. +- AC1 On a platform absent from the entry, `ui/agents.list` marks it unavailable with a reason. `unit` + +**R-013 (P0)** Authentication states are first-class: `authMethods` from `initialize` are +exposed; an auth error becomes `auth_required` with the agent's own instructions. +- AC1 Given Copilot CLI without credentials, then the session attempt yields `auth_required` + and the UI shows "Run `copilot login`" (the agent's `authMethods[].description`). `integration` +- AC2 Given an agent with a protocol login method, then `authenticate`/`auth/login` is + invoked from the UI and, on success, the session proceeds without restarting the process. `integration` +- AC3 After authenticating, the agent's auth state is reflected in the catalog. `e2e` + +**R-014 (P1)** Secrets needed as env vars are stored encrypted at `~/.devmentorai/credentials` +(`0600`) and injected only into the target agent's process env. +- AC1 No API response, log line or error message ever contains a stored secret value. `unit` +- AC2 The file is unreadable by other users and unusable without the local key. `unit` + +**R-015 (P0)** Every session declares an absolute workspace `cwd`, defaulting to a +configurable workspace root; the UI always shows it. +- AC1 `session/new` is rejected client-side if `cwd` is missing, relative, or outside the + configured root. `unit` +- AC2 The session header displays the effective `cwd`. `e2e` + +--- + +## C. Chat and streaming + +**R-020 (P0)** The extension talks to the backend over a WebSocket JSON-RPC channel that +supports backend→UI *requests*. +- AC1 A prompt streams assistant text incrementally into the panel. `e2e` +- AC2 Closing and reopening the panel mid-turn restores the in-flight turn from the buffer + without duplicated or lost text. `e2e` +- AC3 With the backend down, the UI shows a disconnected state and retries with backoff. `e2e` + +**R-021 (P0)** Session configuration is driven exclusively by ACP config options (model, +mode, reasoning/thought level); DevMentorAI ships no hardcoded model list. +- AC1 Given `config_option_update`, then the UI renders exactly those options with current + values. `e2e` +- AC2 Selecting one calls `session/set_config_option` and the UI adopts the returned + complete state. `integration` +- AC3 Given an agent that advertises no model option (e.g. Copilot ACP, where reasoning and + tool filtering are fixed at server start), then no model selector is shown and the + limitation is explained in the UI. `e2e` + +**R-022 (P0)** Streaming turn timeouts are configurable and no longer Copilot-specific; +an idle stall produces a recoverable error, not a hung UI. +- AC1 Given a fixture agent that stalls past the idle timeout, then the turn ends with a + timeout error and the session stays usable. `integration` + +**R-023 (P0)** Usage and session info are surfaced: token/context usage and +agent-generated titles. +- AC1 `usage_update` updates a context indicator. `e2e` +- AC2 `session_info_update` renames the session unless the user set a local title. `integration` + +--- + +## D. Slash commands + +**R-025 (P0)** Commands advertised via `available_commands_update` are stored per session as +a complete replacement of any previous list. +- AC1 Two consecutive notifications leave exactly the second list. `unit` + +**R-026 (P0)** Typing `/` in the composer opens a palette of the advertised commands with +name, description and `input.hint`, filterable, keyboard-navigable. +- AC1 Given a session advertising `/plan` and `/usage`, then both appear with their + descriptions and are insertable via keyboard. `e2e` +- AC2 Given a session advertising none, then no palette appears. `e2e` +- AC3 The palette updates live when a new `available_commands_update` arrives. `e2e` + +**R-027 (P0)** A command is sent as an ordinary prompt whose text is `"/name args"` in a +single text block, and its output renders like any other turn. +- AC1 `/usage` produces the agent's output without a model turn. `integration` +- AC2 A command may be combined with images/context blocks in the same prompt when the + agent supports them. `integration` + +**R-028 (P1)** Unadvertised commands are handled honestly: the UI warns that the text will +be sent to the model as plain text. +- AC1 Typing an unknown `/foo` shows the warning before sending. `e2e` + +--- + +## E. Content: images, context, files + +**R-030 (P0)** Images are sent as `image` blocks (base64 + `mimeType`) through the existing +resize/compress pipeline. +- AC1 A pasted screenshot reaches the agent as one `image` block and the agent's reply + references it. `integration` + `manual` (real agent) +- AC2 Oversized images are downscaled before encoding and the request stays under the body + limit. `unit` + +**R-031 (P0)** When `promptCapabilities.image` is false, the image control is disabled with +an explanation and no `image` block is ever sent. +- AC1 Fixture agent without image support: attaching is blocked and the outgoing prompt + contains no image block. `integration` + `e2e` + +**R-032 (P0)** Browser context (page text, selection, metadata) is sent as embedded +`resource` blocks with a stable `uri`, `mimeType` and `text`. +- AC1 Context-aware mode produces `resource` blocks, not a giant concatenated string. `integration` +- AC2 Without `embeddedContext`, the same content is inlined into the text block as fenced + sections and the UI says so. `integration` + +**R-033 (P1)** Workspace file references are sent as `resource_link`s. +- AC1 A referenced file appears as a `resource_link` with an absolute `file://` URI. `integration` + +--- + +## F. Tool calls, terminals, plans, permissions + +**R-035 (P0)** Tool calls render as upsert-keyed cards showing `kind`, `status`, title, +content and affected `locations`, with raw input/output available on demand. +- AC1 The `pending → in_progress → completed` sequence updates one card, never three. `e2e` +- AC2 An unknown `kind`/`status` renders generically. `unit` +- AC3 `failed` shows the failure content. `e2e` + +**R-036 (P0)** Diff content in tool calls renders as a readable before/after diff with the +file path. +- AC1 An edit tool call with `diff` content renders added/removed lines. `e2e` + +**R-037 (P1)** Terminal output renders as a live-appending block with exit status (v2 +`terminal_update` / `terminal_output_chunk`; v1 embedded terminal tool content). +- AC1 Appended chunks stream in order and the exit code is shown on completion. `integration` + +**R-038 (P0)** Agent plans render as a checklist with per-entry status/priority, updated in +place. +- AC1 Successive plan updates mutate the same list. `e2e` + +**R-039 (P0)** `session/request_permission` blocks the turn and prompts the user with the +agent's title, subject and options; the chosen `optionId` is returned; the pending request +is answered `cancelled` if the user cancels the turn or the session closes. +- AC1 The prompt shows the requested tool/subject and all offered options. `e2e` +- AC2 Rejecting returns the reject option and the tool card shows `rejected`; the session + survives. `integration` +- AC3 `allow_always` for a given agent+tool is remembered and later identical requests are + auto-answered, with that state visible and revocable in settings. `integration` +- AC4 Auto-approve-everything is **off by default** and only enabled explicitly per agent. `unit` +- AC5 No permission request is ever silently auto-approved. `integration` + +**R-040 (P1)** Elicitation (`elicitation/create`, v2 draft) renders a structured form when +the flag is on; otherwise the capability is not advertised. +- AC1 With the flag off, no elicitation capability is advertised. `unit` + +--- + +## G. History and sessions + +**R-045 (P0)** Session list shows sessions across all agents with agent identity, `cwd`, +title and last activity, sourced from the local index. +- AC1 Sessions created on two different agents both appear, correctly attributed. `integration` + +**R-046 (P1)** For agents that support replay (`loadSession` / `session/resume`), opening an +existing session replays the agent's history and the local cache is reconciled by +`messageId`/`toolCallId` instead of duplicated. +- AC1 A replayed session shows each message exactly once. `integration` +- AC2 Local-only cached content that the agent no longer has is retained and marked. `integration` + +**R-047 (P1)** For agents without replay, the local cache is displayed read-only and the UI +states that the agent cannot restore this conversation. +- AC1 Fixture agent with `loadSession: false`: history renders from cache with the notice, + and no replay call is made. `integration` + `e2e` + +**R-048 (P1)** `session/list` from an agent is reconciled with the local index: sessions +missing locally are adopted, locally-known sessions the agent dropped are marked stale. +- AC1 Both directions are handled without duplicates. `integration` + +**R-049 (P0)** Existing pre-migration sessions/messages remain readable after the upgrade. +- AC1 Given a DB written by the current release, when the new backend starts, then + migrations run and old sessions render read-only with an "imported (Copilot SDK)" marker. `integration` +- AC2 No destructive migration: `messages` rows are never deleted by the upgrade. `unit` + +--- + +## H. Feature parity (must not regress) + +**R-050 (P0)** Quick actions (explain/summarise/translate/…) run over ACP. +- AC1 Each quick action produces a streamed answer in the panel. `e2e` + +**R-051 (P0)** The writing assistant runs over ACP with inline replacement intact. +- AC1 Selecting text and applying a rewrite replaces it in the page. `e2e` + +**R-052 (P0)** Context-aware mode parity per R-032. +- AC1 A page-specific question is answered using page content. `e2e` + +**R-053 (P1)** Native messaging host keeps working (or its removal is decided in an ADR). +- AC1 The host round-trips a prompt through the ACP path. `integration` + +--- + +## I. Non-functional + +**R-060 (P0)** `pnpm lint`, `pnpm typecheck`, `pnpm test` and `pnpm build` pass on every PR; +no `any`/`as unknown as` in the ACP layer. +- AC1 CI green; a lint rule forbids `any` under `src/acp/**`. `unit` + +**R-061 (P0)** First streamed token arrives within 500 ms of the agent's first update +(host overhead only), and a 10-minute turn with 5k updates does not leak memory. +- AC1 Benchmark test on the fixture agent. `integration` + +**R-062 (P0)** Diagnostics: a per-agent log with the last N stderr lines, the negotiated +version, capabilities and the last error, downloadable from the UI. +- AC1 After a crash, the diagnostics view contains the failing stderr tail. `e2e` + +**R-063 (P1)** Optional protocol tracing (`ACP_TRACE=1`) writes redacted JSON-RPC traffic to +a file for bug reports. +- AC1 With tracing on, a session produces a trace file containing no credential values. `unit` + +**R-064 (P0)** Docs are updated with the migration: `docs/ARCHITECTURE.md`, a new +`docs/ACP.md` (supported agents, install, auth, troubleshooting), and README screenshots of +the new UI surfaces. +- AC1 Docs reference no Copilot-SDK-only concepts once Phase 8 lands. `manual` + +**R-065 (P0)** The Copilot SDK dependency is fully removed at the end of the migration. +- AC1 `@github/copilot*` appears in no `package.json` and no import. `unit` diff --git a/docs/specs/acp/04-roadmap.md b/docs/specs/acp/04-roadmap.md new file mode 100644 index 0000000..e44bf8e --- /dev/null +++ b/docs/specs/acp/04-roadmap.md @@ -0,0 +1,121 @@ +# 04 — Implementation roadmap + +Nine phases, each one or two reviewable PRs, each independently mergeable and leaving +`master` working. The Copilot SDK stays functional until Phase 8, behind a feature flag, so +the extension is never broken mid-migration. + +Flags: `ACP_ENABLED` (route chat through the ACP host), `ACP_V2` (allow v2 negotiation). +Default: `ACP_ENABLED=false` until Phase 4 exits, then true. + +--- + +### Phase 0 — Spec + spike ✅ (this PR) + +Deliverable: `docs/specs/acp/**` and a validated protocol spike (SDK example agent + +`copilot --acp --stdio` handshake, see 01-research §2.1). +Exit: the user approves scope, ADRs and requirement priorities. + +### Phase 1 — Protocol layer (backend only, no UI) + +Scope: `acp/launcher.ts`, `acp/connection.ts`, `acp/normalize/v1.ts`, `acp/errors.ts`, +`AcpEvent` in `packages/shared`, and the **fixture agent** test harness (an SDK-based +agent that can emit every update variant, request permissions, stall, and crash). +Requirements: R-001..R-007, R-012, R-060. +Exit: integration tests drive a full turn (text, tool call, permission, cancel, crash) +against the fixture agent and the golden-file normalisation tests pass. No UI change. + +### Phase 2 — Agent catalog, workspace, launch, auth surface + +Scope: `catalog/*`, `workspace.ts`, `credentials.ts` (ported from `feat/acp`), +`agents` table, `ui/agents.*` methods, `auth_required` plumbing with the agent's own +instructions. +Requirements: R-010, R-012..R-015, R-013 (Copilot login path), R-062. +Exit: `copilot --acp --stdio` and one npx agent (Gemini or Claude adapter) can be launched +and authenticated from the backend; an unauthenticated agent produces the actionable +`auth_required` error. + +### Phase 3 — WS gateway + extension chat over ACP (Copilot first) + +Scope: `/acp` WebSocket JSON-RPC gateway, turn buffer + reconnect replay, extension +`AcpClient` replacing `api-client`'s SSE path, `useChat` rewritten onto the reduced +`AcpEvent` view model, plain text streaming, cancel. +Requirements: R-020, R-022, R-023, R-045, R-049 (read-only legacy sessions). +Exit: with `ACP_ENABLED=true`, a Copilot ACP session streams into the side panel; with the +flag off, the old SDK path is untouched. Old sessions still render. + +### Phase 4 — The ACP-native UI (MVP completion) + +Scope: slash-command palette, config-option selectors (model/mode/reasoning), tool-call +cards with kinds/status/diffs, plan checklist, permission prompt with remembered +`allow_always`, generic renderer for unknown content, usage/context indicator, error cards +with actions. +Requirements: R-021, R-025..R-027, R-035, R-036, R-038, R-039, R-030..R-032. +Exit: Copilot ACP is fully usable — commands, images, page context, tools, permissions. +`ACP_ENABLED` defaults to true. **This is the MVP.** + +### Phase 5 — Multi-agent rollout + +Scope: registry-driven catalog UI (search/install/uninstall), binary installer with sha256, +per-agent capability matrix surfaced in the UI, capability-driven control disabling, TCP +transport, agent switching per session. +Requirements: R-008, R-011, R-028, R-033, R-037, plus measuring Q2 (replay support) per agent. +Exit: verified end-to-end on at least Copilot, Gemini CLI, Claude adapter, OpenCode and +Devin CLI, with a results table committed to `docs/ACP.md`. + +### Phase 6 — History: ACP-first with local cache + +Scope: replay via `session/load` / `session/resume`, reconciliation by +`messageId`/`toolCallId`, `session/list` sync, stale-session marking, cache demoted to a +display cache, retention policy. +Requirements: R-046..R-048. +Exit: a replay-capable agent restores history from the agent; a non-replay agent falls back +to the cache with a visible notice; no duplicate messages in either case. + +### Phase 7 — Local models as a real ACP agent + +Scope: `apps/acp-openai-agent` — our own ACP *agent* (SDK agent side) fronting any +OpenAI-compatible endpoint (LM Studio, Ollama, vLLM, OpenRouter), reusing the +`openai-compatible.provider.ts` logic from `feat/acp`: streaming, tool calling, and +capability advertisement. Registered as a built-in catalog entry with an endpoint/model +config. +Requirements: R-010 AC3 (as a built-in), plus the A/C/F sets applied to this agent. +Exit: an LM Studio model runs through the identical ACP path — the host contains zero +special-casing for it. + +### Phase 8 — Remove the Copilot SDK, clean up, document + +Scope: delete `copilot.service.ts`, the SSE chat translation, mock mode, the model catalog +and Copilot auth/quota routes; drop `@github/copilot*`; drop superseded session columns; +rewrite `docs/ARCHITECTURE.md`, add `docs/ACP.md`, refresh README. +Requirements: R-064, R-065. +Exit: no Copilot-specific code remains; all P0 requirements verified; CI green. + +### Phase 9 — v2 readiness (opt-in) + +Scope: `normalize/v2.ts`, `state_update`-driven lifecycle, `session/resume` with +`replayFrom`, `plan_update`, `terminal_update`, elicitation, behind `ACP_V2`. +Requirements: R-002 AC2, R-003 AC2, R-040. +Exit: a v2-capable agent works with the flag on and v1 agents are unaffected with it off. + +--- + +## Cross-cutting rules + +- Every PR states its requirement IDs and runs `pnpm lint && pnpm typecheck && pnpm test && pnpm build`. +- No phase merges with a red CI or an unverified P0 acceptance criterion. +- Migrations are additive; no user data is deleted before Phase 8, and even then only + superseded columns (never `messages` rows). +- The fixture agent from Phase 1 is the primary test tool; real agents are used for + verification, not for unit-level determinism. + +## Risks + +| Risk | Mitigation | +| --- | --- | +| ACP v2 stabilises mid-migration and shifts the target | All version-specific code lives in `normalize/*`; v2 stays flagged (ADR-0001) | +| Copilot ACP is public preview and may change | Pin the CLI version in the catalog; capability-driven UI degrades instead of breaking | +| Copilot ACP fixes reasoning/tool-filtering at server start | Expose them as agent-level (not session-level) settings; document the regression (R-021 AC3) | +| Agents must now be installed + authenticated locally | Registry-driven install + explicit auth UX (Phase 2/5); actionable errors instead of silent failures | +| Real permission prompts change the feel of the product | Remembered `allow_always` choices and an opt-in per-agent auto-approve | +| A browser extension has no project directory | Explicit configurable workspace root, shown in the UI (R-015) | +| Scope is large | Phases 1–4 deliver a complete single-agent product; 5–9 are additive | diff --git a/docs/specs/acp/README.md b/docs/specs/acp/README.md new file mode 100644 index 0000000..1d6d88f --- /dev/null +++ b/docs/specs/acp/README.md @@ -0,0 +1,40 @@ +# ACP Migration Spec (Agent Client Protocol) + +This directory holds the spec-driven-development artifacts for migrating DevMentorAI +from the GitHub Copilot SDK to the **Agent Client Protocol (ACP)** as its only +agent integration surface. + +Nothing in this directory changes runtime behaviour. It is the contract that the +implementation PRs are written against and reviewed with. + +## Read in this order + +| Doc | Purpose | +| --- | --- | +| [01-research.md](./01-research.md) | Protocol facts, agent/ecosystem matrix, evidence, open questions | +| [02-architecture.md](./02-architecture.md) | Target architecture, transports, data model, error taxonomy, security | +| [03-requirements.md](./03-requirements.md) | Numbered requirements + acceptance criteria (the definition of done) | +| [04-roadmap.md](./04-roadmap.md) | Phased, PR-sized implementation plan with per-phase exit criteria | +| [adr/](./adr/) | Architecture Decision Records for the choices the plan depends on | + +## Working agreement (spec-driven) + +1. A change starts as a requirement in `03-requirements.md` with acceptance criteria. +2. A decision that constrains more than one requirement becomes an ADR in `adr/`. +3. An implementation PR must state which requirement IDs (`R-xxx`) it satisfies and + how each acceptance criterion is verified (unit / integration / E2E / manual). +4. A requirement is only done when its acceptance criteria are verified by an + automated test, or explicitly marked `manual` with a recorded verification. +5. Requirements and ADRs are amended, never silently dropped. Removing a + requirement requires a note in the doc explaining why. + +## Terminology + +- **ACP** — Agent Client Protocol (), a JSON-RPC 2.0 + protocol between a *Client* (editor / UI) and an *Agent* (coding agent). + Not to be confused with IBM's "Agent Communication Protocol". +- **Agent** — an ACP server process such as `copilot --acp`, `gemini --acp`, + `opencode acp`, `devin acp`, `cursor-agent acp`. +- **ACP host** — the DevMentorAI backend component that spawns agents, speaks ACP as + a Client, and multiplexes sessions to the extension. +- **Session** — an ACP conversation (`session/new` → `sessionId`), owned by the agent. diff --git a/docs/specs/acp/adr/0001-adopt-acp-v1-stable-v2-flagged.md b/docs/specs/acp/adr/0001-adopt-acp-v1-stable-v2-flagged.md new file mode 100644 index 0000000..478ce9b --- /dev/null +++ b/docs/specs/acp/adr/0001-adopt-acp-v1-stable-v2-flagged.md @@ -0,0 +1,30 @@ +# ADR-0001 — Adopt ACP v1 as the implementation target, keep v2 behind a flag + +Status: accepted (Phase 0) + +## Context + +ACP publishes a stable v1 schema and a **draft** v2. The v2 docs explicitly tell +implementers to gate v2 behind version negotiation and feature flags and to keep serving v1 +peers. The official TypeScript SDK 1.3.0 exports `PROTOCOL_VERSION = 1` from its main +entrypoint and hides v2 behind `@agentclientprotocol/sdk/experimental/v2` with a warning +that the wire format may change incompatibly in any release. Our spike measured +`copilot --acp --stdio` negotiating v1, and the SDK example agent likewise. + +v2 is nonetheless where the protocol is going (upsert semantics, `state_update` lifecycle, +`session/resume` replay, display terminals, elicitation, forward-compatible enums). + +## Decision + +Implement v1 as the production path. Negotiate the version per connection. Keep a v2 +implementation behind an `ACP_V2` flag (Phase 9). Confine every version-specific shape to +`acp/normalize/{v1,v2}.ts`, and design the internal `AcpEvent` union along v2 lines +(upserts, ids, complete-state notifications) so v1 is the constrained case rather than the +model. + +## Consequences + +- We can ship against every agent available today. +- v1's blocking `session/prompt` means the host must synthesise turn state (R-004). +- Adopting v2 later is a normaliser plus flag flip, not a rewrite. +- Cost: two normalisers and their golden-file tests. diff --git a/docs/specs/acp/adr/0002-backend-as-acp-host.md b/docs/specs/acp/adr/0002-backend-as-acp-host.md new file mode 100644 index 0000000..738b1bd --- /dev/null +++ b/docs/specs/acp/adr/0002-backend-as-acp-host.md @@ -0,0 +1,27 @@ +# ADR-0002 — The backend is the ACP host; the extension never speaks ACP directly + +Status: accepted (Phase 0) + +## Context + +ACP's universal transport is stdio: the Client spawns the agent as a subprocess and +exchanges NDJSON over its pipes. A browser extension cannot spawn processes, read stderr, +manage a `cwd`, or hold OS credentials. Some agents also offer TCP, but stdio is the only +mode every agent supports, and process lifecycle (crash, restart, shutdown) has to live +somewhere with an OS. + +## Decision + +`apps/backend` becomes the ACP host: it owns agent processes, the ACP Client implementation, +capabilities, credentials, the workspace root, and session↔agent mapping. The extension is +a thin view over a DevMentorAI-specific UI protocol (ADR-0003) and contains no ACP wire +knowledge. Only `apps/backend/src/acp/**` may import the ACP SDK. + +## Consequences + +- One place enforces capability checks, permissions and the workspace boundary. +- The backend becomes a required component for all agents (it already was for Copilot). +- Multiple UI clients (side panel, quick actions, writing assistant, native messaging) share + one host and one session store. +- Alternative rejected: a native-messaging-only host — it would duplicate the host in a less + testable place and lose the existing REST/DB infrastructure. diff --git a/docs/specs/acp/adr/0003-websocket-jsonrpc-ui-transport.md b/docs/specs/acp/adr/0003-websocket-jsonrpc-ui-transport.md new file mode 100644 index 0000000..9a7bf02 --- /dev/null +++ b/docs/specs/acp/adr/0003-websocket-jsonrpc-ui-transport.md @@ -0,0 +1,29 @@ +# ADR-0003 — Replace SSE with a bidirectional WebSocket JSON-RPC channel + +Status: accepted (Phase 0) + +## Context + +Today the extension receives Copilot events over SSE (`/api/sessions/:id/chat`), which is +one-way. ACP requires the Client to *answer requests from the agent* mid-turn: +`session/request_permission` blocks the turn until the user chooses, and v2 adds +`elicitation/create`. Cancellation, config changes and session control also need to travel +upstream while a turn is running. Bolting POST callbacks onto SSE would reinvent a worse +JSON-RPC. + +## Decision + +A single WebSocket endpoint `/acp` carrying JSON-RPC 2.0 in both directions. UI→backend +requests (`ui/prompt`, `ui/cancel`, `ui/setConfigOption`, `ui/agents.*`, `ui/sessions.*`), +backend→UI notifications (`ui/event`, `ui/agentStatus`, `ui/error`), and backend→UI requests +answered by the UI (`ui/permission.request`, `ui/elicitation.create`). The backend keeps a +per-session turn buffer so a reconnecting panel can replay the in-flight turn. REST survives +only for non-streaming utilities (health, image upload, catalog). + +## Consequences + +- The UI protocol mirrors ACP's shape, so the host is a thin, testable relay. +- Permission and elicitation UX becomes possible at all. +- Reconnect handling and message ordering are now our responsibility (turn buffer, ids). +- The extension gains a WebSocket client it does not currently have; SSE parsing code is + deleted in Phase 8. diff --git a/docs/specs/acp/adr/0004-history-acp-first-with-local-cache.md b/docs/specs/acp/adr/0004-history-acp-first-with-local-cache.md new file mode 100644 index 0000000..f59bff1 --- /dev/null +++ b/docs/specs/acp/adr/0004-history-acp-first-with-local-cache.md @@ -0,0 +1,31 @@ +# ADR-0004 — History is ACP-first, with our store demoted to a display cache + +Status: accepted (Phase 0) + +## Context + +The original request was to drop our own history now that ACP can provide it. ACP does offer +`session/list` plus replay (`session/load` in v1, `session/resume` + `replayFrom` in v2) — +but both are capability-gated and per-agent. The spike measured Copilot CLI with +`loadSession: true` and the SDK example agent returning `-32601` for `session/list`, +`session/load` *and* `session/resume`. Each agent also keeps its own separate session store, +and the side panel must list sessions across agents, offline, before any agent is spawned. + +## Decision + +The agent is the source of truth for conversation content when it advertises replay. We keep +a **local index** (session ⇄ agent, `cwd`, title, activity, capabilities) as the source of +truth for *listing*, and keep `messages`/`session_contexts` as an explicit display cache: +used for instant paint, offline viewing, and as the only history for agents without replay. +Cache entries are keyed by `messageId`/`toolCallId` so replay reconciles instead of +duplicating. Nothing is deleted by the migration. + +## Consequences + +- Cross-agent session listing keeps working without launching every agent. +- Users with non-replay agents do not lose their history. +- Cost: reconciliation logic and a retention policy; the cache can drift and must be marked + as cache in the UI (R-047). +- Rejected: deleting our history entirely (breaks non-replay agents and cross-agent listing); + keeping our store authoritative (diverges from the agent's real context and re-creates + today's coupling). diff --git a/docs/specs/acp/adr/0005-agent-catalog-from-acp-registry.md b/docs/specs/acp/adr/0005-agent-catalog-from-acp-registry.md new file mode 100644 index 0000000..a7d9a08 --- /dev/null +++ b/docs/specs/acp/adr/0005-agent-catalog-from-acp-registry.md @@ -0,0 +1,28 @@ +# ADR-0005 — Agents come from the ACP registry plus user-defined commands, not from code + +Status: accepted (Phase 0) + +## Context + +The goal is to support roughly what Devin Desktop supports: Copilot, Claude, Gemini, Codex, +OpenCode, Cursor, Devin CLI, goose, Kimi, Qwen, Droid and more. The ACP project publishes a +machine-readable registry at +`https://cdn.agentclientprotocol.com/registry/v1/latest/registry.json` (38 agents today) +where every entry carries `id`, `name`, `description`, `icon`, `version` and a `distribution` +block: either `npx` (`package`, `args`, `env`) or per-platform `binary` (`archive`, `cmd`, +`args`, `sha256`). Registry inclusion requires the agent to advertise valid `authMethods`. + +## Decision + +The catalog is data: a small curated built-in set (pinned versions, ships offline) merged with +the cached registry and with user-defined custom agents (`cmd`/`args`/`env`). Launch specs are +resolved per platform; binary installs are sha256-verified. Adding an agent requires no +DevMentorAI code, and the UI derives every affordance from advertised capabilities. + +## Consequences + +- Provider coverage grows without releases; new agents appear as registry data. +- We must handle catalog staleness, offline mode, platform gaps and untrusted custom commands + (user-authored, clearly labelled). +- No per-agent adapters — which is precisely what makes the `feat/acp` provider abstraction + unnecessary. diff --git a/docs/specs/acp/adr/0006-local-models-via-own-acp-agent.md b/docs/specs/acp/adr/0006-local-models-via-own-acp-agent.md new file mode 100644 index 0000000..d4093df --- /dev/null +++ b/docs/specs/acp/adr/0006-local-models-via-own-acp-agent.md @@ -0,0 +1,30 @@ +# ADR-0006 — Local/OpenAI-compatible models are supported by shipping our own ACP agent + +Status: accepted (Phase 0) + +## Context + +The request named LM Studio alongside the ACP agents. LM Studio exposes an OpenAI-compatible +API (with MCP support), **not** ACP; the same is true of Ollama, vLLM, llama.cpp and +OpenRouter. Third-party bridges exist (e.g. `acp-bridge`, a Rust adapter in the registry +pipeline), and the `feat/acp` branch already contains a working +`openai-compatible.provider.ts` with streaming and tool calls. + +Options: (a) special-case a non-ACP provider inside the host, (b) depend on a third-party +bridge, (c) ship our own minimal ACP *agent* that fronts an OpenAI-compatible endpoint. + +## Decision + +Option (c): `apps/acp-openai-agent`, built with the ACP SDK's agent side, reusing the +OpenAI-compatible client from `feat/acp`. It is registered as a built-in catalog entry and +spawned like any other agent. The host stays a pure ACP Client with zero special cases. + +## Consequences + +- One code path for every model, local or hosted; the capability negotiation is honest + (we advertise only what the endpoint really supports). +- We own an agent implementation (tool-call loop, streaming, cancellation) — real work, but + isolated in its own package and independently testable. +- Rejected (a): reintroduces the provider abstraction the refactor exists to remove. +- Rejected (b): an unvetted external binary in the trust path for a core feature; may be + revisited as an optional extra catalog entry. diff --git a/docs/specs/acp/adr/0007-credentials-and-permissions.md b/docs/specs/acp/adr/0007-credentials-and-permissions.md new file mode 100644 index 0000000..4c1f7a6 --- /dev/null +++ b/docs/specs/acp/adr/0007-credentials-and-permissions.md @@ -0,0 +1,32 @@ +# ADR-0007 — Credentials stay out of the client; permissions become user decisions + +Status: accepted (Phase 0) + +## Context + +Today the backend auto-approves every Copilot tool call (`approveAll`) and relies on the +Copilot SDK for auth. Under ACP, agents authenticate themselves: `initialize` returns +`authMethods`, and login is either an out-of-band CLI step (`copilot login`, +`devin auth login`) or the protocol's `authenticate` / `auth/login`. Some agents accept keys +via env (`COPILOT_PROVIDER_*`, `WINDSURF_API_KEY`, OpenAI-compatible endpoints). Meanwhile +ACP agents genuinely expect a human to answer `session/request_permission` before they edit +files or run commands. + +## Decision + +1. Prefer the agent's own login: surface `authMethods` and the agent's instructions verbatim, + offer `auth/login` when the agent implements it, and never proxy or store provider tokens + we don't need. +2. Where env credentials are unavoidable, store them encrypted at `~/.devmentorai/credentials` + (`0600`), inject them only into that agent's process environment, and never return them + over any API, log or error. +3. Permission requests are answered by the user. `allow_always` decisions are remembered per + agent+tool and are revocable; blanket auto-approve is an explicit per-agent opt-in, off by + default, and shown in the session header. + +## Consequences + +- Removes today's silent auto-approval of file edits and command execution. +- Adds friction to flows that used to be invisible — mitigated by remembered choices. +- Auth failures become actionable UI states (`auth_required`) instead of opaque errors, which + the spike showed is exactly what an unauthenticated Copilot CLI produces. diff --git a/docs/specs/acp/adr/0008-supersede-feat-acp-provider-abstraction.md b/docs/specs/acp/adr/0008-supersede-feat-acp-provider-abstraction.md new file mode 100644 index 0000000..0ebb92b --- /dev/null +++ b/docs/specs/acp/adr/0008-supersede-feat-acp-provider-abstraction.md @@ -0,0 +1,34 @@ +# ADR-0008 — Supersede the `feat/acp` provider abstraction; keep three pieces of it + +Status: accepted (Phase 0) + +## Context + +`origin/feat/acp` (37 commits, +5852/−923) is named for ACP but contains none: no ACP SDK, no +JSON-RPC framing, no protocol types. It adds an in-house multi-provider layer — +`llm-provider.service.ts`, `providers/copilot.provider.ts`, +`providers/cli-command.provider.ts` (spawns CLIs and scrapes stdout into synthetic +Copilot-shaped events), `providers/openai-compatible.provider.ts`, `credential.service.ts`, +`routes/providers.ts` — plus extension UI for provider-grouped models and availability +states. Its interface is still typed in Copilot SDK event shapes and each adapter +reconstructs its own history; `restoreSession()` is a stub. + +## Decision + +Do not merge or build on that abstraction. ACP *is* the provider abstraction, standardised +and maintained upstream, and stdout scraping is exactly the fragility ACP removes. Cherry-pick +three things: + +1. `credential.service.ts` → `acp/credentials.ts` (ADR-0007). +2. `openai-compatible.provider.ts` → the guts of `apps/acp-openai-agent` (ADR-0006). +3. The extension UX patterns: provider-grouped selectors, agent badges, availability and + recovery states → reused for the agent catalog and capability-driven controls. + +The branch stays as a reference and is not merged; `master` is the base for the ACP work. + +## Consequences + +- Avoids maintaining two competing abstractions. +- Real work from the branch is preserved where it is still correct. +- The branch's per-provider event shapes, CLI scraping and per-adapter history are abandoned + deliberately — this ADR is the record of why. From 1f8ce95dbc1176a53611784d5952d1a0e13960da Mon Sep 17 00:00:00 2001 From: Edwar Diaz Date: Sat, 8 Aug 2026 21:46:02 +0000 Subject: [PATCH 2/3] docs(acp): target every ACP agent via registry + profiles, add conformance probe --- .agents/skills/acp-client/SKILL.md | 12 ++- docs/specs/acp/01-research.md | 98 ++++++++++++++++--- docs/specs/acp/02-architecture.md | 5 +- docs/specs/acp/03-requirements.md | 35 ++++++- docs/specs/acp/04-roadmap.md | 32 +++--- .../0005-agent-catalog-from-acp-registry.md | 24 +++-- 6 files changed, 165 insertions(+), 41 deletions(-) diff --git a/.agents/skills/acp-client/SKILL.md b/.agents/skills/acp-client/SKILL.md index ae2ee1f..d2bbf7f 100644 --- a/.agents/skills/acp-client/SKILL.md +++ b/.agents/skills/acp-client/SKILL.md @@ -61,11 +61,17 @@ Launch specs are data, from `https://cdn.agentclientprotocol.com/registry/v1/lat | Gemini CLI | `npx @google/gemini-cli --acp` | | Claude | `npx @agentclientprotocol/claude-agent-acp` | | Codex | `npx @agentclientprotocol/codex-acp` | -| Devin CLI | `devin acp` | +| Devin CLI | `devin acp` (add `--cloud` to relay to Devin cloud, `--model`, `--agent-type`) | | OpenCode / Cursor / goose | `opencode acp` / `cursor-agent acp` / `goose acp` | +| Kilo / GLM / Qwen / Kimi / Droid | `kilo acp` / `npx glm-acp-agent` / `npx @qwen-code/qwen-code --acp` / `kimi acp` / `npx droid exec --output-format acp-daemon` | -LM Studio, Ollama and other OpenAI-compatible endpoints are **not** ACP; they are served by -our own agent, `apps/acp-openai-agent`. +The registry also ships `uvx` distributions (fast-agent, Minion Code), so the launcher handles +`npx`, `uvx` and downloaded binaries. Anything not in the registry — private builds, MiniMax's +`mini-agent-acp` — is a user-defined **profile** (`cmd`/`args`/`env`); never add per-agent code. + +Not ACP: LM Studio, Ollama and other OpenAI-compatible endpoints are served by our own agent, +`apps/acp-openai-agent`. Antigravity (`agy`) has no ACP server yet and must not be wrapped +(Google's ToS); track `google-antigravity/antigravity-cli#31`. ## Slash commands diff --git a/docs/specs/acp/01-research.md b/docs/specs/acp/01-research.md index b2a12f1..97d3b0e 100644 --- a/docs/specs/acp/01-research.md +++ b/docs/specs/acp/01-research.md @@ -152,22 +152,88 @@ Machine-readable catalog: `https://cdn.agentclientprotocol.com/registry/v1/lates either `npx` (`package`, `args`, `env`) or `binary` (per-platform `archive`, `cmd`, `args`, `sha256`). This is exactly the metadata our agent catalog needs, including checksums. -Agents relevant to the user's request: - -| Agent | Launch | Notes | -| --- | --- | --- | -| GitHub Copilot CLI | `npx @github/copilot --acp [--stdio\|--port N]` | Public preview since 2026-01-28. Registry entry `github-copilot-cli`. Tool filtering / reasoning effort are **server-start flags**, not per-session — a behavioural regression vs today's SDK (see R-021). BYOK via `COPILOT_PROVIDER_*` can run without GitHub login. | -| Claude (Claude Code / Agent SDK) | `npx @agentclientprotocol/claude-agent-acp` | Official adapter; the old `@zed-industries/claude-agent-acp` is deprecated. | -| Gemini CLI | `npx @google/gemini-cli --acp` | Native flag. | -| Codex CLI | `npx @agentclientprotocol/codex-acp` | Adapter. | -| OpenCode | `opencode acp` (binary, per-platform archive + sha256) | Native. | -| Cursor | `cursor-agent acp` (binary archive) | Native. | -| Devin CLI | `devin acp` (binary archive) | Native, stdio only. Advertises its full slash-command set over ACP; credentials from `devin auth login` / `WINDSURF_API_KEY` / the ACP `authenticate` request. | -| goose, Kimi, Qwen, Factory Droid, Qoder, Kiro, Amp, Cline, Junie, … | registry | Free with the same catalog mechanism — no per-agent code. | -| **LM Studio / Ollama / any OpenAI-compatible endpoint** | **no native ACP** | Confirmed: LM Studio exposes an OpenAI-compatible + MCP API, not ACP. Options: a third-party bridge (`acp-bridge`) or ship our own tiny ACP *agent* that fronts an OpenAI-compatible endpoint. See ADR-0006. | - -Consequence: supporting "as many agents as Devin Desktop" needs **zero per-agent code** for -registry agents — only a catalog, a launcher, and a strictly capability-driven UI. +### 3.1 Coverage model + +The goal is **every ACP agent on the market**, not a curated list, so coverage is defined by +tiers rather than by agent names: + +| Tier | What it covers | Mechanism | Per-agent code | +| --- | --- | --- | --- | +| **T1 — registry** | Every agent in the ACP registry (38 today, growing without our releases) | catalog entry resolved from `registry.json` | none | +| **T2 — custom / variant** | ACP agents outside the registry, private/enterprise builds, and *variants* of a T1 agent (different flags, env, model, cloud relay) | user-defined `cmd`/`args`/`env` **agent profile** (R-016) | none | +| **T3 — non-ACP endpoints** | LM Studio, Ollama, vLLM, OpenRouter, any OpenAI-compatible API | our own ACP agent, `apps/acp-openai-agent` (ADR-0006) | one agent, not one adapter per vendor | +| **T4 — no ACP path** | Products with no ACP server and no licence to wrap one (see Antigravity below) | documented as unsupported, tracked upstream | none | + +Because T1+T2 need no code, "supported agents" is a *data and verification* problem, which is +why the plan adds a conformance probe (R-017) instead of hand-written integrations. + +### 3.2 T1 — the full registry (snapshot, 38 agents) + +| Agent | id | Dist | Launch | +| --- | --- | --- | --- | +| Agoragentic | `agoragentic-acp` | npx | `npx agoragentic-mcp --acp` | +| Amp | `amp-acp` | binary | `amp-acp` | +| Auggie CLI | `auggie` | npx | `npx @augmentcode/auggie --acp` | +| Autohand Code | `autohand` | npx | `npx @autohandai/autohand-acp` | +| Claude Agent | `claude-acp` | npx | `npx @agentclientprotocol/claude-agent-acp` | +| Cline | `cline` | npx | `npx cline --acp` | +| Codebuddy Code | `codebuddy-code` | npx | `npx @tencent-ai/codebuddy-code --acp` | +| Codex | `codex-acp` | npx | `npx @agentclientprotocol/codex-acp` | +| Cortex Code | `cortex-code` | binary | `cortex acp` | +| Corust Agent | `corust-agent` | binary | — | +| crow-cli | `crow-cli` | binary | — | +| **Cursor** | `cursor` | binary | `cursor-agent acp` | +| DeepAgents | `deepagents` | npx | `npx deepagents-acp` | +| **Devin** | `devin` | binary | `devin acp` (+ `--cloud`, `--model`, `--agent-type`) | +| DimCode | `dimcode` | npx | `npx dimcode acp` | +| Dirac | `dirac` | npx | `npx dirac-cli --acp` | +| Factory Droid | `factory-droid` | npx | `npx droid exec --output-format acp-daemon` | +| fast-agent | `fast-agent` | uvx | `uvx fast-agent-acp -x` | +| Gemini CLI | `gemini` | npx | `npx @google/gemini-cli --acp` | +| **GitHub Copilot** | `github-copilot-cli` | npx | `npx @github/copilot --acp [--stdio\|--port N]` | +| **GLM Agent** | `glm-acp-agent` | npx | `npx glm-acp-agent` | +| goose | `goose` | binary | `goose acp` | +| Grok Build | `grok-build` | npx | `npx @xai-official/grok agent stdio` | +| Harn | `harn` | binary | — | +| Junie (JetBrains) | `junie` | binary | — | +| **Kilo** | `kilo` | binary | `kilo acp` (opencode-based, uses the ACP SDK) | +| Kimi CLI | `kimi` | binary | `kimi acp` | +| Minion Code | `minion-code` | uvx | `uvx minion-code acp` | +| Mistral Vibe | `mistral-vibe` | binary | — | +| Nova | `nova` | npx | `npx @compass-ai/nova acp` | +| **OpenCode** | `opencode` | binary | `opencode acp` | +| pi ACP | `pi-acp` | npx | `npx pi-acp` | +| Poolside | `poolside` | binary | — | +| Qoder CLI | `qoder` | npx | `npx @qoder-ai/qodercli --acp` | +| Qwen Code | `qwen-code` | npx | `npx @qwen-code/qwen-code --acp` | +| siGit Code | `sigit` | binary | — | +| Stakpak | `stakpak` | binary | — | +| VT Code | `vtcode` | binary | — | + +Registry inclusion requires the agent to advertise valid `authMethods`, verified by their CI — +a useful quality floor for us. `uvx` distributions exist too, so the launcher must support +`npx`, `uvx` and downloaded binaries. + +### 3.3 Agent-specific notes that affect the design + +| Agent | Note | +| --- | --- | +| GitHub Copilot CLI | ACP is public preview (2026-01-28). Tool filtering and reasoning effort are **server-start flags**, not per-session — a regression vs today's SDK (R-021 AC3). BYOK via `COPILOT_PROVIDER_*` runs without GitHub login. Supports stdio **and** TCP. Measured: v1, `loadSession: true`, image + embeddedContext. | +| Copilot *cloud* | No ACP surface. The cloud coding agent is reachable over GitHub's own APIs/MCP, not ACP; "Copilot over ACP" means the CLI. Out of scope until GitHub ships one. | +| Devin CLI | `devin acp` over stdio, full slash-command set advertised, elicitation support, per-tool metadata. **`devin acp --cloud` (insiders) relays to Devin cloud** — i.e. local vs cloud is a *launch variant*, exactly what T2 profiles model. | +| Claude / Codex | Official `@agentclientprotocol/*` adapters; the old `@zed-industries/claude-agent-acp` is deprecated. | +| Gemini CLI | Native `--acp`, but Google announced the transition of Gemini CLI to **Antigravity CLI**; treat the Gemini entry as at-risk and pin versions (see risks in 04-roadmap). | +| **Antigravity (`agy`)** | **No ACP today.** `agy --acp` is an open feature request (`google-antigravity/antigravity-cli#31`); a community bridge (`agy-acp`) exists, but Google's ToS state that using third-party software with an Antigravity login violates the terms, and the ToS question raised by the bridge author is unresolved. **Decision: T4 — do not ship a wrapper.** Track the upstream issue; the moment `agy --acp` exists it becomes a registry/T2 entry with zero code from us. | +| Kilo | In the registry (`kilo acp`), built on opencode and the ACP SDK. | +| GLM (Zhipu) | In the registry as `glm-acp-agent`. | +| MiniMax | The MiniMax **CLI** has an open ACP request, but **Mini Agent ships an ACP server** (`mini-agent-acp`, `uv tool install`) documented for Zed. Not in the registry → supported as a T2 custom profile. | +| LM Studio / Ollama / vLLM / OpenRouter | Not ACP (OpenAI-compatible + MCP). T3 via `apps/acp-openai-agent` (ADR-0006). | + +### 3.4 Consequence + +Supporting "all the ACP agents of 2026" needs **zero per-agent code**: a catalog, a launcher +that speaks npx/uvx/binary/TCP, launch *profiles* for variants, a strictly capability-driven +UI, and an automated conformance probe that records what each agent actually supports. ## 4. What the existing `feat/acp` branch actually is diff --git a/docs/specs/acp/02-architecture.md b/docs/specs/acp/02-architecture.md index 927bd84..4a8d1e2 100644 --- a/docs/specs/acp/02-architecture.md +++ b/docs/specs/acp/02-architecture.md @@ -47,8 +47,9 @@ agent→UI *requests* such as permissions), [ADR-0006](./adr/0006-local-models-v | Module | Responsibility | | --- | --- | -| `catalog/agent-catalog.ts` | Built-in curated agents + cached ACP registry (`registry.json`) + user-defined custom agents. Resolves a platform-specific `LaunchSpec { kind: npx\|binary\|command\|tcp, cmd, args, env, sha256? }`. | -| `catalog/agent-installer.ts` | Optional download/extract of binary distributions into `~/.devmentorai/agents///`, sha256-verified; npx agents resolve lazily. | +| `catalog/agent-catalog.ts` | Built-in curated agents + the full cached ACP registry (`registry.json`) + user-defined agents, all exposed as **profiles** (named launch tuples, so `devin acp` and `devin acp --cloud` are two entries). Resolves a platform-specific `LaunchSpec { kind: npx\|uvx\|binary\|command\|tcp, cmd, args, env, sha256? }`. | +| `catalog/agent-installer.ts` | Optional download/extract of binary distributions into `~/.devmentorai/agents///`, sha256-verified; `npx`/`uvx` agents resolve lazily. | +| `catalog/conformance.ts` | Scripted probe of a profile (initialize → prompt → command → image → permission → history → cancel) that records what the agent really supports; feeds the UI support matrix and the generated table in `docs/ACP.md`. | | `launcher.ts` | Spawns the agent, wires `stdin`/`stdout` into `ndJsonStream`, keeps `stderr` in a bounded ring buffer for diagnostics, owns process lifecycle (exit, crash, kill on idle timeout, graceful shutdown). | | `connection.ts` | One `AcpConnection` per running agent: `initialize` + version negotiation, capability record, `authenticate`/`auth/login`, and all Client-side handlers (`sessionUpdate`, `requestPermission`, `elicitation/create` when v2). | | `normalize/v1.ts`, `normalize/v2.ts` | Map version-specific wire shapes onto one internal `AcpEvent` union (see §4). All version differences are confined here. | diff --git a/docs/specs/acp/03-requirements.md b/docs/specs/acp/03-requirements.md index 9da242c..3eedabe 100644 --- a/docs/specs/acp/03-requirements.md +++ b/docs/specs/acp/03-requirements.md @@ -73,13 +73,44 @@ and owns its lifecycle (spawn, stderr ring buffer, exit detection, graceful shut ## B. Agent catalog, install, auth -**R-010 (P0)** An agent catalog exposes built-in curated agents, the ACP registry +**R-010 (P0)** An agent catalog exposes built-in curated agents, the **whole** ACP registry (`registry.json`, cached with a TTL and an offline fallback), and user-defined custom -agents (`cmd` + `args` + `env`). +agents (`cmd` + `args` + `env`). No agent is hardcoded: a new registry entry becomes usable +without a DevMentorAI release. - AC1 `ui/agents.list` returns id, name, description, icon, version, source, install state, auth state and platform availability. `integration` - AC2 With the network unavailable, the cached/built-in catalog is still returned. `unit` - AC3 A custom agent defined by the user can be launched and used end-to-end. `integration` +- AC4 Given a registry containing an agent unknown to our code, then it is listed and + launchable with no code change. `integration` +- AC5 `npx`, `uvx` and `binary` distributions are all resolvable. `unit` + +**R-016 (P0)** Agents are configured as **profiles**: a named `{ agentId | custom cmd, args, +env, cwd default, transport }` tuple, so the same agent can exist several times with +different configuration (e.g. `devin acp` vs `devin acp --cloud`, `copilot --acp --stdio` vs +`--acp --port N`, one profile per BYOK provider, `mini-agent-acp` from MiniMax). +- AC1 Two profiles of the same agent can run simultaneously with independent sessions and + independent auth state. `integration` +- AC2 A profile is created, edited, duplicated and deleted from the UI, and the sessions it + owns keep working after an edit (new sessions use the new config). `e2e` +- AC3 Profile `env` values that reference stored credentials are resolved at spawn time and + never displayed. `unit` + +**R-017 (P1)** A **conformance probe** can be run against any agent profile: it performs +`initialize`, `session/new`, a scripted prompt, a slash command, an image block, a permission +round-trip, a history probe and a cancel, then records the observed capabilities and results. +- AC1 Running the probe against a profile produces a machine-readable capability record + (protocol version, `promptCapabilities`, `loadSession`, advertised commands, auth methods, + failures) stored with the agent. `integration` +- AC2 The UI shows that record as the agent's support matrix, including "not verified". `e2e` +- AC3 A repo script regenerates the support table in `docs/ACP.md` from probe runs, so agent + coverage is documented by measurement, not by hand. `manual` +- AC4 A probe failure never leaves a stray agent process running. `integration` + +**R-018 (P2)** Agents with no ACP server are documented, not wrapped: where a vendor's terms +forbid third-party wrapping of their CLI (e.g. Antigravity `agy` today), DevMentorAI ships no +bridge and instead links the upstream tracking issue. +- AC1 `docs/ACP.md` lists the known non-ACP products, why, and what would unblock them. `manual` **R-011 (P1)** Binary distributions can be installed on demand into `~/.devmentorai/agents///`, verified against the registry `sha256`. diff --git a/docs/specs/acp/04-roadmap.md b/docs/specs/acp/04-roadmap.md index e44bf8e..b12de9a 100644 --- a/docs/specs/acp/04-roadmap.md +++ b/docs/specs/acp/04-roadmap.md @@ -26,10 +26,10 @@ against the fixture agent and the golden-file normalisation tests pass. No UI ch ### Phase 2 — Agent catalog, workspace, launch, auth surface -Scope: `catalog/*`, `workspace.ts`, `credentials.ts` (ported from `feat/acp`), -`agents` table, `ui/agents.*` methods, `auth_required` plumbing with the agent's own -instructions. -Requirements: R-010, R-012..R-015, R-013 (Copilot login path), R-062. +Scope: `catalog/*` (full registry, npx/uvx/binary), agent **profiles** (variants of the same +agent), `workspace.ts`, `credentials.ts` (ported from `feat/acp`), `agents` table, +`ui/agents.*` methods, `auth_required` plumbing with the agent's own instructions. +Requirements: R-010, R-012..R-016, R-062. Exit: `copilot --acp --stdio` and one npx agent (Gemini or Claude adapter) can be launched and authenticated from the backend; an unauthenticated agent produces the actionable `auth_required` error. @@ -53,14 +53,20 @@ Requirements: R-021, R-025..R-027, R-035, R-036, R-038, R-039, R-030..R-032. Exit: Copilot ACP is fully usable — commands, images, page context, tools, permissions. `ACP_ENABLED` defaults to true. **This is the MVP.** -### Phase 5 — Multi-agent rollout - -Scope: registry-driven catalog UI (search/install/uninstall), binary installer with sha256, -per-agent capability matrix surfaced in the UI, capability-driven control disabling, TCP -transport, agent switching per session. -Requirements: R-008, R-011, R-028, R-033, R-037, plus measuring Q2 (replay support) per agent. -Exit: verified end-to-end on at least Copilot, Gemini CLI, Claude adapter, OpenCode and -Devin CLI, with a results table committed to `docs/ACP.md`. +### Phase 5 — All agents: catalog UI, profiles, conformance probe + +Scope: registry-driven catalog UI for the **whole registry** (search/install/uninstall), +binary + `uvx` installers with sha256, profile editor (custom commands, args, env, cloud +variants such as `devin acp --cloud`), TCP transport, agent switching per session, +capability-driven control disabling, and the **conformance probe** (R-017) that measures each +agent instead of us hand-integrating it. +Requirements: R-008, R-011, R-016..R-018, R-028, R-033, R-037; answers Q2 (replay support) +per agent as probe data. +Exit: the probe runs green against a first wave — Copilot CLI, Claude, Codex, Gemini, +OpenCode, Cursor, Devin (local **and** `--cloud`), Kilo, GLM, goose, Qwen, Kimi, Droid — and +`docs/ACP.md` carries a generated support table. Any other registry agent is usable without +further work; MiniMax `mini-agent-acp` and private/enterprise agents are covered as custom +profiles. ### Phase 6 — History: ACP-first with local cache @@ -114,6 +120,8 @@ Exit: a v2-capable agent works with the flag on and v1 agents are unaffected wit | --- | --- | | ACP v2 stabilises mid-migration and shifts the target | All version-specific code lives in `normalize/*`; v2 stays flagged (ADR-0001) | | Copilot ACP is public preview and may change | Pin the CLI version in the catalog; capability-driven UI degrades instead of breaking | +| Gemini CLI is being transitioned to Antigravity CLI, which has **no ACP** yet (upstream issue open, and Google's ToS forbid third-party wrapping of `agy`) | Pin the Gemini CLI catalog version, keep Antigravity as T4/documented (R-018), and rely on profiles so the day `agy --acp` ships it needs no code from us | +| "All agents" is unbounded and cannot be hand-tested | Coverage is data (registry + profiles) plus the automated conformance probe (R-017); the generated support table shows measured, not claimed, support | | Copilot ACP fixes reasoning/tool-filtering at server start | Expose them as agent-level (not session-level) settings; document the regression (R-021 AC3) | | Agents must now be installed + authenticated locally | Registry-driven install + explicit auth UX (Phase 2/5); actionable errors instead of silent failures | | Real permission prompts change the feel of the product | Remembered `allow_always` choices and an opt-in per-agent auto-approve | diff --git a/docs/specs/acp/adr/0005-agent-catalog-from-acp-registry.md b/docs/specs/acp/adr/0005-agent-catalog-from-acp-registry.md index a7d9a08..5bd0c54 100644 --- a/docs/specs/acp/adr/0005-agent-catalog-from-acp-registry.md +++ b/docs/specs/acp/adr/0005-agent-catalog-from-acp-registry.md @@ -4,8 +4,9 @@ Status: accepted (Phase 0) ## Context -The goal is to support roughly what Devin Desktop supports: Copilot, Claude, Gemini, Codex, -OpenCode, Cursor, Devin CLI, goose, Kimi, Qwen, Droid and more. The ACP project publishes a +The goal is to support **every ACP agent available in 2026** — Copilot, Claude, Gemini, +Codex, OpenCode, Cursor, Devin (local and cloud), Kilo, GLM, MiniMax, Amp, Cline, Junie, +goose, Kimi, Qwen, Droid, Grok, Mistral and whatever ships next — not a hand-picked list. The ACP project publishes a machine-readable registry at `https://cdn.agentclientprotocol.com/registry/v1/latest/registry.json` (38 agents today) where every entry carries `id`, `name`, `description`, `icon`, `version` and a `distribution` @@ -15,13 +16,24 @@ block: either `npx` (`package`, `args`, `env`) or per-platform `binary` (`archiv ## Decision The catalog is data: a small curated built-in set (pinned versions, ships offline) merged with -the cached registry and with user-defined custom agents (`cmd`/`args`/`env`). Launch specs are -resolved per platform; binary installs are sha256-verified. Adding an agent requires no -DevMentorAI code, and the UI derives every affordance from advertised capabilities. +the **entire** cached registry and with user-defined agents. Launch specs are resolved per +platform and support `npx`, `uvx` and downloaded binaries (sha256-verified). Configuration is +expressed as **profiles** — a named launch tuple — so variants of one agent coexist: +`devin acp` vs `devin acp --cloud`, `copilot --acp --stdio` vs `--acp --port N`, one profile +per BYOK provider, or a non-registry agent such as MiniMax's `mini-agent-acp`. Support is +established by an automated conformance probe (R-017) whose output generates the published +support table, so coverage claims are measured rather than asserted. + +Where a product has no ACP server, we do not wrap it. Antigravity (`agy`) is the current +example: ACP is an open upstream request and Google's terms forbid third-party software using +an Antigravity login, so it stays documented-as-unsupported (R-018) until `agy --acp` exists — +at which point it needs no code from us. ## Consequences -- Provider coverage grows without releases; new agents appear as registry data. +- Provider coverage grows without releases; new agents appear as registry data, and anything + unlisted is one profile away. +- "Which agents are supported?" becomes a generated, measured table instead of a promise. - We must handle catalog staleness, offline mode, platform gaps and untrusted custom commands (user-authored, clearly labelled). - No per-agent adapters — which is precisely what makes the `feat/acp` provider abstraction From 1b58693c511b20892f5c4ae484cfae758debe923 Mon Sep 17 00:00:00 2001 From: Edwar Diaz Date: Mon, 10 Aug 2026 14:03:55 +0000 Subject: [PATCH 3/3] docs(acp): fix requirement traceability --- docs/specs/acp/01-research.md | 2 +- docs/specs/acp/03-requirements.md | 2 ++ docs/specs/acp/04-roadmap.md | 7 ++++--- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/specs/acp/01-research.md b/docs/specs/acp/01-research.md index 97d3b0e..03037f9 100644 --- a/docs/specs/acp/01-research.md +++ b/docs/specs/acp/01-research.md @@ -290,7 +290,7 @@ event interface, and per-adapter history reconstruction. `loadSession: true`, `image: true`, `audio: false`, `embeddedContext: true`. - **Q2** Which of the *other* target agents support `session/load`/`session/resume` replay in practice? Copilot does; the SDK example does not. Determines how much of our local - history we can retire (R-040). Measured per agent during Phase 5. + history we can retire (R-046/R-047). Measured per agent during Phase 5. - **Q3** Do we bundle any agent (e.g. pin `@github/copilot`) or always resolve at runtime from the registry with a user-visible install step? - **Q4** Native messaging host: keep it as-is, or route it through the same ACP host? diff --git a/docs/specs/acp/03-requirements.md b/docs/specs/acp/03-requirements.md index 3eedabe..ffada77 100644 --- a/docs/specs/acp/03-requirements.md +++ b/docs/specs/acp/03-requirements.md @@ -84,6 +84,8 @@ without a DevMentorAI release. - AC4 Given a registry containing an agent unknown to our code, then it is listed and launchable with no code change. `integration` - AC5 `npx`, `uvx` and `binary` distributions are all resolvable. `unit` +- AC6 `apps/acp-openai-agent` is registered as a built-in catalog entry and can be + launched and used end-to-end with its endpoint/model configuration. `integration` **R-016 (P0)** Agents are configured as **profiles**: a named `{ agentId | custom cmd, args, env, cwd default, transport }` tuple, so the same agent can exist several times with diff --git a/docs/specs/acp/04-roadmap.md b/docs/specs/acp/04-roadmap.md index b12de9a..61807ce 100644 --- a/docs/specs/acp/04-roadmap.md +++ b/docs/specs/acp/04-roadmap.md @@ -20,7 +20,7 @@ Exit: the user approves scope, ADRs and requirement priorities. Scope: `acp/launcher.ts`, `acp/connection.ts`, `acp/normalize/v1.ts`, `acp/errors.ts`, `AcpEvent` in `packages/shared`, and the **fixture agent** test harness (an SDK-based agent that can emit every update variant, request permissions, stall, and crash). -Requirements: R-001..R-007, R-012, R-060. +Requirements: R-001..R-007, R-012, R-060, R-061, R-063. Exit: integration tests drive a full turn (text, tool call, permission, cancel, crash) against the fixture agent and the golden-file normalisation tests pass. No UI change. @@ -49,7 +49,8 @@ Scope: slash-command palette, config-option selectors (model/mode/reasoning), to cards with kinds/status/diffs, plan checklist, permission prompt with remembered `allow_always`, generic renderer for unknown content, usage/context indicator, error cards with actions. -Requirements: R-021, R-025..R-027, R-035, R-036, R-038, R-039, R-030..R-032. +Requirements: R-021, R-025..R-027, R-035, R-036, R-038, R-039, R-030..R-032, +R-050..R-052. Exit: Copilot ACP is fully usable — commands, images, page context, tools, permissions. `ACP_ENABLED` defaults to true. **This is the MVP.** @@ -84,7 +85,7 @@ OpenAI-compatible endpoint (LM Studio, Ollama, vLLM, OpenRouter), reusing the `openai-compatible.provider.ts` logic from `feat/acp`: streaming, tool calling, and capability advertisement. Registered as a built-in catalog entry with an endpoint/model config. -Requirements: R-010 AC3 (as a built-in), plus the A/C/F sets applied to this agent. +Requirements: R-010 AC6, plus the A/C/F sets applied to this agent. Exit: an LM Studio model runs through the identical ACP path — the host contains zero special-casing for it.