跳至内容

官方 HookBridge 与兼容层

证据范围:本文解释上游 deepseek-ai/deepseek-harness 固定提交 aa6c361a972c8369148dea7380bb5c21c24e07ec0.1.1-rc.2)中的 hook-protocol、Claude Code bridge、Codex bridge、对应 README、Agent Note 和测试源码。人工对照完成于 2026-08-16(Asia/Shanghai)的上一个固定基线;迁移到当前基线后只验证了链接路径仍存在,没有逐行复读版本差异。

证据状态:下面写“测试证据”时,意思是静态阅读了固定提交中的测试断言和测试名称;本次没有运行上游测试,没有启动 DSH 或任何 Test-DSH*,也没有做真实 shell hook、模型、Profile 或 state 验证。

术语边界:固定提交把 hooks-claude-codehooks-codex 做成 Cordis 的插件形态,但本文称它们为“上游 bridge 包”或“兼容层”,不把 bridge 宣传成 marketplace 意义上的“官方插件”;外部 hooks.json、shell 脚本和社区项目也不因此获得官方身份。

bridge 的裁决最终落在 DSH 的瀑布事件上,而瀑布事件有一条仓库级不变式:监听器要么调用 next() 把控制权交还链条,要么直接返回一个终端决定;不调用 next() 就返回,链条在这一环短路,后面的监听器不再执行。下面的组件把这条规则做成可以亲手触发的实验——审计、策略、默认放行三个监听器挂在同一次派发上,切换策略的返回方式,看兜底还会不会执行、最终结果由谁写出。

互动组件Hook 瀑布短路实验单独打开

正在载入互动组件……

不看组件也能读的说明

时间线是固定教学模型:三个监听器、两条规则(委托或短路)、一份确定性步骤表。它不加载真实 bridge,不执行外部命令,也不测量协议超时;真实 bridge 对 undefined 返回的处理以源码和测试为准。不打开组件也能继续读正文——下面的合并规则与退出码解码表给出同样的结论。

一、先把四个词分开

初学者可以先把一次 hook 运行想成一次跨进程翻译:外部程序只知道“读一段 JSON、执行命令、写 stdout/stderr、返回退出码”,而 DSH 内部需要的是带类型的事件监听器和带类型的决策对象。

