docs: translate remaining READMEs

This commit is contained in:
Tianyi Cui
2026-07-26 05:03:53 +08:00
parent 37bfac749b
commit 226dc7a249
559 changed files with 11227 additions and 1 deletions

View File

@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
README.md: 23478fb5e9b813a3370ce465104b1f9db8b0a26a
README.zh.md: 21c75f0476c76c0be75dc3af25ffb9a2be28dc4e

View File

@@ -1,5 +1,7 @@
# hooks/ — hook bridges + shared protocol
English | [中文](README.zh.md)
The hooks subsystem lets users extend the agent at lifecycle points the way Claude Code and Codex do — by pointing a bridge plugin at an existing `hooks.json` (or settings) so those external shell hooks run faithfully. The canonical extension surface itself is the harness's typed interception seams ([the interception-seams Agent Note](../../.agents/notes/implemented/feature/2026-06-30-interception-seams.md)); a "native hook" is just an ordinary cordis plugin on those seams. These packages are the **bridges** that translate the external shell-hook protocol onto that same surface, plus the shared wire-protocol library they build on.
| Package | Role | Shape |

View File

@@ -0,0 +1,13 @@
# hooks/hook 桥接 + 共享协议
[English](README.md) | 中文
hooks 子系统让用户可以像使用 Claude Code 和 Codex 一样,在 agent 生命周期节点扩展 agent把桥接插件指向现有的 `hooks.json`(或 settings即可忠实运行这些外部 shell hook。规范的扩展表层本身是 harness 的类型化拦截 seam见[拦截 seam Agent Note](../../.agents/notes/implemented/feature/2026-06-30-interception-seams.md));「原生 hook」只是这些 seam 上的普通 cordis 插件。这些包是把外部 shell-hook 协议转换到同一表层的**桥接**,另含它们共同依赖的共享协议格式库。
| 包 | 职责 | 形态 |
|---|---|---|
| `hook-protocol/` | 共享协议格式核心matcher 原语、退出码stdout codec、`runHook`(通过 `ctx.bash`)、最严格合并、`hook/*` 会话事件、分离运行完全停稳 | 库(非插件) |
| `hooks-claude/` | Claude Code `hooks.json`settings 的桥接 | 插件 |
| `hooks-codex/` | Codex `hooks.json` 的桥接 | 插件 |
Codex 有意重新实现 Claude Code 协议的一个*子集*`hooks.json` 形状相同、5 个事件而非 CC 的众多事件、仅命令、仅正则表达式 matcher、没有 env替换因此 `hook-protocol` 拥有真正相同的原语,每个桥接只拥有不同部分(逐事件 stdin 载荷、env以及把 hook 的中性结果映射到 harness 类型化 Decision 的方式)。参见 [hook-protocol/README.md](hook-protocol/README.md)。

View File

@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
README.md: ec25bce4b00102d4de587e69d27e8009ca199b78
README.zh.md: c0a4c199293eebfeec8a773feb434bbd43e0504b

View File

@@ -1,5 +1,7 @@
# @deepseek-ai/dsh-hook-protocol
English | [中文](README.zh.md)
The **shared core** of the Claude Code / Codex hook wire protocol. NOT a cordis plugin — it registers nothing and injects nothing. It is a **library** of dialect-neutral primitives the two bridge plugins (`@deepseek-ai/dsh-hooks-claude`, `@deepseek-ai/dsh-hooks-codex`) import so neither re-implements the identical halves of the protocol.
Why a shared lib at all: Codex deliberately reimplements a *subset* of the Claude Code hook protocol — the same `hooks.json` matcher-group shape, the same exit-code/stdout output contract, the same command-hook execution model. The genuinely-shared parts live here; each bridge owns only what differs.

View File

