Hooks 钩子系统实现
Gemini CLI — Hooks 钩子系统实现
学习目标
- 理解钩子系统的六类钩子和完整生命周期
- 掌握 Planner → Runner → Aggregator → EventHandler 四组件架构
- 分析可信钩子系统和 hookStateMap 管理机制
前置知识
本章涉及 Hook 系统的通用原理,建议先阅读:
下文假设你已理解钩子的基本概念和生命周期,直接聚焦 Gemini CLI 的具体实现。
项目实践
六类钩子
Gemini CLI 支持六类钩子,覆盖 Agent 执行的完整生命周期:
| 钩子 | 触发时机 | 可修改内容 |
|---|---|---|
| BeforeAgent | Agent 收到用户输入后、开始处理前 | 阻止执行、注入额外上下文 |
| AfterAgent | Agent 处理完成后、返回结果前 | 停止执行、请求继续 |
| BeforeModel | 调用模型 API 前 | 修改模型、配置、内容 |
| AfterModel | 模型 API 返回后 | 修改响应内容 |
| BeforeToolSelection | 工具执行前 | 修改工具列表、配置 |
| AfterToolSelection | 工具执行后 | 修改工具结果 |
四组件架构
钩子系统由四个核心组件协作完成:
1. HookRegistry:
- 存储所有已注册的钩子
- 每个钩子有名称、触发条件、优先级、可信级别
- 按钩子类型索引
2. HookPlanner:
- 根据当前事件类型收集适用的钩子
- 按优先级排序
- 生成执行计划(
HookPlan) - 支持并行评估多个钩子
3. HookRunner:
- 按计划顺序执行钩子
- 处理钩子的输入和输出
- 传递前一个钩子的输出给下一个钩子(链式传递)
4. HookAggregator:
- 收集所有钩子的结果
- 处理冲突(如多个钩子修改同一配置)
- 按优先级合并修改
- 生成最终的
AggregatedHookResult
钩子配置格式
钩子通过 YAML/TOML 配置声明:
hooks: - name: security-check events: ["BeforeModel"] priority: 100 strategy: type: "allow" tools: ["read_file", "glob"] trusted: true配置项:
events:触发的事件类型列表priority:优先级(数值越大越先执行)strategy:钩子策略(allow/deny/modify)trusted:是否为可信钩子
可信钩子系统
TrustedHooks 区分可信和不可信钩子:
| 维度 | 可信钩子 | 不可信钩子 |
|---|---|---|
| 来源 | 内置、管理员配置 | 用户、扩展 |
| 权限 | 可修改模型和配置 | 仅可注入上下文 |
| 验证 | 启动时加载 | 运行时验证来源 |
为什么区分:不可信钩子(如第三方扩展)可能被恶意利用,限制其权限是安全防线。
hookStateMap 管理
client.ts 中的 hookStateMap 管理每个 prompt_id 的钩子状态:
interface HookState { hasFiredBeforeAgent: boolean; // BeforeAgent 是否已触发 activeCalls: number; // 当前活跃的钩子调用数}生命周期:
- BeforeAgent 触发前检查
hasFiredBeforeAgent,已触发则跳过 - AfterAgent 触发时,
activeCalls计数 - 所有钩子完成后,
activeCalls递减 activeCalls <= 0且无 pending 工具时,删除状态
HookTranslator
HookTranslator 将钩子事件翻译为 MessageBus 消息:
HookEventHandler → HookTranslator → MessageBus.publish(MessageType)这使得钩子结果可以传递到系统的其他部分(如 UI、遥测)。
钩子结果处理
BeforeAgent 结果:
| 结果类型 | 行为 |
|---|---|
AgentExecutionStopped | 立即返回,不执行 Agent 循环 |
AgentExecutionBlocked | 立即返回,不执行 Agent 循环 |
additionalContext | 将内容包装为 <hook_context> 注入请求 |
undefined | 正常执行 |
AfterAgent 结果:
| 结果类型 | 行为 |
|---|---|
shouldStopExecution() | 返回停止事件,可选清除上下文 |
isBlockingDecision() | 请求继续,自动递归调用 sendMessageStream |
| 其他 | 正常返回结果 |
陷阱与对策
钩子执行顺序的隐性依赖
问题:多个钩子可能隐式依赖执行顺序(如 A 修改了配置,B 依赖 A 的修改)。
对策:
priority字段显式声明顺序- HookPlanner 按优先级排序
- 文档中明确推荐的优先级值范围
HookAggregator 冲突处理
问题:两个钩子同时修改同一配置(如都修改了模型),Aggregator 需要决定使用哪个。
对策:
- 按优先级排序,高优先级钩子的修改覆盖低优先级
- 同优先级时,最后一个执行的钩子获胜
- 记录冲突到调试日志
钩子性能
问题:每个 turn 都执行多个钩子,可能显著增加延迟。
对策:
- 钩子仅在适用事件触发时执行(非每个 turn 都执行所有钩子)
- HookPlanner 仅收集适用的钩子,不执行无关注
- 钩子实现应轻量,避免在钩子中调用 LLM
设计取舍
Planner → Runner 两阶段 vs 直接执行
| 方案 | 优势 | 代价 |
|---|---|---|
| 两阶段 | Planner 可并行评估、Runner 可按优先级排序 | 增加间接性、调试需要追踪两阶段 |
| 直接执行 | 简单直接 | 无法优化、难以扩展 |
Gemini CLI 的选择:两阶段。未来可能需要添加更多钩子评估策略(如条件评估、远程评估),两阶段架构支持水平扩展。