名称它面对的世界它真正做的事
外部 shell hookClaude Code 或 Codex 的 hooks.json 与命令进程从 stdin 读取 JSON,根据自己的规则输出 JSON/文本,靠退出码表达成功或阻断
dsh-hook-protocol两种外部协议都共有的部分提供 matcher、stdin/环境传递、退出码和 stdout 解码、多个 hook 合并、hook/* 日志以及 detached 运行跟踪;它本身“不注册、不注入”,不是 Cordis 插件protocol README
Hook bridge外部协议与 DSH 之间的适配层读取配置、选择匹配器、构造某个方言的 payload、调用 shell、把中性结果映射成 DSH typed Decision
DSH typed interception pointDSH 内部的公开扩展面例如 agent/pre-steptools/pre-executetools/post-executeagent/turn-stopping,监听器通过类型化的 PreStepDecisionPreToolDecisionPostToolDecision 参与流程interception note

最重要的设计判断是:bridge 不是一套新的 DSH 权限模型,也不是把任意字符串“塞进” agent loop; 它只能把外部协议中已经被映射的子集送到 DSH 已经定义好的 typed point。 上游 Agent Note 明确说,原生 hook 其实只是订阅这些 canonical lifecycle event 的普通 Cordis 插件; bridge 的价值是兼容已有的外部 shell hook,而不是提供比原生插件更强的能力hook bridges note

二、共享协议层:命令怎样变成中性结果

1. 配置和 matcher

两种配置都采用“事件名 → matcher group → hooks 数组”的结构。一个 group 可以没有 matcher;缺失、空字符串和 * 都表示匹配全部。解析器只保留自己支持的事件,坏的 group 或缺少 command 的条目会被丢弃,非 command 条目会被记录为 skipped 并告警。

方言matcher 规则matcher 查询值没有匹配主体的事件
Claude Code只含字母、数字、下划线和 | 的模式([A-Za-z0-9_|]+)按字面量处理:先按 | 拆成备选项,再与查询值做整串精确比较,Bash|Read 只匹配 BashRead;含其他字符的模式按不自动加锚点的正则解释PreToolUse/PostToolUse 用工具名;SessionStart 用 session source;SubagentStart/SubagentStop 用固定的 general-purposeUserPromptSubmitStop 的 matcher 在解析时被丢弃
Codex所有非空模式都是不自动加锚点的正则,没有 Claude Code 的字面量快捷规则PreToolUse/PostToolUse 用真实工具名;SessionStart 用 session sourceUserPromptSubmitStop 的 matcher 在解析时被丢弃

例如 Claude Code 的 Bash 只精确匹配 Bash,不会匹配 BashOutput; Codex 的 Bash/Bash/,会匹配包含 Bash 的工具名。 固定提交的 matcher 测试同时检查了这两个容易混淆的差异,并检查了非法正则在运行时不会向 agent loop 抛异常,而配置解析阶段会给出稳定诊断matcher source matcher tests

matcher 有两个时机:matcherDiagnostic() 在 bridge 注册前检查被消费的配置,非法正则会使本次配置加载失败并注册零个 hook; matchesMatcher() 在运行时把非法模式当成“不匹配”。 因此 UserPromptSubmit 上写了非法 matcher 不会影响它,因为该事件本来就没有 matcher subject; 在 PreToolUse 上写非法 matcher 则会被报告。 这个行为由两套 config 测试分别覆盖Claude config tests Codex config tests

2. stdin、环境、工作目录和超时

共享 runHook() 要求调用方显式传入 AbortSignal,把 payload 序列化为 JSON 写入 stdin,把每个 hook 的 timeoutSec 从秒转换成毫秒; 没有 per-hook timeout 时使用 bridge 配置的默认值,参考默认是 600_000 ms,也就是十分钟。 它通过 ctx.shellShellExecutor 执行,源码注释和测试说明该路径复用凭据清理、进程组取消和等待机制; 执行器基础设施失败时返回 exitCode: undefined 的非阻断结果,而不是把 agent loop 弄崩runner source runner tests

两个 bridge 都把 agent-scoped hook 的工作目录设为 session header 的 cwd,所以相对路径指向用户项目目录,而不是 DSH 进程的启动目录。 Claude Code 还会把 CLAUDE_PROJECT_DIR 放入 hook 环境:显式 projectDir 优先,否则默认使用相同的 session workspace,并在配置解析时替换 ${CLAUDE_PLUGIN_ROOT}${CLAUDE_PROJECT_DIR}。 Codex bridge 不做这两类 placeholder substitution,也不额外注入 Codex 专属环境变量; 它仍然使用 ctx.shell 提供的执行环境Claude source Codex source

stdin 的末尾换行是一个真实的 wire-level 差异:Claude Code bridge 传 trailingNewline: true,Codex bridge 传 false。固定提交的 runner 测试分别断言有换行和无换行,不能把两个 bridge 简化成“都执行同一个 JSON command”runner tests

3. 退出码和 stdout/stderr 字段

共享 codec 先保留 trim 后的 stdout 和 stderr,再按退出码解码:

外部命令结果HookOutput 的含义bridge 后续通常怎样用
exit 0成功;stdout 若以 { 开头则尝试解析 JSON,格式不对时仍保留为普通 stdoutClaude Code 在有些事件只消费结构化字段;Codex 的 SessionStart/UserPromptSubmit 还可把干净的普通 stdout 当作 additionalContext
exit 2decision: 'block';stderr 成为阻断理由,空 stderr 仍然阻断但没有理由映射成 prompt reject、tool deny/block 或 Stop steering
其他非零退出码非阻断错误;exit code 和 stderr 可记录,但没有阻断 decision当前桥接不会仅因为 exit 1/127 就拒绝工具
进程无法运行或被信号终止exitCodeundefined;错误放在 stderr,仍是非阻断错误写入结果证据(若有开放 turn),不把基础设施错误伪装成 hook deny

结构化 stdout 的字段分成两个频道。 顶层 decision 只接受 approveblockallowdenyask 只能来自 hookSpecificOutput.permissionDecision。 后者还可以带 hookEventNamepermissionDecisionReasonadditionalContextupdatedInputhookSpecificOutput 的事件名与当前 firing event 不一致,或者缺少事件名时,codec 只丢弃事件专属字段,仍保留顶层的 continuestopReason 等事件无关字段; 这样一个写错事件名的 block 不会意外阻断另一个 pointcodec source

continue: falsestopReasonsystemMessage 会先进入中性 HookOutput,多个结果合并时第一个 continue: false 会形成 sticky stop,additionalContext 与 system message 按 hook 顺序收集,权限结果按 deny > ask > allow 合并, 理由只保留最终获胜等级的理由merge source merge tests。 但是“被解析”不等于“已实现完整控制”:当前两套 bridge 都会把 continue: false 记录为 stop 却不真正硬停整个 run; systemMessage 会告警但不展示; updatedInput 会被解析并告警,但不进入可执行的工具参数改写。 这些是兼容子集的边界,不应写成 DSH 已经支持输入重写或全局 halt。

三、从外部事件到 DSH typed point

下面的“支持”表示固定提交中 bridge 实际注册了映射,并不表示已经覆盖外部产品全部协议字段。

外部事件Claude Code bridgeCodex bridgeDSH typed point 与结果
SessionStart支持 JSON additionalContext支持 JSON additionalContext,也支持干净的普通 stdoutagent/session-start 是 emit/通知点,不能阻断启动;结果准备好后调用 agent.inject()。两者都 detached,可能赶不上第一请求
UserPromptSubmit支持 exit 2/deny 和 JSON additionalContext;普通 stdout 不作为 context支持 exit 2/block、JSON additionalContext 和干净普通 stdoutagent/pre-step waterfall;阻断返回 PreStepDecision.reject;只有 context 时先 next(),再把来源明确的消息加到下游 enter,避免短路后面的策略监听器
PreToolUsedenyPreToolDecision.denyaskPreToolDecision.askblock/denyPreToolDecision.deny;不承诺 allow/asktools/pre-execute waterfall;没有阻断时必须 next(),因此“hook 没说话”不等于自动 allow
PostToolUsedeny → PostToolDecision.block 加反馈;context 单独附加同样 block 加反馈;context 单独附加tools/post-execute waterfall;bridge 先委托后合并 context,下游 listener 仍可替换或阻断;工具输出被压平成文本,不能假设结构化 tool output 原样保留
Stop支持阻断 Stop支持阻断 Stopagent/turn-stopping 是已等待的停止边界;bridge 用 agent.steer() 写入下一步模型可见理由,让当前 turn 继续;当前总是上报 stop_hook_active: false,无条件阻断的脚本必须自行限次
SubagentStart支持;context 只注入存活的本地 in-process child不支持,配置解析时丢弃subagent/start emit/detached;匹配 subject 固定为 general-purpose,不能从 DSH seam 得到任意 Claude agent kind
SubagentStop支持,但只能观察,不能阻断或注入不支持,配置解析时丢弃subagent/end emit/detached;Claude bridge 会保留 child 到配对的 end 边缘,以便 stop hook 仍能用正确 workspace

这张表里有三个值得记住的类型化规则。 第一,context-only hook 不能直接返回一个新的 enter,否则它会截断后来注册的 sandbox/policy listener; 固定提交的 Claude 和 Codex coverage tests 都有“先 delegate,再保留 context”的场景。 第二,ask 是 DSH 的审批路径,不是 shell hook 自己授予操作系统权限; Claude 的 PreToolUse ask 需要可用的 approval service/answerer,没有它会 fail closed 为 deny,Codex bridge 没有这条路径。 第三,native plugin 直接返回 typed Decision,不会因为使用 native path 自动产生 hook/* 外部协议日志; hook/* 属于 protocol bridge 的审计记录bridge note Claude bridge tests Codex bridge tests

Claude Code README 记录的固定子集是其当前 30 个 hook event 中的 7 个:SessionStartUserPromptSubmitPreToolUsePostToolUseStopSubagentStartSubagentStop; 其余 SetupPermissionRequestPreCompact 等事件不会被这个 bridge 注册。 Codex README 记录的是当前 10 个中的 5 个:PreToolUsePostToolUseSessionStartUserPromptSubmitStopPermissionRequestPreCompactPostCompactSubagentStartSubagentStop 被丢弃Claude README Codex README

两种 payload 的最小心智模型

Claude Code 的基础字段是 session_id、字符串形态的 transcript_pathcwdhook_event_name; 工具事件再带 tool_name、原始的 tool_inputtool_use_id 和文本化的 tool_response,prompt 事件带 prompt,Stop 带 stop_hook_active: false。 Subagent payload 还带 child 的 agent_id 和固定 agent_type: "general-purpose"

Codex 的基础字段是 session_idtranscript_path: string | nullcwdhook_event_name、静态配置的 modelpermission_mode: "default"; turn-scoped 事件带字符串 turn_id。 工具 payload 的 tool_name 是真实的 DSH tool name,tool_input 被有意缩减成 { command },没有 command 参数时传空字符串; PostToolUse 的 tool_response 是文本。 这个缩减意味着 Codex bridge 不能忠实暴露任意非 shell tool 的全部参数Codex source

两个 bridge 注入的 context 都带 { kind: 'plugin', plugin: 'hooks-claude-code' | 'hooks-codex' },所以 hook 生成的内容不会被误记为用户原话。普通 stdout、JSON additionalContext 和错误 stderr 的消费方式由方言决定,不能只看字段名就假定两个 bridge 行为相同。

四、detached hook 的 abort 与 drain

“detached”不是“丢到后台然后不管”。它表示 DSH 的 emit-shaped point 不会等待外部命令完成,所以 bridge 必须自己拥有 promise 生命周期,否则插件销毁后,旧 hook 仍可能向已释放的 context 注入内容或写日志。

固定协议库的 createDetachedRuns() 为每个 bridge apply() 建立一个 AbortController 和一个 in-flight Set。 bridge 把“hook 执行 + .then() 里的 inject/后续动作 + .catch()”作为完整 promise chain 交给 track(),并把 tracker 的 signal 传给 runHook()。 bridge 的 ctx.effect() disposer 调用 drain(),因此 Cordis 的 fiber.dispose() 只有在 detached 工作真正停稳后才会完成detached source

可以按下面的时间顺序理解:

  1. emit point 到达时,tracker signal 尚未 abort,bridge 启动 runPoint() 并立即 track() 整条 chain。
  2. runHook() 把同一个 signal 交给 ctx.shell;如果是 turn-scoped point,则 point 自己的 signal 也能在取消时终止正在运行的命令。
  3. bridge 被卸载时,drain() 先以 hook bridge disposed 为原因触发 abort,避免 detached hook 被十分钟默认 timeout 拖住。
  4. ctx.shell 的取消路径负责进程组 kill/join;tracker 等待整条 chain,而不只是等待子进程返回。
  5. 如果第一批 promise 在 drain 过程中又 track 了第二批,drain() 会重新观察 Set,直到所有波次都 settle;rejected chain 也不会让 settlement bookkeeping 永久悬挂。

因此,Claude Code 的三个 emit point 是 SessionStartSubagentStartSubagentStop,Codex 的 detached point 是 SessionStart。 turn-scoped 的 prompt/tool/stop point 则在其 waterfall 或停止边界中 await runPoint(),并在开放 turn 内写审计 pair。 固定 detached tests 静态断言 signal 初始未触发、drain 会 abort、drain 会等待当前 promise、会等待 drain 期间追加的 promise,并能处理 rejected run; 两套 bridge tests 还分别有“dispose aborts a still-running hook and drains to quiescence”的场景detached tests Claude lifecycle test Codex lifecycle test

detached 有一个产品语义后果:SessionStart 不能阻断启动,context 是“准备好就注入”的 best effort。若 shell hook 较慢,第一份模型请求可能已经发出,后来的 context 只能进入后续请求;固定提交把这项能力标成 TODO(session-start-gating),不能把测试中的快速脚本结果推广成“首请求必达”。

五、生命周期、日志与权限边界

生命周期和配置

两个 bridge 都只在加载时读取一次 configPath; 相对路径相对于进程启动 cwd,而不是每个 session/new.cwd。 读取/JSON 解析失败、被消费事件的非法 matcher 或其他加载错误会记录 warning,并注册零个 hook,而不是让整个 agent boot 失败。 Claude 只运行 shell-form type: "command",会跳过 httpmcp_toolpromptagent; Codex 只运行同步 command,会跳过非 command 和 async: true。 完整的多层 user/project/session/system 配置发现、信任控制和 live reload 都不在此固定子集内。

turn-scoped point 在有开放 turn 时为每次命令写一对 log-only session event:hook/invoked 记录 turn、point、dialect、matcher、handlerId; hook/result 用相同 handlerId 关联,记录 decision、可选 exitCode、最多 500 字符的 stderrSummary 和 durationMs。 它们不是 SurfaceEventType,没有 surfaceOp,也不是用户可见消息。 SessionStart 在 turn 1 之前运行,因此没有 hook/* pair; 这不表示命令没有运行,而是协议要求 hook 日志位于开放 turn 内events source events tests

执行权限不是 hook decision

要区分两种“权限”:

  1. PreToolUse 返回 ask/deny 是 DSH 对一次工具调用的 typed policy decision。Claude Code bridge 的 ask 进入 DSH approval seam;无 approval service 或 answerer 时 fail closed。Codex bridge 只有 block/deny,没有 allow/ask 的审批映射。
  2. 外部 command 本身以 DSH 进程能够使用的 shell 权限运行。bridge 通过 ctx.shell 复用宿主的执行、凭据清理、timeout 和取消能力;Claude 还主动传 CLAUDE_PROJECT_DIR,而两个 bridge 都把工作目录放到用户 session workspace。bridge 没有凭此把一个不可信脚本变成隔离沙箱,也没有把配置路径的多层 trust/hash 模型补齐;实际是否允许文件、网络或其他 shell 行为,必须看部署给 ctx.shell 的策略和操作系统账号。

这也是为什么“某个 hook 返回 allow”不能写成“它取得了系统权限”:在 Claude Code bridge 中 allow 不会自动预批准工具,在 Codex bridge 中更不会进入 pre-tool approval path;外部 command 的 OS 权限和 DSH tool 的 policy decision 是两条不同的链。

版本边界

本章固定的是根包 0.1.1-rc.2、提交 aa6c361a972c8369148dea7380bb5c21c24e07ec 的源码; 本地固定版本说明也提醒该项目仍是 Developer Preview,未来允许破坏性变化固定版本说明。 因此“Claude 7/30、Codex 5/10”是该提交 README 对其外部协议快照的支持子集,不是对未来 Claude Code、Codex 或 DSH 的永久承诺。 协议增加字段、改变 matcher、改变 timeout、改变 config discovery 或新增 typed point 后,都必须重新核对 bridge 源码、README 和测试。

六、四种扩展方式怎么选

方式入口和能力证据/兼容性适合什么情况主要代价
公开扩展点上的原生 Cordis 插件apply(ctx) 中订阅 typed point,直接返回 PreStepDecisionPreToolDecisionPostToolDecision,或调用 agent.inject()/agent.steer()固定 Agent Note 把 native hook 定义为普通 Cordis plugin;没有 shell JSON 序列化,也不自动产生 hook/* bridge 日志新写的 DSH 行为、需要完整 ctx、需要可靠类型和更细控制的逻辑需要按 DSH API 编写和测试,不能直接复用任意外部 hooks.json
Hook bridge读取现有 Claude Code/Codex command hook,经过 stdin/stdout/退出码协议后映射到少数 typed point上游固定提交有两个 bridge README、实现和真实 loop 测试源码;只证明固定提交中的映射子集已有 shell hook 想在 DSH 中继续工作,且能接受字段和生命周期降级序列化边界、方言差异、支持子集、串行执行、process-level config、未实现 hard halt/input rewrite
源码 patch 或维护 fork修改 DSH 自己的 loop、事件类型或 bridge/工具实现,可增加真正新的 typed point这是 fork 的维护选择,不是固定上游公开扩展点的兼容证明;每次升级都要重放 patch 并补测试必须改变核心语义、需要新的一致性保证,且团队愿意维护 fork合并冲突、版本漂移、构建/测试矩阵和长期同步成本都由 fork 维护者承担
运行时注入触碰 loader 内部对象、模块缓存、Fiber/private Map、目录链接或进程运行时状态固定上游 Hook README、typed interception Note 和 bridge tests 没有把它定义成 hook API;社区项目的自述也不能补足官方兼容证据诊断、实验或无法改源时的临时研究,不宜作为稳定产品扩展面私有实现漂移、卸载/重复注册/缓存残留、权限和供应链风险;很难证明 dispose 后真的 quiescent

社区样本的证据对照(2026-08-16)

本节把“上游 bridge”和社区仓库放在同一张表里,是为了防止读者因它们都能被装入 DSH 而混淆身份。日期是本轮访问时间;“项目自述”不是上游背书,也不是安全或兼容性结论。

样本本轮公开证据应归入的层尚未证明的事项
@deepseek-ai/dsh-hooks-claude-code@deepseek-ai/dsh-hooks-codex上游固定提交的包 README、源码和测试把它们写成 Cordis bridge plugin;README 分别列出 7 个和 5 个外部事件子集。共享 dsh-hook-protocol README 明确写着 library 不注册、不注入。见上游 bridge READMECodex bridge README协议 README上游仓库交付的协议适配器;“官方”仅指事实来源在上游仓库,不能外推为安全认证或未来版本承诺。没有证明未来协议仍保持相同子集,也没有证明外部 hook 获得 OS 沙箱、全局 hard halt 或 input rewrite。
omdsh-dev/dsh-annotationvlln/dsh-navbar固定 README 自称 dsh.bundle/dsh.client 或纯浏览器 bundle,分别说明 Node half 为空或 0 patch;它们的 package/README 是社区仓库证据。见批注 README导航条 README普通第三方 Bundle/客户端插件候选。没有证明上游维护、固定 DSH 版本运行、权限安全、完整卸载或任何“官方插件”身份。
rpmalouin/deepseek-harnessGitHub 页面显示 forked from deepseek-ai/deepseek-harness;README 自称增加 graph-first review、OpenRouter routing 和 Hermes/delegation。见fork 页面READMEGitHub fork;其额外改动先按项目自述记录,不能仅因 fork 关系称为已验证 patched fork。本轮未完成与上游的逐文件差异、构建、运行和同步矩阵核验。
yjh051108/dsh-super-injector固定提交源码出现 junction、loader.create()loader.internal.loadCache、Fiber/registry 关联和内部路由/客户端表清理;其 cordis.patch.yml 只装入引导器。见[注入器源码][super-injector-source]和[注入器 patch][super-injector-patch]。第三方 Bundle 引导器 + 运行时注入/热重载兼容层。项目自测、README 的成功数字和静态源码都没有证明官方兼容、生产安全、卸载无残留或跨版本稳定。

这里的证据等级顺序是:固定上游源码/测试 > 固定社区源码和 manifest > README/页面自述 > 搜索结果与 stars/count。等级越低,越只能用于发现和分类,越不能支持兼容性、安全性或质量结论。

一句话决策是:已有兼容性债务选 bridge;新能力选原生 typed plugin;必须改核心语义才考虑源码 fork;运行时注入只能被标成非官方、版本敏感的实验手段。GitHub 上采用 DSH Bundle 格式、叫作 plugin、能被某个 loader 找到的社区仓库,都不能仅凭名称、manifest 或可加载性称为 DeepSeek 官方插件;社区项目的身份和风险边界另见社区生态与扩展边界

七、初学者核对清单

在使用兼容层前,按下面顺序问自己:

  1. 这个外部事件是否在固定 bridge 的支持列表里?如果是 PermissionRequestPreCompact、Codex SubagentStart 等,先按“不支持”处理。
  2. 这个 hook 需要的字段是否真的映射?特别检查 CC/Codex 的 tool_input 差异、Codex 静态 modeltranscript_path 的空值形态,以及普通 stdout 是否被该事件消费。
  3. 这个 hook 的目标是阻断工具、请求审批、添加 context 还是改写输入?updatedInputcontinue:false 在固定 bridge 中不能提供完整语义。
  4. 配置是否会被一次性读取到所有 session?相对 configPath 以进程启动目录为基准,hook 运行 cwd 才是 session workspace;不要把这两者混为一谈。
  5. 你是否把测试源码的断言误当成自己的运行验证?本章没有运行 DSH/Test-DSH*,真正上线前还要在受控目录、受控 shell 权限和目标版本上做独立验证。

参考证据

证据类别固定提交链接本文使用的事实
共享协议 READMEhook-protocol README明确 library 不是 Cordis plugin;划分 shared 与 per-dialect 责任;说明 matcher、runner、codec、merge、detached 和 hook/*
中性类型与输出types.tscodec.tsCommandHookMatcherGroupHookOutput 字段,以及事件 discriminator、permissionDecision 和 updatedInput 规则
执行与生命周期runner.tsdetached.ts十分钟参考 timeout、stdin 换行、env/cwd/signal、基础设施错误、abort 后 drain 全部 promise 波次
matcher、合并、日志matcher.tsmerge.tsevents.ts两种 matcher 语义、deny/ask/allow precedence、context 顺序、hook/invoked/hook/result 的 turn 约束
协议单元测试源码codec testsrunner testsdetached testsmatcher testsmerge testsevents tests退出码、字段过滤、换行、signal、非法 matcher、合并和 drain 的静态断言
Claude Code bridgeREADMEconfig.tsindex.tsbridge tests7 个事件、CC payload/env/替换、typed point 映射、detached child 生命周期、配置容错和 ask 行为
Codex bridgeREADMEconfig.tsindex.tsbridge tests5 个事件、regex-only、snake_case/no-newline、静态 model、{ command } 缩减和 SessionStart drain
上游设计决定hook bridges Agent Noteinterception extension-points Agent Notebridge 是兼容适配器;native hook 是 typed interception point 上的普通 Cordis plugin;native 不自动使用 hook/*

本文的结论优先级是:固定提交源码和测试断言高于自动索引,包 README 负责支持子集和已知限制,社区 README 或项目自称不能覆盖上游没有提供的证据。