# ACP Remote / Agent adapter 技术说明 本文记录 Remote 内通用 Agent ACP gateway(#65)与 Cursor backend adapter 的边界。 线协议以 [Remote Protocol v1](protocol.md) 为准。 ## 定位 Host Plugin 提供 backend-neutral 的 **Agent ACP gateway**: ```text ACP Client -> 已认证 Remote channel(agent.acp.*) -> Host ACP gateway -> Cursor `agent acp` adapter(本阶段) ``` - 公共 capability / RPC:`agent.acp.v1`、`agent.acp.*` - Host 配置键 `cursor.enabled` / `cursor.binary` 只控制 **Cursor adapter**,不是线协议名 - 按 Remote connection 隔离;编译期固定 allowlist;未授权 method fail closed - 现有 `codex.app.*` 保持不变;**暂不**把 Codex 迁到 ACP adapter ## 启动与后端配置 `acp.enabled` 控制 gateway;`acp.backends` 内各后端独立使用 `enabled`、`command`、`args`、 可选 `cwd`。当前支持 `cursor` / `antigravity` / `pi` / `opencode` / `claude`,不按 binary 名称推测 adapter,也不把不可用后端的 请求转发到其他后端。仅配置旧 `cursor` 字段时,只启用 Cursor。 对于 `opencode`,Host 运行 `opencode acp` 子进程接入标准 ACP 协议,采用标准 `initialize` 握手(避免发送 Cursor 私有 `cursor_login` 鉴权方法)。默认优先探测官方安装位置的 `~/.opencode/bin/opencode`、`~/.local/bin/opencode` 及系统 PATH。独立 capability 声明为 `agent.acp.opencode.v1`。工作区目录来自标准 `session/list` 返回的 cwd,历史通过独立临时进程的 `session/load` 回放生成,不写入 Harness SessionStore。 启动前通过同一命令的 `--help` 检测参数能力,不硬编码主版本号。仅当 CLI 同时声明 `--hostname`、`--port`、`--mdns` 时追加私有 loopback 参数并启用原生归档;三者均不支持时 移除这些旧网络参数,仅通过 stdio ACP 工作。检测失败、输出超限或仅支持部分参数时显式 返回 `OPENCODE_CLI_PROBE_FAILED`,不猜测监听地址或盲目重试另一套参数;关闭后端会取消检测。 官方 v1 使用 npm `opencode-ai`,v2 使用 `@opencode/cli`。已分别以真实 `1.19.3` / `2.0.32` 验证 Host 本地 ACP 主流程;v2 的内部私有服务不开放网络端口,详见 [官方 ACP 文档](https://opencode.ai/v2/docs/cli/acp/)。这不等同于跨设备或 Prompt 验收。 Cursor/Claude 的 Desktop/Android 模型选择读取真实会话 catalog,并等待上游确认。Cursor 为避免进程级模型设置串会话,每个活动会话使用独立进程。Claude 显示名统一为 Claude,工作区与历史投影见 [接入说明](acp-integration.md)。 ACP 在后台启动,各后端独立初始化,不阻塞 Harness Remote 注册和 Client 初始化。 能力声明表示已启用的协议支持,`workspaceTypes.available` 表示实际就绪;关闭期间会清理 正在启动的 CLI,迟到的初始化结果不得重新注册实例。后端配置保存后立即生效,仅更新发生变化的后端。 设置页根据 Host 上配置的命令及运行时支持的候选位置检测后端是否可发现。 检测不可用时工作区开关显示关闭并禁用;手动检测失败会保存该后端的关闭状态, 并立即更新 Host 运行时,适用于 Cursor、AGY、CodeX 和自定义 ACP 条目。 重新检测通过后可以手动开启,不自动恢复为开启。命令可发现不代表已登录或协议就绪, 真实 Remote 可用性仍以 `workspaceTypes.available` 为准。 AGY 会话发现按数据库中的 file URI 解码后与项目目录完整匹配,支持编码目录名和 原生路径分隔符。指定目录缺少数据库归属信息时返回空列表,不把所有 transcript 推测为该目录的会话。历史读取限制在 AGY brain 内的真实 transcript,拒绝越界 ID 和符号链接。切换会话后,客户端丢弃迟到的历史与订阅结果。 ## 公共方法(allowlist) | 方法 | 用途 | | --- | --- | | `initialize` | Gateway 返回能力描述(不转发 Cursor 私有 initialize) | | `session/new` | 在 Host 上已存在的绝对目录创建会话(强制 `mcpServers: []`) | | `session/load` | 恢复会话 | | `session/prompt` | Cursor 文本 Prompt;AGY 文本/受限图片 Prompt | | `session/cancel` | 中断 | | `dsh/directoryList` | Host 只读单层目录浏览(选 cwd;Remote 扩展) | | `dsh/workspaceList` | 指定后端的 Host 工作区发现 | | `dsh/sessionList` | AGY 持久化会话发现 | | `dsh/sessionHistory` | 历史读取(有界 ACP transfer) | | `dsh/sessionArchive` | OpenCode 原生归档;要求独立 capability | 线 RPC:`agent.acp.call|respond|stream.*|transfer.*`
事件:`agent.acp.frame` / `agent.acp.stream.closed` / `agent.acp.catalog.changed` ## OpenCode 原生归档 Desktop/Android 的 OpenCode 归档经 `dsh/sessionArchive` 直接调用同一个 Host `opencode acp` 进程的原生 HTTP API,设置 `time.archived`,原生确认后才移除默认目录成员。 Host 声明 `agent.acp.opencode.archive.v1`,成功后以 `agent.acp.catalog.changed` 同步其他连接。 归档状态由 OpenCode 持久保存,Host 重启后仍隐藏;历史保留可读。活动回复、加载、模型写入和 待审批时拒绝归档,不隐式停止或删除会话。其他 ACP 后端尚无原生归档,不返回本地伪成功。 纯 stdio ACP 构建没有可用的私有 HTTP API 时返回 `OPENCODE_ARCHIVE_UNAVAILABLE`, 会话列表与历史直接使用标准 ACP,不尝试访问未启动的 HTTP 端口。 当前不提供恢复或已归档列表;实现和上游版本边界见[原生归档设计](design/acp-session-archive.md)。 ## Web 兼容边界 共享浏览器投影接收标准 `session_info_update.title`。OpenCode 的标题生成独立于回复, V2 ACP 未必转发原生 rename,因此回复后以有界重试在二十秒内恢复列表标题;关闭投影 取消重读。`New session - ` / `Child session - ` 视为占位, 原生标题缺失时仅在内存中使用首条提问前 60 个字符,打开历史时同样恢复;后续正式标题 可替换临时标题,但占位、迟到读取不能覆盖较新的投影,手动改名优先。不写 OpenCode 或 Harness 数据库。独立 Remote Web 需同步该投影后部署,不能视为线上已生效。 Web、Desktop 和 Android 共用独立 `agent.acp.*` RPC / stream / transfer / event。 `dsh/` 扩展只存在于 ACP call 内,不注册到 Harness 或 Codex 业务 API。 `virtual-harness.ts` 是可在浏览器使用的内存 UI 投影,不读取本机 AGY 文件;工作区和历史 由 Host ACP 返回。AGY 工作区仅接受 `antigravity:cwd:` ID,不兼容旧 `cursor:cwd:` ID 或无前缀路径。Cursor / AGY 分别需 `agent.acp.cursor.v1` / `agent.acp.antigravity.v1`; `agent.acp.v1` 仅表示公共数据面,不据此推断后端。不提供早期 ACP 配置或工作区 ID 的迁移;该限制不表示 ACP 尚未实现或发布。 独立 Remote Web runtime 的接入和跨设备 E2E 仍待在独立 Server 仓库验证。 ## 配置 Cursor adapter 默认**关闭**。开启前请在 Host 本机完成 `agent login`,或配置 `CURSOR_API_KEY`。也可在 Desktop 插件设置中切换开关(立即生效)。 ```yaml ds-harness-remote: cursor: enabled: true binary: agent # 或 ~/.local/bin/agent 的绝对路径 ``` ## 实现入口 | 路径 | 作用 | | --- | --- | | `packages/plugin/src/acp/gateway.ts` | ACP gateway / 会话归属 / 审批 | | `packages/plugin/src/acp/method-policy.ts` | 公共 allowlist | | `packages/plugin/src/acp/peer-bridge.ts` | 每连接 stream / transfer | | `packages/plugin/src/acp/adapter.ts` | backend adapter 契约 | | `packages/plugin/src/acp/adapters/cursor-process.ts` | Cursor `agent acp` stdio | | `packages/plugin/src/acp/adapters/cursor.ts` | Cursor adapter 工厂 | | `packages/plugin/src/acp/adapters/opencode-process.ts` | OpenCode `opencode acp` stdio | | `packages/plugin/src/acp/adapters/opencode.ts` | OpenCode adapter 工厂 | | `packages/plugin/src/acp/virtual-harness.ts` | Desktop Virtual Harness(cwd → Workspace / Session / Composer) | | `packages/client-core/src/acp-client.ts` | 共享 Client(`AgentAcpClient`) | | `packages/plugin/src/client-runtime.ts` | 探测独立后端 capability 和就绪状态、打开 Virtual Harness | | `packages/plugin/src/client.ts` | Remote 模态框列出 / 添加 Cursor 工作区 | ## Desktop 使用 1. Host:设置中开启 **Agent 后端 → Cursor**(`acp.backends` 的 `cursor.enabled`),完成本机 `agent login`,保存后立即生效。 2. Client:侧栏 Remote → 选择在线 Host → 在 **Cursor virtual workspace** 分组点 `+`,浏览并确认 Host 上已有绝对目录。 3. 打开后复用原生 Workspace / Session / Composer;会话 id 形如 `cursor:`,不写入 DSH SessionStore。 ## Android Client 内存投影(不写入 SessionStore): 1. 探测 Host capability `agent.acp.cursor.v1` 并确认工作区类型就绪后启用 Cursor 工作区分组。 2. 新建 Cursor workspace = 选择 Host 上已有绝对目录;会话经 `AgentAcpClient`(`agent.acp.*`)创建与流式更新。 3. 文本 Prompt / cancel / approval(`allow-once` | `reject-once`);暂不支持图片。 4. Workspace backend id 仍为 `'cursor'`(仅 UI);线协议为 `agent.acp.*`。 ## 验证状态 - [x] Host gateway、capability、RPC 路由(`agent.acp.v1`) - [x] Cursor adapter initialize + authenticate(Host 本地) - [x] Client `initialize` 公共面(gateway 短路) - [x] 方法策略单测 - [x] Desktop 设置开关 `settings.cursor.set`(adapter) - [x] Desktop Virtual Harness(cwd workspace + session/prompt/stream/approval) - [x] Android Client 投影(`AgentAcpClient` / 内存 Cursor workspace) - [x] Android 真机:文本 Prompt、思考/正文流式帧、`prompt_completed` catch-up、多轮气泡分离 - [ ] Desktop ↔ 异机 Client 完整 E2E(审批 / cancel / 长工具轮次) - [ ] 断线重连后 stream 重建与会话内 History 恢复 - [x] Android:重连保留 Cursor 会话并自动重建 ACP stream - [ ] `session/load` 与跨设备会话列表体验 ## 近期后续(不含 Codex→ACP) 1. Android / Desktop:transport 抖动后自动 `stream.open` + `claimSession` 2. Host 侧按 session 保留可分页的展示用 History(不写 DSH SessionStore) 3. 审批 / 提问 UI 与 `agent.acp.respond` 真机矩阵 4. 跨机长时间稳定性与 WebRTC/Relay 丢帧回归 ## Android AGY (2026-10-07) Android 的 `antigravity` 工作区独立使用 AGY catalog/history/prompt 与 ACP stream。首次连接 和刷新均发现 Host 既有目录与会话;新建会话保持空白,回复后更新标题,重连重新加载历史并 订阅 stream。图片通过私有 tmp 缓存与有界 ACP transfer;不调用 Harness Prompt 或写入 SessionStore。批量读取列表不触发预热,选中目录才预热。Hermes 打包及真实 AGY 的 in-process 数据链路已验证;Android 真机与加密跨设备验收尚未完成。