feat(acp-server): 实现 ACP Agent#104
Merged
Merged
Conversation
- 新建 packages/acp-server 包结构(package.json、tsconfig.json、types.ts) - 添加 agent-core host-bridge 子路径导出(HostToolExecutorProxy、LocalHostToolService、JsonRpcPeer) - 在根 package.json 中追加 build:acp-server 构建脚本
- 创建 noop-peer.ts:空操作 JsonRpcPeer 占位符,避免与 ACP ndJSON stdio 冲突 - 创建 config.ts:从环境变量读取配置(SPIRIT_ACP_API_KEY 等) - 创建 runtime-factory.ts:直接加载 host-internal,组装 AgentRuntime 完整实例
- 创建 session-manager.ts:多会话隔离管理(创建/获取/关闭/模式切换) - 创建 acp-agent.ts:实现 initialize/newSession/authenticate/setSessionMode/prompt/cancel/closeSession - 创建 event-mapper.ts:RuntimeEvent → ACP session/update 映射 - 创建 permission-bridge.ts:权限请求桥接(allow_once/allow_always/reject_once) - 创建 tool-call-mapper.ts:工具名 → ACP ToolCall.kind 映射
- event-mapper:使用 tool-call-mapper 映射工具 kind、提取 title/locations - tool-call-mapper:添加 buildToolCallTitle 和 extractToolCallLocations - permission-bridge:增强审批请求(包含 tool call 详情和 prompt 文本) - permission-bridge:添加 handleQuestionsRequest 降级为权限提示 - acp-agent:集成 handleQuestionsRequest 处理 questions-requested 事件
- 创建 stdio-entry.ts:ndJSON over stdin/stdout 入口 - console.log 重定向到 stderr,防止污染 ndJSON 流 - uncaughtException/unhandledRejection 安全处理
- acp-agent.ts:改用 startUserTurnStreaming + waitForCompletedTurnResult 替代 submitUserTurn,使 onEvent 实时触发流式 chunk - event-mapper.ts:将 update-pending-assistant-thinking 映射为 agent_thought_chunk(而非 agent_message_chunk) - agent-core/runtime.ts:将 waitForCompletedTurnResult 从 private 改为 public,供 ACP server 调用
- buildAvailableCommands:将 skill 目录转换为 ACP AvailableCommand - parseSlashCommand:解析 /skill-name 前缀 - buildActiveSkillPayload:读取 SKILL.md 正文 + 资源文件,构建激活负载 - upsertActiveSkill:按 id 去重插入或替换
- 将 activeSkills 从 [] 字面量改为可变数组引用 - 闭包捕获数组引用,后续 mutation 自动生效于系统提示词 - AcpRuntimeResult 新增 activeSkills 字段
- newSession:创建 session 后发送 available_commands_update - prompt:检测 /skill-name 前缀,激活 skill 并注入系统提示词 - AcpSessionState 新增 activeSkills 和 enabledSkillCatalog 字段 - SessionManager.createSession 返回 enabledSkillCatalog
available_commands_update 在 newSession 内 fire-and-forget 时会被 SDK 消息队列排在 session/new 响应之前,导致 Zed 收到通知时 session ID 未知而丢弃。改用 setTimeout(0) 延迟到响应发送后。
Collaborator
Author
|
bugbot run |
setSessionMode 仅调用 setAgentModeToolExposure 更新工具集,但 createToolAgentState 闭包中捕获的 planMetadata 仍为旧值,导致 buildAgentModeSystemMessage 持续输出旧模式。 将 planMetadata 从 const 改为 let 绑定,新增 setAgentMode() 重新加载 instruction metadata 并赋值,闭包在后续轮次自动读取新值。session-manager 的 setSessionMode 改为 async 并调用此函数。
initialize 声明 promptCapabilities.image: true 但 prompt 处理仅提取文本, ImageContent 被静默丢弃。新增 prompt-images.ts 从 ACP ContentBlock 中提取 图片:file:// URI 直接传路径,base64 data 写入临时文件后传路径, 通过 explicitImages 参数传入 startUserTurnStreaming。
Collaborator
Author
|
bugbot run |
之前仅调用 AbortController.abort(),未调用 runtime.abort()。 新请求在 startUserTurnStreaming 因 isBusy() 抛错,旧 turn 继续运行 并向客户端推送流式更新。现在检测到 isBusy() 时先 abort runtime。
之前使用 kind: 'answered' / kind: 'cancelled' 和 as any 强转, 与 agent-core 的 AskQuestionsResult 联合类型不匹配。 改为 status: 'answered' + answers: [] 和 status: 'skipped'。
new URL(uri).pathname 在 Windows 上产生 /C:/... 等非本机路径, fs 无法读取。改用 node:url 的 fileURLToPath 正确处理各平台路径。
之前读取不存在的 questions.prompt 字段,权限弹窗总是显示通用文案。 RuntimePendingQuestions 的实际结构是 .questions (AskQuestionsRequest) 内含 .title,现在正确读取并作为权限请求的描述。
Collaborator
Author
|
bugbot run |
重复调用 prompt 时旧 waitForCompletedTurnResult 可能抢到新 turn 的结果, 或在 abort 后误判 AbortController 状态向 ACP 抛出未处理错误。 新增 promptGenerations Map,每次 prompt 递增,完成时校验代次匹配, 不匹配则直接返回 cancelled。
extractPromptImages 返回 ExtractedImages(paths + cleanup), acp-agent 在 startUserTurnStreaming 后的 finally 块中调用 cleanup, 防止反复发送内嵌图片导致临时目录持续累积磁盘占用。
LlmTokenUsage 不含上下文窗口上限,之前 size 和 used 均设为 totalTokens 导致客户端进度条显示已满。改为不发送 usage_update(返回 undefined), 待后续引入模型上下文窗口查询后再启用。同步更新测试用例。
Collaborator
Author
|
bugbot run |
cleanupImages 在 finally 中紧跟 startUserTurnStreaming,但流式轮次 异步运行,LLM 在 waitForCompletedTurnResult 期间仍可能读取图片文件。 将 finally 块改为包裹 start + waitFor 全流程。
该事件语义为替换当前待定助手全文,但 ACP agent_message_chunk 是追加式的。之前映射会导致已发过的流式片段与替换文本重复错乱。 改为返回 undefined 跳过。
…atch
session/cancel 会 abort 并清空 pendingApproval,但已在途的
handleApprovalRequest 仍可能调用 continuePendingApproval,
抛出「当前没有待确认的工具调用」成为未处理 rejection。添加 .catch(() => {})。
Collaborator
Author
|
bugbot run |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
3 issues from previous reviews remain unresolved.
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 67179ef. Configure here.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Summary
packages/acp-server,作为 Spirit Agent 的第三个 Host 入口(与 CLI/Desktop 同级),实现 ACP (Agent Client Protocol) Agent 端initialize、session/new、session/prompt、session/cancel、session/close、session/set_modestartUserTurnStreaming+waitForCompletedTurnResult,思考内容通过agent_thought_chunk外露(含增量 delta 追踪修复)approval-requested→ ACPrequest_permission(支持allow_once/allow_always/reject_once)available_commands_update注册为 ACP 命令,输入/skill-name时自动读取 SKILL.md 注入系统提示词./host-bridge子路径导出(HostToolExecutorProxy、LocalHostToolService、JsonRpcPeer)waitForCompletedTurnResult从 private 改为 public,供 ACP server 调用Test plan
npm run build:acp-server编译通过npm test40 个测试全部通过/弹出 skill 补全列表/skill-name激活 skill 并注入系统提示词