Repository Wiki
deepseek-ai/deepseek-harness

ACP 自动化协议

ACP 桥接器将 harness 的持久化 Agent 会话通过基于 stdio 的 JSON-RPC 暴露给可信的程序化客户端,处理标准会话控制、提示、配置、语义更新和一次性工具授权。

目的与范围

本文聚焦 @deepseek-ai/dsh-acp 的服务端入口、会话创建与恢复、协议路由及生命周期。MCP 挂载、模型选项生成、提示内容转换和更新编解码分别由 mcp.ts、model-control.ts、content.ts、updates.ts 等模块承担;这里仅说明入口与会话对象如何调用它们,不推断其内部算法。对于子 Agent 作为 ACP 客户端的行为,请参阅相应的 subagent 专题;本文只讨论此服务端桥接。index.ts · session.ts

概述

插件在 Cordis apply 阶段捕获持久化服务,建立连接级的 sessions 映射和恢复中的 activating 集合;每个 AcpSession 拥有一个确切的顶层 Agent 引用、模型控制对象、提示准入状态、顺序输出队列和可复用的关闭 Promise。这样会话事件不会因为 ID 相同而错误地投递给另一 Agent。默认通过进程 stdin/stdout 上的 ndJsonStream 连接 SDK,config.stream 可替代该传输。index.ts · session.ts · index.ts

架构

Loading diagram...

Source: index.ts, session.ts

最后一条回边表示 AcpSession 经传入的 notify 回调把更新送回入口持有的 SDK 客户端,并非重新发起 ACP 请求。ctx.agents.create / resume 的 setup 中先安装模型控制并挂载请求中的 MCP server,成功返回后才构造会话记录。index.ts · session.ts

协议入口与会话生命周期

初始化、路由和依赖

inject 声明 agents、llm、sessionPersistence、sessions;初始化探测当前提供商/模型是否支持 ACP 图片提示,返回 SDK 的 PROTOCOL_VERSION、HTTP MCP 能力、图片能力及 close/list/resume 会话能力。音频与 embedded context 明确为 false,authMethods 为空,authenticate 直接成功。图片能力是在连接初始化时计算,之后传给 record.prompt,而不是每个请求重新计算。index.ts · index.ts · index.ts

SDK 路由覆盖 initialize、authenticate、session/new、list、resume、close、setConfigOption、prompt,以及 session/cancel 通知。底层 handler 的请求 signal 传给有取消意义的会话/持久化操作;cancel 是通知,仅对当前映射中的会话调用 cancel()。index.ts

创建:组成后发布,发布后落盘

newSession 先检查桥接器未关闭与工作目录参数,生成 UUID 类型的会话 ID,再创建带工作目录、MCP 列表、初始模型配置及通知函数的 AcpSession。创建时 ctx.agents.create 在 setup 中安装 AcpModelControl 和 MCP;返回完整记录才加入 sessions。随后查询配置选项并调用 ctx.sessions.flush,使空会话也能持久化。若选项读取或 flush 出错,映射被移除且记录被关闭;若连接在创建中关闭,也先关闭新记录再报错。MCP 配置错误转换为协议 invalidParams。index.ts · session.ts

恢复:排他激活与工作区校验

resumeSession 拒绝当前桥接器中、activating 集合中或全局 ctx.sessions 中的活动 ID。随后从 sessionPersistence.stat 读取头部,排除不存在的、子 Agent 来源及带 parentSession 的会话,并用 sameDirectory 核验工作目录。activating 在异步恢复的 finally 中移除,防止并发恢复同一 ID。恢复过程中 ctx.agents.resume 的 setup 使用最近日志头中的 provider/model 选择,缺失时回退到部署配置,并重新挂载本次请求的 MCP 连接。恢复成功后才加入映射,选项发现失败则反向清理。index.ts · session.ts · session.ts

Loading diagram...

Sources:

列表、配置与关闭

listSessions 要求可选 cwd 为绝对路径,解析游标后从持久化服务列出记录;过滤活动、激活中、子 Agent、缺失/非绝对路径和不匹配目录的会话。按创建时间降序、ID 作平局排序,再按游标与页大小截取;只有尚有后续结果时返回 nextCursor。这是可恢复会话的候选列表,不是活动会话清单。index.ts

setSessionConfigOption 仅面向已映射会话,委托 record.setConfig,将 AcpModelConfigError 转换为 invalidParams。LLM adapter 更新事件使所有记录执行 topologyChanged();它在选项发现后把 config_option_update 串入 outputTail,避免与既有输出乱序。closeSession 即使关闭出错也在 finally 中删除自身映射;错误通过 errorChain 包入内部错误。index.ts · index.ts · session.ts

使用示例

以下均为仓库内的实际代码摘录,展示插件配置、协议装配及按会话隔离的 Agent 组成;并非额外定义的客户端调用 API。

typescript
1export const name = 'acp' 2export const inject = ['agents', 'llm', 'sessionPersistence', 'sessions'] 3 4export interface AcpConfig { 5 provider?: string 6 model?: string 7 sessionListPageSize?: number 8 stream?: Stream 9}

Source: index.ts

inject 是运行依赖,stream 是运行时传输覆盖,Schema 并未将其作为常规部署配置字段。

typescript
1const handle = await ctx.agents.create({ 2 sessionId: options.sessionId, 3 meta: { cwd: options.cwd }, 4 agentOptions: options.agentOptions, 5 signal: options.signal, 6 setup: async (agentCtx) => { 7 modelControl.install(agentCtx) 8 await mountAcpMcpServers(agentCtx, options.mcpServers, options.cwd) 9 }, 10}) 11return new AcpSession(ctx, handle, modelControl, options.notify)

Source: session.ts

