05 · 实战:Hook 执行效果
用 agent 修改文件的任务,展示 after_tool 审计日志 hook 在工具调用链中的实际表现。
Hook 不只是概念
前三节实现了 HookManager、三个内置 hook,并把 hook 集成到了 agent 循环中。这一节用真实任务展示 hook 在运行中的实际效果。
审计日志 Hook:记录每一次工具调用
main.ts 中默认注册了审计日志 hook:
const hookManager = new HookManager();
hookManager.register(createAuditLogHook());给 agent 一个需要修改文件的任务:
> 在 src/tools/search.ts 的 execute 函数开头添加空查询校验:if (!query.trim()) return '搜索关键词不能为空'真实运行结果:
>> 开始处理: 在 src/tools/search.ts 的 execute 函数开头添加空查询校验...
... 思考中
> read_file: src/tools/search.ts
+ read_file: 文件: src/tools/search.ts (94 行)
> 执行操作
> patch_file: src/tools/search.ts
+ patch_file: 已修改 src/tools/search.ts
> 执行操作
= 完成 [3次模型调用, 2次工具调用]审计日志 hook 在每次工具调用后默默记录。任务结束后:
const auditHooks = hookManager.getHooks("after_tool");
// hook 名称: audit_log
// 工具调用总次数: 2每条审计记录包含时间戳、工具名、参数摘要和结果预览。这些信息在 agent 运行时不会显示,但在任务结束后可以回溯——agent 做了什么、改了哪个文件、结果是什么。
Hook 的触发时机
把 patch_file 调用放到 agent 循环中看 hook 的触发时机:
模型返回: patch_file({ path: "src/tools/search.ts", old_content: "...", new_content: "..." })
│
├─ before_tool hook 链 ← 可以拦截或修改参数
│ (无 hook 注册,跳过)
│
├─ 权限检查 ← 独立于 hook 的安全层
│ (自动放行,跳过)
│
├─ 执行 patch_file ← 实际工具调用
│ → "已修改 src/tools/search.ts"
│
├─ after_tool hook 链 ← 审计日志在这里记录
│ audit_log: 记录 { tool: "patch_file", args: "...", result: "已修改..." }
│
└─ after_edit hook 链 ← 自动格式化在这里触发
(auto_format hook 未注册,跳过)一次 patch_file 调用触发了两个 hook 时机:先 after_tool(记录日志),再 after_edit(如果注册了格式化 hook)。每个 hook 时机独立,按注册顺序执行。
Hook 的容错:报错不中断
看 HookManager 中 after_tool 的执行逻辑:
async executeAfterTool(ctx: AfterToolContext): Promise<void> {
const hooks = this.getHooks("after_tool");
for (const hook of hooks) {
try {
await (hook.handler as (ctx: AfterToolContext) => Promise<void>)(ctx);
} catch (error) {
console.error(`Hook ${hook.name} 错误: ${error.message}`);
}
}
}即使某个 hook 抛出异常,它会被 catch 住,打印错误日志,然后继续执行下一个 hook。agent 的主流程不会因为 hook 的 bug 而中断。
这个设计很重要——hook 是"附加逻辑",不是"核心逻辑"。如果审计日志 hook 写文件失败,agent 的修改仍然有效,只是没被记录而已。
自动格式化 Hook:编辑后自动 prettier
如果注册了自动格式化 hook,patch_file 修改 .ts 文件后会自动运行 prettier:
hookManager.register(createAutoFormatHook());
// after_edit hook handler 内部:
const supportedExtensions = [".ts", ".tsx", ".js", ".jsx"];
if (ctx.path.endsWith(".ts")) {
await execFileAsync("npx", ["prettier", "--write", ctx.path], {
timeout: 10000,
});
}agent 可能写出缩进不规范的代码,但最终文件会被自动格式化。如果 prettier 不存在或执行失败,hook 会静默跳过——不影响 agent 的主流程。
before_finish Hook:退出前的最终检查
当 agent 即将返回结果时,before_finish hook 被触发。agent.ts 中的三个退出路径都调用了它:
// 路径一:模型给出纯文本回答
await options.hookManager.executeBeforeFinish({ answer, stats });
// 路径二:所有计划步骤完成
await options.hookManager.executeBeforeFinish({ answer, stats });
// 路径三:达到最大迭代次数
await options.hookManager.executeBeforeFinish({ answer, stats });如果某个 hook 需要在 agent 结束时做汇总(比如输出审计统计、发送通知),注册为 before_finish 时机就行。
这一章做了什么
回顾第 8 章的成果:
- Hook 类型系统:4 个时机(before_tool、after_tool、after_edit、before_finish),每个时机有独立的上下文类型
- HookManager:按时机分组管理 hook,顺序执行,报错不中断
- 审计日志 hook:记录每次工具调用的时间、参数、结果
- 自动格式化 hook:文件编辑后自动运行 prettier
- 编辑验证 hook:文件编辑后自动运行对应测试
- agent 集成:所有 hook 调用都是可选的,不注册则静默跳过
agent 从第 7 章的"能力固定的工具"进化为"行为可插拔的平台"。下一章会实现 MCP 协议,让 agent 能接入外部工具服务器。
登录以继续阅读
解锁完整文档、代码示例及更多高级功能。