跳到主要内容

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,优先级最低(排在系统/用户/项目之后)。

合并规则:

  • 配置项(如 enabledmode):后者覆盖前者(工作区 > 用户 > 系统)
  • 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,分别匹配 sourcereasontriggernotification_typeagent_type
    • FileChanged:匹配工具名文件 basename(任一命中即可)
    • 通过 Claude Code 别名事件加载的 Hook 使用 CC 的 matcher 语义(见下文「Claude Code 兼容」)
  • sequential
    • 任一匹配项为 true 时,该事件下所有 Hook 串行执行
    • 串行时,前面的 Hook 可修改输入供后续 Hook 使用(适用于 BeforeToolBeforeAgentBeforeModel
  • 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 恢复、清空会话后,以及每次对话压缩后

输入要点: sourcestartup | 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%)。

输入要点: reasonexit | clear | logout | prompt_input_exit | other

可执行操作: 主要用于日志与副作用;可返回 systemMessagesuppressOutput。此事件不能注入 prompt 上下文。

BeforeAgent — Agent 轮次开始前

何时触发: 每轮 Agent 向模型发请求之前。

输入要点: 当前轮次的 prompt 文本。

可执行操作:

  • 注入 additionalContext(绑定到当前轮,会进入本轮用户侧内容)
  • 拦截:decision: deny / block + reason
  • 终止:continue: false + stopReason

AfterAgent — Agent 轮次结束后

何时触发: 一轮 Agent 完成(已有模型回复)之后。

输入要点: promptprompt_responsestop_hook_active

