hooks 子系统让外部 CLI 代理——Claude Code 与 Codex——通过它们原生的 command-hook 机制参与 dsh 的代理循环。桥接插件读取该方言的 hooks.json 匹配器组,通过 ctx.shell 执行命中的 hook,把其 exit-code/stdout 契约解码成中性形态,以最严格的规则合并结果,并把一切记录为持久的 hook/* 会话事件。
| 包 | 角色 |
|---|---|
@deepseek-ai/dsh-hook-protocol | 方言中立的协议库(非插件) |
@deepseek-ai/dsh-hooks-claude-code | Claude Code hook 桥接插件 |
@deepseek-ai/dsh-hooks-codex | Codex hook 桥接插件 |
共享的协议库
packages/hooks/hook-protocol 是 Claude Code / Codex hook 通信协议的共享核心。它不是一个 Cordis 插件——它既不注册任何东西,也不注入任何东西。它是一堆方言中立的原语,供两个桥接插件导入,这样两者都无需重复实现协议中相同的那些部分。Codex 刻意重新实现的是 Claude Code hook 协议的子集——同样的 hooks.json 匹配器组形态、同样的 exit-code/stdout 输出契约、同样的命令 hook 执行模型——因此真正共享的部分留在这里,而每个桥接只负责那些不同的部分。
取自 README 的拆分表:
| 关注点 | 这里(dsh-hook-protocol) | 桥接插件 |
|---|---|---|
| 匹配器校验 + 测试 | matcherDiagnostic(pattern, mode)、matchesMatcher(pattern, query, mode) | 挑选自己的 mode(claude-code = 字面量或正则,codex = 始终正则),拒绝携带诊断的配置组 |
| 运行 hook | runHook(bash, hook, opts, now) —— 通过 ctx.shell 发送 stdin 载荷 + 环境,并解码 | 针对每个事件构建 stdin 载荷 + 该方言的 环境 |
| 解码输出 | parseHookOutput(exit, stdout, stderr) → 中性的 HookOutput | 把 HookOutput 映射到扩展点特有的类型化 Decision |
| 合并 N 个 hook | mergeHookOutputs(outputs) → 最严格的 MergedHookOutcome | — |
| 持久记录 | appendHookInvoked / appendHookResult(hook/* 会话事件) | 在每次调用前后调用它们 |
| 分离式运行 | createDetachedRuns() —— 追踪 fire-and-forget 链;drain() 先中止再等待 | 把 signal 传给每个分离的 runHook,将 drain 注册为自身的效果处置器 |
该库位于 packages/hooks/hook-protocol/src/——matcher.ts(模式诊断与匹配)、runner.ts(通过 ShellExecutor 形态的 bash 参数执行 hook)、merge.ts(最严格合并)、events.ts(hook/* 事件形态)、codec.ts、detached.ts(静止态追踪)以及 types.ts。
桥接插件
dsh-hooks-claude-code 与 dsh-hooks-codex 是薄适配器:每个都拥有其方言的配置(src/config.ts)、每个事件的 stdin 载荷与环境,以及从中性 HookOutput 到其扩展点所期望的类型化决策的映射。匹配器模式因设计而异——Claude Code 把匹配器当作字面量或正则,Codex 始终当作正则——而一个携带诊断的配置组会由拥有它的那个桥接拒绝。
配置(两个桥接,取自 src/config.ts)驱动:哪些 hook 事件被启用、匹配器组,以及被执行的命令行。hook 执行经由 ctx.shell,因此一条 hook 命令会继承会话的沙箱/策略世界——包括所组合进来的任意 shell provider(参见 Shell & Terminal)。
hook 决策如何流转
agent loop reaches an extension point (e.g. PreToolUse / Stop)
│
▼
bridge plugin builds stdin payload + env for its dialect
│
▼
runHook(bash, hook, opts) ──► ctx.shell executes the hook command
│
▼
parseHookOutput(exit, stdout, stderr) ──► neutral HookOutput
│
▼
bridge maps HookOutput ──► typed Decision (allow / deny / modify ...)
│
▼
mergeHookOutputs across hooks ──► most-restrictive MergedHookOutcome
│
▼
appendHookInvoked / appendHookResult ──► durable hook/* session events分离式(fire-and-forget)运行由 createDetachedRuns() 追踪,以便进程销毁时可以对它们执行 drain()——先中止再等待——而不会泄漏链。
这对 dsh 的意义
hooks 子系统是 dsh 与更广的代理生态互操作的方式之一:与其重新实现 Claude Code 或 Codex,dsh 组合可以承载它们的 hook 协议,让它们的命令 hook 对 dsh 会话生效。这与把那些 CLI 当作代理运行的 subagent 后端(subagent-claude-code、subagent-codex)互为镜像(参见 Subagents)——在那里的 CLI 是工人,而在这里它的 hook 契约是扩展点。
关键源文件
| 仓库相对路径 | 提供内容 |
|---|---|
packages/hooks/hook-protocol/src/matcher.ts | 匹配器诊断 + 匹配 |
packages/hooks/hook-protocol/src/runner.ts | 通过 ctx.shell 的 runHook |
packages/hooks/hook-protocol/src/merge.ts | mergeHookOutputs |
packages/hooks/hook-protocol/src/events.ts | hook/* 会话事件 |
packages/hooks/hooks-claude-code/src/config.ts | Claude Code 桥接配置 |
packages/hooks/hooks-codex/src/config.ts | Codex 桥接配置 |
延伸阅读
- Subagents —— 另一个方向:把 Claude Code / Codex 作为 subagent 运行
- Shell & Terminal ——
ctx.shell,hook 运行的执行世界 - Session Management ——
hook/*事件在日志中的落点 - Repo:
packages/hooks/hook-protocol/README.md、packages/hooks/hooks-claude-code/README.md、packages/hooks/hooks-codex/README.md