
omo-codex 计划自动续跑引擎解析ulw-execute-continuation Stop Hook 的架构、状态机与安全边界【免费下载链接】oh-my-openagentOmO: Just type mass ulw keyword with your prompt. Now you are the master of graph engineering.项目地址: https://gitcode.com/gh_mirrors/oh/oh-my-openagent本篇文章围绕 omo-codex 插件组件ulw-execute-continuation代码包名code-yeongyu/codex-ulw-execute-continuation展开剖析它如何作为 Codex Stop 钩子在ulw-execute多轮计划执行中自动续跑当一轮回复结束后钩子读取 Boulder 状态与计划清单向 Codex 返回{decision:block}并注入续跑指令直到顶层复选框全部勾选、最终门禁通过。读完本文你将掌握该组件的完整运行链路——从.omo/boulder.json状态解析、计划清单统计、指令渲染到上下文压力抑制与外部阻塞逃生舱——并理解其全部静默返回场景与工程约束可直接用于排查续跑失效或自行实现同类长计划自动续跑钩子。组件定位ulw-execute 计划执行链路中的续跑引擎ulw-execute-continuation是 omo-codex 插件 内专为ulw-execute技能设计的续跑注入器continuation injector。其核心职责可以用一句话概括在 Codex 每一轮 Stop 事件触发时判断这个计划是否还没干完若没干完就返回 block 决策并附上一条最新的续跑指令驱动模型自动进入下一轮。该组件与ulw-execute技能形成配套关系技能负责创建计划、写入.omo/boulder.json并在session_ids中使用codex:前缀标识 Codex 会话本组件只负责读取这些状态并决定是否继续。完整的行为契约定义在 组件 README 与 AGENTS.md 中二者共同构成了仓库内最权威的功能说明。整个决策循环是Codex 结束一轮回复 │ ▼ Stop 钩子触发hooks.json 注册 │ ▼ 读取 .omo/boulder.json 活跃计划 transcript │ ▼ 有未完成顶层任务且无阻断信号 │ ┌────┴────┐ ▼ ▼ block指令 静默无输出 继续一轮 本轮正常收尾核心契约Stop 事件如何被翻译成继续干指令钩子的对外输出形式极其简洁——一行 JSON{decision:block,reason:directive}其中decision固定为blockreason则是把 directive.md 模板中的占位符替换为当前计划状态后得到的完整续跑指令。该模板在每次调用时由 src/directive.ts 通过readFileSync(new URL(../directive.md, import.meta.url))动态加载而不是编译期内联这保证了仓库维护者可以在不重新构建的情况下调整续跑指令。从 src/codex-hook.ts 的实现可以看到runStopHook的完整判定顺序任一条件命中即返回空字符串无输出export function runStopHook(input: unknown, fs: ReadonlyFileSystem): string { if (!isStopInput(input)) return ; if (input.stop_hook_active) return ; if (hasAllowedExternalBlockerMarker(input.last_assistant_message)) return ; if (transcriptHasContextPressureMarker(input.transcript_path, fs)) return ; const state readContinuationState(input.cwd, input.session_id); if (state null) return ; return JSON.stringify({ decision: block, reason: renderDirective(state, input.session_id), } satisfies StopHookOutput); }StopInput的字段定义在 src/types.tshook_event_name仅接受Stop、session_id、turn_id、transcript_path、cwd、model、permission_mode、stop_hook_active布尔以及可选的last_assistant_message。注意isStopInput的运行时类型守卫相当严格任何字段类型不匹配都会让钩子静默退出——这正是绝不让钩子因畸形输入阻塞一轮 Codex 回合约束的实现方式。状态判定boulder.json 读取与四种静默场景状态读取由 src/boulder-reader.ts 负责入口是readContinuationState(cwd, sessionId)见 #L37-L57。它从cwd/.omo/boulder.json读取 Boulder 状态按会话 ID 解析活跃工作项再读取对应计划文件并解析清单。readContinuationState在以下四种情况下返回null钩子随之静默boulder.json缺失或不可解析readBoulderState内所有JSON.parse与结构校验失败都会返回null而不会抛出异常导致钩子崩溃没有任何工作匹配当前会话getWorkForSession在works中按session_ids前缀codex:或opencode:裸 ID 视为opencode:匹配并从多个匹配项中挑选updated_at/started_at最新的一项仅在非 map 结构的旧式legacy mirrorBoulder 文件里才回退匹配顶层mirrorWork工作状态不可续跑只有active与paused可以继续completed与abandoned都会停止续跑——对应源码isContinuableStatus#L174-L176计划清单总数为 0checklist.total 0视为无可读的顶层清单直接返回null。该组件还支持任务级 git worktree当 Boulder 工作项带有worktree_path时resolveBoulderPlanPathForWork会把计划路径重定向到 worktree 内部若该路径真实存在保证计划文件本身也在工作树内编辑这一约束落地。真实状态的样例见测试夹具 boulder-single-codex-work.json它同时展示了新版 map 结构与旧式 mirror 结构的混合形态{ schema_version: 2, active_work_id: work_1, works: { work_1: { work_id: work_1, active_plan: /repo/.omo/plans/plan-with-unchecked.md, plan_name: launch-plan, status: active, started_at: 2026-05-28T00:00:00Z, session_ids: [codex:sess_abc] } }, active_plan: /repo/.omo/plans/plan-with-unchecked.md, plan_name: legacy-launch-plan, started_at: 2026-05-28T00:00:00Z, status: active, session_ids: [codex:sess_legacy] }计划清单解析只统计该数的复选框续跑与否最终取决于计划文件里还剩多少未完成任务。解析逻辑在 src/plan-checklist.ts对外暴露PlanChecklist类型与getPlanChecklist/parsePlanChecklist两个函数。PlanChecklist包含四个字段字段含义completed已勾选- [x]/- [X]的顶层任务数remaining未勾选- [ ]的顶层任务数totalcompleted remainingnextTaskLabel第一个未完成任务的标签全部完成时为null渲染为none (final gate pending)统计规则务必注意这是最容易踩坑的地方只有以下两个章节下的**列 0column-0**复选框会被计数## TODOs匹配- [x] 1. title这类带数字编号的行正则^- \[([ xX])\] ([1-9]\d*\. .)$## Final Verification Wave匹配- [x] F1. title这类Fnumber.前缀行正则^- \[([ xX])\] (F[1-9]\d*\. .)$大小写不敏感而### Acceptance Criteria、### Evidence、### Definition of Done等子章节下的嵌套复选框一律忽略。解析器还做了两件健壮性处理跳过 fenced code block解析过程中维护一个围栏状态机parseOpeningFence/isClosingFence代码块内的- [ ]不会被误计尊重章节边界^#{1,2}命中的#/##标题会切换当前章节状态保证## TODOs之外的复选框不进入统计结构化回退如果整个文档不存在上述两个结构化章节则回退到简单顶层清单模式统计全文列 0 的- [ ]/- [x]复选框SIMPLE_CHECKBOX_PATTERN。测试夹具 plan-scaffold.md 完整演示了这些边界嵌套复选框- [ ] Nested acceptance detail、无编号前缀的行- [ ] Missing numeric prefix must be ignored、## Acceptance Criteria下的复选框、以及## Final verification wave中错误编号的行- [ ] 3. Wrong final-wave label must be ignored都会被正确忽略。一个关键设计语义即使顶层清单全部勾选钩子仍然输出 block只要total 0且状态可续跑直到最终门禁Final gate运行且 Boulder 工作被标记为completed为止。也就是说计划全勾≠结束只有 Boulder 层面的completed状态才能真正终结续跑循环。指令渲染九个占位符如何注入当前计划状态当钩子决定续跑时renderDirectivesrc/codex-hook.ts会把下列九个占位符逐一替换进directive.md模板使用String.prototype.replaceAll占位符来源说明{{PLAN_NAME}}Boulder 工作项plan_name计划名称{{PLAN_PATH}}解析后的计划文件路径续跑时首先读取的对象{{BOULDER_PATH}}cwd/.omo/boulder.json状态文件的绝对路径{{REMAINING_COUNT}}checklist.remaining剩余顶层任务数{{TOTAL_COUNT}}checklist.total顶层任务总数{{NEXT_TASK_LABEL}}checklist.nextTaskLabel下一个未完成任务无剩余时渲染为none (final gate pending){{WORKTREE_BLOCK}}worktreePath无 worktree 时渲染为空串有 worktree 时渲染一行- Worktree: \ (all edits, tests, and commands run inside this directory){{LEDGER_PATH}}cwd/.omo/ulw-execute/ledger.jsonl证据账本路径续跑时先读计划再读账本二者是唯一事实来源{{SESSION_ID}}钩子 payload 中的会话 ID渲染为codex:session_id渲染完成后reason即为一整段结构化的续跑指令。以 directive.md 为例其内容约定包含五大部分State计划名、计划路径、Boulder 路径、剩余/总数、下一任务、worktree、账本路径与 session idWhat to do this turn先读计划与账本不信任模型对前几轮的记忆→ 剩余为 0 则执行 Final gate否则取## TODOs或## Final Verification Wave中第一个未勾选项 → 按ulw-execute技能分层LIGHT 默认、HEAVY 需完整逐标准流程不确定时取 HEAVY→ 拆解原子子任务并通过multi_agent_v1.spawn_agent并行派发除非存在具名阻塞依赖→ 每个子任务消息必须自包含TASK:命令式开头 DELIVERABLE/SCOPE/VERIFY→ 所有 Worker 的 DoneClaim 视为不可信输入必须独立 AdversarialVerify → 全部验证通过后用apply_patch把- [ ]改为- [x]并向账本追加task-completed行Hard constraints先有失败优先证明RED→GREEN→SURFACE才允许写生产代码禁止--dry-run作为证据TUI 视觉证据必须走真实 xterm.js web 终端node script/qa/web-terminal-visual-qa.mjs禁止tmux capture-pane禁止as any/ts-ignore/ts-expect-error对每个触发事实成立的对抗性类别都要探测清理收据cleanup receipt强制PR/分支类工作必须使用任务自有 worktreeFinal gate自行手动 QA 对照验收标准自审仅当用户明确要求严格/严谨/高精度评审时才派生一个门禁评审者每个子任务至多一次通过后才允许创建 PR / 合并 / 最终完成答复并在最终答复前把 Boulder 工作标记为completed--make-pr模式在 PR 打开后交接仅用户明确要求才合并--ship模式则持续工作到 PR 被 MERGED 并清理 worktreeStop conditions for THIS turn勾选一个顶层复选框后本轮即可结束钩子会在下一轮重新评估遇到外部阻塞写ulw-execute-blocked-external同一子任务连续 3 次失败修复后派生一次严格评审仍受阻则以外部阻塞标记交接触碰安全边界破坏性命令、密钥外泄、生产写入立即停止全部勾选且 Final gate 通过则输出ORCHESTRATION COMPLETE并结束。此外directive.md明确约定输出纪律只暴露状态变更子代理已派发、场景 PASS/FAIL 及工件路径、复选框已标记、证据已追加禁止打印Should I continue?等无意义内容——续跑由钩子保证计划与账本才是持久记录。三道安全阀防止无限续跑与错误续跑续跑机制最危险的两个场景是上下文耗尽后仍无限续跑和外部阻塞问题被反复重试。组件为此内置了三重防护均有对应测试用例锁定见 test/codex-hook.test.ts1.stop_hook_active防重入当 payload 中stop_hook_active为true即 Codex 已经因钩子 block 处于续跑激活状态时钩子直接返回空输出避免在一次续跑循环内反复触发测试用例#given stop hook is already active #when hook runs #then returns empty output。2. 上下文压力抑制context-pressure suppression钩子通过注入的ReadonlyFileSystem读取input.transcript_path指向的 transcript 文件若其中出现任何上下文压力标记context compacted、context_length_exceeded、context_too_large、codex ran out of room in the models context window、skill descriptions were shortened、your input exceeds the context window、long threads and multiple compactions等立即返回空输出。这是上下文窗口耗尽时防无限续跑循环的安全阀源码中由transcriptHasContextPressureMarkercodex-hook.ts实现——文件读取失败按无标记处理返回false同样保证钩子永不因 I/O 异常而阻塞回合。3. 外部阻塞逃生舱external blocker escape hatch当计划卡在无论重试多少次都无法解决的外部状态上如凭据缺失、硬件缺失、授权被撤销、第三方服务不可用directive.md指示模型把ulw-execute-blocked-external作为回答的整个第一行输出若 ultrawork 强制要求ULTRAWORK MODE ENABLED!开头则该标记独占第二行。钩子通过hasAllowedExternalBlockerMarker#L50-L61识别这两种结构形态并放行本轮结束把问题交还给用户处理而不是对不变的外部状态无限重试。值得强调的是这个逃生舱的判定有额外防御标记单独出现不足以放行标记之后还必须有至少一行非空内容即具体的阻塞原因与恢复条件。这防止了模型只是复述/回显了标记无论是来自 prompt 的引用还是不可信文本就绕过续跑护栏的情况。directive.md也同步要求标记行之后必须写明确切阻塞原因与恢复工作所需满足的可观察条件。对应的测试覆盖了三种典型形态阻塞标记单独首行ulw-execute-blocked-external 原因 恢复条件、ultrawork 开头 标记第二行、以及仅在回答中途提及标记此时继续续跑。钩子注册与 CLI 接口插件通过 hooks/hooks.json 把钩子注册到 Codex 的Stop事件{ hooks: { Stop: [ { hooks: [ { type: command, command: node \${PLUGIN_ROOT}/components/ulw-execute-continuation/dist/cli.js\ hook stop, timeout: 10, statusMessage: (OmO 5.0.0-beta.74) Checking Ulw-Execute Continuation } ] } ] } }可见钩子以 CLI 子命令形式执行node ${PLUGIN_ROOT}/components/ulw-execute-continuation/dist/cli.js hook stop超时 10 秒。构建产物输出到dist/构建脚本定义在 package.jsonbun build src/cli.ts --target node --format esm --outfile dist/cli.js。CLI 入口 src/cli.ts 的行为如下只接受hook stop与hook subagent-stop两个子命令其余用法输出Usage: omo-ulw-execute-continuation hook stop|subagent-stop并以退出码 1 结束从 stdin 读取 JSON payload空输入直接返回解析失败静默退出调用runStopHook仅在输出非空时写入 stdouthook subagent-stop是刻意保留的兼容性 no-op它未被注册到 hooks.json且永远不会注入根级计划。SubagentStop事件即使被 CLI 接受runStopHook也会因为isStopInput中hook_event_name Stop的守卫而返回空——对应测试#given active Boulder work and a SubagentStop event #when hook runs #then returns empty output。工程约束与代码规范一个可维护的钩子组件该组件自身的工程规范同样值得关注它们是钩子必须永不阻塞回合原则在开发流程层面的落地记录于 AGENTS.md技术栈Node 20、npm、TypeScript 6 strict mode、Biome 2lint/format、Vitest 4测试禁止项as any/as unknown、ts-ignore/ts-expect-error、enum、非空断言、默认导出vitest.config.ts因框架要求豁免——directive.md中禁止as any/ts-ignore的硬约束与之一致文件行数上限每个src/下 TypeScript 文件纯代码不超过 250 行超限前按职责拆分——从实际源码看boulder-reader.ts约 180 行、plan-checklist.ts约 170 行、codex-hook.ts约 100 行均严格遵守测试纪律使用 Vitest 嵌套describe命名遵循#given ... #when ... #then ...形式或行内// given、// when、// then注释禁用 Arrange-Act-Assert 注释风格夹具统一放在test/fixtures/。这一点在 codex-hook.test.ts 中得到充分体现如#given context-window pressure系列用例锁定了上下文压力抑制行为构建与打包package.json的files仅包含dist、directive.md、hooks、README.md、LICENSE、NOTICE确保发布包轻量且自包含。冒烟测试一条命令验证完整续跑链路README 提供了一个开箱即用的冒烟测试脚本可在不依赖真实 Codex 会话的情况下验证钩子行为TMP$(mktemp -d) mkdir -p $TMP/.omo/plans cat $TMP/.omo/plans/test.md EOF ## TODOs - [ ] Task one - [ ] Task two EOF cat $TMP/.omo/boulder.json EOF {schema_version:2,active_work_id:w1,works:{w1:{work_id:w1,active_plan:.omo/plans/test.md,plan_name:test,session_ids:[codex:smoke-session],status:active}}} EOF PAYLOAD{session_id:smoke-session,turn_id:t1,transcript_path:,cwd:$TMP,hook_event_name:Stop,model:gpt-5.5,permission_mode:default,stop_hook_active:false} npm run build echo $PAYLOAD | node dist/cli.js hook stop PAYLOAD_LOOP{session_id:smoke-session,turn_id:t1,transcript_path:,cwd:$TMP,hook_event_name:Stop,model:gpt-5.5,permission_mode:default,stop_hook_active:true} echo $PAYLOAD_LOOP | node dist/cli.js hook stop rm -rf $TMP预期结果第一个命令输出包含decision:block的 JSON计划中还有 2 个未完成任务续跑被触发第二个命令stop_hook_active: true不输出任何内容防重入生效把hook stop换成hook subagent-stop同样无输出兼容 no-op。这个脚本同时验证了 Boulder 解析、清单统计、指令渲染与防重入四层行为非常适合作为接入 CI 或本地排查的最小复现模板。隐私与安全特性最后值得一提的是该组件的隐私设计README 明确声明钩子只读取本地hook payload、.omo/boulder.json、活跃计划文件与随包分发的 directive 模板不发起任何网络调用这也是 AGENTS.md 中绝不让钩子发起网络调用约束的来源不存储任何遥测数据。组件以 MIT 协议开源见 LICENSE版本变更记录见 CHANGELOG.md。总结ulw-execute-continuation用不到 700 行 TypeScript含测试实现了一个只读状态 模板渲染的续跑决策器通过 Boulder 状态机、结构化清单解析、上下文压力抑制与外部阻塞逃生舱四条机制在自动续跑直到计划完成与绝不无限循环、绝不阻塞回合之间取得了可靠平衡。如果你正在为长任务型 Agent 设计自动续跑机制本组件的四层静默判定顺序与三把安全阀是可直接复用的参考范式。【免费下载链接】oh-my-openagentOmO: Just type mass ulw keyword with your prompt. Now you are the master of graph engineering.项目地址: https://gitcode.com/gh_mirrors/oh/oh-my-openagent创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考