02.3 工具执行与状态传播

关注源码

  • src/services/tools/toolOrchestration.ts
  • src/services/tools/StreamingToolExecutor.ts
  • src/services/tools/toolExecution.ts
  • src/state/AppStateStore.ts
  • src/state/onChangeAppState.ts

工具执行的三层结构

1. toolExecution.ts 负责单次工具调用生命周期

单次 tool use 内部会处理:

  • 输入校验
  • hooks
  • permission decision
  • telemetry span
  • progress message
  • tool result block 规范化
  • 持久化与 budget 裁剪

它是真正的“工具调用内核”。

2. toolOrchestration.ts 负责批次编排

这一层按 isConcurrencySafe() 把工具调用分批:

  • 可并发读类工具可以一批执行
  • 非并发安全工具串行执行
  • 并发批次里的 context modifier 延后统一提交

这个策略的重点不是极致性能,而是避免共享状态竞争。

3. StreamingToolExecutor.ts 负责边流边执行

当模型边生成边产出 tool use 时,StreamingToolExecutor 可以:

  • 尽早启动已到达的工具
  • 维持结果按原始顺序输出
  • 在 streaming fallback、兄弟工具错误、用户中断时生成 synthetic error blocks

这是流式体验和执行安全之间的折中层。

AppState 承载什么

AppStateStore.ts 显示,前台状态至少包括这些域:

  • tasks
  • mcp
  • plugins
  • notifications
  • elicitation
  • todos
  • promptSuggestion / speculation
  • permission / footer / panel / teammate 视图

它不是一个轻量的“组件状态容器”,而是交互式会话的运行时镜像。

状态传播路径

一轮工具调用通常会经历:

  1. query loop 发现 tool use
  2. toolExecution.ts 做 permission/hooks/check
  3. 结果写回消息流
  4. 可能更新 ToolUseContext
  5. 可能更新 AppState
  6. onChangeAppState.ts 将关键变更同步到 settings / CCR / SDK metadata

其中最重要的不是 UI 更新,而是“工具执行可以反向改写运行时”。

为什么 onChangeAppState 很重要

它把多个散落的状态副作用集中到了一个 choke point,例如:

  • permission mode 改变后通知 CCR
  • model 改变后写回 settings
  • settings.env 变化后重新应用环境变量

这避免了每个交互入口都各自记一份同步逻辑。

设计上的关键点

1. 工具执行不是黑盒 RPC

它是由 hooks、permissions、telemetry、storage、UI 共同参与的有状态生命周期。

2. 并发策略偏保守

默认不并发,只有明确声明安全才批处理。对代码代理来说,这个选择比“理论吞吐更高”更合理。

3. 状态和副作用被分层

  • 工具执行内核处理单次生命周期
  • 编排层处理批次和顺序
  • AppState 承载前台会话状态
  • onChangeAppState 负责系统级同步