跳到主要内容

数据截至 (上游 commit 9fb509bab3e0)

工具系统:MCP 工具 + 本地 agent 工具

本章一句话: 模型眼里只有「一堆工具」,根本不知道某个工具是外部 MCP 服务器提供的、还是 DeepChat 自带的本地能力——这层屏蔽就是工具系统的价值。

1. 它要解决的小问题

DeepChat 的「手脚」来自三类异构来源:外部 MCP 服务器(标准协议)、本地 agent 工具(读写文件、跑命令、搜代码……直接在主进程做)、以及 ACP agent 自带工具。它们调用方式、权限模型都不同。如果让聊天 runtime 去分辨「这个工具该怎么调」,会到处是 if-else。ToolPresenter 把它们抹平成一个统一接口

2. 思路:聚合 + 来源映射

怎么读:左边是聊天 runtime,它只跟 ToolPresenter 打交道。ToolPresenter 内部
用 ToolMapper 记住「每个工具名来自哪」,调用时按来源分流。

AgentRuntimePresenter
│ getAllToolDefinitions() ← 拿统一的 MCPToolDefinition[]
│ callTool(name, args) ← 按名字调,不关心来源

┌─────────────── ToolPresenter ───────────────┐
│ ToolMapper: toolName ─► 'mcp' | 'agent' │
└───────┬───────────────────────────┬──────────┘
│ source = mcp │ source = agent
▼ ▼
┌───────────────┐ ┌────────────────────────┐
│ McpPresenter │ │ AgentToolManager │
│ 外部 MCP 服务 │ │ ├ FileSystemHandler │
└───────────────┘ │ ├ BashHandler (exec) │
│ ├ FffSearchHandler │
│ ├ chatSettingsTools │
│ ├ SubagentOrchestrator │
│ ├ AgentPlanTool │
│ └ TapeTools / MemoryTools│
└────────────────────────┘

3. 聚合工具定义

ToolPresenter.getAllToolDefinitions() 按顺序做(见 docs/architecture/tool-system.md):

  1. mcpPresenter 拉 MCP 工具。
  2. AgentToolManager 拉本地 agent 工具。
  3. ToolMapper 记来源;重名时优先保留 MCP 工具
  4. 过滤 disabled 的 agent 工具,并为每个 conversation 维护独立映射。

结果:agentRuntimePresenter 只持有统一的 MCPToolDefinition[],完全不知道某个工具真实出身。这就是上一章 processStreamtools 参数的来源。

4. 本地 agent 工具有哪些

AgentToolManager 装配的 model-facing 工具(名字见 agentToolManager.ts):

工具名干什么处理器
read / write / edit读写/编辑文件AgentFileSystemHandler
glob / grep代码/文件搜索(FFF backed)AgentFffSearchHandler
exec / process执行命令 / 管理后台进程 sessionAgentBashHandler
question(QUESTION_TOOL_NAME)反问用户一个结构化问题runtime 内置
update_plan(agent-core/update_plan)更新任务计划(只改 plan state)AgentPlanTool
subagent_orchestrator编排子 agent 并行/串行跑任务SubagentOrchestratorTool
image_generate图像生成AgentImageGenerationTool
tape_info/tape_search/tape_anchors/tape_handoff/tape_context读/搜索磁带、打 handoff anchorAgentTapeToolHandler
skill_list / skill_view列出/查看 SkillsSkillTools

搜索策略很明确:prompt 引导模型走 globgrepread,shell 搜索命令不在 model-facing 代码搜索路径上(docs/architecture/tool-system.md,FFF Search 一节)。

5. 权限:预检查 + 暂停授权

本地工具不直接碰旧 presenter,而是通过注入的 runtime port(toolPresenter/runtimePorts.ts,AgentToolRuntimePort)拿到:conversation 工作目录、已批准路径、settings 审批消费、会话上下文桥接。

权限按能力拆成三个服务:

