Files
deepseek-harness/packages/hooks/hook-protocol/README.zh.md
Tianyi Cui 7f711996ff Merge commit 'ecbf75a5e70f662b6420375140cf12eb6bac7860' into worktree/retarget-pr828-20260729
# Conflicts:
#	packages/hooks/README.i18n.yaml
#	packages/hooks/README.zh.md
#	packages/hooks/hook-protocol/README.i18n.yaml
#	packages/hooks/hook-protocol/README.zh.md
#	packages/hooks/hooks-claude/README.i18n.yaml
#	packages/hooks/hooks-claude/README.zh.md
#	packages/hooks/hooks-codex/README.i18n.yaml
#	packages/hooks/hooks-codex/README.zh.md
2026-07-29 21:34:29 +08:00

7.3 KiB
Raw Blame History

@deepseek-ai/dsh-hook-protocol

English | 中文

Claude CodeCodex hook 协议格式wire format共享核心。它不是 Cordis 插件:不注册也不注入任何内容。它是一个,提供两个桥接插件(@deepseek-ai/dsh-hooks-claude@deepseek-ai/dsh-hooks-codex)导入的方言无关原语,使两者都无需重复实现协议中相同的部分。

共享 lib 存在的原因是Codex 有意重新实现了 Claude Code hook 协议的一个子集,包括相同的 hooks.json matcher group 结构、相同的退出码stdout 输出契约以及相同的 command hook 执行模式。真正共享的部分位于此处;每个桥接只负责不同的部分。

共享内容(此处)与各方言内容(桥接)

