Skip to content

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-codeClaude Code hook 桥接插件
@deepseek-ai/dsh-hooks-codexCodex 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 = 始终正则),拒绝携带诊断的配置组
运行 hookrunHook(bash, hook, opts, now) —— 通过 ctx.shell 发送 stdin 载荷 + 环境,并解码针对每个事件构建 stdin 载荷 + 该方言的 环境
解码输出parseHookOutput(exit, stdout, stderr) → 中性的 HookOutput把 HookOutput 映射到扩展点特有的类型化 Decision
合并 N 个 hookmergeHookOutputs(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 决策如何流转 ​

text
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()——先中止再等待——而不会泄漏链。

不要与 webhook 子系统混淆 ​

上面的 packages/hooks/* 家族与更新的 webhook 子系统(packages/webhook/*)不是一回事:ctx.webhookRuntime 接收经过认证的外部 provider 事件(例如已签名的 GitHub 投递),并运行可信的程序化规则,这些规则可以在 Web Workspace 内创建普通的 root Session——一条 fire-and-forget 的"事件 → Session"桥(参见 Webhook 事件)。而 hooks 桥是外向的命令 hook 契约,由外部 CLI 在 dsh 自己的 agent 循环内部执行。包不同、ctx seam 不同、控制方向不同。

这对 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.tsmergeHookOutputs
packages/hooks/hook-protocol/src/events.tshook/* 会话事件
packages/hooks/hooks-claude-code/src/config.tsClaude Code 桥接配置
packages/hooks/hooks-codex/src/config.tsCodex 桥接配置

延伸阅读 ​

  • 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