Hooks
介绍
Tuanjie AI 支持 Hooks(钩子):在 Agent 运行的关键节点自动调用外部命令。通过 Hooks,您可以在不修改核心代码的前提下,实现自定义的拦截、审计和增强逻辑。
什么是 Hooks?
Hooks 是一种生命周期钩子机制,允许您在 Tuanjie AI Agent 运行的各个关键节点(如工具调用前后、模型请求前后、会话开始/结束等)自动执行外部脚本或命令。
Hooks 与 Gemini CLI 的配置格式兼容。本文只说明如何配置和使用,不涉及内部实现。
Hooks 的核心能力
- 观察:记录日志、采集遥测、审计 Agent 行为
- 拦截:按策略阻止危险操作(如
rm -rf) - 终止:停止当前运行
- 修改:调整工具参数、注入上下文、改写模型请求或响应
推荐脚本语言
Hook 以子进程方式运行。建议使用 node 编写脚本(而非 Python 或 Bash),因为 Node 与 Codely CLI 同栈,在 macOS / Linux / Windows 上行为一致,无 需额外解释器或 shebang 差异。下文示例均使用 Node.js。
快速开始
1. 启用 Hooks
在 settings.json 中开启:
{
"hooks": {
"enabled": true
}
}
2. 添加一个 BeforeTool Hook(拦截危险 shell 命令)
{
"hooks": {
"enabled": true,
"BeforeTool": [
{
"matcher": "shell",
"sequential": true,
"hooks": [
{
"type": "command",
"name": "block-rm-rf",
"command": "node .codely-cli/hooks/block-rm-rf.js",
"timeout": 2000
}
]
}
]
}
}
示例脚本 .codely-cli/hooks/block-rm-rf.js:
#!/usr/bin/env node
const fs = require('fs');
let input = '';
process.stdin.on('data', (chunk) => {
input += chunk;
});
process.stdin.on('end', () => {
const data = JSON.parse(input);
const cmd = data.tool_input?.command ?? '';
if (/\brm\s+-rf\b/.test(cmd)) {
process.stdout.write(
JSON.stringify({
decision: 'deny',
reason: '禁止执行 rm -rf',
}),
);
process.exit(2);
}
process.stdout.write(JSON.stringify({ decision: 'allow' }));
process.exit(0);
});
配置文件位置
Hooks 写在 settings.json 中,支持三个作用域(会合并):
| 作用域 | 路径 |
|---|---|
| 用户(全局) | ~/.codely-cli/settings.json |
| 项目(工作区) | <项目根>/.codely-cli/settings.json |
| 系统 | 见 CLI 配置 |
此外,已激活的扩展(extensions) 也可以通过其配置中的 hooks 字段提供 Hook,优先级最低(排在系统/用户/项目 之后)。
合并规则:
- 配置项(如
enabled、mode):后者覆盖前者(工作区 > 用户 > 系统) disabled列表:各作用域取并集- 各事件的 Hook 数组:按 系统 → 用户 → 工作区 → 扩展 拼接(同名+同命令的 Hook 会去重,项目级优先)
全局配置项
hooks 对象除事件名外,还支持以下字段:
| 字段 | 说明 |
|---|---|
enabled | 是否启用 Hook 系统(默认 false) |
enableUI | 是否启用 /hooks 命令 UI;省略时继承 enabled |
disabled | 要禁用的 Hook 名称列表(按 name 匹配;无 name 时用 command) |
notifications | 是否在 UI 中显示 Hook 开始/结束事件(不影响 Notification 事件本身) |
maxTotalDurationPerTurn | 单轮 Hook 总耗时上限(毫秒,0 = 不限) |
mode | 见下表 |
environmentSanitization | 传给 Hook 子进程的环境变量过滤策略(见「环境变量与安全」) |
mode 取值:
| 值 | 行为 |
|---|---|
permissive(默认) | Hook 执行失败仅警告,主流程继续 |
strict | 对控制类事件,Hook 失败视为致命错误(CI 护栏推荐) |
disabled | 跳过所有 Hook(即使 enabled: true,便于排查或 CI 临时关闭) |
单个 Hook 的定义格式
每个事件(如 BeforeTool)对应一个 HookDefinition 数组:
{
"matcher": "可选",
"sequential": true,
"hooks": [
{
"type": "command",
"name": "建议填写,便于 /hooks 管理",
"command": "node path/to/hook.js",
"timeout": 60000,
"env": { "MY_FLAG": "1" }
}
]
}
字段说明:
matcher(可选)- 工具相关事件(
BeforeTool/AfterTool/AfterToolFailure/PermissionRequest/PermissionDenied):匹配工具名;若可编译为正则则用正则,否则精确匹配 - 触发字符串类事件(精确匹配):
SessionStart/SessionEnd/PreCompress/PostCompress/Notification/SubagentStart/SubagentStop,分别匹配source、reason、trigger、notification_type、agent_type FileChanged:匹配工具名或文件 basename(任一命中即可)- 通过 Claude Code 别名事件加载的 Hook 使用 CC 的 matcher 语义(见下文「Claude Code 兼容」)
- 工具相关事件(
sequential- 任一匹配项为
true时,该事件下所有 Hook 串行执行 - 串行时,前面的 Hook 可修改输入供后续 Hook 使用(适用于
BeforeTool、BeforeAgent、BeforeModel)
- 任一匹配项为
hooks:该匹配组内要执行的命令列表timeout:单次 Hook 超时(毫秒),默认 60000
全部 Hook 事件一览
Tuanjie AI 共支持 18 种 Hook 事件:
| 事件 | 触发时机 | 典型用途 |
|---|---|---|
SessionStart | 会话开始(启动 / 恢复 / 清空 / 压缩) | 注入项目规范、环境说明 |
SessionEnd | 会话结束 | 清理、归档、统计 |
BeforeAgent | 每轮 Agent 开始前 | 注入本轮约束或上下文 |
AfterAgent | 每轮 Agent 结束后 | 校验输出、请求重试或终止 |
BeforeModel | 调用模型前 | 调整温度、token 上限等请求参数 |
AfterModel | 模型返回后 | 脱敏、过滤敏感内容 |
BeforeToolSelection | 选择可用工具前 | 限制本轮可调用的工具 |
BeforeTool | 每次工具调用前 | 拦截或改写工具参数 |
AfterTool | 每次工具调用后 | 补充说明、链式触发后续工具 |
AfterToolFailure | 工具执行失败后 | 失败审计、告警(仅建议性) |
Notification | 工具需用户确认时 | 记录或提示(仅建议性) |
PermissionRequest | 工具等待用户审批时 | 审计(仅建议性) |
PermissionDenied | 用户拒绝工具调用时 | 审计(仅建议性) |
PreCompress | 对话压缩前 | 备份、日志 |
PostCompress | 对话压缩后 | 日志、统计(仅建议性) |
SubagentStart | 子 Agent 启动时 | 审计(仅建议性) |
SubagentStop | 子 Agent 结束时 | 审计(仅建议性) |
FileChanged | 文件被成功修改后 | 审计、通知(仅建议性) |
下面按事件说明你会收到什么以及可以做什么。
SessionStart — 会话开始
何时触发: 新会话启动、从 checkpoint 恢复、清空会话后,以及每次对话压缩后。
输入要点: source 为 startup | resume | clear | compact。
注意:无 matcher 的 SessionStart Hook 在每次压缩后也会触发;如不需要,可用 matcher: "startup|resume|clear" 排除。
可执行操作:
- 注入
additionalContext:内容会包在<hook_context>...</hook_context>中,作为后续对话的上下文(非交互模式下也会拼进 prompt) - 通用:
stopReason终止、decision: deny拦截、systemMessage提示用户
示例输出(注入项目规范):
{
"hookSpecificOutput": {
"hookEventName": "SessionStart",
"additionalContext": "本项目禁止直接修改 main 分支。"
}
}
SessionEnd — 会话结束
何时触发: 退出、清空、登出等会话结束时(尽力触发,不保证 100%)。
输入要点: reason 为 exit | clear | logout | prompt_input_exit | other。
可执行操作: 主要用于日志与副作用;可返回 systemMessage 或 suppressOutput。此事件不能注入 prompt 上下文。
BeforeAgent — Agent 轮次开始前
何时触发: 每轮 Agent 向模型发请求之前。
输入要点: 当前轮次的 prompt 文本。
可执行操作:
- 注入
additionalContext(绑定到当前轮,会进入本轮用户侧内容) - 拦截:
decision: deny/block+reason - 终止:
continue: false+stopReason
AfterAgent — Agent 轮次结束后
何时触发: 一轮 Agent 完成(已有模型回复)之后。
输入要点: prompt、prompt_response、stop_hook_active。
可执行操作:
- 终止运行:
continue: false+stopReason - 拦截 / 要求重试:
decision: deny或hookSpecificOutput.impact: "retry"(可选retryPrompt作为下一轮输入) - 强制停止:
hookSpecificOutput.impact: "halt"(可选haltReason) - 清空上下文:
hookSpecificOutput.clearContext: true
注意:此事件主要用于控制流(停止、重试),不要依赖 additionalContext 做持久化注入;若需注入上下文,请用 BeforeAgent 或 SessionStart。
BeforeModel — 模型请求前
何时触发: 每次向 LLM 发请求之前。
输入要点: llm_request(含 model、messages、config 等)。
可执行操作:
- 修改
llm_request中的config(如temperature、maxOutputTokens) - 修改
messages(谨慎:可能丢失非文本部分;若只改配置,请省略messages) - 拦截或终止(同上)
示例(仅改温度):
{
"hookSpecificOutput": {
"hookEventName": "BeforeModel",
"llm_request": {
"config": { "temperature": 0.2 }
}
}
}
注意:BeforeModel 对消息内容的修改在运行时生效,但不保证恢复会话 后与历史请求逐字节一致。若需可复现的注入,优先使用 SessionStart / BeforeAgent / AfterTool 的 additionalContext。
AfterModel — 模型响应后
何时触发: 每次收到模型响应之后(流式与非流式均会触发;流式模式下可能按块调用,务必保持脚本轻量)。
输入要点: llm_request、llm_response。
可执行操作:
- 修改
llm_response(如脱敏、截断) - 拦截或终止
BeforeToolSelection — 工具选择前
何时触发: 决定本轮模型可使用哪些工具之前。
输入要点: llm_request。
可执行操作: 通过 toolConfig 限制工具:
{
"hookSpecificOutput": {
"hookEventName": "BeforeToolSelection",
"toolConfig": {
"mode": "ANY",
"allowedFunctionNames": ["read_file", "grep"]
}
}
}
mode 可选:AUTO | ANY | NONE。
BeforeTool — 工具调用前
何时触发: 每次工具即将执行之前。
输入要点: tool_name、tool_input;MCP 工具另有 mcp_context。
可执行操作:
- 拦截:
decision: deny或block+reason;进程退出码2等同拦截 - 改写参数:在
hookSpecificOutput.tool_input中返回新参数(Tuanjie AI 会用新参数重新做策略检查,不能绕过内置安全策略)
示例(把 shell 命令替换为安全命令):
{
"decision": "allow",
"hookSpecificOutput": {
"hookEventName": "BeforeTool",
"tool_input": { "command": "echo safe" }
}
}
matcher 示例: "shell" 只匹配 shell 工具;"read_.*" 用正则匹配以 read_ 开头的工具。
AfterTool — 工具调用后
何时触发: 工具执行完成并产生结果之后。
输入要点: tool_name、tool_input、tool_response。
可执行操作:
additionalContext:附加到该工具结果上下文中(不是单独的用户消息)tailToolCallRequest:立即再执行一次指定工具(高级用法;结果会替换原工具响应)
{
"hookSpecificOutput": {
"hookEventName": "AfterTool",
"additionalContext": "注意:该文件含自动生成代码,请勿手动编辑标记区域。"
}
}