可执行操作:

  • 终止运行continue: false + stopReason
  • 拦截 / 要求重试decision: denyhookSpecificOutput.impact: "retry"(可选 retryPrompt 作为下一轮输入)
  • 强制停止hookSpecificOutput.impact: "halt"(可选 haltReason
  • 清空上下文hookSpecificOutput.clearContext: true

注意:此事件主要用于控制流(停止、重试),不要依赖 additionalContext 做持久化注入;若需注入上下文,请用 BeforeAgentSessionStart

BeforeModel — 模型请求前

何时触发: 每次向 LLM 发请求之前。

输入要点: llm_request(含 modelmessagesconfig 等)。

可执行操作:

  • 修改 llm_request 中的 config(如 temperaturemaxOutputTokens
  • 修改 messages谨慎:可能丢失非文本部分;若只改配置,请省略 messages
  • 拦截或终止(同上)

示例(仅改温度):

{
"hookSpecificOutput": {
"hookEventName": "BeforeModel",
"llm_request": {
"config": { "temperature": 0.2 }
}
}
}

注意:BeforeModel 对消息内容的修改在运行时生效,但不保证恢复会话后与历史请求逐字节一致。若需可复现的注入,优先使用 SessionStart / BeforeAgent / AfterTooladditionalContext

AfterModel — 模型响应后

何时触发: 每次收到模型响应之后(流式与非流式均会触发;流式模式下可能按块调用,务必保持脚本轻量)。

输入要点: llm_requestllm_response

可执行操作:

  • 修改 llm_response(如脱敏、截断)
  • 拦截或终止

BeforeToolSelection — 工具选择前

何时触发: 决定本轮模型可使用哪些工具之前。

输入要点: llm_request

可执行操作: 通过 toolConfig 限制工具:

{
"hookSpecificOutput": {
"hookEventName": "BeforeToolSelection",
"toolConfig": {
"mode": "ANY",
"allowedFunctionNames": ["read_file", "grep"]
}
}
}

mode 可选:AUTO | ANY | NONE

BeforeTool — 工具调用前

何时触发: 每次工具即将执行之前。

输入要点: tool_nametool_input;MCP 工具另有 mcp_context

可执行操作:

  • 拦截decision: denyblock + 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_nametool_inputtool_response

可执行操作:

  • additionalContext:附加到该工具结果上下文中(不是单独的用户消息)
  • tailToolCallRequest:立即再执行一次指定工具(高级用法;结果会替换原工具响应)
{
"hookSpecificOutput": {
"hookEventName": "AfterTool",
"additionalContext": "注意:该文件含自动生成代码,请勿手动编辑标记区域。"
}
}

Notification — 工具需确认时

何时触发: 某工具需要用户确认权限时(如危险操作)。

输入要点: notification_type(如 ToolPermission)、messagedetails

可执行操作: 仅建议性——可写日志或 systemMessage不能阻止确认流程。

注意:matcher 按 notification_type 精确匹配(早期版本中 Notification 的 matcher 会被忽略,现在已生效;想保持"总是运行"请去掉 matcher 或写 "*")。

PreCompress — 对话压缩前

何时触发: 手动或自动压缩对话历史之前。

输入要点: triggermanual | auto

可执行操作: 日志、备份类副作用;可返回 systemMessage不能注入 prompt。

PostCompress — 对话压缩后

何时触发: 对话历史压缩完成之后。

输入要点: triggermanual | auto

可执行操作: 仅建议性——日志、统计;可返回 systemMessage

AfterToolFailure — 工具执行失败后

何时触发: 工具执行返回错误时(在 AfterTool 之前触发)。

输入要点: tool_nametool_inputerror(错误信息),可选 tool_response

可执行操作: 仅建议性——失败审计、告警。

PermissionRequest / PermissionDenied — 工具审批

何时触发: PermissionRequest 在工具等待用户审批时触发;PermissionDenied 在用户拒绝工具调用时触发。

输入要点: tool_nametool_inputPermissionDenied 另有可选 reason

可执行操作: 仅建议性——审计、通知;不会阻塞或替代审批流程。

SubagentStart / SubagentStop — 子 Agent 生命周期

何时触发: 子 Agent(如 task 工具派发的代理)启动 / 结束时。

输入要点: agent_idagent_typeSubagentStop 另有可选 result。matcher 按 agent_type 精确匹配。

可执行操作: 仅建议性——审计、统计。

FileChanged — 文件变更后

何时触发: Agent 成功修改文件之后。

输入要点: file_pathchange_typetool_name、可选 metadata

可执行操作: 仅建议性——审计、通知;可返回 systemMessage不能修改文件或阻止已完成的变更。

注意:matcher 匹配工具名文件 basename(任一命中即可)。

Hook 脚本的输入与输出约定

每个 Hook 作为独立命令运行:

格式说明
stdinJSON事件输入(含 session_idcwdhook_event_name 等公共字段 + 事件专有字段)
stdoutJSONHook 输出(见下文「通用输出字段」)
stderr任意文本日志、调试信息

黄金法则

stdout 只输出 JSON;所有日志写到 stderr。stdout 非 JSON 时 Tuanjie AI 会尝试按纯文本兼容解析,但不应依赖此行为。

退出码

退出码含义
0成功
2拦截(等同 decision: deny
其他非致命错误(permissive 下为警告;strict 下可能终止)

公共输入字段(所有事件均有)

  • session_id — 当前会话 ID
  • transcript_path — transcript 路径(可能为空)
  • cwd — 当前工作目录
  • hook_event_name — 事件名
  • timestamp — ISO 时间戳

可选字段(存在时才出现):

  • permission_mode — 当前审批模式:default | acceptEdits | bypassPermissions | plan
  • agent_id / agent_type — 当 Hook 由子 Agent(而非主循环)触发时携带,可据此区分调用来源

通用输出字段(所有事件均可使用)

{
"continue": false,
"stopReason": "Hook 请求停止运行",
"decision": "deny",
"reason": "不允许此操作",
"systemMessage": "展示给用户的消息",
"suppressOutput": true,
"hookSpecificOutput": {}
}
字段作用
continue: false + stopReason停止整个运行
decision: "deny""block" + reason拦截当前动作(是否生效取决于事件)
decision: "allow"显式允许继续
systemMessage在 UI 中提示用户(可被 suppressOutput 抑制)
hookSpecificOutput事件专有 payload(见上文各事件说明)

环境变量与安全

Tuanjie AI 注入的环境变量

Hook 子进程会自动获得:

变量说明
CODELY_PROJECT_DIR / CODELY_CWD项目目录(当前相同)
CODELY_SESSION_ID会话 ID
CODELY_TRANSCRIPT_PATHTranscript 路径

兼容别名(与 Gemini / Claude 生态对齐):GEMINI_PROJECT_DIRGEMINI_SESSION_IDGEMINI_CWDCLAUDE_PROJECT_DIR

命令中的路径占位符

command 字符串中可使用(会自动 shell 转义):

  • $CODELY_PROJECT_DIR
  • $GEMINI_PROJECT_DIR
  • $CLAUDE_PROJECT_DIR

示例:

"command": "node $CODELY_PROJECT_DIR/.codely-cli/hooks/my-hook.js"

环境变量脱敏

默认会对 Hook 子进程环境做脱敏,避免泄露密钥。可在 settings 中配置:

{
"hooks": {
"environmentSanitization": {
"enableEnvironmentVariableRedaction": true,
"allowedEnvironmentVariables": [],
"blockedEnvironmentVariables": []
}
}
}

在 GitHub Actions 等环境(检测到 GITHUB_SHASURFACE=Github)中,脱敏策略会强制严格,与上述配置无关。

项目级 Hook 信任机制

前置条件:若当前工作区文件夹本身未受信任(folder trust),项目级 Hook 会整体禁用,并提示先信任工作区。以下逐 Hook 的指纹信任是在工作区已受信任之上的第二层防护。

工作区内的 Hook 在首次出现或变更后,默认被阻止,直到你显式信任:

  1. 运行 /hooks panel 查看被阻止的 Hook 及指纹
  2. 信任方式:
    • /hooks trust-project — 信任本项目全部 Hook
    • /hooks trust <序号|指纹|名称> — 信任指定 Hook

已信任的 Hook 记录在 ~/.codely-cli/trusted_hooks.json(按项目根目录索引)。

环境变量快捷方式(开发 / CI):

变量效果
CODELY_BYPASS_HOOK_TRUST=1跳过信任检查,不读写信任文件
CODELY_TRUST_ALL_HOOKS=1自动信任并持久化本项目所有 Hook

调试与管理命令

启用 Hooks UI(enableUI: trueenabled: true)后,可在 CLI 中使用:

命令作用
/hooks panel列出 Hook、状态、统计与最近执行
`/hooks logs [nhook-name]`
/hooks test <名称或指纹> <事件> [jsonOverrides]用模拟数据单独跑一个 Hook
/hooks dry-run <事件> [jsonOverrides]模拟跑整次事件的 Hook 计划
`/hooks enabledisable
/hooks trust-project / /hooks trust ...信任项目 Hook

非交互模式

Hooks 在交互式与非交互式(如 codely -p "...")下行为一致,差异在于:

  • Hook 的警告/提示会输出到 stderr
  • SessionStart 注入的 additionalContext 会作为 <hook_context>...</hook_context> 拼进 prompt

在 CI 中作护栏时,建议:

{
"hooks": {
"enabled": true,
"mode": "strict",
"maxTotalDurationPerTurn": 5000
}
}

并配合较短的 timeout

未配置 Hooks 时

若未配置任何 Hook:

  • 不会启动 Hook 子进程,无额外开销
  • 各事件直接返回安全默认值

常见场景速查

场景推荐事件做法
禁止危险 shellBeforeToolmatcher: "shell"decision: deny
项目规范注入SessionStartBeforeAgentadditionalContext
工具结果补充说明AfterTooladditionalContext
CI 只允许读操作BeforeToolSelectionallowedFunctionNames 白名单
模型输出脱敏AfterModel修改 llm_response(保持脚本快速)
轮次质量门禁AfterAgentimpact: "retry"deny
压缩前备份PreCompress写 stderr 日志或外部备份脚本
文件变更审计FileChanged写日志(建议性)

Claude Code 兼容

Tuanjie AI 可直接加载 Claude Code(CC)风格的 hooks 配置:事件名、输出协议、Hook 类型与字段由兼容层(packages/core/src/hooks-system/ccCompat.ts)自动转换。完整说明见英文文档的 Claude Code compatibility 章节,要点如下:

事件别名

CC 别名事件自动映射到 Tuanjie AI 事件:PreToolUse → BeforeToolPostToolUse → AfterToolPostToolUseFailure → AfterToolFailureUserPromptSubmit → BeforeAgentStop → AfterAgentPreCompact → PreCompressPostCompact → PostCompress;同名事件(SessionStartSessionEndNotificationFileChangedPermissionRequestPermissionDeniedSubagentStartSubagentStop)直接通用。

不支持的 CC 事件(SetupTeammateIdleTask* 等)会警告并跳过,不会导致加载失败。

CC 别名事件的专有语义

仅对写在 CC 别名事件名下的 Hook 生效(原生事件名保持原生语义):

  • timeout 按秒解释(CC 约定)并自动转换为毫秒;原生 Hook 仍按毫秒
  • matcherWrite|Edit 为竖线分隔的精确匹配列表,简单名称为精确匹配(非子串正则);CC 工具名(BashReadWriteEdit 等)自动映射到 Tuanjie AI 内置工具
  • /hooks panel 会以 (CC: <事件名>) 标注通过别名加载的 Hook

输出协议与 Hook 类型

  • CC 输出自动归一化:decision: "approve"allowpermissionDecision → 顶层 decisionupdatedInputtool_input
  • 新增三种 Hook 类型:http(POST 输入 JSON 到 url,header 仅插值 allowedEnvVars 白名单内的大写环境变量)、prompt(询问模型,$ARGUMENTS 替换为输入 JSON)、agent(受限验证子代理);项目级实例与 command Hook 走同一套信任流程
  • 新增字段:if(权限规则条件,如 Bash(git *))、once(每会话最多成功执行一次)、statusMessage;command Hook 另支持 shellasyncasyncRewake

对既有原生配置的行为变更

升级保持原生配置兼容(信任指纹、毫秒超时、正则 matcher、输出协议均不变),但有三处行为差异:

  1. Notification 的 matcher 开始生效:早期版本会忽略它(Hook 总是运行),现在按 notification_type 匹配。想保持旧行为请去掉 matcher 或写 "*"
  2. SessionStart 在每次压缩后也会触发source: "compact");可用 matcher: "startup|resume|clear" 排除
  3. FileChanged 的 matcher 同时匹配文件 basename,宽泛正则可能比以前更频繁触发

迁移(CC settings.json → Tuanjie AI)

大多数 CC hooks 配置可直接复制到 Tuanjie AI settings 并设置 hooks.enabled: true。注意:

  • Tuanjie AI 的总开关是 hooks.enabled(CC 为 disableAllHooks
  • 项目级 Hook 需先 /hooks trust-project 信任
  • Tuanjie AI 独有事件(BeforeModelAfterModelBeforeToolSelection)无 CC 对应,照常可用

常见问题

Q: Hooks 会在非交互模式下工作吗?

A: 会的。Hooks 在交互式与非交互式(如 codely -p "...")下行为一致,差异仅在于警告/提示输出到 stderr,以及 SessionStart 注入的 additionalContext 会拼进 prompt。

Q: 未配置任何 Hook 时会有性能开销吗?

A: 不会。未配置 Hook 时不会启动 Hook 子进程,各事件直接返回安全默认值,无额外开销。

Q: Hook 脚本应该用什么语言编写?

A: 推荐使用 Node.js,因为它与 Tuanjie AI 同栈,在 macOS / Linux / Windows 上行为一致,无需额外解释器或 shebang 差异。

Q: 如何调试 Hook?

A: 使用 /hooks panel 查看 Hook 状态与最近执行,/hooks logs 查看 stdout/stderr 片段,/hooks test 用模拟数据单独测试某个 Hook,/hooks dry-run 模拟跑整次事件。

最佳实践

开发 Hook

  1. stdout 只输出 JSON:所有日志写到 stderr,避免污染 Hook 输出解析
  2. 保持脚本轻量:尤其是 AfterModel 在流式模式下可能按块调用,务必快速返回
  3. 合理设置超时:CI 场景建议较短 timeout,并使用 mode: "strict"
  4. 使用 Node.js:跨平台一致,避免 shebang 差异

安全建议

  1. 环境变量脱敏:默认开启,避免泄露密钥;在 CI 环境中会强制严格
  2. 项目级信任:首次使用项目 Hook 需显式信任,防止恶意 Hook 执行
  3. 最小权限:Hook 脚本应只请求必要的权限和文件访问