跳转到内容

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 shim

plugin.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 CLI
spawn("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-startSessionStartnative
keyword-detectorUserPromptSubmitnative
ultragoal steeringUserPromptSubmitnative
pre-tool-use(仅 Bash)PreToolUsenative-partial
post-tool-use(仅 Bash)PostToolUsenative-partial
stop/continuationStopnative-partial
ask-user-questionnoneruntime-fallback
session-end / session-idlenoneruntime-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 超过 1MBJSON 解析失败1MB 限制 + autopilot 继续检查
Stop Hook 子进程异常退出活跃工作流被静默终止子进程失败时发出 block 决策
旧版 Codex 无 plugin_hooksHook 不触发回退到 .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(通知调度器)