跳转到内容

Hooks 钩子系统实现

Gemini CLI — Hooks 钩子系统实现

学习目标

  • 理解钩子系统的六类钩子和完整生命周期
  • 掌握 Planner → Runner → Aggregator → EventHandler 四组件架构
  • 分析可信钩子系统和 hookStateMap 管理机制

前置知识

本章涉及 Hook 系统的通用原理,建议先阅读:

下文假设你已理解钩子的基本概念和生命周期,直接聚焦 Gemini CLI 的具体实现。


项目实践

六类钩子

Gemini CLI 支持六类钩子,覆盖 Agent 执行的完整生命周期:

钩子触发时机可修改内容
BeforeAgentAgent 收到用户输入后、开始处理前阻止执行、注入额外上下文
AfterAgentAgent 处理完成后、返回结果前停止执行、请求继续
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; // 当前活跃的钩子调用数
}

生命周期

  1. BeforeAgent 触发前检查 hasFiredBeforeAgent,已触发则跳过
  2. AfterAgent 触发时,activeCalls 计数
  3. 所有钩子完成后,activeCalls 递减
  4. 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 的选择:两阶段。未来可能需要添加更多钩子评估策略(如条件评估、远程评估),两阶段架构支持水平扩展。


参考来源