关注点 此处(dsh-hook-protocol 桥接(dsh-hooks-claude / -codex
Matcher 校验 + 测试 compileMatchers(patterns, mode) 从同一注册表提供诊断与配置生命周期内的重复匹配Codex 使用有界且跨重载稳定的 Rust 正则 internermatcherDiagnosticmatchesMatcher 是隔离的一次性辅助函数 选择自身原生正则 modeclaude = JavaScriptcodex = Rust regex),将可运行的唯一 pattern 只编译一次,拒绝带有注册表诊断的配置组,并在失败或 teardown 时释放配置注册表
运行 hook runHook(bash, hook, opts, now):通过 ctx.bash 提供 stdin payload + env再解码 构造每个事件的 stdin payload + 该方言的 env
解码输出 parseHookOutput(exit, stdout, stderr) → 中性 HookOutput 将中性 HookOutput 映射到 seam 特定的类型化 Decision
合并 N 个 hook mergeHookOutputs(outputs) → 最严格的 MergedHookOutcome (无)
持久记录 appendHookInvoked / appendHookResulthook/* 会话事件;结果的 decisionstderrSummary 从此处的 HookOutput 派生) 在每次调用前后调用它们
脱离运行完全停稳 createDetachedRuns():跟踪触发后不等待的运行链;drain() 先 abort再等待它们 signal 传给每个脱离的 runHook,并将 drain 注册为 effect disposer

原语

  • compileMatchers(matchers, mode) / matcherDiagnostic(matcher, mode) / matchesMatcher(matcher, query, mode):缺失、'''*' 时匹配全部;两种方言都将纯 [A-Za-z0-9_|]+ pattern 视为按 pipe 分隔的精确多选。其他 pattern 会用原生方言编译为未锚定正则Claude Code 使用 JavaScriptCodex 使用 Rust regex(包括 (?i) 等内联 flag。桥接解析器会先丢弃没有 matcher 匹配对象的事件所带字段,收集其余可运行的配置组,再将它们的唯一 pattern 只编译一次。解析器直接从这些实例读取 registry.diagnostic(pattern)pattern 被拒绝时,会先释放配置注册表再抛错,否则把该注册表交给运行时,并在 teardown 时先 drain 脱离运行再释放它。Codex 的有效实例和无效诊断会 intern 在同步 rregex 依赖模块上,因此无需使用 globalThis,也能跨 hook-protocolCordis 重载保留;一次性辅助函数共享同一 interner。由于 rregexfree() 后也不能缩小 WASM 分配,进程会有意最多保留 MAX_INTERNED_CODEX_REGEX_PATTERNS128个不同的非字面 pattern。容量用满后新的不同 pattern 会在调用 WASM 前被容量诊断拒绝;已经 intern 的 pattern 继续工作,重启进程会重置预算。这样既覆盖相同 pattern 重载,也能在恶意唯一 pattern 重载下保持有界,而无需无界缓存。
  • runHook(bash, hook, options, now):要求并转发调用方拥有的 options.signal,将 options.payload 序列化到 hook stdin当且仅当 options.trailingNewline 时添加尾随换行符),在执行器凭证清理后合并 options.envdsh-bash 受信任插件接口),遵循 hook 的 timeoutSec(否则使用 options.defaultTimeoutMs;默认值属于桥接,其配置默认为 lib 的 DEFAULT_HOOK_TIMEOUT_MS 10 分钟参考值),再解码结果(将 options.expectedEventName 传递给 codec。因此取消会到达执行器的进程组终止与 join 边界。它绝不抛出异常:执行器拒绝(基础设施故障)会变为 HookOutput,其 exitCode: undefined(非阻塞错误)。now 会被注入,以便测试持续时间。
  • parseHookOutput(exitCode, stdout, stderr, expectedEventName?) 解码退出状态与结构化 stdout。退出码为 2 时,会以 stderr 内容阻止执行;其他失败不阻塞。匹配的 hook 特定权限决策会覆盖遗留顶层决策;事件判别字段不匹配或缺失只会抑制事件特定字段。顶层字段仍与事件无关,成功但非 JSON 的输出会留给桥接处理。
  • mergeHookOutputs(outputs):折叠在一个点上匹配的每个 hook 结果:权限优先级为 deny > ask > allow,从首个 continue:falsehalt 状态保持不变,阻塞原因用 \n\n 连接,additionalContextsystemMessages 按顺序累积。
  • createDetachedRuns():跟踪以 emit 形式脱离运行的点是否完全停稳(没有 seam 等待它们)。桥接会跟踪每条运行链,包括 hook 运行及其 continuation并将 drain() 注册为 effect disposer。drain 会触发 tracker 的 abort signal(因此仍在运行的 hook 进程会通过 runHook 终止,而不是等待到超时),随后在所有已跟踪链结算后 resolve。因此 fiber.dispose() resolve 时,不会遗留任何可能作用于已 dispose资源释放的上下文的脱离 hook 工作(见 防御模式dispose 必须达到完全停稳)。

hook/* 会话事件

通过 declaration merging 合并到 SessionEventMap(仅日志,与 compact/* 相同;不是 SurfaceEventType,没有 surfaceOphook/invokedhook 命令已运行)与 hook/result(其结果,按 handlerId 配对,决策规则由 appendHookResult 负责。Payload 与每事件 JSDoc 位于生成的 持久化日志事件目录stderrSummary 会截断到记录的 stderrSummaryMaxChars(桥接配置,参考默认值 DEFAULT_STDERR_SUMMARY_MAX_CHARS = 500为空时省略

Hook 溯源记录必须位于一个尚未结束的轮次内。轮次中的点(PreToolUsePostToolUseStop)按构造满足这条由所有者定义的关系。SessionStart 与轮次前的 UserPromptSubmit 准入 seam 没有 hook/* 记录;获准的上下文改由其带来源的 user/message 作为证据,详见 hooks Agent Noteagent 决策记录)。

模型体验

通过 dsh-hooks-claudedsh-hooks-codex 间接影响;它们可以将解析后 hook 输出转为提示词上下文、已阻塞结果或 continuation 反馈。

KV Cache 影响

不会直接失效;请求前缀变更由上述消费方负责。

已知限制与暂缓事项

  • HookOutput.updatedInput 会被解析但不会应用:输入改写是已暂缓的一致性设计问题(见 pre-tool-input-rewrite Agent Note);当 hook 设置它时,桥接会记录 + 警告。完整契约见 src/types.ts