DeepSeek Harness 源码解析 · 第 39 讲
DeepSeek Harness 源码解析系列 第 39 讲:Hook 桥接(Claude Code/Codex) 基于 DeepSeek Harness 源码 · 2026-09-22 💡 本讲一句话: Claude Code 和 Codex 的用户习惯用 hooks.json 挂 shell 命令来管住 Agent——DeepSeek Harness 不让你重写这些 hook,而是造了两座"桥":把外部 shell hook 协议原样翻译到自己的拦截扩展点上。共享库管执行/解码/合并,两个桥只管方言差异;读完你会明白 "faithful-but-degraded(忠实但降级)" 是怎么落地的。 一、定位:不重写你的 hook,而是把你的 hook 搬进来 前面几讲分析的工具、沙箱、MCP,都是 DeepSeek Harness 自己提供 的能力。但真实用户手里往往已经有一堆为 Claude Code 或 Codex 写好的 hook——一段 shell 命令,在 PreToolUse 时检查参数、在 Stop 时阻止过早收尾。这些资产不该因为换了 harness 就作废。 packages/hooks/ 的 README 把定位说得很直白:让 hook 子系统"lets users extend the agent at lifecycle points the way Claude Code and Codex do "——把一个桥插件指向你现有的 hooks.json,那些外部 shell hook 就能 原样(unmodified) 跑起来。源码分三个包: 包 角色 形态 hook-protocol 共享协议库:匹配、执行、解码、合并、审计事件、detached 静默 library(非插件) hooks-claude-code CC 桥:7 事件方言、payload/环境变量/命令替换、deny+ask 映射 Cordis 插件 hooks-codex Codex 桥:5 事件方言、纯正则 matcher、无环境变量、只认 block Cordis 插件 🔑 关键区分:桥(bridge)≠ 原生 hook。 README 原话:"a 'native hook' is just an ordinary Cordis plugin on those extension points"——harness 真正的扩展面是 类型化拦截点 (tools/pre-execute、agent/turn-stopping 这些事件),写一个原生插件挂上去就是"原生 hook"。桥只是把外部 shell-hook 协议翻译到同一组拦截点上,让你不用重写 hooks.json。想要定制行为?写原生插件;想复用存量资产?上桥。 这个"共享库 + 两个方言桥"的拆分是本讲所有代码的组织原则: 协议里两种方言共有的行为(退出码契约、JSON 解码、最严格合并)沉到 hook-protocol;只有方言差异(payload 字段名、matcher 语义、stdin 帧格式、决策映射)留在各自的桥里 。下面按这个顺序拆。 二、方言中立的词汇表:HookOutput hook-protocol/src/types.ts (137 行)定义了共享词汇。核心是 HookOutput ——一条 hook 跑完后解码出的"中立结果"。注意它的 JSDoc:每个字段都是可选的,因为"一条 hook 可能只用到任意子集; 哪个字段在哪个 hook 点有意义、哪些被忽略,由桥决定 "(faithful-but-degraded)。 📄 packages/hooks/hook-protocol/src/types.ts (第 89-137 行) export interface HookOutput { // 方言中立的 hook 结果词汇表:两个桥共享的"通用语",各自再映射到类型化 Decision exitCode: number | undefined // 进程退出码;spawn 失败时为 undefined——没有干净退出码就没有可依据的决策 stderr: string // trim 后的 stderr——exit 2 阻塞时它就是给模型/用户看的 blo…