Launch Plan
【免费下载链接】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
TODOs
- Top-level
Acceptance Criteria
- Nested under acceptance criteria
- Nested done
Final Checklist
- Nested under final checklist
这份夹具刻意混合了多种元素:`## TODOs` 下有一个**列 0(column-0)的未勾选顶层条目**;`### Acceptance Criteria`(H3 子标题)下有两个**带缩进的嵌套复选框**(一未勾选一已勾选);`## Final Checklist` 下还有一个带缩进的嵌套复选框。它与同目录的 [plan-scaffold.md](https://link.gitcode.com/i/c03c938b65905b7678715d2b33411d21)、[plan-with-unchecked.md](https://link.gitcode.com/i/0d6b61cda1ec6e6d2cb6d6cbf0e6e023)、[plan-all-done.md](https://link.gitcode.com/i/821ccebbeb1d61246ff88241307e2724) 共同构成该组件的计划样本集,用来界定"延续钩子到底该统计什么"。 ## ulw-execute 延续钩子:为什么需要统计计划复选框 在 OmO 的 `ulw-execute` 技能([SKILL.md](https://link.gitcode.com/i/d14548773f3d76e3f593d4c99732bcca))中,Agent 按 launch plan 逐项推进任务。为了让多轮自动延续不依赖模型的记忆,组件 `codex-ulw-execute-continuation`([README.md](https://link.gitcode.com/i/1e8be3f4aa701b22d9bb15878b841a10))以 Codex Stop hook 的形式注册:每当一轮回复结束时,hook 读取钩子负载 `cwd` 下的 `.omo/boulder.json`,解析活跃工作项,检查活跃计划顶层清单,只要还有未勾选任务、或最终 review/debugging 关卡未完成,就输出: ```json {"decision":"block","reason":"<directive>"}reason每次调用都会从 directive.md 加载并填入当前计划状态——包括剩余/总数复选框、下一个未完成任务等。Codex 收到block后会把reason注入下一轮,从而形成"计划未完成 → 自动延续 → 勾选一项 → 再评估"的循环。清单统计的准确性因此直接决定自动延续循环的启停:统计口径一旦出错,要么提前放行导致任务半途而废,要么永远阻塞在已完成计划上。
计数规则:只有哪些复选框算数
组件 README 的 "Counted plan checkboxes" 一节给出了官方口径:
- 只有位于以下两个 section 下、且处于**第 0 列(无缩进)**的复选框才会被统计:
## TODOs## Final Verification Wave
- 嵌套在
### Acceptance Criteria、### Evidence、### Definition of Done下的复选框一律忽略。
这份规则同样写进了延续指令 directive.md 的第 2 条:
Pick the FIRST unchecked top-level checkbox in
## TODOsor## Final Verification Wave. Ignore nested checkboxes under Acceptance Criteria / Evidence / Definition of Done.
也就是说,### Acceptance Criteria这类子标题下的细节清单是验收证据而非顶层任务,勾选它们不应驱动延续循环;只有## TODOs与## Final Verification Wave中的顶层条目才是"活"的任务清单。这正是 plan-with-nested-checkboxes.md 这份夹具要表达的核心语义:嵌套复选框存在,但不应被计入。
解析器实现:plan-checklist.ts 逐层拆解
规则由 plan-checklist.ts 实现,核心入口是parsePlanChecklist(markdown)(L41-L78),返回{ completed, remaining, total, nextTaskLabel }。其关键正则如下:
| 常量 | 正则(L10-L16) | 作用 |
|---|---|---|
TODO_HEADING_PATTERN | ^##[ \t]+TODOs(?:[ \t]+#+)?[ \t]*$(忽略大小写) | 识别## TODOs统计区,进入 todo 区 |
FINAL_VERIFICATION_HEADING_PATTERN | ^##[ \t]+Final Verification Wave(?:[ \t]+#+)?[ \t]*$(忽略大小写) | 识别## Final Verification Wave统计区 |
SECTION_BOUNDARY_HEADING_PATTERN | ^#{1,2}(?:[ \t]+|$) | 只有 H1/H2 标题才会重置统计区,H3 及更深标题不构成边界 |
FENCE_PATTERN/isClosingFence | 反引号/波浪线围栏(L13、L149-L167) | 代码围栏内的内容全部跳过,防止误计示例代码中的- [ ] |
TODO_CHECKBOX_PATTERN | ^- \[([ xX])\] ([1-9]\d*\. .+)$ | todo 区只统计列 0、带1.编号前缀的条目 |
FINAL_WAVE_CHECKBOX_PATTERN | ^- \[([ xX])\] (F[1-9]\d*\. .+)$(忽略大小写) | Final Verification Wave 区只统计F1.编号格式条目 |
SIMPLE_CHECKBOX_PATTERN | ^[-*][ \t]*\[[ \t]*([xX]?)[ \t]*\][ \t]+(.+)$ | 无统计区结构时的回退解析,此时任意列 0 的- [ ]/* [ ]均计入 |
解析过程要点:
- 先由
hasStructuredSection(L80-L95)判断是否存在## TODOs或## Final Verification Wave;存在则走严格结构化解析,否则回退到parseSimpleChecklist(L97-L124)的简单模式。 - 结构化模式下,只有
#{1,2}开头的标题会更新当前 section(todo/final-wave/other);其余行若不在统计区直接跳过。 - 复选框的勾选判定为
marker.toLowerCase() === "x",因此[x]与[X]等价;nextTaskLabel取第一个未勾选项的标签,用于指令渲染。 - 任何解析异常(文件不存在、不可读等)都会由
getPlanChecklist(L30-L39)兜底为全 0 的空清单。
夹具逐行演练:plan-with-nested-checkboxes.md 的解析结果
把关联文档 plan-with-nested-checkboxes.md 逐行喂给parsePlanChecklist,按当前实现可得到如下推演:
| 行内容 | 解析动作 | 结果 |
|---|---|---|
# Launch Plan | H1,重置 section | section =other |
## TODOs | 命中TODO_HEADING_PATTERN | section =todo |
- [ ] Top-level | todo 区,但缺少1.编号前缀,不命中TODO_CHECKBOX_PATTERN | 不计入 |
### Acceptance Criteria | H3,不命中SECTION_BOUNDARY_HEADING_PATTERN(只匹配#{1,2}) | section 保持todo |
- [ ] Nested under acceptance criteria | 带缩进,非列 0,不命中结构化复选框正则 | 不计入 |
- [x] Nested done | 带缩进 + 无编号 | 不计入 |
## Final Checklist | H2 边界,但不属于 TODO/Final Wave | section =other |
- [ ] Nested under final checklist | section 为other | 不计入 |
最终得到{ completed: 0, remaining: 0, total: 0, nextTaskLabel: null }。也就是说,这份夹具中的任何复选框都不会被延续钩子统计:顶层条目缺编号前缀,嵌套条目要么带缩进、要么挂在非统计 section 下。这正是 README 中"计划无可读顶层清单(plan has no readable top-level checklist)时钩子静默"的样本。
对比同目录 plan-scaffold.md(被 codex-hook.test.ts 直接引用)即可看出"合规写法":- [ ] 1. Implement checklist parser parity带编号且位于列 0 才被计入,而- [ ] 3. Missing numeric prefix must be ignored、- [ ] Outside tracked sections must be ignored分别因缺编号、位于## Acceptance Criteria(非统计区)而被忽略,- [ ] F1. Exercise the Codex Stop surface则以F1.前缀进入 Final Wave 统计。
清单消费链路:从 boulder.json 到 block 决策
计数结果被 boulder-reader.ts 与 codex-hook.ts 两级消费:
1.readContinuationState(L37-L57)——决定"是否存在可延续的工作":
- 读取
<cwd>/.omo/boulder.json(schema_version 2,含works映射表);解析失败直接返回null。 - 通过 session 归属匹配活跃工作:
session_ids中的 id 统一归一化处理,钩子自身 session 以codex:前缀匹配(L35、L163-L166);裸 id 会被当作旧的opencode:前缀,无法匹配codex:会话,从而保证只延续自己的 Codex 会话。 - 只有
status为active或paused的工作可延续(L174-L176);completed/abandoned直接放行。 - 解析计划路径(若工作带
worktree_path,计划优先在任务所属 worktree 内解析,L131-L143),再调用getPlanChecklist;checklist.total === 0时返回null——这正是嵌套夹具走向静默的关键分支。 - 台账路径固定为
<cwd>/.omo/ulw-execute/ledger.jsonl。
2.runStopHook(L6-L17)——决定是否输出 block。以下任一情况输出为空(静默放行):
stop_hook_active为true(防循环,避免 hook 自己触发自己);- 上一条助手消息以外部阻塞标记
<ulw-execute-blocked-external>开头(或紧跟ULTRAWORK MODE ENABLED!首行、位于第二行),且标记后至少有一行说明阻塞原因与恢复条件(L50-L61)——防止仅回显标记就跳过延续守卫; - 转录文件中出现
context compacted、context_too_large、codex ran out of room in the model's context window等上下文压力标记(L63-L81); readContinuationState返回null(无工作 / 已完成 / 会话不匹配 /清单 total 为 0)。
否则输出{"decision":"block","reason":...}。reason由renderDirective(L19-L41)把 directive.md 中的占位符替换为实际状态:{{PLAN_NAME}}、{{PLAN_PATH}}、{{BOULDER_PATH}}、{{REMAINING_COUNT}}、{{TOTAL_COUNT}}、{{NEXT_TASK_LABEL}}(无剩余时渲染为none (final gate pending))、{{WORKTREE_BLOCK}}、{{LEDGER_PATH}}、{{SESSION_ID}}。注意一个细节:即使顶层清单全部勾选(remaining = 0),只要 total > 0,钩子仍会 block 一次,用于执行 Final gate(自测 + 对照验收标准评审),通过后才把 Boulder 工作标记为 completed——这一点在测试#given active codex work with zero remaining tasks #when hook runs #then blocks for the final gate中有明确断言。
CLI 入口 cli.ts 从 stdin 读取 JSON 负载,支持hook stop与hook subagent-stop两个子命令;SubagentStop事件在 codex-hook.ts 中被刻意失活(de-wired),只会静默返回。
实测验证:测试用例与 smoke test
单元测试:codex-hook.test.ts 以#given/#when/#then风格覆盖了全部静默分支与 block 分支,包括:stop_hook_active静默、SubagentStop 静默、两种外部阻塞标记形式放行、裸标记(无阻塞说明)仍 block、上下文压力静默、会话归属不匹配(opencode:前缀/裸 id)静默、已完成工作静默、畸形 boulder JSON 静默、畸形输入静默、以及"零剩余仍 block 等 Final gate"。这些用例与上文推演一一对应。
smoke test:组件 README 记录了一条可直接运行的端到端验证流程,它同时演示了 hook 的接线方式:
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"README 期望第一条命令输出含"decision":"block"的 JSON、防循环命令无输出。需要说明的是:当前 plan-checklist.ts 实现要求结构化 section 内的条目带编号前缀才会计数,因此编写真实计划时请使用- [ ] 1. Task one这类编号格式(与 plan-scaffold.md 一致),可据此观察 block 输出的实际差异。
钩子接线:hooks.json 将 Stop 事件映射到node "${PLUGIN_ROOT}/components/ulw-execute-continuation/dist/cli.js" hook stop,超时 10 秒,状态提示为 "(OmO 5.0.0-beta.74) Checking Ulw-Execute Continuation"——即每次 Codex 回合收尾时自动执行该检查。
如何写出能被正确统计的计划清单
综合以上实现,编写一份能被 ulw-execute 延续循环正确消费的 launch plan 时,建议遵循以下约束:
- 统计区只放两个:
## TODOs(普通任务)与## Final Verification Wave(终验任务)。 - 顶层条目必须位于列 0 并带编号:todo 区用
- [ ] 1. Task,终验区用- [ ] F1. Task;勾选后写- [x]或- [X]。 - 验收细节下沉到 H3 子区:把
### Acceptance Criteria、### Evidence、### Definition of Done等子区的条目写为带缩进的嵌套复选框(如两个空格),它们不会被统计,但作为验收证据供 Final gate 评审。 - 代码示例中的
- [ ]要放进围栏:解析器会跳过
【免费下载链接】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),仅供参考