close

Branch · Step 17

tool-bash:Consumer 的注入面与三条路径

packages/shell/tool-bash/src/index.ts(394 行)。这是旁支的最后一页,也是主链 14 页「工具从哪来」的最终答案:一个 394 行的插件,用 defineTool('bash') 把 shell 能力变成模型可见的工具

15 seam 全景:Consumer 位 tool-bash/src/index.ts apply:190 → defineTool:242 → execute:330 (回到主干) 13/14 页的调度器与流水线执行它

示例本次示例:bash 工具的三个路径实况

示例轨迹 17-1 · 前台路径(我们的 ls -la)
# 模型调用 bash({ command:'ls -la' })
# → execute(330 行)→ ctx.shellEnv.collect(exec)(341 行)
# → ctx.shell.run(ctx.shell.resolve({...}))(380 行)
# → ShellRunResult { exitCode:0, stdout:'total 8\ndrwxr-xr-x ...', timedOut:false }
# → renderResult(render.ts)→ 模型看到的文本:
#   "total 8
#    drwxr-xr-x ..."
#   (退出码 0 无 marker;非零会附加 [exit code: N])
# → 14 页 post-execute → tool/result 落盘(seq 9)
来源:17 页行级解读 + 16 页 ShellRunResult
示例轨迹 17-2 · 后台路径与沙箱升级路径
# 后台:模型请求 background:true
# 354 行 ctx.get('jobs') 有值 → 370 行 ctx.shell.start(resolve(request))
# → ShellProcess → background.ts 的 processOutcome → jobs 任务
# → 结果变成「任务已启动,id=...」;模型稍后用 tool-jobs 查看(25 页)

# 沙箱升级:挂载了 bash-sandbox 时(15 页示例 15-2)
# 模型传 sandbox_permissions:['network'] 请求升级
# → 223-227 行 approveEscalation → approver.request
# → approval/request waterfall → 用户批准/拒绝(29 页)
# → 批准后按升级的权限跑;拒绝则按原权限或失败
来源:17 页 330-382 行 + 25 页 jobs + 29 页审批

§1inject 数组:依赖面声明

packages/shell/tool-bash/src/index.ts插件声明(节选)30-34
30export const name = 'tool-bash'
31export const inject = ['tools', 'shell', 'systemPrompt', 'shellEnv']
30-31

inject 就是依赖面:这四个服务全部就绪,本插件才激活(03 页的 PENDING 审计靠它诊断)。逐一看:tools(注册 bash 工具的注册表)、shell(能力本体,16 页的 provider)、systemPrompt(工具 schema 进提示词的通道,12 页)、shellEnv(环境事实注册表,16 页的 dshEnv 来源)。

§2apply:注册工具与沙箱升级

packages/shell/tool-bash/src/index.tsapply 主体(节选)190-242
190export function apply(ctx: Context, config: Config = {}): void {
192  const defaultMode = ctx.shell.sandboxMode
194  const sandboxPolicy = defaultMode === undefined
195    ? undefined
196    : ctx.get('sandboxPolicy')
200    sandboxPolicy?.resolve(exec.agent === undefined ? {} : { session: exec.agent.session })
223    return approveEscalation(
226      { approver: ctx.get('approval'), ... },
227    )
242  ctx.tools.register(defineTool({
243    name: 'bash',
330    async execute(args: BashToolArgs, exec) { ... },
190

插件入口是 apply——Cordis 插件协议(03 页的 Inject/Plugin)。树挂载时 Loader 调它,返回 void(注册是副作用,卸载时由 fiber 逆转)。

192-196

能力探针:读 ctx.shell.sandboxMode(15 页接口里的可选能力声明)。本地 executor 返回 undefined(不沙箱);沙箱化 executor 返回具体 mode——tool-bash 由此决定是否启用升级审批路径。

223-227

沙箱升级走审批approveEscalation(来自 dsh-sandbox)最终调 approver.requestapproval/request waterfall——这是 shell seam 唯一相交的 Cordis 事件(15 页图里标过)。

242-243

ctx.tools.register(defineTool({name:'bash',...}))——「工具从哪来」的最终答案:一个插件用 defineTool 定义,register 进 registry(14 页),schema 经 systemPrompt 进提示词(12 页),模型就能调它。

330

execute 是工具的 body——14 页流水线的 dispatchToolBody 最终调到这里。

§3execute:三条路径

packages/shell/tool-bash/src/index.tsexecute 的分支(节选)330-382
330    async execute(args: BashToolArgs, exec) {
341      const dshEnv = ctx.shellEnv.collect(exec)
354        const jobs = ctx.get('jobs')
370            const proc = ctx.shell.start(ctx.shell.resolve(request))
380      const result = await ctx.shell.run(ctx.shell.resolve({ ... }))
341

能力事实采集ctx.shellEnv.collect(exec) 把 DSH_* 环境快照收进 request 的 dshEnv——16 页环境合并第④段的来源。

354

后台路径探针:ctx.get('jobs')——jobs 服务挂载时后台命令走它(ShellProcess → jobs 任务 outcome),否则只能前台。

370

后台路径ctx.shell.start(ctx.shell.resolve(request))——注意「先 resolve 再 start」的字面形态,15 页的心智模型在这里落点。

380

前台路径await ctx.shell.run(ctx.shell.resolve({...}))——resolve → run 一段式。run 返回的 ShellRunResult 经 renderResult(render.ts)变成模型看到的文本 + 退出码 marker([exit code: N] / [killed by signal: X],正反解析共享 parseExitStatus)。

§4旁支结束:回望与后续

旁支走完了。回望这段树:

主链 14 页的工具结果来自 registry → registry 里的工具来自插件(15 的分岔)→ 工具 execute 调 ctx.shell 服务(17)→ 服务由 Provider 实现(16)→ Provider 调下层 ctx.subprocess → 进程树。

这套「Definition / Provider / Consumer + inject」的读法就是读其他能力的模板——fs、lsp、web、terminal、subagent 都同构。读新 seam 时的检查清单:

  1. Service Definition 在哪(super(ctx,…) 与抽象方法);
  2. resolve 拆分有没有(Request → Spec);
  3. 默认值归谁(Provider 的 config);
  4. Provider 的 teardown 责任(ctx.effect 挂什么);
  5. Consumer 的 inject 数组(依赖面);
  6. 相交事件是哪些(Service 型往往只有 approval/request;事件 gate 型看 fs/*)。

整站到这里为止。回到树状总览,或继续扩展新的旁支:持久化(session-persistence-jsonl / session-query-sqlite)、压缩(compaction + surface replace)、子代理(subagent seam)、审批权限(approval/request + permission)、沙箱(sandbox seam + fs-observation-policy)、Cordis 运行时(vendor/ Loader/Include/Context 内部)、Web 层(api/remotes BFF + ACP)。