File size: 8,319 Bytes
f0634fb | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 | # Hooks
Hooks(钩子)是一种自动触发机制:你预先告诉 Kimi Code CLI"每当发生 X,运行这个脚本"。脚本在你的本机执行,你可以在里面写任何逻辑。典型的使用场景:
- **安全拦截**:Agent 要执行 Shell 命令前,检查是否包含危险操作(如 `rm -rf`),包含则阻断执行
- **桌面通知**:后台任务完成时,弹出系统通知提醒你回来查看结果
- **自动检查**:每次用户提交消息时,自动在上下文里附加一些背景信息(如当前 Git 分支)
## Hooks 是怎么工作的
配置一条 hook 规则,需要指定三件事:**在什么事件上触发**、**匹配哪些目标**、**运行哪个脚本**。
触发时,CLI 会把事件的详细信息(触发原因、工具名称、命令内容等)打包成 JSON,通过**标准输入**(stdin,程序运行时用来接收外部数据的通道)传给脚本。脚本读取这些信息后,决定怎么响应。
脚本的响应结果由两样东西决定:
- **退出码**(exit code,程序结束时向操作系统报告的状态数字):`0` 表示放行,`2` 表示阻断,其他数字默认放行
- **标准输出**(stdout,脚本打印到终端的内容):可以附带说明文字
即使脚本报错或超时,CLI 也**不会因此中断你的工作**。这种"出错就放行"的设计称为 fail-open(失败开放),避免 hook 异常阻塞主流程。
::: warning 注意
正因为 fail-open,Hooks 适合做提醒和轻量拦截,但**不应作为唯一的安全防线**。对真正高风险的操作,仍需依赖权限审批和人工确认。
:::
## 快速上手:一个最简单的 hook
下面这条 hook 会在每次后台任务完成时,在终端标题栏闪一下通知(macOS 需要安装 `terminal-notifier`):
```toml
# 写在 ~/.kimi-code/config.toml 里
[[hooks]]
event = "Notification" # 触发时机:后台任务状态变化时
matcher = "task\\.completed" # 只关心"已完成"的通知
command = "terminal-notifier -title Kimi -message 'Task done'"
```
保存配置、重开会话,下次后台任务完成时就会弹出通知。
## 配置
所有 hook 规则写在 `~/.kimi-code/config.toml` 的 `[[hooks]]` 数组里:
| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `event` | `string` | 是 | 触发事件名,取值见 [事件一览](#事件一览) |
| `matcher` | `string` | 否 | 用正则表达式(一种字符串匹配语法)过滤事件目标;不填则匹配全部 |
| `command` | `string` | 是 | 触发时要运行的 Shell 命令 |
| `timeout` | `integer` | 否 | 超时秒数,范围 1–600;默认 30 秒 |
`[[hooks]]` 只允许这四个字段,多写会导致配置文件加载失败。
**同一事件匹配多条规则时**,所有命中的 hook 并行运行;`command` 完全相同的多条规则只运行一次。
Hook 命令的工作目录是当前会话的项目目录。
<details>
<summary>进程组与超时处理</summary>
非 Windows 平台上,hook 进程运行在独立进程组中;超时后 CLI 先发送信号让脚本有机会善后,再强制终止。
</details>
### 事件数据格式
每次触发时,CLI 都会把以下基础信息通过 stdin 传给脚本:
```json
{
"hook_event_name": "PreToolUse",
"session_id": "session_abc",
"session_title": "修复登录页",
"client_type": "kimi_code_cli",
"cwd": "/path/to/project"
}
```
具体事件还会附带额外字段(如工具名称、命令内容),见 [事件一览](#事件一览)。所有字段名使用下划线命名(snake_case)。
## 返回值
脚本结束后,CLI 根据退出码判断 hook 的意图:
| 退出码 | 含义 | CLI 怎么处理 |
| --- | --- | --- |
| `0` | 正常结束,放行 | 继续执行,若标准输出(stdout)有内容可附加到上下文 |
| `2` | 主动阻断 | 停止当前操作;错误输出(stderr,`console.error` 打印的内容)作为阻断原因 |
| 其他非零值 | 脚本出错 | 默认放行(fail-open) |
| 超时或崩溃 | 脚本异常 | 默认放行(fail-open) |
也可以通过标准输出返回一段 JSON 来阻断:
```json
{
"hookSpecificOutput": {
"permissionDecision": "deny",
"permissionDecisionReason": "请用 rg 代替 grep"
}
}
```
::: info 说明
只有**可阻断事件**(`PreToolUse`、`Stop`、`UserPromptSubmit`)的返回值会影响主流程。其余事件属于**观察型事件**:触发后即发即忘,不管脚本返回什么,主流程都不会改变。
:::
## 事件一览
| 事件 | Matcher 匹配的是 | 会触发阻断? | 说明 |
| --- | --- | --- | --- |
| `UserPromptSubmit` | 用户提交的文本内容 | ✓ | 用户发送消息时触发;返回文本会附加到上下文,阻断则本轮不调用模型 |
| `UserPromptQueued` | 排队消息的文本内容 | — | 上一回合仍在运行、新消息进入队列时触发;payload 含 `prompt_id`、`prompt`、`queue_length` |
| `PreToolUse` | 工具名 | ✓ | 工具调用前、权限检查前触发;阻断后工具不会执行 |
| `Stop` | 空字符串 | ✓ | 模型准备结束本轮时触发;阻断后可追加一条消息让模型继续 |
| `TurnStarted` | 回合来源类型(如 `user`、`task`、`system_trigger`) | — | 新回合开始时触发;payload 含 `turn_id`、`origin_kind`、`origin_name`、`prompt` |
| `PostToolUse` | 工具名 | — | 工具成功执行后触发 |
| `PostToolUseFailure` | 工具名 | — | 工具失败或被阻断后触发 |
| `PermissionRequest` | 工具名 | — | 即将等待用户审批前触发 |
| `PermissionResult` | 工具名 | — | 审批结束后触发 |
| `SessionStart` | `startup` 或 `resume` | — | 新会话启动或历史会话恢复后触发;payload 含 `source`、`model`、`profile` |
| `SessionEnd` | `exit` 或 `archive` | — | 会话关闭后触发;`archive` 表示会话被归档而非退出 |
| `SessionHeartbeat` | 空字符串 | — | 会话存活期间每 60 秒触发一次,仅配置本事件时计时器才运行;payload 含 `uptime_ms` |
| `SubagentStart` | subagent 名称 | — | subagent 开始运行前触发 |
| `SubagentStop` | subagent 名称 | — | subagent 成功完成后触发 |
| `TaskStarted` | 任务类型(`agent`、`process` 或 `question`) | — | 后台任务启动时触发;payload 含 `task_id`、`description`、`detached` |
| `StopFailure` | 错误类型 | — | 本轮因错误失败后触发 |
| `Interrupt` | 空字符串 | — | 用户中断本轮时触发(如按 Esc);超时等程序性中断不触发,此时 `Stop` 由本事件替代;payload 含 `reason` |
| `PreCompact` | `manual` 或 `auto` | — | 上下文压缩开始前触发;返回值被完全忽略 |
| `PostCompact` | `manual` 或 `auto` | — | 上下文压缩完成后触发 |
| `Notification` | 通知类型(如 `task.completed`) | — | 后台任务状态变化时触发 |
## 示例:阻断危险 Shell 命令
下面的 hook 在 Agent 调用 `Bash` 工具前检查命令内容,命中 `rm -rf` 时阻断:
```toml
[[hooks]]
event = "PreToolUse"
matcher = "Bash"
command = "node ~/.kimi-code/hooks/block-dangerous-bash.mjs"
timeout = 5
```
```js
// block-dangerous-bash.mjs
// 从 stdin 读取 CLI 传来的事件数据
let input = '';
process.stdin.on('data', (chunk) => { input += chunk; });
process.stdin.on('end', () => {
const payload = JSON.parse(input); // 解析事件数据
const command = payload.tool_input?.command ?? '';
if (command.includes('rm -rf')) {
// 通过 stderr 说明阻断原因,退出码 2 表示阻断
console.error('检测到危险命令,已阻断');
process.exit(2);
}
// 正常退出(退出码 0)表示放行
});
```
阻断后,Kimi Code CLI 会把阻断原因写回上下文,模型可以据此选择更安全的替代方案。
::: warning 注意
此示例仅演示阻断机制,不是生产级的安全解析器。真实场景更适合用白名单,或用专门的 Shell 解析器处理引号、变量展开和多段命令。
:::
## 下一步
- [配置](#配置) — `[[hooks]]` 在 `config.toml` 中的完整字段声明
- [Agent 与 subagent](./agents.md) — 利用 `SubagentStop` 事件在 subagent 完成后触发通知
|