返回 DeepSeek-Reasonix
DESKTOP_HOOKS.zh-CN.md
根目录 / docs / DESKTOP_HOOKS.zh-CN.md
1 # 桌面端 Hooks 使用说明
2
3 <a href="../README.zh-CN.md">README</a>
4 &nbsp;·&nbsp;
5 <a href="./GUIDE.zh-CN.md">使用指南</a>
6 &nbsp;·&nbsp;
7 <a href="./SPEC.md">规格</a>
8
9 Hooks 让 Reasonix 在会话、用户输入、工具调用、模型返回、压缩上下文等节点执行本地 shell 命令。桌面端在“设置 -> Hooks”里提供图形化编辑入口,本质上读写同一份 `settings.json`。
10
11 > Hook 命令会在本机执行 shell。全局和项目 hooks 都会从各自配置位置自动加载。
12
13 ## 快速开始
14
15 1. 打开桌面端“设置 -> Hooks”。
16 2. 选择范围:
17 - “全局”:保存到 `<Reasonix home>/settings.json`,始终加载;Windows 默认是 `%APPDATA%\reasonix\settings.json`,macOS/Linux 默认是 `~/.reasonix/settings.json`。
18 - “项目”:保存到当前工作区的 `.reasonix/settings.json`,打开项目时自动加载。
19 3. 在 JSON 配置框里编辑 `hooks`。
20 4. 保存后,重启桌面端,让新配置进入会话。`/new` 只开启新对话,不会重新读取 hooks 配置。
21
22 示例:
23
24 ```json
25 {
26 "hooks": {
27 "PreToolUse": [
28 {
29 "match": "bash",
30 "command": "node .reasonix/hooks/check-bash.js",
31 "description": "Block dangerous shell commands",
32 "timeout": 5000
33 }
34 ],
35 "Stop": [
36 {
37 "command": "echo Reasonix turn finished"
38 }
39 ]
40 }
41 }
42 ```
43
44 ## 配置文件位置
45
46 | 范围 | 文件 | 加载方式 | 加载顺序 |
47 | --- | --- | --- | --- |
48 | 全局 | `<Reasonix home>/settings.json` | 自动 | 项目 hooks 之后 |
49 | 项目 | `<workspace>/.reasonix/settings.json` | 自动 | 全局 hooks 之前 |
50
51 同一个事件下,项目 hooks 先运行,全局 hooks 后运行;同一范围内按数组顺序运行。阻塞型事件遇到第一个阻塞 hook 后,会停止继续执行后面的 hook。
52
53 ## 配置 JSON 格式
54
55 推荐写法是一个带 `hooks` 字段的对象:
56
57 ```json
58 {
59 "hooks": {
60 "PreToolUse": [
61 { "match": "bash", "command": "node .reasonix/hooks/pre-tool.js" }
62 ],
63 "UserPromptSubmit": [
64 { "command": "node ~/.reasonix/hooks/check-prompt.js" }
65 ],
66 "Stop": [
67 { "command": "osascript -e 'display notification \"Turn done\" with title \"Reasonix\"'" }
68 ]
69 }
70 }
71 ```
72
73 桌面端 JSON 编辑器也接受两种便捷输入,保存前会格式化回 `{"hooks": ...}`:
74
75 ```json
76 {
77 "PreToolUse": [
78 { "match": "bash", "command": "node .reasonix/hooks/pre-tool.js" }
79 ],
80 "Stop": [
81 { "command": "echo done" }
82 ]
83 }
84 ```
85
86 ```json
87 [
88 { "event": "PreToolUse", "match": "bash", "command": "node .reasonix/hooks/pre-tool.js" },
89 { "event": "Stop", "command": "echo done" }
90 ]
91 ```
92
93 每个 hook 对象支持这些字段:
94
95 | 字段 | 类型 | 说明 |
96 | --- | --- | --- |
97 | `command` | string | 必填。通过平台 shell 执行的命令。空字符串会被忽略。 |
98 | `match` | string | 仅 `PreToolUse`、`PostToolUse` 使用。锚定正则,空字符串或 `*` 表示匹配所有工具。 |
99 | `description` | string | 可选。显示在 hooks 列表或设置页里的说明。 |
100 | `timeout` | number | 可选。毫秒数。未设置时,阻塞型事件默认 5000ms,其它事件默认 30000ms。 |
101 | `cwd` | string | 可选。覆盖 hook 命令工作目录。默认使用当前会话的 `cwd`。 |
102
103 `match` 是锚定正则:`"file"` 不会匹配 `read_file`,需要写成 `".*file"`。正则非法时该 hook 不会触发。
104
105 `command` 默认通过平台 shell 执行:macOS/Linux 使用 `sh -c`,Windows 使用
106 `cmd /c`。Windows 上如果命令本身指向带 POSIX shell shebang 的脚本文件,Reasonix
107 会自动改用 Git Bash;如果 Windows hook 自己显式写了裸命令 `sh -c` 或 `bash -c`,Reasonix
108 会查找 Git for Windows 自带的 Bash 并直接使用它;带目录的显式解释器路径保持不变。
109 通过 `[tools.shell]` 配置的自定义 Bash 路径同样会被 Hook 复用;找不到 Git Bash 时,
110 插件 Doctor 和能力诊断会提前显示可操作的依赖提示。Hook stdout/stderr 中的 Windows 旧代码页
111 文本会转换为 UTF-8,避免中文错误信息显示成乱码。stdin 是 Reasonix 写入的一行 JSON,
112 见下面的 payload 表。
113
114 ## 配置里的事件 key
115
116 下面这些字符串就是 `hooks` 对象里的事件 key,也是在数组写法中 `event` 字段的取值:
117
118 | 事件 key | 触发时机 | 是否可阻塞 | stdout 特殊作用 |
119 | --- | --- | --- | --- |
120 | `PreToolUse` | 工具权限已通过、工具真正执行前 | 是 | 无特殊作用 |
121 | `PostToolUse` | 工具执行后,不论成功或失败 | 否 | 无特殊作用 |
122 | `UserPromptSubmit` | 用户输入提交后、本轮模型调用前 | 是 | 无特殊作用 |
123 | `Stop` | 一轮对话结束后 | 否 | 无特殊作用 |
124 | `PostLLMCall` | 模型流式返回完成后,reasoning 入库前 | 否 | exit 0 且 stdout 非空时,用 stdout 替换展示的 reasoning |
125 | `SessionStart` | 会话第一次变为活跃,或 `/new`、清空后新会话开始 | 否 | stdout 会作为下一轮模型上下文注入 |
126 | `SessionEnd` | 会话关闭、切换、`/new`、清空或控制器释放时 | 否 | 无特殊作用 |
127 | `SubagentStop` | 前台 `task` 子代理完成后 | 否 | 无特殊作用 |
128 | `Notification` | 需要用户注意时,例如等待工具审批 | 否 | 无特殊作用 |
129 | `PreCompact` | 上下文压缩开始前 | 否 | stdout 会追加为压缩摘要的额外指导 |
130
131 只有 `PreToolUse` 和 `UserPromptSubmit` 是阻塞型事件。阻塞型事件中,命令 `exit 2` 或超时会阻断后续执行。
132
133 ## Hook 命令收到的 payload
134
135 Reasonix 会把一行 JSON 写入 hook 命令的 stdin。所有 payload 都至少有:
136
137 | key | 类型 | 说明 |
138 | --- | --- | --- |
139 | `event` | string | 当前事件 key。 |
140 | `cwd` | string | 当前会话工作目录,也就是 hook 默认执行目录。 |
141
142 其它 key 按事件出现;空值会被省略。
143
144 | 事件 key | 额外 payload key | 示例 |
145 | --- | --- | --- |
146 | `PreToolUse` | `toolName`, `toolArgs` | `{"event":"PreToolUse","cwd":"/repo","toolName":"bash","toolArgs":{"command":"go test ./..."}}` |
147 | `PostToolUse` | `toolName`, `toolArgs`, `toolResult` | `{"event":"PostToolUse","cwd":"/repo","toolName":"bash","toolArgs":{"command":"go test ./..."},"toolResult":"ok"}` |
148 | `UserPromptSubmit` | `prompt`, `turn` | `{"event":"UserPromptSubmit","cwd":"/repo","prompt":"修复测试","turn":1}` |
149 | `Stop` | `lastAssistantText`, `turn` | `{"event":"Stop","cwd":"/repo","lastAssistantText":"已修复","turn":1}` |
150 | `PostLLMCall` | `reasoning`, `turn` | `{"event":"PostLLMCall","cwd":"/repo","reasoning":"raw reasoning","turn":1}` |
151 | `SessionStart` | 无 | `{"event":"SessionStart","cwd":"/repo"}` |
152 | `SessionEnd` | 无 | `{"event":"SessionEnd","cwd":"/repo"}` |
153 | `SubagentStop` | `lastAssistantText` | `{"event":"SubagentStop","cwd":"/repo","lastAssistantText":"子代理结论"}` |
154 | `Notification` | `message` | `{"event":"Notification","cwd":"/repo","message":"approval needed: bash go test ./..."}` |
155 | `PreCompact` | `trigger` | `{"event":"PreCompact","cwd":"/repo","trigger":"manual"}` |
156
157 `toolArgs` 是工具参数的原始 JSON。比如 `bash` 通常会带 `{"command":"..."}`,其它工具会按自己的 schema 传入。`Notification.message` 会做必要的隐私收敛,例如记忆审批只包含工具名,不把记忆正文发给外部通知 hook。
158
159 ## 退出码和输出
160
161 | 结果 | 阻塞型事件 | 非阻塞型事件 |
162 | --- | --- | --- |
163 | exit 0 | 通过 | 通过 |
164 | exit 2 | 阻塞 | 警告 |
165 | 其它非零退出码 | 警告,不阻塞 | 警告 |
166 | 超时 | 阻塞 | 警告 |
167 | 命令启动失败 | 错误提示,不阻塞 | 错误提示 |
168
169 stdout 和 stderr 会被捕获、去掉首尾空白,并限制单路输出最多 256KB。非通过结果会显示为 warning,优先展示 stderr,其次展示 stdout。
170
171 特殊 stdout 行为:
172
173 - `PostLLMCall`:exit 0 且 stdout 非空时,stdout 会替换用户看到的 reasoning。若 provider 的 reasoning 带签名,Reasonix 会保留原始 signed reasoning 用于后续请求,同时仍展示 hook 转换后的文本。
174 - `SessionStart`:exit 0 且 stdout 非空时,stdout 会作为一次性 `<hook-context event="SessionStart">` 注入下一轮真实用户输入。纯文本 stdout 会原样作为上下文;也可以输出 Claude Code / Codex 兼容 JSON:
175
176 ```json
177 {
178 "hookSpecificOutput": {
179 "hookEventName": "SessionStart",
180 "additionalContext": "Load the workspace conventions before editing."
181 }
182 }
183 ```
184
185 `hookEventName` 必须与当前事件一致。该上下文不会写入 system prompt、工具 schema 或项目记忆;它只影响下一轮模型请求。单个 hook 上下文最多保留约 10000 字符,总量最多约 20000 字符,超出会截断并标记。
186 - `PreCompact`:所有非空 stdout 会按换行拼接,作为本次压缩摘要的额外指导。
187 - 其它事件:stdout 只在非通过结果中作为提示文本使用,不会自动进入模型上下文。
188
189 ## 示例:SessionStart 注入启动上下文
190
191 ```json
192 {
193 "hooks": {
194 "SessionStart": [
195 {
196 "command": "printf '%s\\n' '{\"hookSpecificOutput\":{\"hookEventName\":\"SessionStart\",\"additionalContext\":\"Before coding, check the available skills and follow matching workflows.\"}}'"
197 }
198 ]
199 }
200 }
201 ```
202
203 这适合把插件或工作流的 bootstrap 说明带入会话。比如 Superpowers 不需要内置到 Reasonix;可以让它自己的 `hooks/session-start-codex` 在 `SessionStart` 输出 `additionalContext`,或让插件根目录 `CLAUDE.md` 被插件包兼容层直接作为 `SessionStart` 上下文读取,Reasonix 会在下一轮把这段说明注入模型上下文。插件包兼容层也会读取 `.claude/settings.json` 里的 command hooks,并按同名事件映射到 Reasonix hooks。Reasonix 默认允许 `max_subagent_depth = 2`,因此 Superpowers 的父会话或第一层 workflow subagent 可以再派发 reviewer/implementer subagent;第二层不会继续获得递归委派工具。若要恢复旧的单层边界,设 `agent.max_subagent_depth = 1`。这会改变子代理可见工具面,可能影响子代理请求的 prompt cache,但不会把 Superpowers 写进 Reasonix 的稳定 system prompt。
204
205 ## 示例:阻止危险 bash 命令
206
207 `.reasonix/settings.json`:
208
209 ```json
210 {
211 "hooks": {
212 "PreToolUse": [
213 {
214 "match": "bash",
215 "command": "node .reasonix/hooks/block-dangerous-bash.js",
216 "description": "Block risky bash commands",
217 "timeout": 3000
218 }
219 ]
220 }
221 }
222 ```
223
224 `.reasonix/hooks/block-dangerous-bash.js`:
225
226 ```js
227 const fs = require("fs");
228
229 const payload = JSON.parse(fs.readFileSync(0, "utf8"));
230 const command = payload.toolArgs?.command || "";
231
232 if (/\brm\s+-rf\b/.test(command) || /\bgit\s+push\b/.test(command)) {
233 console.error(`blocked dangerous command: ${command}`);
234 process.exit(2);
235 }
236 ```
237
238 `exit 2` 会让 `PreToolUse` 阻断该工具调用,并把错误信息反馈给界面和模型。
239
240 ## 示例:压缩前追加摘要重点
241
242 ```json
243 {
244 "hooks": {
245 "PreCompact": [
246 {
247 "command": "printf '%s\n' 'Keep exact user decisions, file paths, and unresolved TODOs.'"
248 }
249 ]
250 }
251 }
252 ```
253
254 当自动压缩或 `/compact` 触发时,stdout 会加入摘要指令。
255
256 ## 排障
257
258 - 保存后当前会话没有变化:Hooks 在会话构建时加载。重启桌面端后才会重新读取配置;`/new` 只开启新对话,不会重新加载 hooks。
259 - 项目 hooks 不执行:确认当前是项目工作区、配置保存在 `.reasonix/settings.json`,并重启 Reasonix 重新加载。也可用只读诊断:`reasonix doctor capabilities` 或桌面端 **设置 → 诊断**(见 [能力诊断](./CAPABILITY_DIAGNOSTICS.zh-CN.md)),关注 `hook.invalid_matcher` / `hook.malformed_settings`。
260 - `match` 没生效:它只对 `PreToolUse` 和 `PostToolUse` 生效,并且是锚定正则。
261 - JSON 报 unknown hook event:事件 key 必须完全等于上表的大小写。
262 - hook 输出太长:每路 stdout/stderr 最多捕获 256KB,超出会截断并显示截断提示。
263
263 lines MARKDOWN