@@ -0,0 +1,45 @@
# @deepseek-ai/dsh-hook-protocol
[English](README.md) | 中文
Claude CodeCodex hook 协议格式的**共享核心**。它不是 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 测试 | `matchesMatcher(pattern, query, mode)`:根据 `mode` 使用字面匹配或正则匹配 | 选择自身 `mode``claude` = 字面或正则,`codex` = 始终使用正则) |
| 运行 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` / `appendHookResult``hook/*` 会话事件;结果的 `decision``stderrSummary` 从此处的 `HookOutput` 派生) | 在每次调用前后调用它们 |
| 脱离运行完全停稳 | `createDetachedRuns()`:跟踪发射后不再等待的运行链;`drain()` 先 abort再等待它们 | 将 `signal` 传给每个脱离的 `runHook`,并将 `drain` 注册为 effect disposer |
## 原语
- **`matchesMatcher(matcher, query, mode)`**:缺失、`''``'*'` 时匹配全部;`claude` 模式将纯 `[A-Za-z0-9_|]+` pattern 视为字面值pipe = 精确匹配交替),其他 pattern 视为正则;`codex` 模式始终使用未锚定正则。无效正则不匹配任何内容(绝不抛出异常)。
- **`runHook(bash, hook, options, now)`**:要求并转发调用方拥有的 `options.signal`,将 `options.payload` 序列化到 hook stdin当且仅当 `options.trailingNewline` 时添加尾随换行符),在执行器凭证清理后合并 `options.env``dsh-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:false` 使 halt 粘滞,阻塞原因用 `\n\n` 连接,`additionalContext``systemMessages` 按顺序累积。
- **`createDetachedRuns()`**:为脱离运行的 emit 形状点跟踪完全停稳(没有 seam 等待它们)。桥接会跟踪每条运行链,包括 hook 运行及其 continuation并将 `drain()` 注册为 effect disposer。drain 会触发 tracker 的 abort `signal`(因此仍在运行的 hook 进程会通过 `runHook` 终止,而不是等待到超时),随后在所有已跟踪链结算后 resolve。因此 `fiber.dispose()` resolve 时,没有脱离 hook 工作会留下并触发已 dispose 的上下文(见 [防御模式](../../../docs/defensive-patterns.md)dispose 必须达到完全停稳)。
## `hook/*` 会话事件
通过 declaration merging 合并到 `SessionEventMap`(仅日志,与 `compact/*` 相同;不是 `SurfaceEventType`,没有 `surfaceOp``hook/invoked`hook 命令已运行)与 `hook/result`(其结果,按 `handlerId` 配对,由 `appendHookResult` 拥有决策规则。Payload 与每事件 JSDoc 位于生成的 [持久化日志事件目录](../../../docs/persistence-catalog.md)`stderrSummary` 会截断到记录的 `stderrSummaryMaxChars`(桥接配置,参考默认值 `DEFAULT_STDERR_SUMMARY_MAX_CHARS` = 500为空时省略
与每个事件一样,它们必须位于开启轮次内。轮次中点(`PreToolUse``PostToolUse``UserPromptSubmit``Stop`)按构造位于 loop 的开启轮次中;`SessionStart` 没有 `hook/*` 记录(其注入的 `user/message` 是持久证据),详见 hooks Agent Note。
## 模型体验
通过 `dsh-hooks-claude``dsh-hooks-codex` 间接影响;它们可以将解析后 hook 输出转为提示词上下文、已阻塞结果或 continuation 反馈。
#### KV Cache 影响
不会直接失效;请求前缀变更由具名消费方负责。
## 已知限制与暂缓事项
- **`HookOutput.updatedInput` 会被解析但不会应用**:输入改写是已暂缓的一致性设计问题(见 [pre-tool-input-rewrite Agent Note](../../../.agents/notes/proposed/feature/2026-06-30-pre-tool-input-rewrite.md));当 hook 设置它时,桥接会记录 + 警告。完整契约见 `src/types.ts`
- **无效 matcher 正则会静默地不匹配任何内容**`matchesMatcher` 绝不抛出异常;显示该错误需要返回诊断的变体或解析时验证(`TODO(matcher-diagnostics)`)。

View File

@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
README.md: 25709d440146e3afef954940080b29a3e1c1ec1a
README.zh.md: 07eb53673f9a954589eac3a8a7e55e4ef95b44dd

View File

@@ -1,5 +1,7 @@
# @deepseek-ai/dsh-hooks-claude
English | [中文](README.zh.md)
A cordis plugin that runs the supported command-hook subset of a user's existing **Claude Code** hook config (a `hooks.json`, or a settings file's `hooks` key) on the harness's canonical interception seams. It is the **CC dialect** half of the hooks subsystem: it owns the bridge's CC-shaped per-event stdin payloads, CC's env + `${CLAUDE_PLUGIN_ROOT}`/`${CLAUDE_PROJECT_DIR}` substitution, and the mapping from a hook's neutral outcome onto the harness's typed Decisions. The dialect-agnostic primitives (matcher, exit-code/stdout codec, `ctx.bash` execution, most-restrictive merge, the `hook/*` events) come from [`@deepseek-ai/dsh-hook-protocol`](../hook-protocol/README.md).
A native cordis plugin could do everything this bridge does — more powerfully, with typed returns and no serialization boundary. **The bridge exists only as a compatibility path for the mapped CC command-hook subset**; anything bespoke should be a native plugin on the same seams (see [the interception-seams Agent Note](../../../.agents/notes/implemented/feature/2026-06-30-interception-seams.md)).

View File

@@ -0,0 +1,97 @@
# @deepseek-ai/dsh-hooks-claude
[English](README.md) | 中文
一个 cordis 插件,在 harness 的规范拦截 seam 上运行 user 现有 **Claude Code** hook 配置(`hooks.json` 或 settings 文件的 `hooks` key中受支持的 command hook 子集。它是 hooks 子系统的 **CC 方言** 一半,拥有桥接的 CC 形状每事件 stdin payload、CC env + `${CLAUDE_PLUGIN_ROOT}``${CLAUDE_PROJECT_DIR}` 替换,以及从 hook 中性结果到 harness 类型化 Decision 的映射。方言无关原语matcher、退出码stdout codec、`ctx.bash` 执行、最严格合并、`hook/*` 事件)来自 [`@deepseek-ai/dsh-hook-protocol`](../hook-protocol/README.md)。
原生 cordis 插件可以完成此桥接的所有工作,功能更强,且具有类型化返回,没有序列化边界。**该桥接只是已映射 CC command hook 子集的兼容路径**;所有定制行为都应当使用相同 seam 上的原生插件(见 [拦截 seam Agent Note](../../../.agents/notes/implemented/feature/2026-06-30-interception-seams.md))。
## 配置
```ts
import type { Config } from '@deepseek-ai/dsh-hooks-claude'
const config: Config = {
configPath: '/path/to/hooks.json', // required: a hooks.json or a settings file with a `hooks` key
pluginRoot: '/path/to/plugin', // optional: replaces ${CLAUDE_PLUGIN_ROOT} in command strings
projectDir: '/path/to/project', // optional: replaces ${CLAUDE_PROJECT_DIR} AND sets the hook env var; defaults to the session cwd when omitted
defaultTimeoutMs: 600_000, // optional: per-hook timeout when a hook sets none (CC default)
stderrSummaryMaxChars: 500, // optional: char cap on the hook/result event's persisted stderr summary
}
```
`cordis.yml` 中:
```yaml
- dsh-hooks-claude:
configPath: ./.claude/hooks.json
pluginRoot: ./.claude/plugins/my-plugin
projectDir: .
```
配置只在加载时解析**一次**。`configPath` 是**进程级** 配置:相对路径在加载时根据进程启动 cwd 解析,因此一份配置应用于整个进程。尚未进行每会话(`session/new.cwd`)配置发现(`TODO(per-session-hook-config)`)。读取/解析失败会被容纳:桥接记录警告且不注册任何内容,而不是使启动崩溃(路径拼写错误不应使 agent 停止)。只运行 shell 形式 `type: 'command'` hook`http``mcp_tool``prompt``agent` hook 会被解析并跳过,同时记录警告。没有每 hook `timeout` 的 hook 会使用协议参考默认值 `DEFAULT_HOOK_TIMEOUT_MS`(来自 `dsh-hook-protocol`10 分钟,即 CC 默认值)。
hook **本身** 会在 agent 的会话工作区中运行:对 agent scope 点,桥接会将会话 `cwd``session/new.cwd`)作为 hook 进程工作目录,因此 hook 的 `pwd`相对路径marker 作用于 user 项目树,而非服务器启动目录。
## Hook 点 → seam Decision
| CC hook | Harness seam | 映射 |
|---|---|---|
| `SessionStart` | `agent/session-start`emit | additionalContext → `agent.inject()` 到新会话(无法阻塞) |
| `UserPromptSubmit` | `agent/prompt-submit`waterfall | `deny``PromptDecision.block`;仅 additionalContext → 通过 `next()` 委托,再将一个单独标记源的上下文前置到下游 `additionalContexts`(后续 listener 仍可阻塞/改写) |
| `PreToolUse` | `tools/pre-execute`waterfall | `deny``PreToolDecision.deny``ask``PreToolDecision.ask` |
| `PostToolUse` | `tools/post-execute`waterfall | `deny` → 带反馈的 `block`;仅 additionalContext → 通过 `next()` 委托再将一个单独标记源的上下文前置到下游决策Code Mode 将子调用上下文延迟到外层 `run_code` 结果 |
| `Stop` | `agent/turn-continuation`waterfall | 阻塞 Stop hook 强制 `continue`,并将原因作为下一步 steering |
| `SubagentStart` | `subagent/start`emit | additionalContext → `agent.inject()` 到实时同进程 child远程 child 没有本地注入目标 |
| `SubagentStop` | `subagent/end`emit | 只观测 |
三个 emit 点都脱离运行:没有 seam 会等待 `SessionStart``SubagentStart``SubagentStop` hook。每条运行链都会被跟踪dispose 桥接会中止仍在运行的 hook 进程,再排空 continuation然后 dispose resolve`createDetachedRuns`,位于 `dsh-hook-protocol`)。
matcher subject 是工具名称(`PreToolUse``PostToolUse`)、会话源(`SessionStart`),或常量 `agent_type`,其值为 `general-purpose``SubagentStart``SubagentStop`。harness subagent seam 不携带每 kind label因此桥接报告 Claude Code 自身 Task 工具默认值;默认/`*`/空 `agent_type` matcher 会触发,特定 kind matcher 不会触发。`UserPromptSubmit``Stop` 忽略 matcher。一个点上文件配置的多个 hook 会**按配置顺序串行运行**,并按最严格方式折叠(`deny > ask > allow`,见 `dsh-hook-protocol`)。串行使每个 hook 的 `hook/invoked``hook/result` 对在日志中相邻,决策折叠与顺序无关(见 Agent Note 的「run serially, not concurrently」说明
每个 agent scope stdin payload 都携带 `session_id` 与字符串形状的 `transcript_path`。可用时,桥接通过 `ctx.sessionPersistence.locate(session.header)` 解析后者,否则发送 `''`。查找不会创建或 flush 产物,因此第一个轮次结束检查点之前路径可能不存在,也可能省略当前开启轮次。
## 上下文源
注入上下文携带显式 `{ kind: 'plugin', plugin: 'hooks-claude' }` 源。`agent.inject()` 会将缺失源默认为 `{ kind: 'user' }`,这会将插件上下文错误标记为 user 提示词,因此桥接始终标注自身。
## 模型体验
### Hook 提供的上下文
#### 模型看到的内容
`SessionStart`、已接受提示词、工具后和实时同进程 subagent-start hook 可以添加带源归因的上下文消息;阻塞 `Stop` hook 将原因添加为下一步 steering。远程 child 注入没有本地目标。
#### Token 影响
hook 不返回上下文时没有成本。Hook 文本取决于数据,会被记录,并在后续会话请求中重发,直到压缩。
#### KV Cache 影响
仅追加;新可见内容位于可复用请求前缀之后,不会使现有 KV-cache 配置项失效。
### 已阻塞提示词或工具结果
#### 模型看到的内容
提供方提供的原因逐字传递。缺失原因时,已阻塞提示词精确使用 `blocked by UserPromptSubmit hook`,已拒绝工具变为 `Error: blocked by PreToolUse hook`,已阻塞工具后反馈精确为 `blocked by PostToolUse hook`,阻塞 stop 则精确添加 steering `continue: blocked by Stop hook``systemMessage``updatedInput` 会被记录或警告,但在此实现中对模型不可见。
#### Token 影响
阻塞提示词会移除该提示词的请求 token拒绝或反馈会添加保留的回退或提供方文本强制 continuation 需要另一个完整请求。
#### KV Cache 影响
已阻塞提示词不发送请求,不会导致失效。拒绝、反馈与强制 continuation 上下文会追加在可复用前缀之后,不改写前缀。
## 已知限制与暂缓事项
- **不支持的 hook 事件Claude Code 当前 30 项中的 23 项):** `Setup``InstructionsLoaded``UserPromptExpansion``MessageDisplay``PermissionRequest``PostToolUseFailure``PostToolBatch``PermissionDenied``Notification``TaskCreated``TaskCompleted``StopFailure``TeammateIdle``ConfigChange``CwdChanged``FileChanged``WorktreeCreate``WorktreeRemove``PreCompact``PostCompact``SessionEnd``Elicitation``ElicitationResult`。这些事件的配置会被解析,但绝不分派。比较基线是 Claude Code [官方 hook 事件参考](https://code.claude.com/docs/en/hooks#hook-events)。
- **`SessionStart` 只支持部分功能:** 会消费 JSON `additionalContext`,但不支持纯 stdout 上下文、`initialUserMessage``sessionTitle``watchPaths``reloadSkills``CLAUDE_ENV_FILE`。hook 脱离运行,因此上下文可能错过第一个请求(`TODO(session-start-gating)`payload 会省略 `model``agent_type``session_title` 等当前可选字段。
- **`UserPromptSubmit` 只支持部分功能:** 支持阻塞与 JSON `additionalContext`,但不支持纯 stdout 上下文、`sessionTitle``suppressOriginalPrompt`。除非被覆盖,否则桥接还会使用自身 600 秒默认值,而非 Claude Code 的事件特定 30 秒 command 超时。
- **`PreToolUse` 只支持部分功能:** `deny``ask` 决策可用;`allow` 不会预批准,不支持 `defer``additionalContext` 会被忽略,`updatedInput` 会被记录 + 警告但不应用(见 [pre-tool-input-rewrite Agent Note](../../../.agents/notes/proposed/feature/2026-06-30-pre-tool-input-rewrite.md))。
- **`PostToolUse` 只支持部分功能:** 支持阻塞反馈与 JSON `additionalContext`,但不支持 `updatedToolOutput``updatedMCPToolOutput``tool_response` 会展平为文本。
- **`SubagentStart``SubagentStop` 只支持部分功能:** 两者均报告常量 `agent_type`,其值为 `general-purpose`,并在 Claude Code 报告父会话的位置使用 child 会话 id。Start 上下文是尽力而为,且只能到达实时同进程 childstop 只观测,无法阻塞 subagent 或向其提供上下文。Start 省略 `transcript_path`stop 还省略 `agent_transcript_path``last_assistant_message``background_tasks``session_crons`,并始终报告 `stop_hook_active: false`
- **`Stop` 只支持部分功能:** 阻塞会强制另一个模型轮次,但 `stop_hook_active` 始终为 `false`,会省略 `last_assistant_message``background_tasks``session_crons`,且未实现连续阻塞上限(`TODO(stop-loop-guard)`)。因此,无条件阻塞 hook 会在每个步骤中强制 continuation除非它自我限制。
- **通用 payload 与输出字段只支持部分功能:** 已映射事件会省略 Claude Code 原本会提供的 `prompt_id``transcript_path``permission_mode``effort``systemMessage` 会被记录 + 警告但不呈现;`{"continue": false}` 会被记录但不会停止运行;不会应用 `suppressOutput``stopReason``terminalSequence``TODO(hook-continue-false)`)。
- **Handler 与配置只支持部分功能:** 只运行 shell 形式 command handler。会跳过 `http``mcp_tool``prompt``agent` handler不遵循 `args``async``asyncRewake``shell``if``once``statusMessage` 等 command handler 选项。匹配 handler 串行运行且不去重,而 Claude Code 会并行运行并对相同 handler 去重。一个进程级 `configPath` 会在加载时解析一次;尚未实现 Claude Code 的分层项目、user、plugin 与 policy 发现和实时重新加载(`TODO(per-session-hook-config)`)。

View File

@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
README.md: 451d497a0e397bae4ecd5059d1e6c3d5f8d9705a
README.zh.md: a7148f8046ecb1183adbf41d11744778cf4a7dcb

View File

@@ -1,5 +1,7 @@
# @deepseek-ai/dsh-hooks-codex
English | [中文](README.zh.md)
A cordis plugin that runs the supported subset of a user's existing **Codex** hook config on the harness's canonical interception seams. The **Codex dialect** half of the hooks subsystem. The dialect-agnostic primitives come from [`@deepseek-ai/dsh-hook-protocol`](../hook-protocol/README.md); this bridge owns the Codex-shaped payloads, matcher mode, and decision mapping.
This bridge implements a deliberate subset of Codex's current hook protocol:

View File

@@ -0,0 +1,100 @@
# @deepseek-ai/dsh-hooks-codex
[English](README.md) | 中文
一个 cordis 插件,在 harness 的规范拦截 seam 上运行 user 现有 **Codex** hook 配置的受支持子集。它是 hooks 子系统的 **Codex 方言** 一半。方言无关原语来自 [`@deepseek-ai/dsh-hook-protocol`](../hook-protocol/README.md);该桥接拥有 Codex 形状 payload、matcher 模式和决策映射。
该桥接实现 Codex 当前 hook 协议的一个明确子集:
- **10 个 hook 点中的 5 个:** `PreToolUse``PostToolUse``SessionStart``UserPromptSubmit``Stop`
- **只使用正则 matcher**没有字面快速路径matcher 始终是未锚定正则)。
- **snake_case stdin payload**,携带 `turn_id``model` 额外字段,写入时**不带** 尾随换行符。
- **没有 Codex 插件 env 注入,也没有配置时 placeholder 替换**(命令仍会接收执行器环境,并通过其 shell 运行)。
- **没有工具前批准或改写路径**hook 可以阻塞,但桥接不会预批准或替换工具输入。
原生 cordis 插件可以完成此桥接的所有工作,并且功能更强;该桥接只是已映射 Codex 子集的兼容路径(见 [拦截 seam Agent Note](../../../.agents/notes/implemented/feature/2026-06-30-interception-seams.md))。
## 配置
```ts
import type { Config } from '@deepseek-ai/dsh-hooks-codex'
const config: Config = {
configPath: '/path/to/.codex/hooks.json', // required
model: 'deepseek-v4', // optional: stamped on every payload (Codex includes `model`)
defaultTimeoutMs: 600_000, // optional: per-hook timeout when a hook sets none
stderrSummaryMaxChars: 500, // optional: char cap on the hook/result event's persisted stderr summary
}
```
`cordis.yml` 中:
```yaml
- dsh-hooks-codex:
configPath: ./.codex/hooks.json
model: deepseek-v4
```
配置只在加载时解析**一次**。`configPath` 是**进程级** 配置:相对路径在加载时根据进程启动 cwd 解析,而非每会话解析(`TODO(per-session-hook-config)`)。读取/解析失败会被容纳(记录 + 不注册任何内容)。只运行同步 `type: 'command'` hook非 command 或 `async: true` hook 会被解析并跳过同时记录警告。hook 接受 `timeout``timeoutSec` alias两者都未设置时使用协议参考默认值 `DEFAULT_HOOK_TIMEOUT_MS`(来自 `dsh-hook-protocol`10 分钟)。五个桥接支持点之外的事件会在解析时丢弃。
hook 本身会在 agent 的会话工作区中运行:对 agent scope 点,桥接会将会话 `cwd` 作为 hook 进程工作目录,因此 hook 作用于 user 项目树,而非服务器启动目录。
## Hook 点 → seam Decision
| Codex hook | Harness seam | 映射 |
|---|---|---|
| `SessionStart` | `agent/session-start`emit | 纯 stdout hook 的输出 → additionalContext → `agent.inject()` |
| `UserPromptSubmit` | `agent/prompt-submit`waterfall | `block`(退出码 2`PromptDecision.block`;仅 additionalContext → 通过 `next()` 委托,再将一个单独标记源的上下文前置到下游 `additionalContexts` |
| `PreToolUse` | `tools/pre-execute`waterfall | `block``PreToolDecision.deny`(没有 `allow``ask` |
| `PostToolUse` | `tools/post-execute`waterfall | `block` → 带反馈的 `block`;仅 additionalContext → 通过 `next()` 委托再将一个单独标记源的上下文前置到下游决策Code Mode 将子调用上下文延迟到外层 `run_code` 结果 |
| `Stop` | `agent/turn-continuation`waterfall | 阻塞 Stop hook 使用原因作为下一步 steering强制 `continue` |
工具调用的 payload 携带真实 `tool_name`matcher 测试的相同值)与 Codex `tool_input: { command }` 形状(存在 `command` arg 时使用该值,否则使用 `''`。matcher subject 是工具名称(`PreToolUse``PostToolUse`)或会话源(`SessionStart``UserPromptSubmit``Stop` 忽略 matcher。
每个 agent scope stdin payload 都携带 `session_id``transcript_path`。可用时,桥接通过 `ctx.sessionPersistence.locate(session.header)` 解析后者,否则发送 `null`,保留 Codex `string | null` 形状。查找不会创建或 flush 产物,因此第一个轮次结束检查点之前路径可能不存在,也可能省略当前开启轮次。
`SessionStart` 是唯一的 emit 点它会脱离运行。每条运行链都会被跟踪dispose 桥接会中止仍在运行的 hook 进程,再排空 continuation然后 dispose resolve`createDetachedRuns`,位于 `dsh-hook-protocol`)。
## 上下文源
注入上下文携带显式 `{ kind: 'plugin', plugin: 'hooks-codex' }` 源(否则 `agent.inject()` 会将其默认为 `{ kind: 'user' }`)。
## 模型体验
### Hook 提供的上下文
#### 模型看到的内容
`SessionStart`、已接受提示词和工具后 hook 可以添加带源归因的上下文消息;阻塞 `Stop` hook 将其原因添加为下一步 steering。
#### Token 影响
hook 不返回上下文时没有成本。Hook 文本取决于数据,会被记录,并重发直到压缩。
#### KV Cache 影响
仅追加;新可见内容位于可复用请求前缀之后,不会使现有 KV-cache 配置项失效。
### 已阻塞提示词或工具结果
#### 模型看到的内容
提供方提供的原因逐字传递。缺失原因时,已阻塞提示词精确使用 `blocked by UserPromptSubmit hook`,已拒绝工具变为 `Error: blocked by PreToolUse hook`,已阻塞工具后反馈精确为 `blocked by PostToolUse hook`,阻塞 stop 则精确添加 steering `continue: blocked by Stop hook`。Codex `systemMessage` 不会呈现。
#### Token 影响
阻塞提示词会移除其请求 token拒绝或反馈会添加保留的回退或提供方文本强制 continuation 需要另一个完整请求。
#### KV Cache 影响
已阻塞提示词不发送请求,不会导致失效。拒绝、反馈与强制 continuation 上下文会追加在可复用前缀之后,不改写前缀。
## 已知限制与暂缓事项
- **不支持的 hook 事件Codex 当前 10 项中的 5 项):** `PermissionRequest``PreCompact``PostCompact``SubagentStart``SubagentStop`。这些事件的配置会在解析期间静默丢弃。比较基线是 Codex [官方 hook 参考](https://learn.chatgpt.com/docs/hooks)。
- **`SessionStart` 只支持部分功能:** 支持纯 stdout 与 JSON `additionalContext`,但 hook 脱离运行,因此上下文可能错过第一个请求(`TODO(session-start-gating)`)。
- **`UserPromptSubmit` 只支持部分功能:** 支持阻塞加纯 stdout 或 JSON 上下文,但不会强制执行通用 `systemMessage``{"continue": false}` 控制。
- **`PreToolUse` 只支持部分功能:** 支持阻塞,但会忽略 `additionalContext``permissionDecision: "allow"``updatedInput`。每个工具都表示为 `tool_input: { command }`,因此非 shell 工具参数不会如实公开给 hook。
- **`PostToolUse` 只支持部分功能:** 支持阻塞反馈与 JSON `additionalContext`,但不会强制执行 `{"continue": false}`,非 shell 工具参数会缩减为 `{ command }`,结构化工具输出会在 `tool_response` 中展平为文本。
- **`Stop` 只支持部分功能:** 阻塞会强制另一个模型轮次,但 `stop_hook_active` 始终为 `false``last_assistant_message` 始终为 `null`,且不会强制执行 `{"continue": false}`。因此,无条件阻塞 hook 会在每个步骤中强制 continuation除非它自我限制`TODO(stop-loop-guard)`)。
- **通用 payload 与输出字段只支持部分功能:** 每个已映射事件都报告 `transcript_path: null`、静态配置的 `model``permission_mode: "default"`,而非当前 Codex 运行时值。`systemMessage` 会被记录 + 警告但不呈现,`{"continue": false}` 会被记录但不会应用 Codex 事件特定停止行为(`TODO(hook-continue-false)`)。
- **配置加载与执行只支持部分功能:** 一个进程级 `configPath` 会在加载时解析;尚未实现 Codex 的活动 user、project、session、system/managed 和 plugin 分层、信任控制与内联 `config.toml` hook 形式(`TODO(per-session-hook-config)`)。只运行同步 `command` handler忽略 `statusMessage``commandWindows` 等当前元数据,匹配 handler 串行运行,而非使用 Codex 的并发启动语义。