外观
02 · 一次工具调用的完整生命周期
One tool call, end to end
模型每次想干一件事,都会经过五段钩子。下面的时序即一次中风险 bash 调用的标准旅程(各步骤数字均可在代码定位):
- ① 模型 → DSH模型发出工具调用(如 `bash`、`write`、`apply_patch`、`web_fetch`)。
- ② 同步硬拒闸门 ·
ctx.tools.guard()index.ts:L"anyCtx.tools?.guard?.("不是事件,而是 tools 服务的**同步注册守卫**:`isAutoExecution` 后先 `hardDenyReason`(凭据物质 / 受保护路径 / shell 熔断),再 `symlinkEscapeReason`(realpath 逃逸工作区)。命中即返回原因字符串 → 直接拒,**不弹窗**。这一层只做硬拒,永不挂类别分类;命中即拒、**不可被升级为放行**(与 Cedar `forbid` 同构);宿主按注册序征询、首个返回原因的 guard 生效,同一 exec 至多征询一次(monotonic)。可达性边界:本层与 ③ 同随宿主 `tools/pre-execute` 瀑布征询生效——对端监听器若先行返回非决策对象且不带 `reason`,宿主直接派发执行,两层都不被征询(三形态与不可自检结论见 [§09](./09-defense-in-depth))。 - ③ 静态评估 + 类别收紧 ·
tools/pre-executeindex.ts:L"anyCtx.on('tools/pre-execute', async"`assessTool` → `deny`(硬拒 `[dsh-auto-approval-llm] hard deny`,**落 `hard-deny` 历史记录 + debug 行**)/ `allow`(直接放行)/ `ask`。中间还有一层**类别收紧**(index.ts:L"Category tightening"):三态开关配成 `deny` 的类别在这里终端拒绝、配成 `ask` 的无条件跳过 classifier 快径直接转人工(详见 [§17](./17-category-switches))。之后若 `classifierEligible`,交给 LLM 预分类器(`classifier.classify`)再定 `allow | deny | ask` —— 快路径的放行/拒绝各自落 `classifier-allow` / `classifier-deny` 历史记录(`ask` 除外,留待 answerer 记终局);分类器不可用 → 一律向人工(`classifier unavailable`)。 - ④ 终局裁决 ·
approval/requestindex.ts:L"anyCtx.on('approval/request', async"(prepend+global;门卫读 raw identity `auto-approval`,宿主 <0.1.6 兼容 `auto` 别名)本插件档(machine value `auto-approval`)的**核心决策管线**(详见 [§04](./04-adjudicator-pipeline)):声明规则 → 名单 → 类别开关 → 评审模式 → 熔断 → 策略硬拒 → 学习放行 → 风险分档 → LLM 复审 + 人工倒计时。产出 `allowed-once` 或 `rejected`,或委托官方面板走人工竞速。 - ⑤ 执行与产物登记 ·
tools/resultindex.ts:Lartifacts.settle`artifacts.settle`:把本会话**成功创建**的文件登记进 `ArtifactRegistry`(后续删除该文件时可豁免)——入参 `plannedCreates` 来自评估阶段的 `artifacts.plan`(L2931)。同一事件也是通知队列的「已执行」标记点:只决定**该不该发**通知,不决定位置(见 ⑦)。 - ⑥ 喂回模型 ·
tools/post-executeindex.ts:L"anyCtx.on('tools/post-execute', (exec"(global)若这次工具结果 `isError` 且有超时/决策标记(`timeoutFeedback` / `decisionFeedback`),注入 `{kind:'block', feedback}` —— 让模型知道「不是因为命令错,而是被审批挡下(超时 / 规则 / 模型拒绝)」。另有独立一段**结果脱敏**(index.ts:L"config.redactResults && isAutoExecution(exec)"):`redactResults` 开启时,成功结果同样过一遍脱敏器并记审计——喂回模型的文本不夹带秘密。 - ⑦ 通知投递 · agent inbox(
agent.inject) notices.ts:LwatchNotices(watchNotices 内 notices.ts:L"event?.type === 'step/end'" 的 step/end 分流)「✅ Model approved」「已学习放行」与 first-use onboarding 通知**不再直写会话日志**:队列在 `tools/result`(L3217-3218)只标记「该工具确实执行了」,`step/end`(L1250)时把已执行的通知交给 `agent.inject`(notices.ts:LinjectNotice)送入 agent inbox —— driver 在**最近的 step 边界**认领,因此消息**不可能**插进 assistant `tool_calls` 与 `tool/result` 之间(OpenAI 兼容 provider 会拒收整个会话)。未产生结果(拒绝/取消)的通知仅落控制台,不进会话。**权衡**:inject 不唤醒空闲 driver、可能错过已认领的批次、会话销毁时丢弃 pending —— 通知可能延迟到下一次交互甚至丢失;这是「宁可晚/丢,绝不插错位置」的有意取舍(`notifyUser` / `onboardingMessageEnabled` 可关)。
注意
②③ 在 approval/request 之前就把「无争议放行 / 明显拒绝」消化掉,只有真正的模糊区(ask)才进入审批面板 + LLM 复审,吞吐与安全兼得。