能力权限服务触发的错误
文件访问filePermissionServiceFilePermissionRequiredError
settings 变更settingsPermissionService
shell/命令CommandPermissionServiceCommandPermissionRequiredError

流程上,危险操作不是「先做了再问」:AgentToolManager 在调用前做 preCheckToolPermission,需要授权就抛出对应的 *PermissionRequiredError;processStream 把它转成 pending interaction,循环暂停,授权块回到 renderer 等用户点批准(见 02-chat-loop.md 的「三个出口」)。这也是为什么 read 这种只读工具被列进 PARALLEL_READ_ONLY_AGENT_TOOLS(dispatch.ts:159)——只读可并行,写操作要串行 + 授权。

6. Subagent:把活拆给子会话,再 merge / discard 结果

子 agent 编排在 HEAD 换了形态:旧的一次性 subagent_orchestrator 工具(一次派最多 5 个任务)被改成了操作式的 live delegation 工具 deepchat_subagents(LIVE_DELEGATION_AGENT_TOOL_NAME,src/shared/agentTools.ts:3)——父 agent 用 spawn / send / follow_up / list / inspect / read_result / wait / interrupt 八种操作(LIVE_DELEGATION_OPERATIONS,src/shared/orchestration/liveDelegation.ts:31)对子 agent 做全生命周期管理,而不是一把梭交给编排器。schema 在 liveDelegationSchema(src/main/tool/agentTools/liveDelegationTool.ts:24),按 operation 校验必填字段。每个子任务仍跑在独立子会话里(sessionKind='subagent',见 docs/FLOWS.md 第 3 节)。

关键在结果如何回到父会话——这里和 Tape 紧密咬合:

src/main/tape/application/sessionTape.ts:489-495
mergeFork(parentSessionId, forkId) // 把子磁带吸收进父会话
discardFork(parentSessionId, forkId) // 整段丢弃(只追加标记)

子会话以 ${parentSessionId}::fork::${forkId} 的 fork 形式存在(01-tape-system.md 第 6 节,forkService.ts:90);跑完后父会话要么 mergeFork(把子磁带吸收进来)、要么 discardFork(整段丢弃)。因为一切都是 append-only,merge/discard 也只是「在父磁带上追加结果或一个丢弃标记」,不破坏任何过程。

7. 巧妙之处

  • 来源透明:聊天 runtime 持有统一 MCPToolDefinition[],加新工具种类(本地/ MCP / ACP)不动主循环。
  • 权限即暂停:把「要授权」实现成抛错 → 暂停交互,复用了 stream 循环已有的 paused 机制,不需要工具系统自己搞一套阻塞 UI。
  • subagent = fork + live delegation:子 agent 不是临时内存对象,而是真正的 fork 会话;工具面从「一次编排」改成 spawn/wait/read_result 的操作循环,结果经 mergeFork/discardFork 落到父会话,可追溯可丢弃。

8. 边界与局限

  • 重名工具一律 MCP 优先,本地同名工具会被遮蔽——加本地工具要避开常见 MCP 名。
  • FFF 不可用时 glob/grep 直接报工具错误(不静默降级),保证模型能感知搜索失败(docs/architecture/tool-system.md)。

9. 代码地图

主题文件路径符号名
聚合 / 路由src/main/tool/index.tsToolPresentergetAllToolDefinitions
来源映射src/main/tool/toolMapper.tsToolMapper
本地工具装配src/main/tool/agentTools/agentToolManager.tsAgentToolManager
runtime portsrc/main/tool/runtimePorts.tsAgentToolRuntimePort
子 agent live delegationsrc/main/tool/agentTools/liveDelegationTool.tsliveDelegationSchemaLIVE_DELEGATION_AGENT_TOOL_NAME(deepchat_subagents)
Tape 工具src/main/tool/agentTools/agentTapeTools.tsAgentTapeToolHandlerTAPE_TOOL_NAMES
命令权限src/main/tool/permission/commandPermissionService.tsCommandPermissionServiceCommandPermissionRequiredError