Codex 原生 Hook 集成:生命周期事件与插件布局
Codex 原生 Hook 集成:生命周期事件与插件布局
学习目标
- 理解 OMX 的 Codex 原生 Hook 注册和调用链
- 掌握 7 个生命周期事件的用途和 OMX 的处理逻辑
- 了解 Launcher shim 的 stdin 读取与 bounded JSON 解析
- 理解插件布局与回退路径
前置知识
本章聚焦 Oh-My-Codex 的具体工程实践,无需前置阅读。
项目实践
插件布局
plugins/oh-my-codex/├── .codex-plugin/│ └── plugin.json # 插件元数据└── hooks/ ├── hooks.json # Hook 注册表 └── codex-native-hook.mjs # Launcher shimplugin.json:
{ "name": "oh-my-codex", "version": "0.18.9", "description": "Multi-agent orchestration layer for OpenAI Codex CLI"}hooks.json(注册 7 个命令 Hook):
{ "hooks": [ { "event": "SessionStart", "command": "node codex-native-hook.mjs" }, { "event": "PreToolUse", "command": "node codex-native-hook.mjs" }, { "event": "PostToolUse", "command": "node codex-native-hook.mjs" }, { "event": "UserPromptSubmit", "command": "node codex-native-hook.mjs" }, { "event": "PreCompact", "command": "node codex-native-hook.mjs" }, { "event": "PostCompact", "command": "node codex-native-hook.mjs" }, { "event": "Stop", "command": "node codex-native-hook.mjs" } ]}Launcher Shim
伪代码(codex-native-hook.mjs):// 从 stdin 读取 bounded JSON(最大 1MB)stdin = readStdin(maxSize: 1 * 1024 * 1024)
if stdin.length > 1MB: // 超大 stdin 保护 if hasActiveAutopilotWorkflow(): // 在 autopilot 工作流需要继续时,发出继续信号 emitContinueSignal() else: emitNoOp() exit
// 解析 Hook 事件payload = parseJSON(stdin)hookEventName = extractTopLevelStringField(payload, "hook_event_name")
// 委托给 omx CLIspawn("omx", ["codex-native-hook", hookEventName], { stdin: stdin, stdout: "inherit", stderr: "inherit"})
// Stop Hook 特殊处理if hookEventName == "Stop" and childExitCode != 0: // 子进程无输出或异常退出时,阻止会话终止 emitBlockDecision()为什么有 1MB 限制:Codex 的 Hook payload 可能包含大量上下文(如完整的工具使用历史)。OMX 只关心 hook_event_name 和关键字段,不需要完整 payload。
Hook 事件映射矩阵
| 表面 | 原生 Hook | 状态 |
|---|---|---|
| session-start | SessionStart | native |
| keyword-detector | UserPromptSubmit | native |
| ultragoal steering | UserPromptSubmit | native |
| pre-tool-use(仅 Bash) | PreToolUse | native-partial |
| post-tool-use(仅 Bash) | PostToolUse | native-partial |
| stop/continuation | Stop | native-partial |
| ask-user-question | none | runtime-fallback |
| session-end / session-idle | none | runtime-fallback |
各 Hook 的处理逻辑
SessionStart:
- 发送 session-start 生命周期通知
- 分派原生 Hook 事件
- 标签 tmux 会话
- 写入会话绑定信息
UserPromptSubmit:
- 关键词检测引擎运行(显式
$skill→ 意图门控 → 隐式匹配 → 延续检测) - 技能激活状态持久化
- 模式状态文件初始化
- 深度面试输入锁管理
- Ralplan 门控评估
- 任务规模过滤
- Ultragoal steering 指令解析
PreToolUse / PostToolUse(仅 Bash):
- Bash 命令前置/后置处理
- 指标追踪(turn 计数、token 用量)
Stop:
- 检查活跃工作流状态(ralph、autopilot、ultrawork、ultraqa、team、ralplan、deep-interview)
- 如果有活跃工作流,阻止会话终止
- 否则允许正常终止
回退路径
对于旧版 Codex(不支持 plugin_hooks),omx setup 写入 .codex/hooks.json:
{ "hooks": [ { "event": "SessionStart", "command": "node ~/.codex/skills/oh-my-codex/hooks/codex-native-hook.mjs" }, ... ], "features": { "hooks": true }}并在 .codex/config.toml 中设置 [features].hooks = true。
回退观察者
当原生 Hook 不可用时,OMX 使用轮询式回退观察者(src/scripts/notify-fallback-watcher.ts):
伪代码:function runFallbackWatcher(): while true: // 监控状态文件变更 newState = readStateFiles() if newState != prevState: // 重新执行自动推进、团队分派、领导者通知逻辑 handleStateChange(newState) prevState = newState
// 检查面板就绪性 checkPaneReadiness()
// PID 活跃性检查 checkPidsAlive()
sleep(1s)通知调度器
伪代码(notify-dispatcher.ts):function runNotifyDispatcher(event): // 同 turn 合并(最小 10 秒间隔) if lastNotifyTime and now() - lastNotifyTime < 10s: if isSameTurn(event) or isSameSession(event): return // 去重
// 陈旧事件过滤(最大 5 分钟年龄) if now() - event.timestamp > 5 * 60s: return // 跳过陈旧事件
// 先运行用户的通知命令 if userNotifyCommand: run(userNotifyCommand, event)
// 再运行 OMX 的通知 Hook run("omx notify-hook", event)
lastNotifyTime = now()问题与规避
| 问题 | 后果 | OMX 的规避策略 |
|---|---|---|
| Hook payload 超过 1MB | JSON 解析失败 | 1MB 限制 + autopilot 继续检查 |
| Stop Hook 子进程异常退出 | 活跃工作流被静默终止 | 子进程失败时发出 block 决策 |
| 旧版 Codex 无 plugin_hooks | Hook 不触发 | 回退到 .codex/hooks.json |
| 回退观察者轮询间隔过大 | 事件处理延迟 | 1 秒轮询间隔平衡延迟与 CPU |
| 通知事件重复 | 自动推进重复注入 | 同 turn 合并 + 身份去重 |
设计取舍
为什么选择原生 Hook 而非纯运行时观察?
OMX 优先使用 Codex 原生 Hook,仅在不可用时回退到轮询观察。
优势:
- 实时性:Hook 在事件发生时立即触发,无轮询延迟
- 精确性:Hook 提供完整的 payload 上下文
- 低开销:Hook 是事件驱动的,不需要持续轮询
代价:
- 依赖 Codex 版本:旧版 Codex 不支持原生 Hook
- 调试困难:Hook 在 Codex 进程中触发,难以独立调试
- 回退复杂度:需要维护两套处理逻辑(Hook + 轮询)
替代方案:
- 纯轮询观察:不依赖 Codex 版本,但延迟高、CPU 开销大
- 文件监控(inotify/fsevents):比轮询高效,但跨平台兼容性差
参考来源
- Oh-My-Codex v0.18.9 源码 —
plugins/oh-my-codex/hooks/codex-native-hook.mjs(Launcher shim) - Oh-My-Codex v0.18.9 源码 —
plugins/oh-my-codex/hooks/hooks.json(Hook 注册) - Oh-My-Codex v0.18.9 源码 —
docs/codex-native-hooks.md(Hook 映射文档) - Oh-My-Codex v0.18.9 源码 —
src/scripts/notify-fallback-watcher.ts(回退观察者) - Oh-My-Codex v0.18.9 源码 —
src/scripts/notify-dispatcher.ts(通知调度器)