返回 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 hook 自己显式写了裸命令 `sh -c` 或 `bash -c`,Reasonix
107 会查找 Git for Windows 自带的 Bash 并直接使用它;带目录的显式解释器路径保持不变。
108 通过 `[tools.shell]` 配置的自定义 Bash 路径同样会被 Hook 复用;找不到 Git Bash 时,
109 插件 Doctor 和能力诊断会提前显示可操作的依赖提示。Hook stdout/stderr 中的 Windows 旧代码页
110 文本会转换为 UTF-8,避免中文错误信息显示成乱码。stdin 是 Reasonix 写入的一行 JSON,
111 见下面的 payload 表。
112
113 ## 配置里的事件 key
114
115 下面这些字符串就是 `hooks` 对象里的事件 key,也是在数组写法中 `event` 字段的取值:
116
117 | 事件 key | 触发时机 | 是否可阻塞 | stdout 特殊作用 |
118 | --- | --- | --- | --- |
119 | `PreToolUse` | 工具权限已通过、工具真正执行前 | 是 | 无特殊作用 |
120 | `PostToolUse` | 工具执行后,不论成功或失败 | 否 | 无特殊作用 |
121 | `UserPromptSubmit` | 用户输入提交后、本轮模型调用前 | 是 | 无特殊作用 |
122 | `Stop` | 一轮对话结束后 | 否 | 无特殊作用 |
123 | `PostLLMCall` | 模型流式返回完成后,reasoning 入库前 | 否 | exit 0 且 stdout 非空时,用 stdout 替换展示的 reasoning |
124 | `SessionStart` | 会话第一次变为活跃,或 `/new`、清空后新会话开始 | 否 | stdout 会作为下一轮模型上下文注入 |
125 | `SessionEnd` | 会话关闭、切换、`/new`、清空或控制器释放时 | 否 | 无特殊作用 |
126 | `SubagentStop` | 前台 `task` 子代理完成后 | 否 | 无特殊作用 |
127 | `Notification` | 需要用户注意时,例如等待工具审批 | 否 | 无特殊作用 |
128 | `PreCompact` | 上下文压缩开始前 | 否 | stdout 会追加为压缩摘要的额外指导 |
129
130 只有 `PreToolUse` 和 `UserPromptSubmit` 是阻塞型事件。阻塞型事件中,命令 `exit 2` 或超时会阻断后续执行。
131
132 ## Hook 命令收到的 payload
133
134 Reasonix 会把一行 JSON 写入 hook 命令的 stdin。所有 payload 都至少有:
135
136 | key | 类型 | 说明 |
137 | --- | --- | --- |
138 | `event` | string | 当前事件 key。 |
139 | `cwd` | string | 当前会话工作目录,也就是 hook 默认执行目录。 |
140
141 其它 key 按事件出现;空值会被省略。
142
143 | 事件 key | 额外 payload key | 示例 |
144 | --- | --- | --- |
145 | `PreToolUse` | `toolName`, `toolArgs` | `{"event":"PreToolUse","cwd":"/repo","toolName":"bash","toolArgs":{"command":"go test ./..."}}` |
146 | `PostToolUse` | `toolName`, `toolArgs`, `toolResult` | `{"event":"PostToolUse","cwd":"/repo","toolName":"bash","toolArgs":{"command":"go test ./..."},"toolResult":"ok"}` |
147 | `UserPromptSubmit` | `prompt`, `turn` | `{"event":"UserPromptSubmit","cwd":"/repo","prompt":"修复测试","turn":1}` |
148 | `Stop` | `lastAssistantText`, `turn` | `{"event":"Stop","cwd":"/repo","lastAssistantText":"已修复","turn":1}` |
149 | `PostLLMCall` | `reasoning`, `turn` | `{"event":"PostLLMCall","cwd":"/repo","reasoning":"raw reasoning","turn":1}` |
150 | `SessionStart` | 无 | `{"event":"SessionStart","cwd":"/repo"}` |
151 | `SessionEnd` | 无 | `{"event":"SessionEnd","cwd":"/repo"}` |
152 | `SubagentStop` | `lastAssistantText` | `{"event":"SubagentStop","cwd":"/repo","lastAssistantText":"子代理结论"}` |
153 | `Notification` | `message` | `{"event":"Notification","cwd":"/repo","message":"approval needed: bash go test ./..."}` |
154 | `PreCompact` | `trigger` | `{"event":"PreCompact","cwd":"/repo","trigger":"manual"}` |
155
156 `toolArgs` 是工具参数的原始 JSON。比如 `bash` 通常会带 `{"command":"..."}`,其它工具会按自己的 schema 传入。`Notification.message` 会做必要的隐私收敛,例如记忆审批只包含工具名,不把记忆正文发给外部通知 hook。
157
158 ## 退出码和输出
159
160 | 结果 | 阻塞型事件 | 非阻塞型事件 |
161 | --- | --- | --- |
162 | exit 0 | 通过 | 通过 |
163 | exit 2 | 阻塞 | 警告 |
164 | 其它非零退出码 | 警告,不阻塞 | 警告 |
165 | 超时 | 阻塞 | 警告 |
166 | 命令启动失败 | 错误提示,不阻塞 | 错误提示 |
167
168 stdout 和 stderr 会被捕获、去掉首尾空白,并限制单路输出最多 256KB。非通过结果会显示为 warning,优先展示 stderr,其次展示 stdout。
169
170 特殊 stdout 行为:
171
172 - `PostLLMCall`:exit 0 且 stdout 非空时,stdout 会替换用户看到的 reasoning。若 provider 的 reasoning 带签名,Reasonix 会保留原始 signed reasoning 用于后续请求,同时仍展示 hook 转换后的文本。
173 - `SessionStart`:exit 0 且 stdout 非空时,stdout 会作为一次性 `<hook-context event="SessionStart">` 注入下一轮真实用户输入。纯文本 stdout 会原样作为上下文;也可以输出 Claude Code / Codex 兼容 JSON:
174
175 ```json
176 {
177 "hookSpecificOutput": {
178 "hookEventName": "SessionStart",
179 "additionalContext": "Load the workspace conventions before editing."
180 }
181 }
182 ```
183
184 `hookEventName` 必须与当前事件一致。该上下文不会写入 system prompt、工具 schema 或项目记忆;它只影响下一轮模型请求。单个 hook 上下文最多保留约 10000 字符,总量最多约 20000 字符,超出会截断并标记。
185 - `PreCompact`:所有非空 stdout 会按换行拼接,作为本次压缩摘要的额外指导。
186 - 其它事件:stdout 只在非通过结果中作为提示文本使用,不会自动进入模型上下文。
187
188 ## 示例:SessionStart 注入启动上下文
189
190 ```json
191 {
192 "hooks": {
193 "SessionStart": [
194 {
195 "command": "printf '%s\\n' '{\"hookSpecificOutput\":{\"hookEventName\":\"SessionStart\",\"additionalContext\":\"Before coding, check the available skills and follow matching workflows.\"}}'"
196 }
197 ]
198 }
199 }
200 ```
201
202 这适合把插件或工作流的 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。
203
204 ## 示例:阻止危险 bash 命令
205
206 `.reasonix/settings.json`:
207
208 ```json
209 {
210 "hooks": {
211 "PreToolUse": [
212 {
213 "match": "bash",
214 "command": "node .reasonix/hooks/block-dangerous-bash.js",
215 "description": "Block risky bash commands",
216 "timeout": 3000
217 }
218 ]
219 }
220 }
221 ```
222
223 `.reasonix/hooks/block-dangerous-bash.js`:
224
225 ```js
226 const fs = require("fs");
227
228 const payload = JSON.parse(fs.readFileSync(0, "utf8"));
229 const command = payload.toolArgs?.command || "";
230
231 if (/\brm\s+-rf\b/.test(command) || /\bgit\s+push\b/.test(command)) {
232 console.error(`blocked dangerous command: ${command}`);
233 process.exit(2);
234 }
235 ```
236
237 `exit 2` 会让 `PreToolUse` 阻断该工具调用,并把错误信息反馈给界面和模型。
238
239 ## 示例:压缩前追加摘要重点
240
241 ```json
242 {
243 "hooks": {
244 "PreCompact": [
245 {
246 "command": "printf '%s\n' 'Keep exact user decisions, file paths, and unresolved TODOs.'"
247 }
248 ]
249 }
250 }
251 ```
252
253 当自动压缩或 `/compact` 触发时,stdout 会加入摘要指令。
254
255 ## 排障
256
257 - 保存后当前会话没有变化:Hooks 在会话构建时加载。重启桌面端后才会重新读取配置;`/new` 只开启新对话,不会重新加载 hooks。
258 - 项目 hooks 不执行:确认当前是项目工作区、配置保存在 `.reasonix/settings.json`,并重启 Reasonix 重新加载。也可用只读诊断:`reasonix doctor capabilities` 或桌面端 **设置 → 诊断**(见 [能力诊断](./CAPABILITY_DIAGNOSTICS.zh-CN.md)),关注 `hook.invalid_matcher` / `hook.malformed_settings`。
259 - `match` 没生效:它只对 `PreToolUse` 和 `PostToolUse` 生效,并且是锚定正则。
260 - JSON 报 unknown hook event:事件 key 必须完全等于上表的大小写。
261 - hook 输出太长:每路 stdout/stderr 最多捕获 256KB,超出会截断并显示截断提示。
262
262 lines MARKDOWN