diff --git a/specs/GH7326/product.md b/specs/GH7326/product.md new file mode 100644 index 000000000..842dd541d --- /dev/null +++ b/specs/GH7326/product.md @@ -0,0 +1,97 @@ +# Product Spec: Support for Agent Client Protocol (ACP) + +**Issue:** [warpdotdev/warp#7326](https://github.com/warpdotdev/warp/issues/7326) +**Figma:** none provided + +## Summary + +Warp should support the Agent Client Protocol (ACP) as a client, allowing any +ACP-compatible coding agent (e.g. opencode, Kimi CLI, Mistral's Devstral, custom +agents) to run inside Warp's native agent UX — blocks, diff view, file tree, and +conversation history — without requiring a custom Warp integration per agent. + +## Problem + +Today, each third-party coding agent (Claude Code, Codex, Gemini CLI) requires a +dedicated harness in Warp. Adding a new agent means writing new integration code. +Meanwhile, ACP is an emerging open standard (backed by Zed, JetBrains, and a +growing ecosystem) that defines a standard JSON-RPC-over-stdio protocol for +agent-editor communication — analogous to how LSP standardized language server +integration. Without ACP support, Warp users cannot run the growing number of +ACP-native agents inside Warp's UI, and must fall back to raw TUI output. + +## Goals + +- Any ACP-compatible agent can be launched inside Warp and uses Warp's native + agent UI (conversation blocks, diff view, file tree, tool call rendering). +- Users can configure ACP agents the same way they configure existing harnesses + (via the settings UI and/or the Warp config file). +- Users can opt in to passing Warp's MCP servers to an ACP agent session. This is off by default per agent. +- The ACP harness validates that the target agent CLI is installed before launch + and surfaces a helpful install link if not. + +## Non-goals + +- Warp as an ACP *server* (other IDEs using Warp's agent) — this is a follow-up. +- Supporting ACP agents that require remote/HTTP transport (stdio only in v1). +- Replacing existing dedicated harnesses (Claude Code, Codex, Gemini) — those + remain as-is for agents that benefit from custom integrations. + +## User experience + +### Configuring an ACP agent + +1. User opens Settings → Agents → Third-party CLI agents. +2. A new "ACP agents" section lists any configured ACP agents alongside existing + harnesses. +3. User clicks "Add ACP agent" and enters: + - **Name** (e.g. "opencode", "Kimi") + - **Command** (e.g. `opencode`, `kimi`) + - **Args** (optional, e.g. `--model gpt-4o`) +4. Warp validates the command exists on PATH. If not, shows an error with a link + to the agent's install docs (if known). +5. The configured agent appears in the harness selector dropdown in the agent + input footer. + +### Launching an ACP agent session + +1. User selects an ACP agent from the harness selector and sends a prompt. +2. Warp spawns the agent process over stdio and initiates the ACP handshake + (initialize → initialized). +3. The agent's responses render in Warp's native conversation UI: + - Text responses appear as agent message blocks. + - File edits surface in Warp's diff view for review. + - Tool calls (if passed via MCP) render as tool call blocks. +4. The session persists. Session resumption might be out of scope for v1. + +### Edge cases + +- **Agent not installed:** Warp shows an inline error with install instructions + before attempting to spawn the process. +- **Agent crashes mid-session:** Warp surfaces an error block and marks the + conversation as ended, matching behavior of existing harnesses. +- **ACP handshake fails:** Warp shows a descriptive error (e.g. "Agent did not + respond to initialize within 10 seconds") rather than hanging. +- **Agent sends unsupported capability:** Warp ignores unknown capability fields + gracefully (forward-compatible). +- **No MCP servers configured:** ACP session launches without MCP context; + no error is shown (same as existing harness behavior). + +## Success criteria + +1. An ACP agent configured with a valid command launches successfully and + produces a conversation block in Warp's UI. +2. A file edit proposed by the agent appears in Warp's diff view. +3. If the command is not on PATH, Warp shows an error before spawning. +4. If the ACP handshake times out, Warp surfaces a descriptive error. +5. Configured ACP agents appear in the harness selector alongside Claude Code, + Codex, and Gemini. +6. Users can opt in to passing Warp's configured MCP servers to an ACP agent session; passthrough is off by default. +7. Conversation history for ACP sessions persists and is viewable. + +## Open questions + +1. How should Warp handle ACP agents that advertise capabilities Warp doesn't + yet render (e.g. multi-file context beyond diff view)? +2. Should there be a curated list of known ACP agents with pre-filled commands + and install URLs (similar to how Codex and Gemini are pre-configured)? diff --git a/specs/GH7326/tech.md b/specs/GH7326/tech.md new file mode 100644 index 000000000..7007faccd --- /dev/null +++ b/specs/GH7326/tech.md @@ -0,0 +1,213 @@ +# Tech Spec: Support for Agent Client Protocol (ACP) + +**Issue:** [warpdotdev/warp#7326](https://github.com/warpdotdev/warp/issues/7326) +**Product spec:** `specs/GH7326/product.md` + +## Context + +Warp currently supports third-party coding agents through a `ThirdPartyHarness` +trait defined in `app/src/ai/agent_sdk/driver/harness/mod.rs`. Each supported +agent (Claude Code, Gemini) implements this trait in its own file and is +registered as a variant of the `Harness` enum in `crates/warp_cli/src/agent.rs` +and `CLIAgent` enum in `app/src/terminal/cli_agent.rs`. + +ACP (Agent Client Protocol) is a standard JSON-RPC-over-stdio protocol for +agent-editor communication. Rather than adding a new per-agent harness for every +ACP-compatible CLI, a single `AcpHarness` implementation covers all of them. +The user supplies the command (e.g. `opencode`, `kimi`) and Warp handles the +protocol layer generically. + +### Relevant files + +- `crates/warp_cli/src/agent.rs:123` — `pub enum Harness` — needs new `Acp` variant +- `app/src/terminal/cli_agent.rs:108` — `pub enum CLIAgent` — needs new `Acp` variant with user-configurable command +- `app/src/ai/agent_sdk/driver/harness/mod.rs:57` — `ThirdPartyHarness` trait definition — the contract `AcpHarness` must implement +- `app/src/ai/agent_sdk/driver/harness/gemini.rs` — `GeminiHarness` — closest structural analog to `AcpHarness` +- `app/src/ai/agent_sdk/driver/harness/mod.rs:133` — `HarnessKind` enum — may need `Acp` variant +- `app/src/terminal/view/ambient_agent/harness_selector.rs:61` — harness dropdown — needs ACP entry +- `app/src/server/server_api/harness_support.rs` — server-side harness registration — needs ACP format slug + +## Proposed changes + +### 1. `crates/warp_cli/src/agent.rs` + +Add a new `Acp` variant to `pub enum Harness`: + +```rust +/// Delegate to any ACP-compatible agent CLI (user-configured command). +#[value(name = "acp")] +Acp, +``` + +Place between `OpenCode` and the `Unknown` fallback variant. + +### 2. `app/src/terminal/cli_agent.rs` + +Add `Acp` to `pub enum CLIAgent`. Unlike other variants whose commands are +hardcoded, `CLIAgent::Acp` carries the user-supplied command string: + +```rust +/// An ACP-compatible agent with a user-configured command. +Acp(String), +``` + +Update `command_prefix()`, `display_name()`, and `icon()` match arms: + +```rust +// command_prefix +CLIAgent::Acp(cmd) => cmd.as_str(), + +// display_name +CLIAgent::Acp(cmd) => cmd.as_str(), // show the actual command as the name + +// icon — no dedicated icon yet; fall through to None +CLIAgent::Acp(_) => None, +``` + +### 3. New file: `app/src/ai/agent_sdk/driver/harness/acp.rs` + +Create `AcpHarness` implementing `ThirdPartyHarness`. The struct carries the +user-configured command and optional args: + +```rust +pub(crate) struct AcpHarness { + command: String, + args: Vec, +} +``` + +Key `ThirdPartyHarness` method implementations: + +- `harness()` → `Harness::Acp` +- `cli_agent()` → `CLIAgent::Acp(self.command.clone())` +- `validate()` → call `validate_cli_installed(&self.command, self.install_docs_url())` +- **Execution model:** The command is executed directly via argv (not + shell interpolation) to prevent injection. PATH resolution follows + the same rules as existing harnesses. Synced config that causes + command execution on another machine should require explicit user + confirmation before first run. +- `install_docs_url()` → `None` (unknown for arbitrary agents; follow-up can add a registry) +- `prepare_environment_config()` → write ACP session config if needed; for v1 this may be a no-op or minimal JSON config file with MCP server list +- `build_command()` → spawn `self.command` with `self.args`. No additional ACP-specific launch flags are injected by Warp — ACP-compatible agents communicate over stdio by default. Any required flags are user-supplied via the Args field. + +The ACP protocol handshake (JSON-RPC `initialize` / `initialized` exchange over +stdio) is handled in a new `run_acp_session()` function within this file, called +from `run()`. The initialize request must include: + +```json +{ + "jsonrpc": "2.0", + "id": 1, + "method": "initialize", + "params": { + "clientInfo": { "name": "warp", "version": "" }, + "capabilities": {} + } +} +``` + +Warp then reads the `InitializeResult` from the agent's stdout, sends +`initialized`, and transitions to the active session loop. + +**Timeout:** If no `InitializeResult` arrives within 10 seconds, `run()` returns +`AgentDriverError::HarnessConfigSetupFailed` with a descriptive message. + +**MCP passthrough:** After initialization, Warp offers to pass its +configured MCP servers to the ACP agent session. The user must +explicitly enable MCP passthrough per agent in settings — it is off +by default. Only MCP servers the user has already authorized in Warp +are eligible for passthrough. + +### 4. `app/src/ai/agent_sdk/driver/harness/mod.rs` + +- Add `mod acp;` and `pub(crate) use acp::AcpHarness;` +- Add `Acp` variant to `HarnessKind` (line 133) if this enum is exhaustively matched anywhere +- Register `AcpHarness` in the harness dispatch logic alongside `ClaudeHarness` and `GeminiHarness` + +### 5. `app/src/terminal/view/ambient_agent/harness_selector.rs` + +Add `Harness::Acp` to the harness selector dropdown. For user-configured ACP +agents, each appears as a separate entry using its display name (the command +string). This requires reading the list of configured ACP agents from settings +and generating one selector entry per agent. + +### 6. `app/src/server/server_api/harness_support.rs` + +Add an `"acp"` format slug for ACP conversations, used when creating a +conversation record on the server. This follows the pattern of `"gemini_cli"` +for Gemini. + +## Data flow + +``` +User selects ACP agent + sends prompt + → AcpHarness::validate() checks command on PATH + → AcpHarness::build_command() spawns process over stdio + → run_acp_session() sends initialize, awaits InitializeResult (10s timeout) + → sends initialized notification + → passes MCP servers to session config (if any) + → enters prompt/response loop: + Warp sends prompt as ACP session/message + Agent streams response chunks + Warp renders chunks as conversation blocks + File edits → diff view + Tool calls → tool call blocks + → on agent exit or crash → conversation marked ended +``` + +## Tradeoffs + +- **ACP agent config storage:** `CLIAgent::Acp(String)` conflicts with + `CLIAgent`'s current `&'static str` return and `Copy` constraints. + The preferred approach is likely storing ACP agent configuration + (name, command, args) in a dedicated settings model (e.g. + `AcpAgentConfig` struct in the AI settings) and keeping `CLIAgent` + as a simple fieldless `Acp` variant. The harness selector would then + read from that settings model. **Open question for Warp team: what is + the preferred home for per-agent ACP config?** +- **Stdio-only in v1:** Excludes ACP agents that communicate over HTTP. This + covers the majority of current ACP agents (opencode, Kimi, Gemini, Codex all + support stdio) and avoids auth complexity for v1. + +## Testing and validation + +Invariant-to-test mapping (from `product.md` success criteria): + +1. **Unit test in `acp.rs`:** `AcpHarness::validate()` returns `Ok(())` for a + command on PATH and `Err(AgentDriverError::CliNotInstalled)` for a missing + command. +2. **Unit test:** `run_acp_session()` returns a timeout error if the mock agent + process does not send `InitializeResult` within the deadline. +3. **Integration test under `crates/integration/`:** Spawn a minimal ACP echo + agent (a small test binary that implements initialize/initialized and + returns a static response), send a prompt, and assert a conversation block + is produced. +4. **Manual:** Configure `opencode` as an ACP agent, send a prompt, confirm + response renders as a Warp conversation block. +5. **Manual:** Confirm `opencode` appears in the harness selector alongside + Claude Code, Codex, and Gemini. +6. **Regression:** `cargo nextest run` passes across existing harness tests + (`local_harness_launch_tests.rs`, `cli_agent_tests.rs`, + `mod_test.rs` in `driver/harness/`). + +## Risks and mitigations + +- **ACP spec churn:** The ACP spec is still evolving. Mitigation: pin to the + stable `initialize`/`initialized`/`session/message` core and treat unknown + fields as ignored (forward-compatible deserialization with `#[serde(flatten)]` + or `deny_unknown_fields = false`). +- **CLIAgent enum serialization:** Adding `Acp(String)` changes the shape of a + serialized enum. Mitigation: verify that `CLIAgent` is not persisted to disk + or sent over the network in a way that would break existing stored data; if it + is, add a migration. +- **No icon for ACP agents:** The harness selector will show ACP agents without + a logo. Mitigation: acceptable for v1; a generic "agent" icon can be added as + a follow-up. + +## Follow-ups + +- Session resumption for ACP agents (requires ACP `loadSession` capability). +- A curated registry of known ACP agents with pre-filled commands and install URLs. +- Warp as ACP server (exposing Warp Agent to external IDEs like Zed). +- Per-agent icons for known ACP CLIs. +- HTTP transport support for remote ACP agents.