OmX 显式终端停止模型(Explicit Terminal Stop Model)契约:统一工作流终结词汇与交接语义 OmX 显式终端停止模型Explicit Terminal Stop Model契约统一工作流终结词汇与交接语义【免费下载链接】oh-my-codexOmX - Oh My codeX: Your codex is not alone. Add hooks, agent teams, HUDs, and so much more.项目地址: https://gitcode.com/GitHub_Trending/oh/oh-my-codex导读本文围绕 OmXOh My codeX的 显式终端停止模型契约 展开该契约锁定了运行时runtime、原生/回退 Stop 处理器native/fallback Stop handlers、MCP 状态层以及用户可见交接文档所共同使用的规范终端生命周期词汇finished/blocked/failed/userinterlude/askuserQuestion。读完本文你将掌握五个规范终端结局的含义与续跑规则、遗留状态值finish、blocked_on_user、cancelled等的兼容映射策略、状态/MCP 字段的解读优先级以及活动工作流在终止时应当遵循的显式交接契约含被禁止的模糊措辞模式并了解这些规则在仓库源码中的实际落地位置。为什么需要显式终端停止模型在 OmX 的日常运行中一次工作流workflow的终止可能来自多个来源模型自己总结结束、用户主动打断Ctrl-C、interlude、原生 codex hook 的 Stop 事件、回退监视器fallback watcher的观察以及 MCP state server 上其他 Agent 对状态文件的读取。如果各条路径各自使用不同的词汇描述“这一轮结束了”就会出现严重的语义漂移同一个状态被不同组件分别称为finish、completed、done用户打断与“模型必须问一个阻塞问题”被混为一谈cancelled被当作面向用户的终结结果展示导致下游无法判断该不该自动续跑。契约文档开篇即点明其目的锁定活跃 OMX 工作流的规范终端停止词汇让运行时代码、原生/回退 Stop 处理器、MCP 状态与用户交接指引描述同一套“回合结束”语义。该契约状态为 “approved migration contract”覆盖 runtime、hooks、MCP state 与 prompt/文档四个层面。规范终端生命周期结局契约规定面向用户的显式停止模型只存在五种规范终端生命周期结局任何其他写法都不属于公开规范词汇结局含义续跑规则用户侧预期finished工作流成功完成不要自动续跑汇报完成证据与产出工件blocked因缺少某个非用户前置条件而无法继续推进在阻塞条件改变前不要自动续跑汇报阻塞点、其重要性以及所需的交接failed工作流或验证失败在失败被处理前不要自动续跑汇报失败证据、影响与建议的恢复方式userinterlude用户有意中断或暂停本次运行除非用户显式重新启动否则不要自动续跑汇报停止是用户发起的而非模型发起的askuserQuestionOmX 必须在安全继续前向用户提出一个阻塞性问题在问题得到回答前不要自动续跑提出一个具体的阻塞性问题并记录问题元数据这五种结局共同构成一个硬性原则除finished外其余四种结局在条件满足前都禁止自动续跑Do not auto-continue从而避免模型在用户未确认的情况下擅自推进。askuserQuestion与userinterlude的刻意区分契约特别强调这两个结局虽然都表现为“停下来等用户”但语义截然相反askuserQuestion是模型发起的通常应依托omx question或等价的机器可读问题元数据如question_enforcement/obligation_iduserinterlude是用户发起的中断/停止意图。这一区分在 src/question/autopilot-wait.ts 中有直接体现当 autopilot 监督的 deep-interview 子流程需要向用户提问时会写入current_phase: waiting-for-user、run_outcome: blocked_on_user、lifecycle_outcome: askuserQuestion并携带obligation_id与source: omx-question的等待记录只有满足status waiting_for_user source omx-question obligation_id非空时才被认定为待回答的阻塞问题。反观userinterlude则是纯用户侧意图不应带任何omx question元数据。源码中的规范词汇实现规范词汇并非只停留在文档层面它在 src/runtime/run-outcome.ts 中被定义为一等类型与常量export const TERMINAL_RUN_OUTCOMES [finish, blocked_on_user, failed, cancelled] as const; export const NON_TERMINAL_RUN_OUTCOMES [progress, continue] as const; export const TERMINAL_LIFECYCLE_OUTCOMES [finished, blocked, failed, userinterlude, askuserQuestion] as const; export type TerminalLifecycleOutcome (typeof TERMINAL_LIFECYCLE_OUTCOMES)[number];注意这里存在两套词汇遗留的 run outcome 词汇finish/blocked_on_user/failed/cancelled外加非终端的progress/continue与规范的生命周期词汇上面五种。src/runtime/terminal-lifecycle.ts 作为外层封装提供了normalizeTerminalLifecycleOutcome、inferTerminalLifecycleOutcome、preferredRunOutcomeForLifecycleOutcome等入口供运行时与 hooks 直接使用。运行时主循环 src/runtime/run-loop.ts 用classifyRunOutcomeisTerminalRunOutcome判断每一轮迭代是否达到终端状态达到即返回RunLoopTerminalResult含iteration、outcome、state与完整historyshouldContinueRun则依据状态快照决定是否应该继续推进——只要快照判定为 terminal就不再继续。这正是“禁止自动续跑”原则在代码层面的落实。遗留兼容规则与映射迁移期间持久化状态中仍可能出现遗留值。契约明确遗留值只是兼容输入不是公开规范词汇。规范化的完整映射如下遗留值规范解读finish、complete、completed、done归一化为finishedblocked_on_user仅兼容的用户等待信号当问题元数据能证明 OmX 确实提出了阻塞问题时映射为askuserQuestion否则按上下文映射为userinterlude/ 用户等待兼容语义cancelled、canceled、abort、aborted仅作内部遗留/管理性停止兼容不得作为面向用户的规范生命周期结局呈现cancelled策略cancelled依旧对遗留管理状态、拆除teardown或向后兼容读取有效但在显式停止模型中不是面向用户的规范终端生命周期结局。文档、提示词与运行时摘要应优先使用finished/blocked/failed/userinterlude/askuserQuestion五种之一。实现层面src/runtime/run-outcome.ts 用两张别名表落地这套映射RUN_OUTCOME_ALIASESfinished→finish、blocked→blocked_on_user、error→failed、canceled→cancelled等TERMINAL_LIFECYCLE_OUTCOME_ALIASEScomplete→finished、cancelled→userinterlude、interrupted→userinterlude、question→askuserQuestion等。值得注意的一个实现决策在生命周期词汇维度cancelled/cancel/aborted会被归一化为userinterlude见TERMINAL_LIFECYCLE_OUTCOME_ALIASES中cancelled: userinterlude同时在terminal-lifecycle.ts的inferTerminalLifecycleOutcome中遗留run_outcome cancelled也会被映射为userinterlude并附带警告normalized legacy run outcome cancelled - userinterlude。也就是说遗留取消类状态在面向用户时被重新解释为用户侧中断而不是作为独立的规范结局暴露。契约测试 src/hooks/tests/explicit-terminal-stop-model-docs-contract.test.ts 专门断言文档中保留了这些兼容表述并明确cancelled“不是规范的用户可见终端生命周期结局”。状态 / MCP 字段解读优先级契约规定了终端生命周期元数据的解读顺序专用的规范生命周期字段如lifecycle_outcome遗留的run_outcome兼容数据从current_phase、问题元数据及其他持久化上下文做的回退推断fallback inference。配套要点current_phase与生命周期结局相关但不等同——工作流可以继续使用遗留阶段名同时暴露规范的lifecycle_outcome若规范生命周期字段与遗留run_outcome同时存在规范字段优先run_outcome在迁移期间仅作为兼容读写面不是长期公开契约。这一优先级在源码中被精确复刻。src/runtime/terminal-lifecycle.ts 的inferTerminalLifecycleOutcome按序检查lifecycle_outcome/terminal_outcome显式规范字段、run_outcome遗留兼容字段、current_phase回退推断最后才参考completed_at、active等上下文。src/runtime/run-outcome.ts 的applyRunOutcomeContract则负责“规范化 收敛”若规范字段存在则用compatibilityRunOutcomeFromTerminalLifecycleOutcome反推出兼容的run_outcome并回写同时删除遗留的terminal_outcome别名字段若判定为 terminal则强制activefalse并写入completed_at。MCP 侧src/mcp/state-server.ts 的状态 schema 同时暴露run_outcome、lifecycle_outcome与terminal_outcome三个字段其中terminal_outcome明确标注为 “Legacy alias for lifecycle_outcome; canonical writes should prefer lifecycle_outcome”。而运行状态的持久化载体是run-state.json见 src/runtime/run-state.ts 的RunState接口version: 1、mode、active、outcome、lifecycle_outcome、completed_at等buildRunState同样遵循“先显式lifecycle_outcome、再推断、再回退到既有值”的顺序。一个关于blocked_on_user的实现细节文档规定blocked_on_user在有问题时映射为askuserQuestion、否则按上下文映射。实现上src/runtime/run-outcome.ts 为此提供了可配置策略blockedOnUserStrategy?: blocked | askuserQuestion | userinterludeterminal-lifecycle.ts的对外封装默认取blocked而inferTerminalLifecycleOutcome在includeQuestionEnforcement: true时若状态中存在question_enforcement且obligation_id非空、status pending会自动将策略切换为askuserQuestionhasPendingQuestionEnforcement判定。这就把文档中“按上下文判断”的规则落成了可执行的判定逻辑有真实待回答问题元数据 →askuserQuestion否则归入blocked或由调用方指定的策略。Stop / Watcher 解释规则契约要求停止读取方Stop readers、原生 hooks 与回退监视器优先依赖显式生命周期元数据而非助手输出中的散文式启发式判断。迁移期间它们应当优先尊重规范的finished、blocked、failed、userinterlude、askuserQuestion元数据继续尊重遗留的blocked_on_user作为“抑制续跑”的兼容信号避免把可选的助手散文当作生命周期状态的语义所有者在翻译为用户可见的生命周期摘要时将cancelled仅保留为内部遗留值。在仓库中这一规则的实际消费者包括插件侧的 plugins/oh-my-codex/hooks/codex-native-hook.mjs原生 Stop 处理器以及 src/scripts/notify-fallback-watcher.ts回退监视器二者都以规范化后的生命周期字段为准判断是否停止或抑制续跑而不是解析助手文本中的 “I will now…” 之类的措辞。活动工作流终端交接契约当一个活跃工作流产生终端用户可见消息时交接必须是显式且结构化的。终端摘要必须包含以下四要素Outcome结局—— 一个明确的生命周期标签Evidence证据—— 具体的验证输出、失败证据或缺失的依赖/问题Artifacts / State工件/状态—— 相关时列出变更的文件、保存的工件或记录的问题标识符Handoff交接—— 明确的下一个所有者或所需回答不得使用征求许可式的模棱两可措辞。被禁止的终端措辞模式活动工作流的终端交接不得以可选跟进式的软化语收尾例如If you want, I can ...If youd like, I can ...Would you like me to continue?这些短语会让生命周期状态变得模糊。终端结局本身就应该已经说明运行是完成、失败、阻塞、进入用户插曲还是正在等待一个必需的用户问题——再加上“您想让我继续吗”会制造二义性到底是finished还是等待用户重启契约测试同样对此进行了断言见 src/hooks/tests/explicit-terminal-stop-model-docs-contract.test.ts 的 “forbids optional terminal handoff softeners” 用例确保文档层面的禁令不会被后续改动稀释。非目标Non-goals契约明确声明其边界它不要求立刻重命名每一个遗留内部阶段名。它只要求每个已迁移的表面都能暴露并优先使用上述规范终端生命周期概念。也就是说current_phase继续沿用内部阶段名是允许的只要同时携带规范化的lifecycle_outcome字段即可——这与“状态/MCP 优先级”一节中“related but not identical”的表述完全一致。契约的守护文档与代码的双向校验该契约的价值还在于它被纳入了自动化测试。src/hooks/tests/explicit-terminal-stop-model-docs-contract.test.ts 直接加载契约文档并断言文档包含五个规范结局finished/blocked/failed/userinterlude/askuserQuestioncancelled被保留为内部值且明确声明“不是规范结局”blocked_on_user被保留为兼容值userinterlude与askuserQuestion的区分表述、omx question的后盾要求被记录在案禁止的终端软化语三种被明文写出“规范字段优先”的优先级规则被文档化。这意味着文档即契约——任何后续改动若删掉了这些关键条款测试会直接失败。运行时侧则有 src/runtime/tests/run-state.test.ts、src/runtime/tests/run-outcome.test.ts 与状态层的 src/state/tests/workflow-transition.test.ts 等用例从实现侧验证规范化、推断与优先级逻辑。小结显式终端停止模型为 OmX 的多组件协作提供了统一的“回合结束”语义运行时用runUntilTerminal严格执行“非终端不续跑”状态层用lifecycle_outcome承载规范结局并回填兼容的run_outcomehooks 与监视器依据显式元数据而非散文判定停止活动工作流的用户交接则遵循“结局 证据 工件 交接”的结构化四要素、杜绝软化语。对于构建在 OmX 之上的 Agent 工作流开发者而言只需记住一条主线写lifecycle_outcome读规范五结局把cancelled留在内部把blocked_on_user当作兼容信号用omx question元数据区分“需要问”与“用户打断”。若需进一步查阅契约全文见 docs/contracts/explicit-terminal-stop-model.md相关实现与状态模型可对照 src/runtime/run-outcome.ts、src/runtime/terminal-lifecycle.ts、src/runtime/run-state.ts 与 docs/STATE_MODEL.md。【免费下载链接】oh-my-codexOmX - Oh My codeX: Your codex is not alone. Add hooks, agent teams, HUDs, and so much more.项目地址: https://gitcode.com/GitHub_Trending/oh/oh-my-codex创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考