Hook 系统技术架构
重构后:
HookEngine统一调度,三种 Hook 实现共存在同一个引擎下。
宏观架构
AgentLoop
(会话生命周期管理者)
│
┌──────────┬──────────┬────┴────┬──────────┬──────────┐
│ │ │ │ │ │
SessionStart PreToolUse PostTool PostTurn SessionEnd UserPromptSubmit
│ │ │ │ │ │
└──────────┴──────────┴────┬────┴──────────┴──────────┘
│
┌──────────▼──────────┐
│ HookEngine │
│ (Arc, 单例, 可替换) │
└─────────────────────┘
│
┌───────────────┼───────────────┐
▼ ▼ ▼
ShellCommandHook ScriptHook BuiltInHook
(JSON 配置 → shell) (TOML → 脚本文件) (Rust 原生)
│ │ │
▼ ▼ ▼
WebhookHook (HTTP 远程调用)
Legend:
━━━━━ = 调用流(async fn)
─ ─ ─ = 注册流(load_all 时建立)
┌──┐ = 数据/进程
分层拓扑
┌─────────────────────────────────────────────────────────────────┐
│ 第 4 层 · 调用侧 │
│ │
│ agent/mod.rs turn/runner.rs │
│ ├─ SessionStart ├─ PreToolUse (单次触发, Result<Option>) │
│ ├─ SessionEnd ├─ PostToolUse (fire-and-forget) │
│ ├─ UserPromptSubmit └─ PostTurn │
│ └─ ReloadConfig │
│ │
│ 特征:只调用 HookEngine 的 pub trigger_*, │
│ 不引用任何具体的 Hook 实现类 │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 第 3 层 · 统一引擎 │
│ │
│ engine.rs · HookEngine │
│ ├─ load_all(working_dir) ← 唯一的配置入口 │
│ ├─ 12 个 register_* 方法 │
│ └─ 12 个 trigger_*/collect_* 方法 (11 trigger_* + 1 collect_*) │
│ │
│ 关于 Arc: │
│ TurnRunner 持有 Arc<HookEngine>,AgentLoop 持有同引用。 │
│ ReloadConfig 时: │
│ let engine = Arc::new(HookEngine::new()); │
│ engine.load_all(&wd); │
│ self.hook_engine.store(engine); // ArcSwap 原子替换 │
│ │
│ 旧引用继续存活直到所有正在执行的 trigger 返回。 │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 第 2 层 · 适配实现 │
│ │
│ ShellCommandHook ScriptHook WebhookHook │
│ (engine.rs) (script_runner.rs) (webhook.rs) │
│ ┌─────────────────────┐ ┌──────────────┐ ┌───────────┐ │
│ │ command: String │ │ script: Path │ │ url │ │
│ │ matcher: regex │ │ timeout │ │ method │ │
│ │ timeout_ms │ │ script_type │ │ headers │ │
│ │ plugin_root │ └──────────────┘ └───────────┘ │
│ │ │ │
│ │ 实现 6 个 trait: │ 实现 4 个 trait: 实现 12 个 trait: │
│ │ PreToolExecution │ PreToolExecution (除 OnUserPrompt │
│ │ PostToolExecution │ PostToolExecution Submit 外全部) │
│ │ OnSessionStart │ PostTurn │
│ │ OnSessionEnd │ SystemPrompt │
│ │ OnUserPromptSubmit │ │
│ │ OnToolCallStart │ │
│ └─────────────────────┘ │
│ │
│ BuiltInHook (built_in.rs) │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ ToolAuditLogHook → OnToolCallStartHook │ │
│ │ TurnStatsHook → OnTurnStart + OnTurnComplete │ │
│ │ AutoCommitHook → OnTurnComplete │ │
│ │ SessionSummaryHook → OnSessionStart + OnSessionEnd │ │
│ │ ErrorReportHook → OnError │ │
│ │ ResponseValidation → OnModelResponse │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
│ 注意:OnMessageReceivedHook trait 已定义但暂未在 HookEngine 中 │
│ 注册触发,仅 WebhookHook 实现了该 trait(待后续 PR 激活) │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 第 1 层 · 协议适配 │
│ │
│ ShellCommandHook 的两套子进程协议: │
│ │
│ CC 兼容协议 新 stdin JSON 协议 │
│ (JSON 配置旧 hook) (TOML 配置 ScriptHook) │
│ ┌─────────────────────────┐ ┌──────────────────────────┐ │
│ │ Env: │ │ Env: │ │
│ │ ATOMCODE_HOOK_EVENT │ │ ATOMCODE_HOOK_TYPE │ │
│ │ ATOMCODE_HOOK_CONTEXT │ │ ATOMCODE_TOOL_NAME │ │
│ │ ATOMCODE_TOOL_NAME │ │ ATOMCODE_WORKSPACE │ │
│ │ CLAUDE_PLUGIN_ROOT │ │ │ │
│ │ │ │ stdin: │ │
│ │ stdin: │ │ 完整的上下文 JSON │ │
│ │ PreToolUse/PostToolUse │ │ stdout: │ │
│ │ → HookContext JSON │ │ HookResult JSON │ │
│ │ UserPromptSubmit │ │ │ │
│ │ → {prompt, session_id, │ │ 超时: │ │
│ │ hook_event_name, │ │ tokio::time::timeout │ │
│ │ cwd} │ │ + kill_on_drop(true) │ │
│ │ │ │ fail-open │ │
│ │ stdout (last-line JSON):│ │ │ │
│ │ decision: allow│block │ │ │ │
│ │ hookSpecificOutput: │ │ │ │
│ │ additionalContext │ │ │ │
│ │ │ │ │ │
│ │ 非 JSON stdout → │ │ │ │
│ │ 纯文本注入 │ │ │ │
│ │ │ │ │ │
│ │ 超时: fail-open │ │ │ │
│ │ crash: fail-open │ │ │ │
│ └─────────────────────────┘ └──────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
配置加载路径(合并后唯一入口)
HookEngine::load_all(&working_dir)
│
├─ 1. JSON 配置 (老系统, CC 兼容)
│ ├─ ~/.hooks.json ─┐
│ ├─ <working_dir>/.hooks.json ├─▶ json_config::load_hooks_config()
│ └─ plugin hooks.json / plugin.json ─┘ ─▶ Vec<HookConfig>
│ ─▶ ShellCommandHook::from_hook_config()
│ ─▶ engine.register_*()
│
├─ 2. TOML 配置 (新系统)
│ ├─ ~/.atomcode/hooks/hooks.toml ─┐
│ └─ .atomcode/hooks/hooks.toml └─▶ HooksConfig::from_dir()
│ ─▶ register_hooks_to_engine()
│ ─▶ ScriptHook / WebhookHook
│
├─ 3. 内置 Hook
│ ├─ ToolAuditLogHook → OnToolCallStart
│ ├─ TurnStatsHook → OnTurnStart + OnTurnComplete
│ ├─ AutoCommitHook → OnTurnComplete
│ ├─ SessionSummaryHook → OnSessionStart + OnSessionEnd
│ ├─ ErrorReportHook → OnError
│ └─ ResponseValidationHook → OnModelResponse
│
└─ 4. Webhook
└─ HooksConfig::register_webhooks_to_engine()
事件 → Trait → 实现 映射表
| 引擎方法 | 触发时机 | Trait | ShellCommandHook | ScriptHook | BuiltIn |
|---|---|---|---|---|---|
trigger_pre_tool_use |
工具执行前 | PreToolExecutionHook |
✅ | ✅ | — |
trigger_post_tool_use |
工具执行后 | PostToolExecutionHook |
✅ | ✅ | — |
trigger_user_prompt_submit |
用户发消息 | OnUserPromptSubmitHook |
✅ | — | — |
trigger_session_start |
会话开始 | OnSessionStartHook |
✅ | — | ✅ (Summary) |
trigger_session_end |
会话结束 | OnSessionEndHook |
✅ | — | ✅ (Summary) |
trigger_post_turn |
Turn 完成 | PostTurnHook |
— | ✅ | ✅ (AutoCommit) |
trigger_on_turn_start |
Turn 开始 | OnTurnStartHook |
— | — | ✅ (Stats) |
trigger_on_turn_complete |
Turn 完成(详细) | OnTurnCompleteHook |
— | — | ✅ (Stats) |
trigger_on_tool_call_start |
工具调用开始 | OnToolCallStartHook |
— | — | ✅ (Audit) |
trigger_on_error |
错误发生 | OnErrorHook |
— | — | ✅ (Report) |
trigger_on_model_response |
模型响应后 | OnModelResponseHook |
— | — | ✅ (Validation) |
collect_system_prompt_extensions |
构建 prompt | SystemPromptHook |
— | ✅ | — |
结果类型流转
OnUserPromptSubmitHook::on_user_prompt_submit()
└─▶ UserPromptSubmitResult ─▶ HookEngine::trigger_user_prompt_submit()
│ │
│ ├─▶ Continue → UserPromptHookResult::Continue
│ ├─▶ Inject(s) → UserPromptHookResult::Inject(s)
│ └─▶ Block(s) → UserPromptHookResult::Block(s)
│
└── 可转换为 HookResult(用于日志/统计):
Continue → Ok
Inject → Modified
Block → Denied
PreToolExecutionHook::on_pre_tool_execution()
└─▶ HookResult ─▶ HookEngine::trigger_pre_tool_use()
│
├─▶ Ok → 继续, 参数不变
├─▶ Modified(json) → 继续, 参数替换为 json
├─▶ Denied(reason) → 阻塞, 返回 reason (≈ 老系统 Block)
└─▶ Warning(msg) → 继续, 仅打印警告
PostToolExecutionHook::on_post_tool_execution()
└─▶ HookResult → fire-and-forget, 仅 Warning/Denied 会打印日志
并发安全模型
ReloadConfig 触发时:
┌─────────────────────┐
│ 创建新 HookEngine │
│ engine.load_all() │
└─────────┬───────────┘
│
▼
┌─────────────────────────┐
│ ArcSwap 原子替换 │
│ hook_engine.store(arc) │
└─────────┬───────────────┘
│
├──▶ 旧 Arc 引用计数 > 0? 等待正在执行的 trigger 返回
│ (Rust Arc 自动管理, 不需要显式锁)
│
└──▶ 新请求看到新 Arc, 使用新引擎
优点:
- 读路径 (trigger_*) 零锁开销
- 写路径 (reload) 只建新引擎 + 原子替换一次
- 不阻塞任何正在执行的 hook