组成发生在 create 内,随后才向桥接器公布记录;失败的组成不会产生已发布映射。

typescript
1.onRequest(methods.agent.session.new, ({ params, signal }) => implementation.newSession(params, signal)) 2.onRequest(methods.agent.session.list, ({ params, signal }) => implementation.listSessions(params, signal)) 3.onRequest(methods.agent.session.resume, ({ params, signal }) => implementation.resumeSession(params, signal)) 4.onRequest(methods.agent.session.close, ({ params }) => implementation.closeSession(params)) 5.onRequest(methods.agent.session.setConfigOption, ({ params, signal }) => implementation.setSessionConfigOption(params, signal)) 6.onRequest(methods.agent.session.prompt, ({ params, signal }) => implementation.prompt(params, signal)) 7.onNotification(methods.agent.session.cancel, ({ params }) => implementation.cancel(params))

Source: index.ts

配置选项

选项类型默认值作用
providerstring?未指定新 Agent 的提供商路由;缺失时不向 agentOptions 写该字段。
modelstring?未指定新 Agent 的模型;只有与 provider 同时存在才形成初始 ModelSelection。
sessionListPageSizenumber?Schema 中为 100会话列表每页最多返回的摘要数;Schema 限制为至少 1 的自然数;apply 还会通过 resolveSessionListPageSize 取得运行值。
streamStream?stdin/stdout 的 ndJsonStream仅运行时传输覆盖;生产默认使用 stdio,测试可注入。

依据:index.ts · index.ts · index.ts。不要将可选的 provider 或 model 单独配置解释成强制绑定的完整模型路由。

API 参考

下列是读取到的入口处理器与会话方法,ACP wire 方法由 SDK 的 methods.agent.* 常量绑定;不是 HTTP 路由。index.ts · index.ts

方法签名参数与返回已核实的失败或边界
apply(ctx: Context, config: AcpConfig): void注册协议连接与事件订阅;不返回连接句柄。关闭后请求会由 assertOpen 拒绝。
newSession(params: NewSessionRequest, signal: AbortSignal): Promise<NewSessionResponse>用 cwd、mcpServers 创建记录,返回 sessionId、configOptions。MCP 配置错误映射为 invalidParams;激活失败清理记录。
resumeSession(params: ResumeSessionRequest, signal: AbortSignal): Promise<ResumeSessionResponse>恢复 ID 和请求提供的工作区及 MCP,返回配置选项。活动、非合格持久化记录或目录不匹配时 invalidParams。
listSessions(params: ListSessionsRequest, signal: AbortSignal): Promise<ListSessionsResponse>可选 cwd 和游标;返回 sessions 与可选 nextCursor。非绝对路径或错误游标映射为 invalidParams。
setSessionConfigOption(params: SetSessionConfigOptionRequest, signal: AbortSignal): Promise<SetSessionConfigOptionResponse>传递 configId、value,返回完整配置选项。未知会话或 AcpModelConfigError 为 invalidParams。
closeSession(params: CloseSessionRequest): Promise<CloseSessionResponse>关闭记录并返回空对象。关闭失败为带错误链的内部错误。
prompt(params: PromptRequest, requestSignal: AbortSignal): Promise<PromptResponse>委托会话 prompt,传入初始化时确定的图片能力。未知会话为 invalidParams;提示内部处理细节此处未验证。
cancel(params: CancelNotification): Promise<void>通知已有会话取消;总是返回 resolved promise。找不到映射时不执行操作。

处理器证据:index.ts · index.ts。会话层可见的 configOptions(signal?: AbortSignal): Promise<SessionConfigOption[]> 与 setConfig(configId: string, value: unknown, signal?: AbortSignal): Promise<SessionConfigOption[]> 都先调用 assertActive() 再委托 AcpModelControl;具体选项 ID 和有效值应以返回的选项为准,不能从入口代码推断。session.ts

失败、并发与运行注意事项

  • 会话归属:ownedRecord 同时查询 ID 并校验 record.owns(agent);持久化事件校验 ownsSession(session)。这样事件和审批请求只作用于桥接器真正持有的对象。审批要求 callId;否则交给 next();有效请求先 drainUpdates(),然后仅提供 allow-once / reject-once,除 cancelled 外未知选择按拒绝处理。index.ts
  • 并发恢复:activating 覆盖从校验到异步 stat/resume 完成的窗口;列表也排除这段期间的 ID。已有全局活动会话同样不能恢复。index.ts · index.ts
  • 传输与输出:notify 捕获通知发送异常、写日志而不向事件源传播;每会话 outputTail 串行化模型拓扑更新通知。提示准入状态包含 inflight,但未读取完整提示实现,故不在这里推断其所有竞态结论。index.ts · session.ts · session.ts
  • 收尾:连接关闭或 Cordis effect 触发 quiesce;它先标记 closed、复用单一 quiescing Promise、并发 Promise.allSettled 关闭快照中的记录,清除映射,再把失败合并为 AggregateError。传输关闭与收尾错误都记录警告。关闭会话的实现先同步请求取消再进行异步清理(入口代码中的注释明确指出这一顺序)。index.ts
  • 分页成本:列表对 persistence.list() 的结果进行异步过滤、全量排序后分页,而非先由持久化层分页;规模增大时应关注全量扫描和目录比较开销。此处是代码路径推论,不代表已经进行基准测试。index.ts

扩展边界与相关链接

扩展协议行为时,优先区分连接级逻辑(apply 的 SDK 路由、映射与 teardown)和会话级逻辑(AcpSession 的 Agent/MCP 组成与输出队列);改动 wire 能力声明时同步检查初始化返回和真实处理器。插件不在自己的 apply 回调之外延迟访问注入的持久化服务,而是先捕获到局部变量。index.ts · index.ts

Sources

(2 files)