oh-my-codex 0.18.0 → 0.18.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.lock +6 -6
- package/Cargo.toml +1 -1
- package/README.md +45 -19
- package/crates/omx-api/src/lib.rs +66 -9
- package/crates/omx-sparkshell/src/exec.rs +125 -3
- package/crates/omx-sparkshell/src/main.rs +126 -36
- package/crates/omx-sparkshell/tests/execution.rs +225 -1
- package/dist/agents/__tests__/definitions.test.js +14 -0
- package/dist/agents/__tests__/definitions.test.js.map +1 -1
- package/dist/agents/__tests__/native-config.test.js +19 -0
- package/dist/agents/__tests__/native-config.test.js.map +1 -1
- package/dist/agents/definitions.d.ts.map +1 -1
- package/dist/agents/definitions.js +30 -0
- package/dist/agents/definitions.js.map +1 -1
- package/dist/agents/native-config.d.ts +1 -0
- package/dist/agents/native-config.d.ts.map +1 -1
- package/dist/agents/native-config.js +4 -0
- package/dist/agents/native-config.js.map +1 -1
- package/dist/catalog/__tests__/generator.test.js +4 -0
- package/dist/catalog/__tests__/generator.test.js.map +1 -1
- package/dist/cli/__tests__/codex-plugin-layout.test.js +15 -7
- package/dist/cli/__tests__/codex-plugin-layout.test.js.map +1 -1
- package/dist/cli/__tests__/doctor-warning-copy.test.js +137 -8
- package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
- package/dist/cli/__tests__/index.test.js +203 -15
- package/dist/cli/__tests__/index.test.js.map +1 -1
- package/dist/cli/__tests__/install-docs-contract.test.d.ts +2 -0
- package/dist/cli/__tests__/install-docs-contract.test.d.ts.map +1 -0
- package/dist/cli/__tests__/install-docs-contract.test.js +55 -0
- package/dist/cli/__tests__/install-docs-contract.test.js.map +1 -0
- package/dist/cli/__tests__/launch-fallback.test.js +163 -0
- package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
- package/dist/cli/__tests__/question.test.js +29 -43
- package/dist/cli/__tests__/question.test.js.map +1 -1
- package/dist/cli/__tests__/setup-install-mode.test.js +94 -35
- package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
- package/dist/cli/__tests__/sparkshell-cli.test.js +20 -1
- package/dist/cli/__tests__/sparkshell-cli.test.js.map +1 -1
- package/dist/cli/__tests__/sparkshell-packaging.test.js +1 -0
- package/dist/cli/__tests__/sparkshell-packaging.test.js.map +1 -1
- package/dist/cli/__tests__/ultragoal.test.js +227 -4
- package/dist/cli/__tests__/ultragoal.test.js.map +1 -1
- package/dist/cli/__tests__/update.test.js +72 -1
- package/dist/cli/__tests__/update.test.js.map +1 -1
- package/dist/cli/codex-feature-probe.d.ts +5 -0
- package/dist/cli/codex-feature-probe.d.ts.map +1 -1
- package/dist/cli/codex-feature-probe.js +13 -7
- package/dist/cli/codex-feature-probe.js.map +1 -1
- package/dist/cli/doctor.d.ts +7 -0
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +297 -17
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/index.d.ts +9 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +465 -110
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/plugin-marketplace.d.ts +2 -0
- package/dist/cli/plugin-marketplace.d.ts.map +1 -1
- package/dist/cli/plugin-marketplace.js +15 -1
- package/dist/cli/plugin-marketplace.js.map +1 -1
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +71 -11
- package/dist/cli/setup.js.map +1 -1
- package/dist/cli/sparkshell.d.ts +7 -1
- package/dist/cli/sparkshell.d.ts.map +1 -1
- package/dist/cli/sparkshell.js +13 -3
- package/dist/cli/sparkshell.js.map +1 -1
- package/dist/cli/ultragoal.d.ts +1 -1
- package/dist/cli/ultragoal.d.ts.map +1 -1
- package/dist/cli/ultragoal.js +184 -10
- package/dist/cli/ultragoal.js.map +1 -1
- package/dist/cli/update.d.ts +2 -0
- package/dist/cli/update.d.ts.map +1 -1
- package/dist/cli/update.js +14 -3
- package/dist/cli/update.js.map +1 -1
- package/dist/compat/__tests__/doctor-contract.test.js +3 -0
- package/dist/compat/__tests__/doctor-contract.test.js.map +1 -1
- package/dist/config/__tests__/codex-feature-flags.test.js +11 -1
- package/dist/config/__tests__/codex-feature-flags.test.js.map +1 -1
- package/dist/config/__tests__/codex-hooks.test.js +22 -11
- package/dist/config/__tests__/codex-hooks.test.js.map +1 -1
- package/dist/config/__tests__/commit-lore-guard.test.d.ts +2 -0
- package/dist/config/__tests__/commit-lore-guard.test.d.ts.map +1 -0
- package/dist/config/__tests__/commit-lore-guard.test.js +20 -0
- package/dist/config/__tests__/commit-lore-guard.test.js.map +1 -0
- package/dist/config/codex-feature-flags.d.ts +4 -0
- package/dist/config/codex-feature-flags.d.ts.map +1 -1
- package/dist/config/codex-feature-flags.js +4 -0
- package/dist/config/codex-feature-flags.js.map +1 -1
- package/dist/config/codex-hooks.d.ts +1 -0
- package/dist/config/codex-hooks.d.ts.map +1 -1
- package/dist/config/codex-hooks.js +8 -10
- package/dist/config/codex-hooks.js.map +1 -1
- package/dist/config/commit-lore-guard.d.ts +1 -0
- package/dist/config/commit-lore-guard.d.ts.map +1 -1
- package/dist/config/commit-lore-guard.js +29 -3
- package/dist/config/commit-lore-guard.js.map +1 -1
- package/dist/config/generator.d.ts +17 -1
- package/dist/config/generator.d.ts.map +1 -1
- package/dist/config/generator.js +124 -11
- package/dist/config/generator.js.map +1 -1
- package/dist/goal-workflows/__tests__/codex-goal-snapshot.test.js +21 -0
- package/dist/goal-workflows/__tests__/codex-goal-snapshot.test.js.map +1 -1
- package/dist/goal-workflows/codex-goal-snapshot.d.ts +4 -0
- package/dist/goal-workflows/codex-goal-snapshot.d.ts.map +1 -1
- package/dist/goal-workflows/codex-goal-snapshot.js +50 -3
- package/dist/goal-workflows/codex-goal-snapshot.js.map +1 -1
- package/dist/hooks/__tests__/autopilot-skill-contract.test.js +27 -6
- package/dist/hooks/__tests__/autopilot-skill-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/consensus-execution-handoff.test.d.ts +1 -1
- package/dist/hooks/__tests__/consensus-execution-handoff.test.js +13 -11
- package/dist/hooks/__tests__/consensus-execution-handoff.test.js.map +1 -1
- package/dist/hooks/__tests__/deep-interview-contract.test.js +4 -3
- package/dist/hooks/__tests__/deep-interview-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/keyword-detector.test.js +173 -17
- package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-team-tmux-guard.test.js +33 -0
- package/dist/hooks/__tests__/notify-hook-team-tmux-guard.test.js.map +1 -1
- package/dist/hooks/__tests__/prometheus-strict-contract.test.d.ts +2 -0
- package/dist/hooks/__tests__/prometheus-strict-contract.test.d.ts.map +1 -0
- package/dist/hooks/__tests__/prometheus-strict-contract.test.js +320 -0
- package/dist/hooks/__tests__/prometheus-strict-contract.test.js.map +1 -0
- package/dist/hooks/__tests__/prompt-guidance-wave-two.test.js +12 -0
- package/dist/hooks/__tests__/prompt-guidance-wave-two.test.js.map +1 -1
- package/dist/hooks/__tests__/research-workflow-boundaries.test.d.ts +2 -0
- package/dist/hooks/__tests__/research-workflow-boundaries.test.d.ts.map +1 -0
- package/dist/hooks/__tests__/research-workflow-boundaries.test.js +35 -0
- package/dist/hooks/__tests__/research-workflow-boundaries.test.js.map +1 -0
- package/dist/hooks/extensibility/__tests__/dispatcher.test.js +26 -3
- package/dist/hooks/extensibility/__tests__/dispatcher.test.js.map +1 -1
- package/dist/hooks/extensibility/dispatcher.d.ts.map +1 -1
- package/dist/hooks/extensibility/dispatcher.js +29 -14
- package/dist/hooks/extensibility/dispatcher.js.map +1 -1
- package/dist/hooks/keyword-detector.d.ts +1 -1
- package/dist/hooks/keyword-detector.d.ts.map +1 -1
- package/dist/hooks/keyword-detector.js +36 -9
- package/dist/hooks/keyword-detector.js.map +1 -1
- package/dist/hooks/keyword-registry.d.ts.map +1 -1
- package/dist/hooks/keyword-registry.js +1 -0
- package/dist/hooks/keyword-registry.js.map +1 -1
- package/dist/hooks/prompt-guidance-contract.d.ts.map +1 -1
- package/dist/hooks/prompt-guidance-contract.js +14 -2
- package/dist/hooks/prompt-guidance-contract.js.map +1 -1
- package/dist/hud/__tests__/hud-tmux-injection.test.js +36 -8
- package/dist/hud/__tests__/hud-tmux-injection.test.js.map +1 -1
- package/dist/hud/__tests__/reconcile.test.js +122 -11
- package/dist/hud/__tests__/reconcile.test.js.map +1 -1
- package/dist/hud/__tests__/render.test.js +84 -0
- package/dist/hud/__tests__/render.test.js.map +1 -1
- package/dist/hud/__tests__/resource-leak-watch.test.d.ts +2 -0
- package/dist/hud/__tests__/resource-leak-watch.test.d.ts.map +1 -0
- package/dist/hud/__tests__/resource-leak-watch.test.js +28 -0
- package/dist/hud/__tests__/resource-leak-watch.test.js.map +1 -0
- package/dist/hud/__tests__/state.test.js +51 -1
- package/dist/hud/__tests__/state.test.js.map +1 -1
- package/dist/hud/__tests__/tmux.test.js +69 -23
- package/dist/hud/__tests__/tmux.test.js.map +1 -1
- package/dist/hud/index.d.ts +2 -2
- package/dist/hud/index.d.ts.map +1 -1
- package/dist/hud/index.js +17 -6
- package/dist/hud/index.js.map +1 -1
- package/dist/hud/reconcile.d.ts.map +1 -1
- package/dist/hud/reconcile.js +6 -3
- package/dist/hud/reconcile.js.map +1 -1
- package/dist/hud/render.d.ts.map +1 -1
- package/dist/hud/render.js +26 -0
- package/dist/hud/render.js.map +1 -1
- package/dist/hud/state.d.ts +2 -1
- package/dist/hud/state.d.ts.map +1 -1
- package/dist/hud/state.js +62 -1
- package/dist/hud/state.js.map +1 -1
- package/dist/hud/tmux.d.ts +10 -3
- package/dist/hud/tmux.d.ts.map +1 -1
- package/dist/hud/tmux.js +60 -11
- package/dist/hud/tmux.js.map +1 -1
- package/dist/hud/types.d.ts +22 -0
- package/dist/hud/types.d.ts.map +1 -1
- package/dist/hud/types.js.map +1 -1
- package/dist/notifications/__tests__/http-client-resource.test.d.ts +2 -0
- package/dist/notifications/__tests__/http-client-resource.test.d.ts.map +1 -0
- package/dist/notifications/__tests__/http-client-resource.test.js +41 -0
- package/dist/notifications/__tests__/http-client-resource.test.js.map +1 -0
- package/dist/notifications/__tests__/verbosity.test.js +20 -0
- package/dist/notifications/__tests__/verbosity.test.js.map +1 -1
- package/dist/notifications/config.d.ts.map +1 -1
- package/dist/notifications/config.js +6 -3
- package/dist/notifications/config.js.map +1 -1
- package/dist/notifications/http-client.d.ts.map +1 -1
- package/dist/notifications/http-client.js +78 -27
- package/dist/notifications/http-client.js.map +1 -1
- package/dist/notifications/types.d.ts +2 -0
- package/dist/notifications/types.d.ts.map +1 -1
- package/dist/openclaw/__tests__/dispatcher.test.js +49 -1
- package/dist/openclaw/__tests__/dispatcher.test.js.map +1 -1
- package/dist/openclaw/dispatcher.d.ts +7 -4
- package/dist/openclaw/dispatcher.d.ts.map +1 -1
- package/dist/openclaw/dispatcher.js +32 -69
- package/dist/openclaw/dispatcher.js.map +1 -1
- package/dist/pipeline/__tests__/orchestrator.test.js +128 -4
- package/dist/pipeline/__tests__/orchestrator.test.js.map +1 -1
- package/dist/pipeline/__tests__/stages.test.js +460 -9
- package/dist/pipeline/__tests__/stages.test.js.map +1 -1
- package/dist/pipeline/index.d.ts +8 -2
- package/dist/pipeline/index.d.ts.map +1 -1
- package/dist/pipeline/index.js +5 -2
- package/dist/pipeline/index.js.map +1 -1
- package/dist/pipeline/orchestrator.d.ts +5 -4
- package/dist/pipeline/orchestrator.d.ts.map +1 -1
- package/dist/pipeline/orchestrator.js +85 -17
- package/dist/pipeline/orchestrator.js.map +1 -1
- package/dist/pipeline/stages/code-review.d.ts +2 -2
- package/dist/pipeline/stages/code-review.d.ts.map +1 -1
- package/dist/pipeline/stages/code-review.js +5 -3
- package/dist/pipeline/stages/code-review.js.map +1 -1
- package/dist/pipeline/stages/deep-interview.d.ts +15 -0
- package/dist/pipeline/stages/deep-interview.d.ts.map +1 -0
- package/dist/pipeline/stages/deep-interview.js +32 -0
- package/dist/pipeline/stages/deep-interview.js.map +1 -0
- package/dist/pipeline/stages/ralph-verify.d.ts +5 -5
- package/dist/pipeline/stages/ralph-verify.d.ts.map +1 -1
- package/dist/pipeline/stages/ralph-verify.js +2 -2
- package/dist/pipeline/stages/ralph-verify.js.map +1 -1
- package/dist/pipeline/stages/ralplan.d.ts.map +1 -1
- package/dist/pipeline/stages/ralplan.js +41 -6
- package/dist/pipeline/stages/ralplan.js.map +1 -1
- package/dist/pipeline/stages/ultragoal.d.ts +19 -0
- package/dist/pipeline/stages/ultragoal.d.ts.map +1 -0
- package/dist/pipeline/stages/ultragoal.js +38 -0
- package/dist/pipeline/stages/ultragoal.js.map +1 -0
- package/dist/pipeline/stages/ultraqa.d.ts +30 -0
- package/dist/pipeline/stages/ultraqa.d.ts.map +1 -0
- package/dist/pipeline/stages/ultraqa.js +46 -0
- package/dist/pipeline/stages/ultraqa.js.map +1 -0
- package/dist/pipeline/types.d.ts +8 -6
- package/dist/pipeline/types.d.ts.map +1 -1
- package/dist/pipeline/types.js +2 -2
- package/dist/question/__tests__/ui.test.js +43 -10
- package/dist/question/__tests__/ui.test.js.map +1 -1
- package/dist/question/ui.d.ts +12 -0
- package/dist/question/ui.d.ts.map +1 -1
- package/dist/question/ui.js +83 -46
- package/dist/question/ui.js.map +1 -1
- package/dist/ralplan/__tests__/runtime.test.js +200 -10
- package/dist/ralplan/__tests__/runtime.test.js.map +1 -1
- package/dist/ralplan/consensus-gate.d.ts +23 -0
- package/dist/ralplan/consensus-gate.d.ts.map +1 -0
- package/dist/ralplan/consensus-gate.js +212 -0
- package/dist/ralplan/consensus-gate.js.map +1 -0
- package/dist/ralplan/runtime.d.ts +25 -0
- package/dist/ralplan/runtime.d.ts.map +1 -1
- package/dist/ralplan/runtime.js +144 -8
- package/dist/ralplan/runtime.js.map +1 -1
- package/dist/scripts/__tests__/codex-native-hook.test.js +1358 -79
- package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
- package/dist/scripts/__tests__/docs-site-contract.test.d.ts +2 -0
- package/dist/scripts/__tests__/docs-site-contract.test.d.ts.map +1 -0
- package/dist/scripts/__tests__/docs-site-contract.test.js +42 -0
- package/dist/scripts/__tests__/docs-site-contract.test.js.map +1 -0
- package/dist/scripts/__tests__/notify-dispatcher.test.js +115 -2
- package/dist/scripts/__tests__/notify-dispatcher.test.js.map +1 -1
- package/dist/scripts/__tests__/run-test-files.test.js +57 -0
- package/dist/scripts/__tests__/run-test-files.test.js.map +1 -1
- package/dist/scripts/__tests__/smoke-packed-install.test.js +23 -1
- package/dist/scripts/__tests__/smoke-packed-install.test.js.map +1 -1
- package/dist/scripts/__tests__/verify-native-agents.test.js +18 -3
- package/dist/scripts/__tests__/verify-native-agents.test.js.map +1 -1
- package/dist/scripts/cleanup-explore-harness.js +1 -0
- package/dist/scripts/cleanup-explore-harness.js.map +1 -1
- package/dist/scripts/codex-native-hook.d.ts.map +1 -1
- package/dist/scripts/codex-native-hook.js +372 -44
- package/dist/scripts/codex-native-hook.js.map +1 -1
- package/dist/scripts/codex-native-pre-post.d.ts.map +1 -1
- package/dist/scripts/codex-native-pre-post.js +9 -1
- package/dist/scripts/codex-native-pre-post.js.map +1 -1
- package/dist/scripts/notify-dispatcher.js +188 -4
- package/dist/scripts/notify-dispatcher.js.map +1 -1
- package/dist/scripts/notify-hook/process-runner.d.ts.map +1 -1
- package/dist/scripts/notify-hook/process-runner.js +39 -17
- package/dist/scripts/notify-hook/process-runner.js.map +1 -1
- package/dist/scripts/notify-hook/team-dispatch.d.ts.map +1 -1
- package/dist/scripts/notify-hook/team-dispatch.js +9 -5
- package/dist/scripts/notify-hook/team-dispatch.js.map +1 -1
- package/dist/scripts/notify-hook/team-tmux-guard.d.ts +1 -1
- package/dist/scripts/notify-hook/team-tmux-guard.d.ts.map +1 -1
- package/dist/scripts/notify-hook/team-tmux-guard.js +7 -1
- package/dist/scripts/notify-hook/team-tmux-guard.js.map +1 -1
- package/dist/scripts/run-test-files.js +13 -0
- package/dist/scripts/run-test-files.js.map +1 -1
- package/dist/scripts/smoke-packed-install.d.ts +3 -0
- package/dist/scripts/smoke-packed-install.d.ts.map +1 -1
- package/dist/scripts/smoke-packed-install.js +99 -1
- package/dist/scripts/smoke-packed-install.js.map +1 -1
- package/dist/scripts/sync-plugin-mirror.js +2 -2
- package/dist/scripts/sync-plugin-mirror.js.map +1 -1
- package/dist/scripts/verify-native-agents.js +2 -2
- package/dist/scripts/verify-native-agents.js.map +1 -1
- package/dist/sidecar/__tests__/resource-leak-watch.test.d.ts +2 -0
- package/dist/sidecar/__tests__/resource-leak-watch.test.d.ts.map +1 -0
- package/dist/sidecar/__tests__/resource-leak-watch.test.js +38 -0
- package/dist/sidecar/__tests__/resource-leak-watch.test.js.map +1 -0
- package/dist/sidecar/index.d.ts +1 -1
- package/dist/sidecar/index.d.ts.map +1 -1
- package/dist/sidecar/index.js +29 -12
- package/dist/sidecar/index.js.map +1 -1
- package/dist/state/__tests__/operations-ralph-phase.test.js +88 -1
- package/dist/state/__tests__/operations-ralph-phase.test.js.map +1 -1
- package/dist/state/__tests__/workflow-transition.test.js +6 -0
- package/dist/state/__tests__/workflow-transition.test.js.map +1 -1
- package/dist/state/operations.d.ts.map +1 -1
- package/dist/state/operations.js +11 -0
- package/dist/state/operations.js.map +1 -1
- package/dist/state/workflow-transition.d.ts +1 -1
- package/dist/state/workflow-transition.d.ts.map +1 -1
- package/dist/state/workflow-transition.js +7 -0
- package/dist/state/workflow-transition.js.map +1 -1
- package/dist/subagents/tracker.d.ts.map +1 -1
- package/dist/subagents/tracker.js +4 -3
- package/dist/subagents/tracker.js.map +1 -1
- package/dist/team/__tests__/runtime.test.js +36 -44
- package/dist/team/__tests__/runtime.test.js.map +1 -1
- package/dist/team/__tests__/tmux-session.test.js +163 -15
- package/dist/team/__tests__/tmux-session.test.js.map +1 -1
- package/dist/team/runtime.d.ts.map +1 -1
- package/dist/team/runtime.js +10 -20
- package/dist/team/runtime.js.map +1 -1
- package/dist/team/tmux-session.d.ts.map +1 -1
- package/dist/team/tmux-session.js +51 -21
- package/dist/team/tmux-session.js.map +1 -1
- package/dist/ultragoal/__tests__/artifacts.test.js +764 -10
- package/dist/ultragoal/__tests__/artifacts.test.js.map +1 -1
- package/dist/ultragoal/__tests__/docs-contract.test.js +57 -1
- package/dist/ultragoal/__tests__/docs-contract.test.js.map +1 -1
- package/dist/ultragoal/__tests__/steering-fixtures.d.ts +68 -0
- package/dist/ultragoal/__tests__/steering-fixtures.d.ts.map +1 -0
- package/dist/ultragoal/__tests__/steering-fixtures.js +259 -0
- package/dist/ultragoal/__tests__/steering-fixtures.js.map +1 -0
- package/dist/ultragoal/__tests__/steering-fixtures.test.d.ts +2 -0
- package/dist/ultragoal/__tests__/steering-fixtures.test.d.ts.map +1 -0
- package/dist/ultragoal/__tests__/steering-fixtures.test.js +65 -0
- package/dist/ultragoal/__tests__/steering-fixtures.test.js.map +1 -0
- package/dist/ultragoal/artifacts.d.ts +97 -2
- package/dist/ultragoal/artifacts.d.ts.map +1 -1
- package/dist/ultragoal/artifacts.js +837 -256
- package/dist/ultragoal/artifacts.js.map +1 -1
- package/dist/utils/__tests__/sleep-resource.test.d.ts +2 -0
- package/dist/utils/__tests__/sleep-resource.test.d.ts.map +1 -0
- package/dist/utils/__tests__/sleep-resource.test.js +39 -0
- package/dist/utils/__tests__/sleep-resource.test.js.map +1 -0
- package/dist/utils/sleep.d.ts.map +1 -1
- package/dist/utils/sleep.js +17 -6
- package/dist/utils/sleep.js.map +1 -1
- package/package.json +2 -1
- package/plugins/oh-my-codex/.codex-plugin/plugin.json +4 -3
- package/plugins/oh-my-codex/hooks/codex-native-hook.mjs +56 -0
- package/plugins/oh-my-codex/hooks/hooks.json +77 -0
- package/plugins/oh-my-codex/skills/autopilot/SKILL.md +92 -50
- package/plugins/oh-my-codex/skills/autoresearch/SKILL.md +4 -0
- package/plugins/oh-my-codex/skills/autoresearch-goal/SKILL.md +1 -1
- package/plugins/oh-my-codex/skills/best-practice-research/SKILL.md +1 -1
- package/plugins/oh-my-codex/skills/cancel/SKILL.md +2 -2
- package/plugins/oh-my-codex/skills/deep-interview/SKILL.md +8 -8
- package/plugins/oh-my-codex/skills/omx-setup/SKILL.md +1 -1
- package/plugins/oh-my-codex/skills/pipeline/SKILL.md +23 -12
- package/plugins/oh-my-codex/skills/plan/SKILL.md +8 -8
- package/plugins/oh-my-codex/skills/prometheus-strict/README.md +35 -0
- package/plugins/oh-my-codex/skills/prometheus-strict/SKILL.md +219 -0
- package/plugins/oh-my-codex/skills/ralph/SKILL.md +7 -0
- package/plugins/oh-my-codex/skills/ralplan/SKILL.md +22 -7
- package/plugins/oh-my-codex/skills/team/SKILL.md +1 -1
- package/plugins/oh-my-codex/skills/ultragoal/SKILL.md +38 -4
- package/plugins/oh-my-codex/skills/ultrawork/SKILL.md +1 -1
- package/prompts/planner.md +1 -1
- package/prompts/prometheus-strict-metis.md +274 -0
- package/prompts/prometheus-strict-momus.md +82 -0
- package/prompts/prometheus-strict-oracle.md +107 -0
- package/prompts/researcher.md +22 -3
- package/skills/autopilot/SKILL.md +92 -50
- package/skills/autoresearch/SKILL.md +4 -0
- package/skills/autoresearch-goal/SKILL.md +1 -1
- package/skills/best-practice-research/SKILL.md +1 -1
- package/skills/cancel/SKILL.md +2 -2
- package/skills/deep-interview/SKILL.md +8 -8
- package/skills/omx-setup/SKILL.md +1 -1
- package/skills/pipeline/SKILL.md +23 -12
- package/skills/plan/SKILL.md +8 -8
- package/skills/prometheus-strict/README.md +35 -0
- package/skills/prometheus-strict/SKILL.md +219 -0
- package/skills/ralph/SKILL.md +7 -0
- package/skills/ralplan/SKILL.md +22 -7
- package/skills/team/SKILL.md +1 -1
- package/skills/ultragoal/SKILL.md +38 -4
- package/skills/ultrawork/SKILL.md +1 -1
- package/src/scripts/__tests__/codex-native-hook.test.ts +1757 -210
- package/src/scripts/__tests__/docs-site-contract.test.ts +47 -0
- package/src/scripts/__tests__/notify-dispatcher.test.ts +132 -3
- package/src/scripts/__tests__/run-test-files.test.ts +67 -0
- package/src/scripts/__tests__/smoke-packed-install.test.ts +31 -0
- package/src/scripts/__tests__/verify-native-agents.test.ts +23 -3
- package/src/scripts/cleanup-explore-harness.ts +1 -0
- package/src/scripts/codex-native-hook.ts +393 -40
- package/src/scripts/codex-native-pre-post.ts +16 -1
- package/src/scripts/notify-dispatcher.ts +202 -4
- package/src/scripts/notify-hook/process-runner.ts +40 -16
- package/src/scripts/notify-hook/team-dispatch.ts +9 -5
- package/src/scripts/notify-hook/team-tmux-guard.ts +7 -0
- package/src/scripts/run-test-files.ts +13 -0
- package/src/scripts/smoke-packed-install.ts +105 -0
- package/src/scripts/sync-plugin-mirror.ts +3 -3
- package/src/scripts/verify-native-agents.ts +2 -2
- package/templates/catalog-manifest.json +22 -0
|
@@ -15,8 +15,11 @@ import { resetTriageConfigCache } from "../../hooks/triage-config.js";
|
|
|
15
15
|
import { executeStateOperation } from "../../state/operations.js";
|
|
16
16
|
import { OMX_TMUX_HUD_OWNER_ENV } from "../../hud/reconcile.js";
|
|
17
17
|
import { readAllState } from "../../hud/state.js";
|
|
18
|
+
import { renderHud } from "../../hud/render.js";
|
|
18
19
|
import { getLegacyWikiDir, serializePage, writePage } from "../../wiki/storage.js";
|
|
19
20
|
import { WIKI_SCHEMA_VERSION } from "../../wiki/types.js";
|
|
21
|
+
import { createUltragoalPlan, readUltragoalPlan } from "../../ultragoal/artifacts.js";
|
|
22
|
+
import { getBaseStateDir } from "../../state/paths.js";
|
|
20
23
|
function nativeHookScriptPath() {
|
|
21
24
|
return join(process.cwd(), "dist", "scripts", "codex-native-hook.js");
|
|
22
25
|
}
|
|
@@ -39,6 +42,38 @@ async function writeJson(path, value) {
|
|
|
39
42
|
await mkdir(dirname(path), { recursive: true }).catch(() => { });
|
|
40
43
|
await writeFile(path, JSON.stringify(value, null, 2));
|
|
41
44
|
}
|
|
45
|
+
async function withLoreGuardConfig(value, prefix, run) {
|
|
46
|
+
const cwd = await mkdtemp(join(tmpdir(), `omx-native-hook-pretool-git-commit-lore-${prefix}-`));
|
|
47
|
+
const codexHome = await mkdtemp(join(tmpdir(), `omx-native-hook-codex-home-lore-${prefix}-`));
|
|
48
|
+
const defaultHome = await mkdtemp(join(tmpdir(), `omx-native-hook-home-lore-${prefix}-`));
|
|
49
|
+
const originalGuard = process.env.OMX_LORE_COMMIT_GUARD;
|
|
50
|
+
const originalCodexHome = process.env.CODEX_HOME;
|
|
51
|
+
const originalHome = process.env.HOME;
|
|
52
|
+
try {
|
|
53
|
+
delete process.env.OMX_LORE_COMMIT_GUARD;
|
|
54
|
+
process.env.CODEX_HOME = codexHome;
|
|
55
|
+
process.env.HOME = defaultHome;
|
|
56
|
+
await writeFile(join(codexHome, "config.toml"), `[shell_environment_policy.set]\nOMX_LORE_COMMIT_GUARD = "${value}"\n`, "utf-8");
|
|
57
|
+
return await run(cwd);
|
|
58
|
+
}
|
|
59
|
+
finally {
|
|
60
|
+
if (originalGuard === undefined)
|
|
61
|
+
delete process.env.OMX_LORE_COMMIT_GUARD;
|
|
62
|
+
else
|
|
63
|
+
process.env.OMX_LORE_COMMIT_GUARD = originalGuard;
|
|
64
|
+
if (originalCodexHome === undefined)
|
|
65
|
+
delete process.env.CODEX_HOME;
|
|
66
|
+
else
|
|
67
|
+
process.env.CODEX_HOME = originalCodexHome;
|
|
68
|
+
if (originalHome === undefined)
|
|
69
|
+
delete process.env.HOME;
|
|
70
|
+
else
|
|
71
|
+
process.env.HOME = originalHome;
|
|
72
|
+
await rm(cwd, { recursive: true, force: true });
|
|
73
|
+
await rm(codexHome, { recursive: true, force: true });
|
|
74
|
+
await rm(defaultHome, { recursive: true, force: true });
|
|
75
|
+
}
|
|
76
|
+
}
|
|
42
77
|
function buildWorkerStopFakeTmux(tmuxLogPath, options = {}) {
|
|
43
78
|
return `#!/usr/bin/env bash
|
|
44
79
|
set -eu
|
|
@@ -189,7 +224,7 @@ describe("codex native hook config", () => {
|
|
|
189
224
|
assert.equal(sessionStart.matcher, "startup|resume|clear");
|
|
190
225
|
assert.equal(sessionStart.hooks?.[0]?.statusMessage, undefined);
|
|
191
226
|
const preToolUse = config.hooks.PreToolUse[0];
|
|
192
|
-
assert.equal(preToolUse.matcher,
|
|
227
|
+
assert.equal(preToolUse.matcher, undefined);
|
|
193
228
|
assert.match(String(preToolUse.hooks?.[0]?.command || ""), /codex-native-hook\.js"?$/);
|
|
194
229
|
assert.equal(preToolUse.hooks?.[0]?.statusMessage, undefined);
|
|
195
230
|
const postToolUse = config.hooks.PostToolUse[0];
|
|
@@ -567,7 +602,16 @@ describe("codex native hook dispatch", () => {
|
|
|
567
602
|
});
|
|
568
603
|
it("keeps subagent SessionStart from replacing the canonical leader session", async () => {
|
|
569
604
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-subagent-session-start-"));
|
|
570
|
-
|
|
605
|
+
const originalCodexHome = process.env.CODEX_HOME;
|
|
606
|
+
try {
|
|
607
|
+
process.env.CODEX_HOME = join(cwd, "codex-home");
|
|
608
|
+
await writeJson(join(process.env.CODEX_HOME, ".omx-config.json"), {
|
|
609
|
+
notifications: {
|
|
610
|
+
enabled: true,
|
|
611
|
+
verbosity: "session",
|
|
612
|
+
telegram: { enabled: true, botToken: "123:abc", chatId: "456" },
|
|
613
|
+
},
|
|
614
|
+
});
|
|
571
615
|
const stateDir = join(cwd, ".omx", "state");
|
|
572
616
|
const canonicalSessionId = "omx-leader-session";
|
|
573
617
|
const leaderNativeSessionId = "codex-leader-thread";
|
|
@@ -583,6 +627,13 @@ describe("codex native hook dispatch", () => {
|
|
|
583
627
|
iteration: 1,
|
|
584
628
|
max_iterations: 5,
|
|
585
629
|
});
|
|
630
|
+
await mkdir(join(cwd, ".omx", "hooks"), { recursive: true });
|
|
631
|
+
await writeFile(join(cwd, ".omx", "hooks", "record-lifecycle.mjs"), [
|
|
632
|
+
"import { appendFileSync } from 'node:fs';",
|
|
633
|
+
"export async function onHookEvent(event) {",
|
|
634
|
+
" appendFileSync('hook-events.jsonl', `${JSON.stringify({ event: event.event, context: event.context })}\\n`);",
|
|
635
|
+
"}",
|
|
636
|
+
].join("\n"));
|
|
586
637
|
const transcriptPath = join(cwd, "subagent-rollout.jsonl");
|
|
587
638
|
await writeFile(transcriptPath, `${JSON.stringify({
|
|
588
639
|
type: "session_meta",
|
|
@@ -616,6 +667,7 @@ describe("codex native hook dispatch", () => {
|
|
|
616
667
|
const leaderRalph = JSON.parse(await readFile(join(stateDir, "sessions", canonicalSessionId, "ralph-state.json"), "utf-8"));
|
|
617
668
|
assert.equal(leaderRalph.active, true);
|
|
618
669
|
assert.equal(leaderRalph.current_phase, "executing");
|
|
670
|
+
assert.equal(existsSync(join(cwd, "hook-events.jsonl")), false, "subagent SessionStart must not independently dispatch session-start hook notifications");
|
|
619
671
|
const tracking = JSON.parse(await readFile(join(stateDir, "subagent-tracking.json"), "utf-8"));
|
|
620
672
|
assert.equal(tracking.sessions?.[canonicalSessionId]?.leader_thread_id, leaderNativeSessionId);
|
|
621
673
|
assert.equal(tracking.sessions?.[canonicalSessionId]?.threads?.[childNativeSessionId]?.kind, "subagent");
|
|
@@ -623,8 +675,213 @@ describe("codex native hook dispatch", () => {
|
|
|
623
675
|
assert.equal(tracking.sessions?.[leaderNativeSessionId]?.leader_thread_id, leaderNativeSessionId);
|
|
624
676
|
assert.equal(tracking.sessions?.[leaderNativeSessionId]?.threads?.[childNativeSessionId]?.kind, "subagent");
|
|
625
677
|
assert.equal(tracking.sessions?.[leaderNativeSessionId]?.threads?.[childNativeSessionId]?.mode, "critic");
|
|
678
|
+
await dispatchCodexNativeHook({
|
|
679
|
+
hook_event_name: "Stop",
|
|
680
|
+
cwd,
|
|
681
|
+
session_id: childNativeSessionId,
|
|
682
|
+
thread_id: childNativeSessionId,
|
|
683
|
+
turn_id: "child-stop-turn",
|
|
684
|
+
}, { cwd });
|
|
685
|
+
assert.equal(existsSync(join(cwd, "hook-events.jsonl")), false, "subagent Stop must not independently dispatch stop hook notifications");
|
|
686
|
+
}
|
|
687
|
+
finally {
|
|
688
|
+
if (originalCodexHome === undefined) {
|
|
689
|
+
delete process.env.CODEX_HOME;
|
|
690
|
+
}
|
|
691
|
+
else {
|
|
692
|
+
process.env.CODEX_HOME = originalCodexHome;
|
|
693
|
+
}
|
|
694
|
+
await rm(cwd, { recursive: true, force: true });
|
|
695
|
+
}
|
|
696
|
+
});
|
|
697
|
+
it("suppresses child-agent SessionStart hook dispatch at minimal verbosity", async () => {
|
|
698
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-subagent-session-minimal-"));
|
|
699
|
+
const originalCodexHome = process.env.CODEX_HOME;
|
|
700
|
+
try {
|
|
701
|
+
process.env.CODEX_HOME = join(cwd, "codex-home");
|
|
702
|
+
await writeJson(join(process.env.CODEX_HOME, ".omx-config.json"), {
|
|
703
|
+
notifications: {
|
|
704
|
+
enabled: true,
|
|
705
|
+
verbosity: "minimal",
|
|
706
|
+
telegram: { enabled: true, botToken: "123:abc", chatId: "456" },
|
|
707
|
+
},
|
|
708
|
+
});
|
|
709
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
710
|
+
const canonicalSessionId = "omx-leader-session-minimal";
|
|
711
|
+
const leaderNativeSessionId = "codex-leader-thread-minimal";
|
|
712
|
+
const childNativeSessionId = "codex-child-thread-minimal";
|
|
713
|
+
await mkdir(join(stateDir, "sessions", canonicalSessionId), { recursive: true });
|
|
714
|
+
await writeSessionStart(cwd, canonicalSessionId, {
|
|
715
|
+
nativeSessionId: leaderNativeSessionId,
|
|
716
|
+
});
|
|
717
|
+
await mkdir(join(cwd, ".omx", "hooks"), { recursive: true });
|
|
718
|
+
await writeFile(join(cwd, ".omx", "hooks", "record-lifecycle.mjs"), [
|
|
719
|
+
"import { appendFileSync } from 'node:fs';",
|
|
720
|
+
"export async function onHookEvent(event) {",
|
|
721
|
+
" appendFileSync('hook-events.jsonl', `${JSON.stringify({ event: event.event })}\\n`);",
|
|
722
|
+
"}",
|
|
723
|
+
].join("\n"));
|
|
724
|
+
const transcriptPath = join(cwd, "minimal-subagent-rollout.jsonl");
|
|
725
|
+
await writeFile(transcriptPath, `${JSON.stringify({
|
|
726
|
+
type: "session_meta",
|
|
727
|
+
payload: {
|
|
728
|
+
id: childNativeSessionId,
|
|
729
|
+
source: {
|
|
730
|
+
subagent: {
|
|
731
|
+
thread_spawn: {
|
|
732
|
+
parent_thread_id: leaderNativeSessionId,
|
|
733
|
+
agent_role: "verifier",
|
|
734
|
+
},
|
|
735
|
+
},
|
|
736
|
+
},
|
|
737
|
+
},
|
|
738
|
+
})}\n`);
|
|
739
|
+
await dispatchCodexNativeHook({
|
|
740
|
+
hook_event_name: "SessionStart",
|
|
741
|
+
cwd,
|
|
742
|
+
session_id: childNativeSessionId,
|
|
743
|
+
transcript_path: transcriptPath,
|
|
744
|
+
}, { cwd, sessionOwnerPid: process.pid });
|
|
745
|
+
assert.equal(existsSync(join(cwd, "hook-events.jsonl")), false, "subagent SessionStart must be suppressed at minimal verbosity");
|
|
746
|
+
}
|
|
747
|
+
finally {
|
|
748
|
+
if (originalCodexHome === undefined) {
|
|
749
|
+
delete process.env.CODEX_HOME;
|
|
750
|
+
}
|
|
751
|
+
else {
|
|
752
|
+
process.env.CODEX_HOME = originalCodexHome;
|
|
753
|
+
}
|
|
754
|
+
await rm(cwd, { recursive: true, force: true });
|
|
755
|
+
}
|
|
756
|
+
});
|
|
757
|
+
it("allows explicit child-agent lifecycle hook dispatch when includeChildAgents is enabled", async () => {
|
|
758
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-subagent-session-include-"));
|
|
759
|
+
const originalCodexHome = process.env.CODEX_HOME;
|
|
760
|
+
try {
|
|
761
|
+
process.env.CODEX_HOME = join(cwd, "codex-home");
|
|
762
|
+
await writeJson(join(process.env.CODEX_HOME, ".omx-config.json"), {
|
|
763
|
+
notifications: {
|
|
764
|
+
enabled: true,
|
|
765
|
+
verbosity: "session",
|
|
766
|
+
includeChildAgents: true,
|
|
767
|
+
telegram: { enabled: true, botToken: "123:abc", chatId: "456" },
|
|
768
|
+
},
|
|
769
|
+
});
|
|
770
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
771
|
+
const canonicalSessionId = "omx-leader-session-include";
|
|
772
|
+
const leaderNativeSessionId = "codex-leader-thread-include";
|
|
773
|
+
const childNativeSessionId = "codex-child-thread-include";
|
|
774
|
+
await mkdir(join(stateDir, "sessions", canonicalSessionId), { recursive: true });
|
|
775
|
+
await writeSessionStart(cwd, canonicalSessionId, {
|
|
776
|
+
nativeSessionId: leaderNativeSessionId,
|
|
777
|
+
});
|
|
778
|
+
await mkdir(join(cwd, ".omx", "hooks"), { recursive: true });
|
|
779
|
+
await writeFile(join(cwd, ".omx", "hooks", "record-lifecycle.mjs"), [
|
|
780
|
+
"import { appendFileSync } from 'node:fs';",
|
|
781
|
+
"export async function onHookEvent(event) {",
|
|
782
|
+
" appendFileSync('hook-events.jsonl', `${JSON.stringify({ event: event.event })}\\n`);",
|
|
783
|
+
"}",
|
|
784
|
+
].join("\n"));
|
|
785
|
+
const transcriptPath = join(cwd, "included-subagent-rollout.jsonl");
|
|
786
|
+
await writeFile(transcriptPath, `${JSON.stringify({
|
|
787
|
+
type: "session_meta",
|
|
788
|
+
payload: {
|
|
789
|
+
id: childNativeSessionId,
|
|
790
|
+
source: {
|
|
791
|
+
subagent: {
|
|
792
|
+
thread_spawn: {
|
|
793
|
+
parent_thread_id: leaderNativeSessionId,
|
|
794
|
+
agent_role: "verifier",
|
|
795
|
+
},
|
|
796
|
+
},
|
|
797
|
+
},
|
|
798
|
+
},
|
|
799
|
+
})}\n`);
|
|
800
|
+
await dispatchCodexNativeHook({
|
|
801
|
+
hook_event_name: "SessionStart",
|
|
802
|
+
cwd,
|
|
803
|
+
session_id: childNativeSessionId,
|
|
804
|
+
transcript_path: transcriptPath,
|
|
805
|
+
}, { cwd, sessionOwnerPid: process.pid });
|
|
806
|
+
await dispatchCodexNativeHook({
|
|
807
|
+
hook_event_name: "Stop",
|
|
808
|
+
cwd,
|
|
809
|
+
session_id: childNativeSessionId,
|
|
810
|
+
thread_id: childNativeSessionId,
|
|
811
|
+
turn_id: "included-child-stop-turn",
|
|
812
|
+
}, { cwd });
|
|
813
|
+
const hookEvents = await readFile(join(cwd, "hook-events.jsonl"), "utf-8");
|
|
814
|
+
assert.match(hookEvents, /"event":"session-start"/);
|
|
815
|
+
assert.match(hookEvents, /"event":"stop"/);
|
|
816
|
+
}
|
|
817
|
+
finally {
|
|
818
|
+
if (originalCodexHome === undefined) {
|
|
819
|
+
delete process.env.CODEX_HOME;
|
|
820
|
+
}
|
|
821
|
+
else {
|
|
822
|
+
process.env.CODEX_HOME = originalCodexHome;
|
|
823
|
+
}
|
|
824
|
+
await rm(cwd, { recursive: true, force: true });
|
|
825
|
+
}
|
|
826
|
+
});
|
|
827
|
+
it("allows child-agent lifecycle hook dispatch at agent verbosity", async () => {
|
|
828
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-subagent-session-agent-"));
|
|
829
|
+
const originalCodexHome = process.env.CODEX_HOME;
|
|
830
|
+
try {
|
|
831
|
+
process.env.CODEX_HOME = join(cwd, "codex-home");
|
|
832
|
+
await writeJson(join(process.env.CODEX_HOME, ".omx-config.json"), {
|
|
833
|
+
notifications: {
|
|
834
|
+
enabled: true,
|
|
835
|
+
verbosity: "agent",
|
|
836
|
+
telegram: { enabled: true, botToken: "123:abc", chatId: "456" },
|
|
837
|
+
},
|
|
838
|
+
});
|
|
839
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
840
|
+
const canonicalSessionId = "omx-leader-session-agent";
|
|
841
|
+
const leaderNativeSessionId = "codex-leader-thread-agent";
|
|
842
|
+
const childNativeSessionId = "codex-child-thread-agent";
|
|
843
|
+
await mkdir(join(stateDir, "sessions", canonicalSessionId), { recursive: true });
|
|
844
|
+
await writeSessionStart(cwd, canonicalSessionId, {
|
|
845
|
+
nativeSessionId: leaderNativeSessionId,
|
|
846
|
+
});
|
|
847
|
+
await mkdir(join(cwd, ".omx", "hooks"), { recursive: true });
|
|
848
|
+
await writeFile(join(cwd, ".omx", "hooks", "record-lifecycle.mjs"), [
|
|
849
|
+
"import { appendFileSync } from 'node:fs';",
|
|
850
|
+
"export async function onHookEvent(event) {",
|
|
851
|
+
" appendFileSync('hook-events.jsonl', `${JSON.stringify({ event: event.event })}\\n`);",
|
|
852
|
+
"}",
|
|
853
|
+
].join("\n"));
|
|
854
|
+
const transcriptPath = join(cwd, "agent-verbosity-subagent-rollout.jsonl");
|
|
855
|
+
await writeFile(transcriptPath, `${JSON.stringify({
|
|
856
|
+
type: "session_meta",
|
|
857
|
+
payload: {
|
|
858
|
+
id: childNativeSessionId,
|
|
859
|
+
source: {
|
|
860
|
+
subagent: {
|
|
861
|
+
thread_spawn: {
|
|
862
|
+
parent_thread_id: leaderNativeSessionId,
|
|
863
|
+
agent_role: "verifier",
|
|
864
|
+
},
|
|
865
|
+
},
|
|
866
|
+
},
|
|
867
|
+
},
|
|
868
|
+
})}\n`);
|
|
869
|
+
await dispatchCodexNativeHook({
|
|
870
|
+
hook_event_name: "SessionStart",
|
|
871
|
+
cwd,
|
|
872
|
+
session_id: childNativeSessionId,
|
|
873
|
+
transcript_path: transcriptPath,
|
|
874
|
+
}, { cwd, sessionOwnerPid: process.pid });
|
|
875
|
+
const hookEvents = await readFile(join(cwd, "hook-events.jsonl"), "utf-8");
|
|
876
|
+
assert.match(hookEvents, /"event":"session-start"/);
|
|
626
877
|
}
|
|
627
878
|
finally {
|
|
879
|
+
if (originalCodexHome === undefined) {
|
|
880
|
+
delete process.env.CODEX_HOME;
|
|
881
|
+
}
|
|
882
|
+
else {
|
|
883
|
+
process.env.CODEX_HOME = originalCodexHome;
|
|
884
|
+
}
|
|
628
885
|
await rm(cwd, { recursive: true, force: true });
|
|
629
886
|
}
|
|
630
887
|
});
|
|
@@ -1218,6 +1475,62 @@ describe("codex native hook dispatch", () => {
|
|
|
1218
1475
|
await rm(cwd, { recursive: true, force: true });
|
|
1219
1476
|
}
|
|
1220
1477
|
});
|
|
1478
|
+
it("does not repeat performance-goal reconciliation after a recorded objective mismatch blocker", async () => {
|
|
1479
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-performance-mismatch-blocked-stop-"));
|
|
1480
|
+
try {
|
|
1481
|
+
await writeJson(join(cwd, ".omx", "goals", "performance", "latency", "state.json"), {
|
|
1482
|
+
version: 1,
|
|
1483
|
+
workflow: "performance-goal",
|
|
1484
|
+
slug: "latency",
|
|
1485
|
+
objective: "Reduce latency",
|
|
1486
|
+
status: "blocked",
|
|
1487
|
+
lastValidation: {
|
|
1488
|
+
status: "blocked",
|
|
1489
|
+
evidence: "omx performance-goal complete rejected the fresh get_goal snapshot: Codex goal objective mismatch: expected \"reduce latency\", got \"legacy objective\".",
|
|
1490
|
+
recordedAt: "2026-05-20T00:00:00.000Z",
|
|
1491
|
+
},
|
|
1492
|
+
});
|
|
1493
|
+
const result = await dispatchCodexNativeHook({
|
|
1494
|
+
hook_event_name: "Stop",
|
|
1495
|
+
cwd,
|
|
1496
|
+
session_id: "sess-performance-mismatch-blocked-stop",
|
|
1497
|
+
thread_id: "thread-performance-mismatch-blocked-stop",
|
|
1498
|
+
last_assistant_message: "Performance goal complete; next call update_goal({status: \"complete\"}).",
|
|
1499
|
+
}, { cwd });
|
|
1500
|
+
assert.notEqual(result.outputJson?.decision, "block");
|
|
1501
|
+
assert.doesNotMatch(JSON.stringify(result.outputJson), /omx performance-goal complete --slug latency/);
|
|
1502
|
+
assert.doesNotMatch(JSON.stringify(result.outputJson), /get_goal snapshot reconciliation/);
|
|
1503
|
+
}
|
|
1504
|
+
finally {
|
|
1505
|
+
await rm(cwd, { recursive: true, force: true });
|
|
1506
|
+
}
|
|
1507
|
+
});
|
|
1508
|
+
it("does not block Stop for an already complete performance-goal state", async () => {
|
|
1509
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-performance-complete-stop-"));
|
|
1510
|
+
try {
|
|
1511
|
+
await writeJson(join(cwd, ".omx", "goals", "performance", "latency", "state.json"), {
|
|
1512
|
+
version: 1,
|
|
1513
|
+
workflow: "performance-goal",
|
|
1514
|
+
slug: "latency",
|
|
1515
|
+
objective: "Reduce latency",
|
|
1516
|
+
status: "complete",
|
|
1517
|
+
completedAt: "2026-05-20T00:00:00.000Z",
|
|
1518
|
+
});
|
|
1519
|
+
const result = await dispatchCodexNativeHook({
|
|
1520
|
+
hook_event_name: "Stop",
|
|
1521
|
+
cwd,
|
|
1522
|
+
session_id: "sess-performance-complete-stop",
|
|
1523
|
+
thread_id: "thread-performance-complete-stop",
|
|
1524
|
+
last_assistant_message: "Performance goal complete; next call update_goal({status: \"complete\"}).",
|
|
1525
|
+
}, { cwd });
|
|
1526
|
+
assert.notEqual(result.outputJson?.decision, "block");
|
|
1527
|
+
assert.doesNotMatch(JSON.stringify(result.outputJson), /omx performance-goal complete --slug latency/);
|
|
1528
|
+
assert.doesNotMatch(JSON.stringify(result.outputJson), /get_goal snapshot reconciliation/);
|
|
1529
|
+
}
|
|
1530
|
+
finally {
|
|
1531
|
+
await rm(cwd, { recursive: true, force: true });
|
|
1532
|
+
}
|
|
1533
|
+
});
|
|
1221
1534
|
it("blocks ultragoal Stop for concise generic goal completion claims", async () => {
|
|
1222
1535
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-generic-complete-stop-"));
|
|
1223
1536
|
try {
|
|
@@ -1262,7 +1575,7 @@ describe("codex native hook dispatch", () => {
|
|
|
1262
1575
|
await rm(cwd, { recursive: true, force: true });
|
|
1263
1576
|
}
|
|
1264
1577
|
});
|
|
1265
|
-
it("blocks ultragoal Stop with blocked checkpoint and
|
|
1578
|
+
it("blocks ultragoal Stop with blocked checkpoint and available-goal-context remediation for completed legacy snapshots", async () => {
|
|
1266
1579
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-legacy-stop-"));
|
|
1267
1580
|
try {
|
|
1268
1581
|
await writeJson(join(cwd, ".omx", "ultragoal", "goals.json"), {
|
|
@@ -1281,7 +1594,11 @@ describe("codex native hook dispatch", () => {
|
|
|
1281
1594
|
assert.equal(result.outputJson?.decision, "block");
|
|
1282
1595
|
assert.match(output, /omx ultragoal checkpoint --goal-id G001-demo --status complete/);
|
|
1283
1596
|
assert.match(output, /--status blocked/);
|
|
1284
|
-
assert.match(output, /
|
|
1597
|
+
assert.match(output, /Codex goal context/);
|
|
1598
|
+
assert.match(output, /no such table: thread_goals/);
|
|
1599
|
+
assert.match(output, /unavailable get_goal error JSON or path/);
|
|
1600
|
+
assert.match(output, /safe-recovery blocker/);
|
|
1601
|
+
assert.doesNotMatch(output, /fresh (?:Codex )?(?:thread|session)s?/i);
|
|
1285
1602
|
assert.match(output, /Hooks must not mutate Codex goal state/);
|
|
1286
1603
|
}
|
|
1287
1604
|
finally {
|
|
@@ -1294,7 +1611,7 @@ describe("codex native hook dispatch", () => {
|
|
|
1294
1611
|
await writeJson(join(cwd, ".omx", "ultragoal", "goals.json"), {
|
|
1295
1612
|
version: 1,
|
|
1296
1613
|
codexGoalMode: "aggregate",
|
|
1297
|
-
codexObjective: "Complete
|
|
1614
|
+
codexObjective: "Complete the durable ultragoal plan in .omx/ultragoal/goals.json, including later accepted/appended stories, under the original brief constraints; use .omx/ultragoal/ledger.jsonl as the audit trail.",
|
|
1298
1615
|
activeGoalId: "G001-micro",
|
|
1299
1616
|
aggregateCompletion: {
|
|
1300
1617
|
status: "complete",
|
|
@@ -1552,6 +1869,30 @@ describe("codex native hook dispatch", () => {
|
|
|
1552
1869
|
await rm(cwd, { recursive: true, force: true });
|
|
1553
1870
|
}
|
|
1554
1871
|
});
|
|
1872
|
+
it("injects autopilot ralplan consensus gate guidance on prompt activation", async () => {
|
|
1873
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autopilot-ralplan-gate-"));
|
|
1874
|
+
try {
|
|
1875
|
+
await mkdir(join(cwd, ".omx", "state"), { recursive: true });
|
|
1876
|
+
const result = await dispatchCodexNativeHook({
|
|
1877
|
+
hook_event_name: "UserPromptSubmit",
|
|
1878
|
+
cwd,
|
|
1879
|
+
session_id: "sess-autopilot-ralplan-gate",
|
|
1880
|
+
thread_id: "thread-autopilot-ralplan-gate",
|
|
1881
|
+
turn_id: "turn-autopilot-ralplan-gate",
|
|
1882
|
+
prompt: "$autopilot implement issue #2430",
|
|
1883
|
+
}, { cwd });
|
|
1884
|
+
assert.equal(result.omxEventName, "keyword-detector");
|
|
1885
|
+
assert.equal(result.skillState?.skill, "autopilot");
|
|
1886
|
+
const message = String(result.outputJson?.hookSpecificOutput?.additionalContext || "");
|
|
1887
|
+
assert.match(message, /Autopilot protocol:/);
|
|
1888
|
+
assert.match(message, /deep-interview -> ralplan -> ultragoal -> code-review -> ultraqa/);
|
|
1889
|
+
assert.match(message, /Planner output has been reviewed sequentially by Architect and then Critic/);
|
|
1890
|
+
assert.match(message, /do not hand off to Ultragoal or implementation until .*ralplan_architect_review.*ralplan_critic_review/);
|
|
1891
|
+
}
|
|
1892
|
+
finally {
|
|
1893
|
+
await rm(cwd, { recursive: true, force: true });
|
|
1894
|
+
}
|
|
1895
|
+
});
|
|
1555
1896
|
it("records ultragoal prompt skill activation with goal-tool handoff guidance", async () => {
|
|
1556
1897
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-"));
|
|
1557
1898
|
try {
|
|
@@ -1566,14 +1907,229 @@ describe("codex native hook dispatch", () => {
|
|
|
1566
1907
|
}, { cwd });
|
|
1567
1908
|
assert.equal(result.omxEventName, "keyword-detector");
|
|
1568
1909
|
assert.equal(result.skillState?.skill, "ultragoal");
|
|
1569
|
-
assert.equal(result.skillState?.initialized_mode,
|
|
1910
|
+
assert.equal(result.skillState?.initialized_mode, "ultragoal");
|
|
1911
|
+
assert.equal(result.skillState?.initialized_state_path, ".omx/state/sessions/sess-ultragoal-1/ultragoal-state.json");
|
|
1570
1912
|
const message = String(result.outputJson?.hookSpecificOutput?.additionalContext || "");
|
|
1571
1913
|
assert.match(message, /"\$ultragoal" -> ultragoal/);
|
|
1572
1914
|
assert.match(message, /Ultragoal protocol:/);
|
|
1573
1915
|
assert.match(message, /get_goal/);
|
|
1574
1916
|
assert.match(message, /create_goal/);
|
|
1575
1917
|
assert.match(message, /update_goal/);
|
|
1576
|
-
assert.
|
|
1918
|
+
assert.match(message, /does not call `\/goal clear`/);
|
|
1919
|
+
assert.match(message, /multiple sequential ultragoal runs/);
|
|
1920
|
+
assert.equal(existsSync(join(cwd, ".omx", "state", "sessions", "sess-ultragoal-1", "ultragoal-state.json")), true);
|
|
1921
|
+
}
|
|
1922
|
+
finally {
|
|
1923
|
+
await rm(cwd, { recursive: true, force: true });
|
|
1924
|
+
}
|
|
1925
|
+
});
|
|
1926
|
+
it("deactivates active deep-interview state on explicit ultragoal handoff", async () => {
|
|
1927
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-deep-interview-handoff-"));
|
|
1928
|
+
try {
|
|
1929
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
1930
|
+
const sessionDir = join(stateDir, "sessions", "sess-ultragoal-handoff");
|
|
1931
|
+
await mkdir(sessionDir, { recursive: true });
|
|
1932
|
+
await writeJson(join(sessionDir, "skill-active-state.json"), {
|
|
1933
|
+
version: 1,
|
|
1934
|
+
active: true,
|
|
1935
|
+
skill: "deep-interview",
|
|
1936
|
+
phase: "planning",
|
|
1937
|
+
session_id: "sess-ultragoal-handoff",
|
|
1938
|
+
active_skills: [{ skill: "deep-interview", phase: "planning", active: true, session_id: "sess-ultragoal-handoff" }],
|
|
1939
|
+
});
|
|
1940
|
+
await writeJson(join(sessionDir, "deep-interview-state.json"), {
|
|
1941
|
+
active: true,
|
|
1942
|
+
mode: "deep-interview",
|
|
1943
|
+
current_phase: "intent-first",
|
|
1944
|
+
session_id: "sess-ultragoal-handoff",
|
|
1945
|
+
question_enforcement: {
|
|
1946
|
+
obligation_id: "obligation-ultragoal-handoff",
|
|
1947
|
+
source: "omx-question",
|
|
1948
|
+
status: "pending",
|
|
1949
|
+
requested_at: "2026-05-21T03:00:00.000Z",
|
|
1950
|
+
},
|
|
1951
|
+
});
|
|
1952
|
+
const result = await dispatchCodexNativeHook({
|
|
1953
|
+
hook_event_name: "UserPromptSubmit",
|
|
1954
|
+
cwd,
|
|
1955
|
+
session_id: "sess-ultragoal-handoff",
|
|
1956
|
+
thread_id: "thread-ultragoal-handoff",
|
|
1957
|
+
turn_id: "turn-ultragoal-handoff",
|
|
1958
|
+
prompt: "$ultragoal turn the clarified spec into goals",
|
|
1959
|
+
}, { cwd });
|
|
1960
|
+
assert.equal(result.omxEventName, "keyword-detector");
|
|
1961
|
+
assert.equal(result.skillState?.skill, "ultragoal");
|
|
1962
|
+
assert.match(JSON.stringify(result.outputJson), /mode transiting: deep-interview -> ultragoal/);
|
|
1963
|
+
const completed = JSON.parse(await readFile(join(sessionDir, "deep-interview-state.json"), "utf-8"));
|
|
1964
|
+
assert.equal(completed.active, false);
|
|
1965
|
+
assert.equal(completed.current_phase, "completed");
|
|
1966
|
+
assert.equal(completed.question_enforcement?.status, "cleared");
|
|
1967
|
+
assert.equal(completed.question_enforcement?.clear_reason, "handoff");
|
|
1968
|
+
assert.equal(existsSync(join(sessionDir, "ultragoal-state.json")), true);
|
|
1969
|
+
const edit = await dispatchCodexNativeHook({
|
|
1970
|
+
hook_event_name: "PreToolUse",
|
|
1971
|
+
cwd,
|
|
1972
|
+
session_id: "sess-ultragoal-handoff",
|
|
1973
|
+
thread_id: "thread-ultragoal-handoff",
|
|
1974
|
+
tool_name: "Edit",
|
|
1975
|
+
tool_use_id: "tool-ultragoal-post-handoff-edit",
|
|
1976
|
+
tool_input: { file_path: "src/implementation.ts", old_string: "a", new_string: "b" },
|
|
1977
|
+
}, { cwd });
|
|
1978
|
+
assert.equal(edit.outputJson, null);
|
|
1979
|
+
}
|
|
1980
|
+
finally {
|
|
1981
|
+
await rm(cwd, { recursive: true, force: true });
|
|
1982
|
+
}
|
|
1983
|
+
});
|
|
1984
|
+
it("applies only explicit structured UserPromptSubmit ultragoal steering directives", async () => {
|
|
1985
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-steer-"));
|
|
1986
|
+
try {
|
|
1987
|
+
await createUltragoalPlan(cwd, {
|
|
1988
|
+
brief: "G002-cli-and-prompt-submit-bridge .omx/ultragoal hook steering fixture",
|
|
1989
|
+
goals: [{ title: "First", objective: "Complete first milestone with tests." }],
|
|
1990
|
+
});
|
|
1991
|
+
const prose = await dispatchCodexNativeHook({
|
|
1992
|
+
hook_event_name: "UserPromptSubmit",
|
|
1993
|
+
cwd,
|
|
1994
|
+
session_id: "sess-ultragoal-steer-1",
|
|
1995
|
+
prompt: "Please add a subgoal for docs later; this is normal prose, not a directive.",
|
|
1996
|
+
}, { cwd });
|
|
1997
|
+
assert.equal(prose.outputJson, null);
|
|
1998
|
+
assert.equal((await readUltragoalPlan(cwd)).goals.length, 1);
|
|
1999
|
+
const jsonExample = await dispatchCodexNativeHook({
|
|
2000
|
+
hook_event_name: "UserPromptSubmit",
|
|
2001
|
+
cwd,
|
|
2002
|
+
session_id: "sess-ultragoal-steer-1",
|
|
2003
|
+
prompt: `Here is an inert example:\n\`\`\`json\n${JSON.stringify({
|
|
2004
|
+
kind: "add_subgoal",
|
|
2005
|
+
source: "user_prompt_submit",
|
|
2006
|
+
evidence: "Example JSON should not mutate .omx/ultragoal.",
|
|
2007
|
+
rationale: "Only explicit steering fences or labels are executable.",
|
|
2008
|
+
title: "Inert JSON example",
|
|
2009
|
+
objective: "This example must not be added.",
|
|
2010
|
+
})}\n\`\`\``,
|
|
2011
|
+
}, { cwd });
|
|
2012
|
+
assert.equal(jsonExample.outputJson, null);
|
|
2013
|
+
assert.equal((await readUltragoalPlan(cwd)).goals.length, 1);
|
|
2014
|
+
const result = await dispatchCodexNativeHook({
|
|
2015
|
+
hook_event_name: "UserPromptSubmit",
|
|
2016
|
+
cwd,
|
|
2017
|
+
session_id: "sess-ultragoal-steer-1",
|
|
2018
|
+
prompt: `OMX_ULTRAGOAL_STEER: ${JSON.stringify({
|
|
2019
|
+
kind: "add_subgoal",
|
|
2020
|
+
source: "user_prompt_submit",
|
|
2021
|
+
evidence: "Prompt-submit supplied a structured .omx/ultragoal directive for G002-cli-and-prompt-submit-bridge.",
|
|
2022
|
+
rationale: "Add bounded hook regression work while preserving all completion gates.",
|
|
2023
|
+
title: "Prompt bridge regression",
|
|
2024
|
+
objective: "Verify UserPromptSubmit bounded steering bridge with tests.",
|
|
2025
|
+
})}`,
|
|
2026
|
+
}, { cwd });
|
|
2027
|
+
const message = String(result.outputJson?.hookSpecificOutput?.additionalContext || "");
|
|
2028
|
+
assert.match(message, /bounded \.omx\/ultragoal steering/);
|
|
2029
|
+
assert.match(message, /G002-cli-and-prompt-submit-bridge/);
|
|
2030
|
+
assert.match(message, /accepted/);
|
|
2031
|
+
const plan = await readUltragoalPlan(cwd);
|
|
2032
|
+
assert.equal(plan.goals.length, 2);
|
|
2033
|
+
assert.equal(plan.goals[1]?.title, "Prompt bridge regression");
|
|
2034
|
+
}
|
|
2035
|
+
finally {
|
|
2036
|
+
await rm(cwd, { recursive: true, force: true });
|
|
2037
|
+
}
|
|
2038
|
+
});
|
|
2039
|
+
it("does not apply UserPromptSubmit ultragoal steering from native subagent prompts", async () => {
|
|
2040
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-steer-subagent-"));
|
|
2041
|
+
try {
|
|
2042
|
+
await createUltragoalPlan(cwd, {
|
|
2043
|
+
brief: "G002-cli-and-prompt-submit-bridge .omx/ultragoal subagent steering fixture",
|
|
2044
|
+
goals: [{ title: "First", objective: "Complete first milestone with tests." }],
|
|
2045
|
+
});
|
|
2046
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
2047
|
+
const canonicalSessionId = "sess-ultragoal-parent";
|
|
2048
|
+
const leaderNativeSessionId = "native-ultragoal-parent";
|
|
2049
|
+
const childNativeSessionId = "native-ultragoal-child";
|
|
2050
|
+
const nowIso = new Date().toISOString();
|
|
2051
|
+
await writeJson(join(stateDir, "session.json"), {
|
|
2052
|
+
session_id: canonicalSessionId,
|
|
2053
|
+
native_session_id: leaderNativeSessionId,
|
|
2054
|
+
});
|
|
2055
|
+
await writeJson(join(stateDir, "subagent-tracking.json"), {
|
|
2056
|
+
schemaVersion: 1,
|
|
2057
|
+
sessions: {
|
|
2058
|
+
[canonicalSessionId]: {
|
|
2059
|
+
session_id: canonicalSessionId,
|
|
2060
|
+
leader_thread_id: leaderNativeSessionId,
|
|
2061
|
+
updated_at: nowIso,
|
|
2062
|
+
threads: {
|
|
2063
|
+
[leaderNativeSessionId]: {
|
|
2064
|
+
thread_id: leaderNativeSessionId,
|
|
2065
|
+
kind: "leader",
|
|
2066
|
+
first_seen_at: nowIso,
|
|
2067
|
+
last_seen_at: nowIso,
|
|
2068
|
+
turn_count: 1,
|
|
2069
|
+
},
|
|
2070
|
+
[childNativeSessionId]: {
|
|
2071
|
+
thread_id: childNativeSessionId,
|
|
2072
|
+
kind: "subagent",
|
|
2073
|
+
first_seen_at: nowIso,
|
|
2074
|
+
last_seen_at: nowIso,
|
|
2075
|
+
turn_count: 1,
|
|
2076
|
+
mode: "architect",
|
|
2077
|
+
},
|
|
2078
|
+
},
|
|
2079
|
+
},
|
|
2080
|
+
},
|
|
2081
|
+
});
|
|
2082
|
+
const result = await dispatchCodexNativeHook({
|
|
2083
|
+
hook_event_name: "UserPromptSubmit",
|
|
2084
|
+
cwd,
|
|
2085
|
+
session_id: childNativeSessionId,
|
|
2086
|
+
thread_id: childNativeSessionId,
|
|
2087
|
+
turn_id: "turn-ultragoal-child-1",
|
|
2088
|
+
prompt: `OMX_ULTRAGOAL_STEER: ${JSON.stringify({
|
|
2089
|
+
kind: "add_subgoal",
|
|
2090
|
+
source: "user_prompt_submit",
|
|
2091
|
+
evidence: "Subagent prompt text must be literal delegated context.",
|
|
2092
|
+
rationale: "Subagent prompts should not mutate the parent .omx/ultragoal ledger.",
|
|
2093
|
+
title: "Subagent should not add this",
|
|
2094
|
+
objective: "This must remain literal prompt text.",
|
|
2095
|
+
})}`,
|
|
2096
|
+
}, { cwd });
|
|
2097
|
+
assert.equal(result.outputJson, null);
|
|
2098
|
+
const plan = await readUltragoalPlan(cwd);
|
|
2099
|
+
assert.equal(plan.goals.length, 1);
|
|
2100
|
+
const ledger = await readFile(join(cwd, ".omx/ultragoal/ledger.jsonl"), "utf-8");
|
|
2101
|
+
assert.equal((ledger.match(/"event":"steering_accepted"/g) ?? []).length, 0);
|
|
2102
|
+
assert.equal((ledger.match(/"event":"steering_rejected"/g) ?? []).length, 0);
|
|
2103
|
+
}
|
|
2104
|
+
finally {
|
|
2105
|
+
await rm(cwd, { recursive: true, force: true });
|
|
2106
|
+
}
|
|
2107
|
+
});
|
|
2108
|
+
it("dedupes repeated UserPromptSubmit ultragoal steering directives by prompt signature", async () => {
|
|
2109
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-steer-dedupe-"));
|
|
2110
|
+
try {
|
|
2111
|
+
await createUltragoalPlan(cwd, {
|
|
2112
|
+
brief: "G002-cli-and-prompt-submit-bridge .omx/ultragoal dedupe fixture",
|
|
2113
|
+
goals: [{ title: "First", objective: "Complete first milestone with tests." }],
|
|
2114
|
+
});
|
|
2115
|
+
const prompt = `\`\`\`omx-ultragoal-steer
|
|
2116
|
+
${JSON.stringify({
|
|
2117
|
+
kind: "add_subgoal",
|
|
2118
|
+
source: "user_prompt_submit",
|
|
2119
|
+
evidence: "Structured prompt-submit directive adds exactly one deduped goal.",
|
|
2120
|
+
rationale: "Use idempotent bridge semantics for repeated hook delivery.",
|
|
2121
|
+
title: "Deduped bridge regression",
|
|
2122
|
+
objective: "Verify repeated UserPromptSubmit steering does not duplicate goals.",
|
|
2123
|
+
})}
|
|
2124
|
+
\`\`\``;
|
|
2125
|
+
await dispatchCodexNativeHook({ hook_event_name: "UserPromptSubmit", cwd, session_id: "sess-dedupe", prompt }, { cwd });
|
|
2126
|
+
const second = await dispatchCodexNativeHook({ hook_event_name: "UserPromptSubmit", cwd, session_id: "sess-dedupe", prompt }, { cwd });
|
|
2127
|
+
const message = String(second.outputJson?.hookSpecificOutput?.additionalContext || "");
|
|
2128
|
+
assert.match(message, /deduped/);
|
|
2129
|
+
const plan = await readUltragoalPlan(cwd);
|
|
2130
|
+
assert.equal(plan.goals.filter((goal) => goal.title === "Deduped bridge regression").length, 1);
|
|
2131
|
+
const ledger = await readFile(join(cwd, ".omx/ultragoal/ledger.jsonl"), "utf-8");
|
|
2132
|
+
assert.equal((ledger.match(/"event":"steering_accepted"/g) ?? []).length, 1);
|
|
1577
2133
|
}
|
|
1578
2134
|
finally {
|
|
1579
2135
|
await rm(cwd, { recursive: true, force: true });
|
|
@@ -2729,52 +3285,199 @@ exit 0
|
|
|
2729
3285
|
const result = await dispatchCodexNativeHook({
|
|
2730
3286
|
hook_event_name: "PreToolUse",
|
|
2731
3287
|
cwd,
|
|
2732
|
-
source: "codex-app",
|
|
2733
|
-
session_id: "sess-team-node-native-block",
|
|
2734
|
-
tool_name: "Bash",
|
|
2735
|
-
tool_use_id: "tool-team-node-native-block",
|
|
2736
|
-
tool_input: { command: "node ./dist/cli/omx.js team status my-team" },
|
|
3288
|
+
source: "codex-app",
|
|
3289
|
+
session_id: "sess-team-node-native-block",
|
|
3290
|
+
tool_name: "Bash",
|
|
3291
|
+
tool_use_id: "tool-team-node-native-block",
|
|
3292
|
+
tool_input: { command: "node ./dist/cli/omx.js team status my-team" },
|
|
3293
|
+
}, { cwd });
|
|
3294
|
+
assert.equal(result.omxEventName, "pre-tool-use");
|
|
3295
|
+
assert.equal(result.outputJson?.decision, "block");
|
|
3296
|
+
assert.match(String(result.outputJson?.systemMessage || ""), /Codex App\/native outside-tmux sessions/);
|
|
3297
|
+
}
|
|
3298
|
+
finally {
|
|
3299
|
+
await rm(cwd, { recursive: true, force: true });
|
|
3300
|
+
}
|
|
3301
|
+
});
|
|
3302
|
+
it("preserves direct CLI outside-tmux omx team Bash behavior", async () => {
|
|
3303
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-team-cli-outside-"));
|
|
3304
|
+
try {
|
|
3305
|
+
const result = await dispatchCodexNativeHook({
|
|
3306
|
+
hook_event_name: "PreToolUse",
|
|
3307
|
+
cwd,
|
|
3308
|
+
source: "cli",
|
|
3309
|
+
session_id: "sess-team-cli-outside",
|
|
3310
|
+
tool_name: "Bash",
|
|
3311
|
+
tool_use_id: "tool-team-cli-outside",
|
|
3312
|
+
tool_input: { command: "omx team status my-team" },
|
|
3313
|
+
}, { cwd });
|
|
3314
|
+
assert.equal(result.omxEventName, "pre-tool-use");
|
|
3315
|
+
assert.equal(result.outputJson, null);
|
|
3316
|
+
}
|
|
3317
|
+
finally {
|
|
3318
|
+
await rm(cwd, { recursive: true, force: true });
|
|
3319
|
+
}
|
|
3320
|
+
});
|
|
3321
|
+
it("preserves source-less outside-tmux omx team Bash behavior when no native session evidence exists", async () => {
|
|
3322
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-team-cli-nosource-"));
|
|
3323
|
+
try {
|
|
3324
|
+
const result = await dispatchCodexNativeHook({
|
|
3325
|
+
hook_event_name: "PreToolUse",
|
|
3326
|
+
cwd,
|
|
3327
|
+
session_id: "sess-team-cli-nosource",
|
|
3328
|
+
tool_name: "Bash",
|
|
3329
|
+
tool_use_id: "tool-team-cli-nosource",
|
|
3330
|
+
tool_input: { command: "omx team status my-team" },
|
|
3331
|
+
}, { cwd });
|
|
3332
|
+
assert.equal(result.omxEventName, "pre-tool-use");
|
|
3333
|
+
assert.equal(result.outputJson, null);
|
|
3334
|
+
}
|
|
3335
|
+
finally {
|
|
3336
|
+
await rm(cwd, { recursive: true, force: true });
|
|
3337
|
+
}
|
|
3338
|
+
});
|
|
3339
|
+
it("blocks implementation file edits while deep-interview remains active after a clarified answer", async () => {
|
|
3340
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-deep-interview-edit-block-"));
|
|
3341
|
+
try {
|
|
3342
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
3343
|
+
const sessionDir = join(stateDir, "sessions", "sess-di-edit-block");
|
|
3344
|
+
await mkdir(sessionDir, { recursive: true });
|
|
3345
|
+
await writeJson(join(stateDir, "session.json"), { session_id: "sess-di-edit-block", cwd });
|
|
3346
|
+
await writeJson(join(sessionDir, "skill-active-state.json"), {
|
|
3347
|
+
version: 1,
|
|
3348
|
+
active: true,
|
|
3349
|
+
skill: "deep-interview",
|
|
3350
|
+
phase: "planning",
|
|
3351
|
+
session_id: "sess-di-edit-block",
|
|
3352
|
+
thread_id: "thread-di-edit-block",
|
|
3353
|
+
active_skills: [{ skill: "deep-interview", phase: "planning", active: true, session_id: "sess-di-edit-block", thread_id: "thread-di-edit-block" }],
|
|
3354
|
+
});
|
|
3355
|
+
await writeJson(join(sessionDir, "deep-interview-state.json"), {
|
|
3356
|
+
active: true,
|
|
3357
|
+
mode: "deep-interview",
|
|
3358
|
+
current_phase: "intent-first",
|
|
3359
|
+
session_id: "sess-di-edit-block",
|
|
3360
|
+
thread_id: "thread-di-edit-block",
|
|
3361
|
+
rounds: [{ answer: "Implement by editing src/hooks/keyword-detector.ts and add tests." }],
|
|
3362
|
+
});
|
|
3363
|
+
const result = await dispatchCodexNativeHook({
|
|
3364
|
+
hook_event_name: "PreToolUse",
|
|
3365
|
+
cwd,
|
|
3366
|
+
session_id: "sess-di-edit-block",
|
|
3367
|
+
thread_id: "thread-di-edit-block",
|
|
3368
|
+
tool_name: "Edit",
|
|
3369
|
+
tool_use_id: "tool-di-edit-block",
|
|
3370
|
+
tool_input: { file_path: "src/hooks/keyword-detector.ts", old_string: "a", new_string: "b" },
|
|
2737
3371
|
}, { cwd });
|
|
2738
3372
|
assert.equal(result.omxEventName, "pre-tool-use");
|
|
2739
3373
|
assert.equal(result.outputJson?.decision, "block");
|
|
2740
|
-
assert.match(String(result.outputJson?.
|
|
3374
|
+
assert.match(String(result.outputJson?.reason ?? ""), /Deep-interview is active/);
|
|
3375
|
+
assert.match(JSON.stringify(result.outputJson), /requirements\/spec mode/);
|
|
3376
|
+
assert.match(JSON.stringify(result.outputJson), /\$ralplan/);
|
|
2741
3377
|
}
|
|
2742
3378
|
finally {
|
|
2743
3379
|
await rm(cwd, { recursive: true, force: true });
|
|
2744
3380
|
}
|
|
2745
3381
|
});
|
|
2746
|
-
it("
|
|
2747
|
-
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-
|
|
3382
|
+
it("allows deep-interview artifact and state writes while blocking implementation Bash writes", async () => {
|
|
3383
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-deep-interview-artifact-"));
|
|
2748
3384
|
try {
|
|
2749
|
-
const
|
|
3385
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
3386
|
+
const sessionDir = join(stateDir, "sessions", "sess-di-artifact");
|
|
3387
|
+
await mkdir(sessionDir, { recursive: true });
|
|
3388
|
+
await writeJson(join(stateDir, "session.json"), { session_id: "sess-di-artifact", cwd });
|
|
3389
|
+
await writeJson(join(sessionDir, "skill-active-state.json"), {
|
|
3390
|
+
version: 1,
|
|
3391
|
+
active: true,
|
|
3392
|
+
skill: "deep-interview",
|
|
3393
|
+
phase: "planning",
|
|
3394
|
+
session_id: "sess-di-artifact",
|
|
3395
|
+
active_skills: [{ skill: "deep-interview", phase: "planning", active: true, session_id: "sess-di-artifact" }],
|
|
3396
|
+
});
|
|
3397
|
+
await writeJson(join(sessionDir, "deep-interview-state.json"), {
|
|
3398
|
+
active: true,
|
|
3399
|
+
mode: "deep-interview",
|
|
3400
|
+
current_phase: "intent-first",
|
|
3401
|
+
session_id: "sess-di-artifact",
|
|
3402
|
+
});
|
|
3403
|
+
const allowedWrite = await dispatchCodexNativeHook({
|
|
2750
3404
|
hook_event_name: "PreToolUse",
|
|
2751
3405
|
cwd,
|
|
2752
|
-
|
|
2753
|
-
|
|
3406
|
+
session_id: "sess-di-artifact",
|
|
3407
|
+
tool_name: "Write",
|
|
3408
|
+
tool_use_id: "tool-di-spec-write",
|
|
3409
|
+
tool_input: { file_path: ".omx/specs/deep-interview-demo.md", content: "# Spec" },
|
|
3410
|
+
}, { cwd });
|
|
3411
|
+
assert.equal(allowedWrite.outputJson, null);
|
|
3412
|
+
const allowedBash = await dispatchCodexNativeHook({
|
|
3413
|
+
hook_event_name: "PreToolUse",
|
|
3414
|
+
cwd,
|
|
3415
|
+
session_id: "sess-di-artifact",
|
|
2754
3416
|
tool_name: "Bash",
|
|
2755
|
-
tool_use_id: "tool-
|
|
2756
|
-
tool_input: { command: "omx
|
|
3417
|
+
tool_use_id: "tool-di-context-bash",
|
|
3418
|
+
tool_input: { command: "cat > .omx/context/demo.md <<'EOF'\n# Context\nEOF" },
|
|
2757
3419
|
}, { cwd });
|
|
2758
|
-
assert.equal(
|
|
2759
|
-
|
|
3420
|
+
assert.equal(allowedBash.outputJson, null);
|
|
3421
|
+
const allowedAppendBash = await dispatchCodexNativeHook({
|
|
3422
|
+
hook_event_name: "PreToolUse",
|
|
3423
|
+
cwd,
|
|
3424
|
+
session_id: "sess-di-artifact",
|
|
3425
|
+
tool_name: "Bash",
|
|
3426
|
+
tool_use_id: "tool-di-context-append-bash",
|
|
3427
|
+
tool_input: { command: "echo more context >> .omx/context/demo.md" },
|
|
3428
|
+
}, { cwd });
|
|
3429
|
+
assert.equal(allowedAppendBash.outputJson, null);
|
|
3430
|
+
const blockedBash = await dispatchCodexNativeHook({
|
|
3431
|
+
hook_event_name: "PreToolUse",
|
|
3432
|
+
cwd,
|
|
3433
|
+
session_id: "sess-di-artifact",
|
|
3434
|
+
tool_name: "Bash",
|
|
3435
|
+
tool_use_id: "tool-di-src-bash",
|
|
3436
|
+
tool_input: { command: "cat > src/implementation.ts <<'EOF'\nexport const x = 1;\nEOF" },
|
|
3437
|
+
}, { cwd });
|
|
3438
|
+
assert.equal(blockedBash.outputJson?.decision, "block");
|
|
2760
3439
|
}
|
|
2761
3440
|
finally {
|
|
2762
3441
|
await rm(cwd, { recursive: true, force: true });
|
|
2763
3442
|
}
|
|
2764
3443
|
});
|
|
2765
|
-
it("
|
|
2766
|
-
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-
|
|
3444
|
+
it("allows implementation tools after an explicit deep-interview handoff deactivates the mode", async () => {
|
|
3445
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-deep-interview-handoff-"));
|
|
2767
3446
|
try {
|
|
3447
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
3448
|
+
const sessionDir = join(stateDir, "sessions", "sess-di-handoff");
|
|
3449
|
+
await mkdir(sessionDir, { recursive: true });
|
|
3450
|
+
await writeJson(join(sessionDir, "skill-active-state.json"), {
|
|
3451
|
+
version: 1,
|
|
3452
|
+
active: true,
|
|
3453
|
+
skill: "deep-interview",
|
|
3454
|
+
phase: "planning",
|
|
3455
|
+
session_id: "sess-di-handoff",
|
|
3456
|
+
active_skills: [{ skill: "deep-interview", phase: "planning", active: true, session_id: "sess-di-handoff" }],
|
|
3457
|
+
});
|
|
3458
|
+
await writeJson(join(sessionDir, "deep-interview-state.json"), {
|
|
3459
|
+
active: true,
|
|
3460
|
+
mode: "deep-interview",
|
|
3461
|
+
current_phase: "intent-first",
|
|
3462
|
+
session_id: "sess-di-handoff",
|
|
3463
|
+
});
|
|
3464
|
+
await dispatchCodexNativeHook({
|
|
3465
|
+
hook_event_name: "UserPromptSubmit",
|
|
3466
|
+
cwd,
|
|
3467
|
+
session_id: "sess-di-handoff",
|
|
3468
|
+
prompt: "$ralph implement the clarified spec in src/implementation.ts",
|
|
3469
|
+
}, { cwd });
|
|
2768
3470
|
const result = await dispatchCodexNativeHook({
|
|
2769
3471
|
hook_event_name: "PreToolUse",
|
|
2770
3472
|
cwd,
|
|
2771
|
-
session_id: "sess-
|
|
2772
|
-
tool_name: "
|
|
2773
|
-
tool_use_id: "tool-
|
|
2774
|
-
tool_input: {
|
|
3473
|
+
session_id: "sess-di-handoff",
|
|
3474
|
+
tool_name: "Edit",
|
|
3475
|
+
tool_use_id: "tool-di-post-handoff-edit",
|
|
3476
|
+
tool_input: { file_path: "src/implementation.ts", old_string: "a", new_string: "b" },
|
|
2775
3477
|
}, { cwd });
|
|
2776
|
-
assert.equal(result.omxEventName, "pre-tool-use");
|
|
2777
3478
|
assert.equal(result.outputJson, null);
|
|
3479
|
+
const completed = JSON.parse(await readFile(join(sessionDir, "deep-interview-state.json"), "utf-8"));
|
|
3480
|
+
assert.equal(completed.active, false);
|
|
2778
3481
|
}
|
|
2779
3482
|
finally {
|
|
2780
3483
|
await rm(cwd, { recursive: true, force: true });
|
|
@@ -3109,7 +3812,7 @@ exit 0
|
|
|
3109
3812
|
cwd,
|
|
3110
3813
|
tool_name: "Bash",
|
|
3111
3814
|
tool_use_id: "tool-slop-git-priority",
|
|
3112
|
-
tool_input: { command: 'git commit -m "quick hack fallback if it fails"' },
|
|
3815
|
+
tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 git commit -m "quick hack fallback if it fails"' },
|
|
3113
3816
|
}, { cwd });
|
|
3114
3817
|
assert.equal(result.omxEventName, "pre-tool-use");
|
|
3115
3818
|
assert.equal(result.outputJson?.decision, "block");
|
|
@@ -3128,7 +3831,7 @@ exit 0
|
|
|
3128
3831
|
cwd,
|
|
3129
3832
|
tool_name: "Bash",
|
|
3130
3833
|
tool_use_id: "tool-git-commit-invalid",
|
|
3131
|
-
tool_input: { command: 'git commit -m "fix tests"' },
|
|
3834
|
+
tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 git commit -m "fix tests"' },
|
|
3132
3835
|
}, { cwd });
|
|
3133
3836
|
assert.equal(result.omxEventName, "pre-tool-use");
|
|
3134
3837
|
assert.deepEqual(result.outputJson, {
|
|
@@ -3153,11 +3856,35 @@ exit 0
|
|
|
3153
3856
|
await rm(cwd, { recursive: true, force: true });
|
|
3154
3857
|
}
|
|
3155
3858
|
});
|
|
3156
|
-
it("
|
|
3859
|
+
it("blocks PreToolUse git commit when process env explicitly enables the Lore commit guard", async () => {
|
|
3860
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-env-enabled-"));
|
|
3861
|
+
const original = process.env.OMX_LORE_COMMIT_GUARD;
|
|
3862
|
+
try {
|
|
3863
|
+
process.env.OMX_LORE_COMMIT_GUARD = "1";
|
|
3864
|
+
const result = await dispatchCodexNativeHook({
|
|
3865
|
+
hook_event_name: "PreToolUse",
|
|
3866
|
+
cwd,
|
|
3867
|
+
tool_name: "Bash",
|
|
3868
|
+
tool_use_id: "tool-git-commit-lore-env-enabled",
|
|
3869
|
+
tool_input: { command: 'git commit -m "fix tests"' },
|
|
3870
|
+
}, { cwd });
|
|
3871
|
+
assert.equal(result.omxEventName, "pre-tool-use");
|
|
3872
|
+
assert.equal(result.outputJson?.decision, "block");
|
|
3873
|
+
assert.match(JSON.stringify(result.outputJson), /Lore protocol/);
|
|
3874
|
+
}
|
|
3875
|
+
finally {
|
|
3876
|
+
if (original === undefined)
|
|
3877
|
+
delete process.env.OMX_LORE_COMMIT_GUARD;
|
|
3878
|
+
else
|
|
3879
|
+
process.env.OMX_LORE_COMMIT_GUARD = original;
|
|
3880
|
+
await rm(cwd, { recursive: true, force: true });
|
|
3881
|
+
}
|
|
3882
|
+
});
|
|
3883
|
+
it("allows non-Lore git commit messages when the Lore commit guard is disabled by default", async () => {
|
|
3157
3884
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-disabled-"));
|
|
3158
3885
|
const original = process.env.OMX_LORE_COMMIT_GUARD;
|
|
3159
3886
|
try {
|
|
3160
|
-
process.env.OMX_LORE_COMMIT_GUARD
|
|
3887
|
+
delete process.env.OMX_LORE_COMMIT_GUARD;
|
|
3161
3888
|
const result = await dispatchCodexNativeHook({
|
|
3162
3889
|
hook_event_name: "PreToolUse",
|
|
3163
3890
|
cwd,
|
|
@@ -3176,6 +3903,60 @@ exit 0
|
|
|
3176
3903
|
await rm(cwd, { recursive: true, force: true });
|
|
3177
3904
|
}
|
|
3178
3905
|
});
|
|
3906
|
+
it("blocks non-Lore git commit messages when the Lore commit guard is enabled in CODEX_HOME config.toml", async () => {
|
|
3907
|
+
await withLoreGuardConfig("1", "config-enabled", async (cwd) => {
|
|
3908
|
+
const result = await dispatchCodexNativeHook({
|
|
3909
|
+
hook_event_name: "PreToolUse",
|
|
3910
|
+
cwd,
|
|
3911
|
+
tool_name: "Bash",
|
|
3912
|
+
tool_use_id: "tool-git-commit-lore-config-enabled",
|
|
3913
|
+
tool_input: { command: 'git commit -m "fix: conventional"' },
|
|
3914
|
+
}, { cwd });
|
|
3915
|
+
assert.equal(result.omxEventName, "pre-tool-use");
|
|
3916
|
+
assert.equal(result.outputJson?.decision, "block");
|
|
3917
|
+
assert.match(JSON.stringify(result.outputJson), /Lore protocol/);
|
|
3918
|
+
});
|
|
3919
|
+
});
|
|
3920
|
+
it("allows non-Lore git commit messages when the Lore commit guard is disabled in CODEX_HOME config.toml", async () => {
|
|
3921
|
+
await withLoreGuardConfig("0", "config-disabled", async (cwd) => {
|
|
3922
|
+
const result = await dispatchCodexNativeHook({
|
|
3923
|
+
hook_event_name: "PreToolUse",
|
|
3924
|
+
cwd,
|
|
3925
|
+
tool_name: "Bash",
|
|
3926
|
+
tool_use_id: "tool-git-commit-lore-config-disabled",
|
|
3927
|
+
tool_input: { command: 'git commit -m "fix: use conventional commit"' },
|
|
3928
|
+
}, { cwd });
|
|
3929
|
+
assert.equal(result.omxEventName, "pre-tool-use");
|
|
3930
|
+
assert.equal(result.outputJson, null);
|
|
3931
|
+
});
|
|
3932
|
+
});
|
|
3933
|
+
it("lets inline Lore commit guard values override a disabled CODEX_HOME config.toml", async () => {
|
|
3934
|
+
await withLoreGuardConfig("0", "config-inline-enabled", async (cwd) => {
|
|
3935
|
+
const result = await dispatchCodexNativeHook({
|
|
3936
|
+
hook_event_name: "PreToolUse",
|
|
3937
|
+
cwd,
|
|
3938
|
+
tool_name: "Bash",
|
|
3939
|
+
tool_use_id: "tool-git-commit-lore-config-inline-enabled",
|
|
3940
|
+
tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 git commit -m "fix: conventional"' },
|
|
3941
|
+
}, { cwd });
|
|
3942
|
+
assert.equal(result.omxEventName, "pre-tool-use");
|
|
3943
|
+
assert.equal(result.outputJson?.decision, "block");
|
|
3944
|
+
assert.match(JSON.stringify(result.outputJson), /Lore protocol/);
|
|
3945
|
+
});
|
|
3946
|
+
});
|
|
3947
|
+
it("restores default-off Lore guard when env -u removes a disabled CODEX_HOME config source", async () => {
|
|
3948
|
+
await withLoreGuardConfig("0", "config-codex-home-unset", async (cwd) => {
|
|
3949
|
+
const result = await dispatchCodexNativeHook({
|
|
3950
|
+
hook_event_name: "PreToolUse",
|
|
3951
|
+
cwd,
|
|
3952
|
+
tool_name: "Bash",
|
|
3953
|
+
tool_use_id: "tool-git-commit-lore-config-codex-home-unset",
|
|
3954
|
+
tool_input: { command: 'env -u CODEX_HOME git commit -m "fix: conventional"' },
|
|
3955
|
+
}, { cwd });
|
|
3956
|
+
assert.equal(result.omxEventName, "pre-tool-use");
|
|
3957
|
+
assert.equal(result.outputJson, null);
|
|
3958
|
+
});
|
|
3959
|
+
});
|
|
3179
3960
|
it("allows non-Lore git commit messages when the Lore commit guard is disabled inline", async () => {
|
|
3180
3961
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-inline-disabled-"));
|
|
3181
3962
|
try {
|
|
@@ -3193,7 +3974,30 @@ exit 0
|
|
|
3193
3974
|
await rm(cwd, { recursive: true, force: true });
|
|
3194
3975
|
}
|
|
3195
3976
|
});
|
|
3196
|
-
it("
|
|
3977
|
+
it("allows inline disabled guard to override an enabled process env", async () => {
|
|
3978
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-inline-override-disabled-"));
|
|
3979
|
+
const original = process.env.OMX_LORE_COMMIT_GUARD;
|
|
3980
|
+
try {
|
|
3981
|
+
process.env.OMX_LORE_COMMIT_GUARD = "1";
|
|
3982
|
+
const result = await dispatchCodexNativeHook({
|
|
3983
|
+
hook_event_name: "PreToolUse",
|
|
3984
|
+
cwd,
|
|
3985
|
+
tool_name: "Bash",
|
|
3986
|
+
tool_use_id: "tool-git-commit-lore-inline-override-disabled",
|
|
3987
|
+
tool_input: { command: 'OMX_LORE_COMMIT_GUARD=0 git commit -m "fix: conventional"' },
|
|
3988
|
+
}, { cwd });
|
|
3989
|
+
assert.equal(result.omxEventName, "pre-tool-use");
|
|
3990
|
+
assert.equal(result.outputJson, null);
|
|
3991
|
+
}
|
|
3992
|
+
finally {
|
|
3993
|
+
if (original === undefined)
|
|
3994
|
+
delete process.env.OMX_LORE_COMMIT_GUARD;
|
|
3995
|
+
else
|
|
3996
|
+
process.env.OMX_LORE_COMMIT_GUARD = original;
|
|
3997
|
+
await rm(cwd, { recursive: true, force: true });
|
|
3998
|
+
}
|
|
3999
|
+
});
|
|
4000
|
+
it("does not treat newline-separated Lore guard assignment as inline git commit opt-in", async () => {
|
|
3197
4001
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-newline-assignment-"));
|
|
3198
4002
|
try {
|
|
3199
4003
|
const result = await dispatchCodexNativeHook({
|
|
@@ -3201,21 +4005,33 @@ exit 0
|
|
|
3201
4005
|
cwd,
|
|
3202
4006
|
tool_name: "Bash",
|
|
3203
4007
|
tool_use_id: "tool-git-commit-lore-newline-assignment",
|
|
3204
|
-
tool_input: { command: 'OMX_LORE_COMMIT_GUARD=
|
|
4008
|
+
tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1\ngit commit -m "fix: conventional"' },
|
|
3205
4009
|
}, { cwd });
|
|
3206
4010
|
assert.equal(result.omxEventName, "pre-tool-use");
|
|
3207
|
-
assert.equal(result.outputJson
|
|
3208
|
-
assert.match(JSON.stringify(result.outputJson), /Lore protocol/);
|
|
4011
|
+
assert.equal(result.outputJson, null);
|
|
3209
4012
|
}
|
|
3210
4013
|
finally {
|
|
3211
4014
|
await rm(cwd, { recursive: true, force: true });
|
|
3212
4015
|
}
|
|
3213
4016
|
});
|
|
3214
|
-
it("restores default-
|
|
4017
|
+
it("restores default-off Lore guard when env -u unsets a config.toml fallback", async () => {
|
|
4018
|
+
await withLoreGuardConfig("1", "config-env-unset", async (cwd) => {
|
|
4019
|
+
const result = await dispatchCodexNativeHook({
|
|
4020
|
+
hook_event_name: "PreToolUse",
|
|
4021
|
+
cwd,
|
|
4022
|
+
tool_name: "Bash",
|
|
4023
|
+
tool_use_id: "tool-git-commit-lore-config-env-unset",
|
|
4024
|
+
tool_input: { command: 'env -u OMX_LORE_COMMIT_GUARD git commit -m "fix: conventional"' },
|
|
4025
|
+
}, { cwd });
|
|
4026
|
+
assert.equal(result.omxEventName, "pre-tool-use");
|
|
4027
|
+
assert.equal(result.outputJson, null);
|
|
4028
|
+
});
|
|
4029
|
+
});
|
|
4030
|
+
it("restores default-off Lore guard when env -u unsets an enabled process env", async () => {
|
|
3215
4031
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-env-unset-"));
|
|
3216
4032
|
const original = process.env.OMX_LORE_COMMIT_GUARD;
|
|
3217
4033
|
try {
|
|
3218
|
-
process.env.OMX_LORE_COMMIT_GUARD = "
|
|
4034
|
+
process.env.OMX_LORE_COMMIT_GUARD = "1";
|
|
3219
4035
|
const result = await dispatchCodexNativeHook({
|
|
3220
4036
|
hook_event_name: "PreToolUse",
|
|
3221
4037
|
cwd,
|
|
@@ -3224,8 +4040,7 @@ exit 0
|
|
|
3224
4040
|
tool_input: { command: 'env -u OMX_LORE_COMMIT_GUARD git commit -m "fix: conventional"' },
|
|
3225
4041
|
}, { cwd });
|
|
3226
4042
|
assert.equal(result.omxEventName, "pre-tool-use");
|
|
3227
|
-
assert.equal(result.outputJson
|
|
3228
|
-
assert.match(JSON.stringify(result.outputJson), /Lore protocol/);
|
|
4043
|
+
assert.equal(result.outputJson, null);
|
|
3229
4044
|
}
|
|
3230
4045
|
finally {
|
|
3231
4046
|
if (original === undefined)
|
|
@@ -3235,11 +4050,11 @@ exit 0
|
|
|
3235
4050
|
await rm(cwd, { recursive: true, force: true });
|
|
3236
4051
|
}
|
|
3237
4052
|
});
|
|
3238
|
-
it("restores default-
|
|
4053
|
+
it("restores default-off Lore guard when env -i clears an enabled process env", async () => {
|
|
3239
4054
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-env-ignore-"));
|
|
3240
4055
|
const original = process.env.OMX_LORE_COMMIT_GUARD;
|
|
3241
4056
|
try {
|
|
3242
|
-
process.env.OMX_LORE_COMMIT_GUARD = "
|
|
4057
|
+
process.env.OMX_LORE_COMMIT_GUARD = "1";
|
|
3243
4058
|
const result = await dispatchCodexNativeHook({
|
|
3244
4059
|
hook_event_name: "PreToolUse",
|
|
3245
4060
|
cwd,
|
|
@@ -3248,8 +4063,7 @@ exit 0
|
|
|
3248
4063
|
tool_input: { command: 'env -i PATH=/usr/bin git commit -m "fix: conventional"' },
|
|
3249
4064
|
}, { cwd });
|
|
3250
4065
|
assert.equal(result.omxEventName, "pre-tool-use");
|
|
3251
|
-
assert.equal(result.outputJson
|
|
3252
|
-
assert.match(JSON.stringify(result.outputJson), /Lore protocol/);
|
|
4066
|
+
assert.equal(result.outputJson, null);
|
|
3253
4067
|
}
|
|
3254
4068
|
finally {
|
|
3255
4069
|
if (original === undefined)
|
|
@@ -3259,7 +4073,7 @@ exit 0
|
|
|
3259
4073
|
await rm(cwd, { recursive: true, force: true });
|
|
3260
4074
|
}
|
|
3261
4075
|
});
|
|
3262
|
-
it("keeps Lore commit enforcement
|
|
4076
|
+
it("keeps Lore commit enforcement disabled for unknown inline guard values", async () => {
|
|
3263
4077
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-inline-unknown-"));
|
|
3264
4078
|
try {
|
|
3265
4079
|
const result = await dispatchCodexNativeHook({
|
|
@@ -3270,8 +4084,7 @@ exit 0
|
|
|
3270
4084
|
tool_input: { command: 'OMX_LORE_COMMIT_GUARD=maybe git commit -m "fix: conventional"' },
|
|
3271
4085
|
}, { cwd });
|
|
3272
4086
|
assert.equal(result.omxEventName, "pre-tool-use");
|
|
3273
|
-
assert.equal(result.outputJson
|
|
3274
|
-
assert.match(JSON.stringify(result.outputJson), /Lore protocol/);
|
|
4087
|
+
assert.equal(result.outputJson, null);
|
|
3275
4088
|
}
|
|
3276
4089
|
finally {
|
|
3277
4090
|
await rm(cwd, { recursive: true, force: true });
|
|
@@ -3300,7 +4113,7 @@ exit 0
|
|
|
3300
4113
|
await rm(cwd, { recursive: true, force: true });
|
|
3301
4114
|
}
|
|
3302
4115
|
});
|
|
3303
|
-
it("keeps Lore commit enforcement
|
|
4116
|
+
it("keeps Lore commit enforcement disabled for unknown guard values", async () => {
|
|
3304
4117
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-unknown-"));
|
|
3305
4118
|
const original = process.env.OMX_LORE_COMMIT_GUARD;
|
|
3306
4119
|
try {
|
|
@@ -3313,8 +4126,7 @@ exit 0
|
|
|
3313
4126
|
tool_input: { command: 'git commit -m "fix tests"' },
|
|
3314
4127
|
}, { cwd });
|
|
3315
4128
|
assert.equal(result.omxEventName, "pre-tool-use");
|
|
3316
|
-
assert.equal(result.outputJson
|
|
3317
|
-
assert.match(JSON.stringify(result.outputJson), /Lore protocol/);
|
|
4129
|
+
assert.equal(result.outputJson, null);
|
|
3318
4130
|
}
|
|
3319
4131
|
finally {
|
|
3320
4132
|
if (original === undefined)
|
|
@@ -3407,7 +4219,7 @@ exit 0
|
|
|
3407
4219
|
cwd,
|
|
3408
4220
|
tool_name: "Bash",
|
|
3409
4221
|
tool_use_id: "tool-git-commit-env-invalid",
|
|
3410
|
-
tool_input: { command: 'HUSKY=0 git commit -m "fix tests"' },
|
|
4222
|
+
tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 HUSKY=0 git commit -m "fix tests"' },
|
|
3411
4223
|
}, { cwd });
|
|
3412
4224
|
assert.equal(result.omxEventName, "pre-tool-use");
|
|
3413
4225
|
assert.deepEqual(result.outputJson, {
|
|
@@ -3437,7 +4249,7 @@ exit 0
|
|
|
3437
4249
|
cwd,
|
|
3438
4250
|
tool_name: "Bash",
|
|
3439
4251
|
tool_use_id: "tool-git-commit-option-invalid",
|
|
3440
|
-
tool_input: { command: 'git -c core.editor=true commit -m "fix tests"' },
|
|
4252
|
+
tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 git -c core.editor=true commit -m "fix tests"' },
|
|
3441
4253
|
}, { cwd });
|
|
3442
4254
|
assert.equal(result.omxEventName, "pre-tool-use");
|
|
3443
4255
|
assert.deepEqual(result.outputJson, {
|
|
@@ -3467,7 +4279,7 @@ exit 0
|
|
|
3467
4279
|
cwd,
|
|
3468
4280
|
tool_name: "Bash",
|
|
3469
4281
|
tool_use_id: "tool-git-exe-commit-env-wrapper-invalid",
|
|
3470
|
-
tool_input: { command: 'env git.exe commit -m "fix tests"' },
|
|
4282
|
+
tool_input: { command: 'env OMX_LORE_COMMIT_GUARD=1 git.exe commit -m "fix tests"' },
|
|
3471
4283
|
}, { cwd });
|
|
3472
4284
|
assert.equal(result.omxEventName, "pre-tool-use");
|
|
3473
4285
|
assert.deepEqual(result.outputJson, {
|
|
@@ -3497,7 +4309,7 @@ exit 0
|
|
|
3497
4309
|
cwd,
|
|
3498
4310
|
tool_name: "Bash",
|
|
3499
4311
|
tool_use_id: "tool-git-exe-commit-invalid",
|
|
3500
|
-
tool_input: { command: 'git.exe commit -m "fix tests"' },
|
|
4312
|
+
tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 git.exe commit -m "fix tests"' },
|
|
3501
4313
|
}, { cwd });
|
|
3502
4314
|
assert.equal(result.omxEventName, "pre-tool-use");
|
|
3503
4315
|
assert.deepEqual(result.outputJson, {
|
|
@@ -3527,7 +4339,7 @@ exit 0
|
|
|
3527
4339
|
cwd,
|
|
3528
4340
|
tool_name: "Bash",
|
|
3529
4341
|
tool_use_id: "tool-git-exe-commit-env-flag-wrapper-invalid",
|
|
3530
|
-
tool_input: { command: 'env -i PATH=/usr/bin git.exe commit -m "fix tests"' },
|
|
4342
|
+
tool_input: { command: 'env -i PATH=/usr/bin OMX_LORE_COMMIT_GUARD=1 git.exe commit -m "fix tests"' },
|
|
3531
4343
|
}, { cwd });
|
|
3532
4344
|
assert.equal(result.omxEventName, "pre-tool-use");
|
|
3533
4345
|
assert.deepEqual(result.outputJson, {
|
|
@@ -3557,7 +4369,7 @@ exit 0
|
|
|
3557
4369
|
cwd,
|
|
3558
4370
|
tool_name: "Bash",
|
|
3559
4371
|
tool_use_id: "tool-git-exe-commit-env-value-wrapper-invalid",
|
|
3560
|
-
tool_input: { command: 'env -u FOO git.exe commit -m "fix tests"' },
|
|
4372
|
+
tool_input: { command: 'env -u FOO OMX_LORE_COMMIT_GUARD=1 git.exe commit -m "fix tests"' },
|
|
3561
4373
|
}, { cwd });
|
|
3562
4374
|
assert.equal(result.omxEventName, "pre-tool-use");
|
|
3563
4375
|
assert.deepEqual(result.outputJson, {
|
|
@@ -3587,7 +4399,7 @@ exit 0
|
|
|
3587
4399
|
cwd,
|
|
3588
4400
|
tool_name: "Bash",
|
|
3589
4401
|
tool_use_id: "tool-git-exe-commit-windows-path-invalid",
|
|
3590
|
-
tool_input: { command: '"C:/Program Files/Git/cmd/git.exe" commit -m "fix tests"' },
|
|
4402
|
+
tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 "C:/Program Files/Git/cmd/git.exe" commit -m "fix tests"' },
|
|
3591
4403
|
}, { cwd });
|
|
3592
4404
|
assert.equal(result.omxEventName, "pre-tool-use");
|
|
3593
4405
|
assert.deepEqual(result.outputJson, {
|
|
@@ -3617,7 +4429,7 @@ exit 0
|
|
|
3617
4429
|
cwd,
|
|
3618
4430
|
tool_name: "Bash",
|
|
3619
4431
|
tool_use_id: "tool-git-exe-commit-windows-backslash-path-invalid",
|
|
3620
|
-
tool_input: { command: '"C:\\Program Files\\Git\\cmd\\git.exe" commit -m "fix tests"' },
|
|
4432
|
+
tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 "C:\\Program Files\\Git\\cmd\\git.exe" commit -m "fix tests"' },
|
|
3621
4433
|
}, { cwd });
|
|
3622
4434
|
assert.equal(result.omxEventName, "pre-tool-use");
|
|
3623
4435
|
assert.deepEqual(result.outputJson, {
|
|
@@ -3647,7 +4459,7 @@ exit 0
|
|
|
3647
4459
|
cwd,
|
|
3648
4460
|
tool_name: "Bash",
|
|
3649
4461
|
tool_use_id: "tool-git-commit-path-invalid",
|
|
3650
|
-
tool_input: { command: '/usr/bin/git commit -m "fix tests"' },
|
|
4462
|
+
tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 /usr/bin/git commit -m "fix tests"' },
|
|
3651
4463
|
}, { cwd });
|
|
3652
4464
|
assert.equal(result.omxEventName, "pre-tool-use");
|
|
3653
4465
|
assert.deepEqual(result.outputJson, {
|
|
@@ -3677,7 +4489,7 @@ exit 0
|
|
|
3677
4489
|
cwd,
|
|
3678
4490
|
tool_name: "Bash",
|
|
3679
4491
|
tool_use_id: "tool-git-commit-file",
|
|
3680
|
-
tool_input: { command: "git commit -F .git/COMMIT_EDITMSG" },
|
|
4492
|
+
tool_input: { command: "OMX_LORE_COMMIT_GUARD=1 git commit -F .git/COMMIT_EDITMSG" },
|
|
3681
4493
|
}, { cwd });
|
|
3682
4494
|
assert.equal(result.omxEventName, "pre-tool-use");
|
|
3683
4495
|
assert.deepEqual(result.outputJson, {
|
|
@@ -3706,7 +4518,7 @@ exit 0
|
|
|
3706
4518
|
tool_use_id: "tool-git-commit-missing-omx-coauthor",
|
|
3707
4519
|
tool_input: {
|
|
3708
4520
|
command: [
|
|
3709
|
-
'git commit',
|
|
4521
|
+
'OMX_LORE_COMMIT_GUARD=1 git commit',
|
|
3710
4522
|
'-m "Prevent invalid history from bypassing Lore enforcement"',
|
|
3711
4523
|
'-m "The native pre-tool-use hook now blocks inline git commit messages that skip Lore trailers or the required OmX co-author trailer."',
|
|
3712
4524
|
'-m "Constraint: Native PreToolUse can only inspect the Bash command text"',
|
|
@@ -3741,7 +4553,7 @@ exit 0
|
|
|
3741
4553
|
tool_use_id: "tool-git-commit-valid",
|
|
3742
4554
|
tool_input: {
|
|
3743
4555
|
command: [
|
|
3744
|
-
'git commit',
|
|
4556
|
+
'OMX_LORE_COMMIT_GUARD=1 git commit',
|
|
3745
4557
|
'-m "Prevent invalid history from bypassing Lore enforcement"',
|
|
3746
4558
|
'-m "The native pre-tool-use hook now blocks inline git commit messages that skip Lore trailers or the required OmX co-author trailer."',
|
|
3747
4559
|
'-m "Constraint: Native PreToolUse can only inspect the Bash command text"',
|
|
@@ -3767,7 +4579,7 @@ exit 0
|
|
|
3767
4579
|
tool_use_id: "tool-git-commit-compact-coauthor",
|
|
3768
4580
|
tool_input: {
|
|
3769
4581
|
command: [
|
|
3770
|
-
'git commit',
|
|
4582
|
+
'OMX_LORE_COMMIT_GUARD=1 git commit',
|
|
3771
4583
|
'-m "Launch lvisai.xyz intro site"',
|
|
3772
4584
|
'-m "Co-authored-by: OmX <omx@oh-my-codex.dev>"',
|
|
3773
4585
|
].join(" "),
|
|
@@ -3790,7 +4602,7 @@ exit 0
|
|
|
3790
4602
|
tool_use_id: "tool-git-commit-compact-trailers",
|
|
3791
4603
|
tool_input: {
|
|
3792
4604
|
command: [
|
|
3793
|
-
'git commit',
|
|
4605
|
+
'OMX_LORE_COMMIT_GUARD=1 git commit',
|
|
3794
4606
|
'-m "Launch lvisai.xyz intro site"',
|
|
3795
4607
|
'-m "Constraint: Native PreToolUse can only inspect inline Bash command text\nTested: node --test dist/scripts/__tests__/codex-native-hook.test.js\n\nCo-authored-by: OmX <omx@oh-my-codex.dev>"',
|
|
3796
4608
|
].join(" "),
|
|
@@ -3813,7 +4625,7 @@ exit 0
|
|
|
3813
4625
|
tool_use_id: "tool-git-commit-compact-no-separator",
|
|
3814
4626
|
tool_input: {
|
|
3815
4627
|
command: [
|
|
3816
|
-
'git commit',
|
|
4628
|
+
'OMX_LORE_COMMIT_GUARD=1 git commit',
|
|
3817
4629
|
'--message="Launch lvisai.xyz intro site\nCo-authored-by: OmX <omx@oh-my-codex.dev>"',
|
|
3818
4630
|
].join(" "),
|
|
3819
4631
|
},
|
|
@@ -4622,6 +5434,80 @@ exit 0
|
|
|
4622
5434
|
await rm(cwd, { recursive: true, force: true });
|
|
4623
5435
|
}
|
|
4624
5436
|
});
|
|
5437
|
+
it("allows Stop when terminal Autopilot run-state shadows stale session ralplan state", async () => {
|
|
5438
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-autopilot-terminal-run-state-"));
|
|
5439
|
+
try {
|
|
5440
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
5441
|
+
const sessionId = "sess-stop-autopilot-terminal-run-state";
|
|
5442
|
+
await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
|
|
5443
|
+
await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), {
|
|
5444
|
+
active: true,
|
|
5445
|
+
mode: "autopilot",
|
|
5446
|
+
current_phase: "ralplan",
|
|
5447
|
+
});
|
|
5448
|
+
await writeJson(join(stateDir, "sessions", sessionId, "run-state.json"), {
|
|
5449
|
+
version: 1,
|
|
5450
|
+
active: false,
|
|
5451
|
+
mode: "autopilot",
|
|
5452
|
+
outcome: "finish",
|
|
5453
|
+
lifecycle_outcome: "finished",
|
|
5454
|
+
current_phase: "complete",
|
|
5455
|
+
completed_at: "2026-05-20T11:00:00.000Z",
|
|
5456
|
+
updated_at: "2026-05-20T11:00:00.000Z",
|
|
5457
|
+
});
|
|
5458
|
+
const result = await dispatchCodexNativeHook({
|
|
5459
|
+
hook_event_name: "Stop",
|
|
5460
|
+
cwd,
|
|
5461
|
+
session_id: sessionId,
|
|
5462
|
+
thread_id: "thread-stop-autopilot-terminal-run-state",
|
|
5463
|
+
turn_id: "turn-stop-autopilot-terminal-run-state-1",
|
|
5464
|
+
last_assistant_message: "Done. Verification passed.",
|
|
5465
|
+
}, { cwd });
|
|
5466
|
+
assert.equal(result.omxEventName, "stop");
|
|
5467
|
+
assert.equal(result.outputJson, null);
|
|
5468
|
+
}
|
|
5469
|
+
finally {
|
|
5470
|
+
await rm(cwd, { recursive: true, force: true });
|
|
5471
|
+
}
|
|
5472
|
+
});
|
|
5473
|
+
it("still blocks Stop while Autopilot ralplan state is genuinely non-terminal", async () => {
|
|
5474
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-autopilot-active-ralplan-"));
|
|
5475
|
+
try {
|
|
5476
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
5477
|
+
const sessionId = "sess-stop-autopilot-active-ralplan";
|
|
5478
|
+
await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
|
|
5479
|
+
await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), {
|
|
5480
|
+
active: true,
|
|
5481
|
+
mode: "autopilot",
|
|
5482
|
+
current_phase: "ralplan",
|
|
5483
|
+
});
|
|
5484
|
+
await writeJson(join(stateDir, "sessions", sessionId, "run-state.json"), {
|
|
5485
|
+
version: 1,
|
|
5486
|
+
active: true,
|
|
5487
|
+
mode: "autopilot",
|
|
5488
|
+
outcome: "continue",
|
|
5489
|
+
current_phase: "ralplan",
|
|
5490
|
+
updated_at: "2026-05-20T11:00:00.000Z",
|
|
5491
|
+
});
|
|
5492
|
+
const result = await dispatchCodexNativeHook({
|
|
5493
|
+
hook_event_name: "Stop",
|
|
5494
|
+
cwd,
|
|
5495
|
+
session_id: sessionId,
|
|
5496
|
+
thread_id: "thread-stop-autopilot-active-ralplan",
|
|
5497
|
+
turn_id: "turn-stop-autopilot-active-ralplan-1",
|
|
5498
|
+
}, { cwd });
|
|
5499
|
+
assert.equal(result.omxEventName, "stop");
|
|
5500
|
+
assert.deepEqual(result.outputJson, {
|
|
5501
|
+
decision: "block",
|
|
5502
|
+
reason: "OMX autopilot is still active (phase: ralplan); continue the task and gather fresh verification evidence before stopping.",
|
|
5503
|
+
stopReason: "autopilot_ralplan",
|
|
5504
|
+
systemMessage: "OMX autopilot is still active (phase: ralplan).",
|
|
5505
|
+
});
|
|
5506
|
+
}
|
|
5507
|
+
finally {
|
|
5508
|
+
await rm(cwd, { recursive: true, force: true });
|
|
5509
|
+
}
|
|
5510
|
+
});
|
|
4625
5511
|
it("does not block Stop from stale root Autopilot planning state when the explicit session has no scoped state", async () => {
|
|
4626
5512
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-stale-root-autopilot-planning-"));
|
|
4627
5513
|
try {
|
|
@@ -4772,6 +5658,7 @@ exit 0
|
|
|
4772
5658
|
active: true,
|
|
4773
5659
|
current_phase: "team-exec",
|
|
4774
5660
|
team_name: "review-team",
|
|
5661
|
+
session_id: "sess-stop-team",
|
|
4775
5662
|
});
|
|
4776
5663
|
await writeJson(join(stateDir, "team", "review-team", "phase.json"), {
|
|
4777
5664
|
current_phase: "team-verify",
|
|
@@ -5544,19 +6431,76 @@ exit 0
|
|
|
5544
6431
|
await rm(cwd, { recursive: true, force: true });
|
|
5545
6432
|
}
|
|
5546
6433
|
});
|
|
5547
|
-
it("returns Stop continuation output from canonical team state when coarse mode state is missing", async () => {
|
|
5548
|
-
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-canonical-"));
|
|
6434
|
+
it("returns Stop continuation output from canonical team state when coarse mode state is missing", async () => {
|
|
6435
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-canonical-"));
|
|
6436
|
+
try {
|
|
6437
|
+
await initTeamState("canonical-team", "canonical stop fallback", "executor", 1, cwd, undefined, { ...process.env, OMX_SESSION_ID: "sess-stop-team-canonical" });
|
|
6438
|
+
const result = await dispatchCodexNativeHook({
|
|
6439
|
+
hook_event_name: "Stop",
|
|
6440
|
+
cwd,
|
|
6441
|
+
session_id: "sess-stop-team-canonical",
|
|
6442
|
+
}, { cwd });
|
|
6443
|
+
assert.equal(result.omxEventName, "stop");
|
|
6444
|
+
assert.deepEqual(result.outputJson, {
|
|
6445
|
+
decision: "block",
|
|
6446
|
+
reason: `OMX team pipeline is still active (canonical-team) at phase team-exec; continue coordinating until the team reaches a terminal phase.${TEAM_STOP_COMMIT_GUIDANCE}`,
|
|
6447
|
+
stopReason: "team_team-exec",
|
|
6448
|
+
systemMessage: "OMX team pipeline is still active at phase team-exec.",
|
|
6449
|
+
});
|
|
6450
|
+
}
|
|
6451
|
+
finally {
|
|
6452
|
+
await rm(cwd, { recursive: true, force: true });
|
|
6453
|
+
}
|
|
6454
|
+
});
|
|
6455
|
+
it("does not block Stop from canonical team state owned by another thread", async () => {
|
|
6456
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-canonical-other-thread-"));
|
|
6457
|
+
try {
|
|
6458
|
+
await initTeamState("canonical-other-thread-team", "canonical other-thread stop fallback", "executor", 1, cwd, undefined, { ...process.env, OMX_SESSION_ID: "sess-stop-team-canonical-thread" });
|
|
6459
|
+
const manifestPath = join(cwd, ".omx", "state", "team", "canonical-other-thread-team", "manifest.v2.json");
|
|
6460
|
+
const manifest = JSON.parse(await readFile(manifestPath, "utf-8"));
|
|
6461
|
+
await writeJson(manifestPath, {
|
|
6462
|
+
...manifest,
|
|
6463
|
+
leader: {
|
|
6464
|
+
...manifest.leader,
|
|
6465
|
+
thread_id: "thread-other",
|
|
6466
|
+
},
|
|
6467
|
+
});
|
|
6468
|
+
const result = await dispatchCodexNativeHook({
|
|
6469
|
+
hook_event_name: "Stop",
|
|
6470
|
+
cwd,
|
|
6471
|
+
session_id: "sess-stop-team-canonical-thread",
|
|
6472
|
+
thread_id: "thread-current",
|
|
6473
|
+
}, { cwd });
|
|
6474
|
+
assert.equal(result.omxEventName, "stop");
|
|
6475
|
+
assert.equal(result.outputJson, null);
|
|
6476
|
+
}
|
|
6477
|
+
finally {
|
|
6478
|
+
await rm(cwd, { recursive: true, force: true });
|
|
6479
|
+
}
|
|
6480
|
+
});
|
|
6481
|
+
it("blocks Stop from canonical team state owned by the current thread", async () => {
|
|
6482
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-canonical-current-thread-"));
|
|
5549
6483
|
try {
|
|
5550
|
-
await initTeamState("canonical-team", "canonical stop fallback", "executor", 1, cwd, undefined, { ...process.env, OMX_SESSION_ID: "sess-stop-team-canonical" });
|
|
6484
|
+
await initTeamState("canonical-current-thread-team", "canonical current-thread stop fallback", "executor", 1, cwd, undefined, { ...process.env, OMX_SESSION_ID: "sess-stop-team-canonical-current-thread" });
|
|
6485
|
+
const manifestPath = join(cwd, ".omx", "state", "team", "canonical-current-thread-team", "manifest.v2.json");
|
|
6486
|
+
const manifest = JSON.parse(await readFile(manifestPath, "utf-8"));
|
|
6487
|
+
await writeJson(manifestPath, {
|
|
6488
|
+
...manifest,
|
|
6489
|
+
leader: {
|
|
6490
|
+
...manifest.leader,
|
|
6491
|
+
thread_id: "thread-current",
|
|
6492
|
+
},
|
|
6493
|
+
});
|
|
5551
6494
|
const result = await dispatchCodexNativeHook({
|
|
5552
6495
|
hook_event_name: "Stop",
|
|
5553
6496
|
cwd,
|
|
5554
|
-
session_id: "sess-stop-team-canonical",
|
|
6497
|
+
session_id: "sess-stop-team-canonical-current-thread",
|
|
6498
|
+
thread_id: "thread-current",
|
|
5555
6499
|
}, { cwd });
|
|
5556
6500
|
assert.equal(result.omxEventName, "stop");
|
|
5557
6501
|
assert.deepEqual(result.outputJson, {
|
|
5558
6502
|
decision: "block",
|
|
5559
|
-
reason: `OMX team pipeline is still active (canonical-team) at phase team-exec; continue coordinating until the team reaches a terminal phase.${TEAM_STOP_COMMIT_GUIDANCE}`,
|
|
6503
|
+
reason: `OMX team pipeline is still active (canonical-current-thread-team) at phase team-exec; continue coordinating until the team reaches a terminal phase.${TEAM_STOP_COMMIT_GUIDANCE}`,
|
|
5560
6504
|
stopReason: "team_team-exec",
|
|
5561
6505
|
systemMessage: "OMX team pipeline is still active at phase team-exec.",
|
|
5562
6506
|
});
|
|
@@ -5738,8 +6682,8 @@ exit 0
|
|
|
5738
6682
|
const sharedRoot = join(cwd, "shared-root");
|
|
5739
6683
|
const priorTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT;
|
|
5740
6684
|
try {
|
|
5741
|
-
process.env.OMX_TEAM_STATE_ROOT =
|
|
5742
|
-
await initTeamState("canonical-root-team", "canonical stop root fallback", "executor", 1, cwd, undefined, { ...process.env, OMX_SESSION_ID: "sess-stop-team-root", OMX_TEAM_STATE_ROOT:
|
|
6685
|
+
process.env.OMX_TEAM_STATE_ROOT = sharedRoot;
|
|
6686
|
+
await initTeamState("canonical-root-team", "canonical stop root fallback", "executor", 1, cwd, undefined, { ...process.env, OMX_SESSION_ID: "sess-stop-team-root", OMX_TEAM_STATE_ROOT: sharedRoot });
|
|
5743
6687
|
const result = await dispatchCodexNativeHook({
|
|
5744
6688
|
hook_event_name: "Stop",
|
|
5745
6689
|
cwd,
|
|
@@ -5796,13 +6740,14 @@ exit 0
|
|
|
5796
6740
|
});
|
|
5797
6741
|
it("returns Stop continuation output from canonical team state rooted via OMX_TEAM_STATE_ROOT", async () => {
|
|
5798
6742
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-env-root-"));
|
|
6743
|
+
const teamStateRoot = join(cwd, "shared-team-state");
|
|
5799
6744
|
const previousTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT;
|
|
5800
6745
|
try {
|
|
5801
|
-
process.env.OMX_TEAM_STATE_ROOT =
|
|
6746
|
+
process.env.OMX_TEAM_STATE_ROOT = teamStateRoot;
|
|
5802
6747
|
await initTeamState("env-root-team", "env root stop fallback", "executor", 1, cwd, undefined, {
|
|
5803
6748
|
...process.env,
|
|
5804
6749
|
OMX_SESSION_ID: "sess-stop-team-env-root",
|
|
5805
|
-
OMX_TEAM_STATE_ROOT:
|
|
6750
|
+
OMX_TEAM_STATE_ROOT: teamStateRoot,
|
|
5806
6751
|
});
|
|
5807
6752
|
const result = await dispatchCodexNativeHook({
|
|
5808
6753
|
hook_event_name: "Stop",
|
|
@@ -6961,16 +7906,25 @@ exit 0
|
|
|
6961
7906
|
assert.equal(result.omxEventName, "stop");
|
|
6962
7907
|
const reason = String(result.outputJson?.reason);
|
|
6963
7908
|
assert.match(reason, /Ralph completion audit is missing required evidence/);
|
|
6964
|
-
assert.match(reason, /
|
|
7909
|
+
assert.match(reason, /set "completion_audit" on the Ralph state object/);
|
|
7910
|
+
assert.doesNotMatch(reason, /state\.completion_audit/);
|
|
6965
7911
|
assert.match(reason, /repo-relative JSON file/);
|
|
6966
7912
|
assert.match(reason, /Markdown artifacts and flat top-level checklist\/evidence fields are not accepted/);
|
|
6967
7913
|
assert.equal(result.outputJson?.stopReason, "ralph_completion_audit_missing_completion_audit");
|
|
6968
7914
|
const reopened = JSON.parse(await readFile(statePath, "utf-8"));
|
|
6969
|
-
assert.equal(reopened.active,
|
|
6970
|
-
assert.equal(reopened.current_phase, "
|
|
7915
|
+
assert.equal(reopened.active, false);
|
|
7916
|
+
assert.equal(reopened.current_phase, "complete");
|
|
6971
7917
|
assert.equal(reopened.completion_audit_gate, "blocked");
|
|
6972
7918
|
assert.equal(reopened.completion_audit_missing_reason, "missing_completion_audit");
|
|
6973
|
-
assert.equal(
|
|
7919
|
+
assert.equal(reopened.completed_at, "2026-05-10T12:00:00.000Z");
|
|
7920
|
+
const repeat = await dispatchCodexNativeHook({
|
|
7921
|
+
hook_event_name: "Stop",
|
|
7922
|
+
cwd,
|
|
7923
|
+
session_id: sessionId,
|
|
7924
|
+
last_assistant_message: "Done. Ralph complete.",
|
|
7925
|
+
}, { cwd });
|
|
7926
|
+
assert.equal(repeat.outputJson?.stopReason, "ralph_completion_audit_missing_completion_audit");
|
|
7927
|
+
assert.doesNotMatch(String(repeat.outputJson?.reason), /Ralph is still active/);
|
|
6974
7928
|
}
|
|
6975
7929
|
finally {
|
|
6976
7930
|
await rm(cwd, { recursive: true, force: true });
|
|
@@ -8531,6 +9485,8 @@ exit 0
|
|
|
8531
9485
|
active: true,
|
|
8532
9486
|
current_phase: "team-exec",
|
|
8533
9487
|
team_name: "review-team",
|
|
9488
|
+
session_id: "sess-stop-team-refire",
|
|
9489
|
+
thread_id: "thread-stop-team-refire",
|
|
8534
9490
|
});
|
|
8535
9491
|
await writeJson(join(stateDir, "team", "review-team", "phase.json"), {
|
|
8536
9492
|
current_phase: "team-verify",
|
|
@@ -8719,6 +9675,214 @@ exit 0
|
|
|
8719
9675
|
await rm(cwd, { recursive: true, force: true });
|
|
8720
9676
|
}
|
|
8721
9677
|
});
|
|
9678
|
+
it("does not block Stop from root team state without team_name when no session is known", async () => {
|
|
9679
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-root-team-no-session-no-name-"));
|
|
9680
|
+
try {
|
|
9681
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
9682
|
+
await mkdir(stateDir, { recursive: true });
|
|
9683
|
+
await writeJson(join(stateDir, "team-state.json"), {
|
|
9684
|
+
active: true,
|
|
9685
|
+
mode: "team",
|
|
9686
|
+
current_phase: "starting",
|
|
9687
|
+
});
|
|
9688
|
+
const result = await dispatchCodexNativeHook({
|
|
9689
|
+
hook_event_name: "Stop",
|
|
9690
|
+
cwd,
|
|
9691
|
+
}, { cwd });
|
|
9692
|
+
assert.equal(result.omxEventName, "stop");
|
|
9693
|
+
assert.equal(result.outputJson, null);
|
|
9694
|
+
}
|
|
9695
|
+
finally {
|
|
9696
|
+
await rm(cwd, { recursive: true, force: true });
|
|
9697
|
+
}
|
|
9698
|
+
});
|
|
9699
|
+
it("does not block Stop from root team state without team_name for a foreign session", async () => {
|
|
9700
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-root-team-foreign-no-name-"));
|
|
9701
|
+
try {
|
|
9702
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
9703
|
+
await mkdir(join(stateDir, "sessions", "sess-current"), { recursive: true });
|
|
9704
|
+
await writeJson(join(stateDir, "session.json"), { session_id: "sess-current" });
|
|
9705
|
+
await writeJson(join(stateDir, "team-state.json"), {
|
|
9706
|
+
active: true,
|
|
9707
|
+
mode: "team",
|
|
9708
|
+
current_phase: "starting",
|
|
9709
|
+
});
|
|
9710
|
+
const result = await dispatchCodexNativeHook({
|
|
9711
|
+
hook_event_name: "Stop",
|
|
9712
|
+
cwd,
|
|
9713
|
+
session_id: "sess-current",
|
|
9714
|
+
thread_id: "thread-current",
|
|
9715
|
+
}, { cwd });
|
|
9716
|
+
assert.equal(result.omxEventName, "stop");
|
|
9717
|
+
assert.equal(result.outputJson, null);
|
|
9718
|
+
}
|
|
9719
|
+
finally {
|
|
9720
|
+
await rm(cwd, { recursive: true, force: true });
|
|
9721
|
+
}
|
|
9722
|
+
});
|
|
9723
|
+
it("does not block Stop from another thread's stale root team state when no scoped team state exists", async () => {
|
|
9724
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-stale-root-team-thread-"));
|
|
9725
|
+
try {
|
|
9726
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
9727
|
+
await mkdir(join(stateDir, "sessions", "sess-current"), { recursive: true });
|
|
9728
|
+
await writeJson(join(stateDir, "session.json"), { session_id: "sess-current" });
|
|
9729
|
+
await writeJson(join(stateDir, "team-state.json"), {
|
|
9730
|
+
active: true,
|
|
9731
|
+
current_phase: "starting",
|
|
9732
|
+
team_name: "stale-root-thread-team",
|
|
9733
|
+
session_id: "sess-current",
|
|
9734
|
+
thread_id: "thread-other",
|
|
9735
|
+
});
|
|
9736
|
+
await writeJson(join(stateDir, "team", "stale-root-thread-team", "phase.json"), {
|
|
9737
|
+
current_phase: "team-exec",
|
|
9738
|
+
max_fix_attempts: 3,
|
|
9739
|
+
current_fix_attempt: 0,
|
|
9740
|
+
transitions: [],
|
|
9741
|
+
updated_at: new Date().toISOString(),
|
|
9742
|
+
});
|
|
9743
|
+
const result = await dispatchCodexNativeHook({
|
|
9744
|
+
hook_event_name: "Stop",
|
|
9745
|
+
cwd,
|
|
9746
|
+
session_id: "sess-current",
|
|
9747
|
+
thread_id: "thread-current",
|
|
9748
|
+
}, { cwd });
|
|
9749
|
+
assert.equal(result.omxEventName, "stop");
|
|
9750
|
+
assert.equal(result.outputJson, null);
|
|
9751
|
+
}
|
|
9752
|
+
finally {
|
|
9753
|
+
await rm(cwd, { recursive: true, force: true });
|
|
9754
|
+
}
|
|
9755
|
+
});
|
|
9756
|
+
it("does not block Stop from root team state with matching session but missing thread ownership", async () => {
|
|
9757
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-root-team-missing-thread-"));
|
|
9758
|
+
try {
|
|
9759
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
9760
|
+
await mkdir(join(stateDir, "sessions", "sess-current"), { recursive: true });
|
|
9761
|
+
await writeJson(join(stateDir, "session.json"), { session_id: "sess-current" });
|
|
9762
|
+
await writeJson(join(stateDir, "team-state.json"), {
|
|
9763
|
+
active: true,
|
|
9764
|
+
current_phase: "starting",
|
|
9765
|
+
team_name: "root-missing-thread-team",
|
|
9766
|
+
session_id: "sess-current",
|
|
9767
|
+
});
|
|
9768
|
+
await writeJson(join(stateDir, "team", "root-missing-thread-team", "phase.json"), {
|
|
9769
|
+
current_phase: "team-exec",
|
|
9770
|
+
max_fix_attempts: 3,
|
|
9771
|
+
current_fix_attempt: 0,
|
|
9772
|
+
transitions: [],
|
|
9773
|
+
updated_at: new Date().toISOString(),
|
|
9774
|
+
});
|
|
9775
|
+
const result = await dispatchCodexNativeHook({
|
|
9776
|
+
hook_event_name: "Stop",
|
|
9777
|
+
cwd,
|
|
9778
|
+
session_id: "sess-current",
|
|
9779
|
+
thread_id: "thread-current",
|
|
9780
|
+
}, { cwd });
|
|
9781
|
+
assert.equal(result.omxEventName, "stop");
|
|
9782
|
+
assert.equal(result.outputJson, null);
|
|
9783
|
+
}
|
|
9784
|
+
finally {
|
|
9785
|
+
await rm(cwd, { recursive: true, force: true });
|
|
9786
|
+
}
|
|
9787
|
+
});
|
|
9788
|
+
it("does not block Stop from root team state when canonical phase is missing", async () => {
|
|
9789
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-root-team-missing-phase-"));
|
|
9790
|
+
try {
|
|
9791
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
9792
|
+
await mkdir(join(stateDir, "sessions", "sess-current"), { recursive: true });
|
|
9793
|
+
await mkdir(join(stateDir, "team", "root-missing-phase-team"), { recursive: true });
|
|
9794
|
+
await writeJson(join(stateDir, "session.json"), { session_id: "sess-current" });
|
|
9795
|
+
await writeJson(join(stateDir, "team-state.json"), {
|
|
9796
|
+
active: true,
|
|
9797
|
+
current_phase: "starting",
|
|
9798
|
+
team_name: "root-missing-phase-team",
|
|
9799
|
+
session_id: "sess-current",
|
|
9800
|
+
thread_id: "thread-current",
|
|
9801
|
+
});
|
|
9802
|
+
const result = await dispatchCodexNativeHook({
|
|
9803
|
+
hook_event_name: "Stop",
|
|
9804
|
+
cwd,
|
|
9805
|
+
session_id: "sess-current",
|
|
9806
|
+
thread_id: "thread-current",
|
|
9807
|
+
}, { cwd });
|
|
9808
|
+
assert.equal(result.omxEventName, "stop");
|
|
9809
|
+
assert.equal(result.outputJson, null);
|
|
9810
|
+
}
|
|
9811
|
+
finally {
|
|
9812
|
+
await rm(cwd, { recursive: true, force: true });
|
|
9813
|
+
}
|
|
9814
|
+
});
|
|
9815
|
+
it("does not block Stop from session-scoped team state owned by another thread", async () => {
|
|
9816
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-scoped-team-other-thread-"));
|
|
9817
|
+
try {
|
|
9818
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
9819
|
+
await mkdir(join(stateDir, "sessions", "sess-current"), { recursive: true });
|
|
9820
|
+
await writeJson(join(stateDir, "session.json"), { session_id: "sess-current" });
|
|
9821
|
+
await writeJson(join(stateDir, "sessions", "sess-current", "team-state.json"), {
|
|
9822
|
+
active: true,
|
|
9823
|
+
current_phase: "starting",
|
|
9824
|
+
team_name: "scoped-other-thread-team",
|
|
9825
|
+
session_id: "sess-current",
|
|
9826
|
+
thread_id: "thread-other",
|
|
9827
|
+
});
|
|
9828
|
+
await writeJson(join(stateDir, "team", "scoped-other-thread-team", "phase.json"), {
|
|
9829
|
+
current_phase: "team-exec",
|
|
9830
|
+
max_fix_attempts: 3,
|
|
9831
|
+
current_fix_attempt: 0,
|
|
9832
|
+
transitions: [],
|
|
9833
|
+
updated_at: new Date().toISOString(),
|
|
9834
|
+
});
|
|
9835
|
+
const result = await dispatchCodexNativeHook({
|
|
9836
|
+
hook_event_name: "Stop",
|
|
9837
|
+
cwd,
|
|
9838
|
+
session_id: "sess-current",
|
|
9839
|
+
thread_id: "thread-current",
|
|
9840
|
+
}, { cwd });
|
|
9841
|
+
assert.equal(result.omxEventName, "stop");
|
|
9842
|
+
assert.equal(result.outputJson, null);
|
|
9843
|
+
}
|
|
9844
|
+
finally {
|
|
9845
|
+
await rm(cwd, { recursive: true, force: true });
|
|
9846
|
+
}
|
|
9847
|
+
});
|
|
9848
|
+
it("blocks Stop from session-scoped team state owned by the current session and thread", async () => {
|
|
9849
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-scoped-team-current-thread-"));
|
|
9850
|
+
try {
|
|
9851
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
9852
|
+
await mkdir(join(stateDir, "sessions", "sess-current"), { recursive: true });
|
|
9853
|
+
await writeJson(join(stateDir, "session.json"), { session_id: "sess-current" });
|
|
9854
|
+
await writeJson(join(stateDir, "sessions", "sess-current", "team-state.json"), {
|
|
9855
|
+
active: true,
|
|
9856
|
+
current_phase: "starting",
|
|
9857
|
+
team_name: "scoped-current-team",
|
|
9858
|
+
session_id: "sess-current",
|
|
9859
|
+
thread_id: "thread-current",
|
|
9860
|
+
});
|
|
9861
|
+
await writeJson(join(stateDir, "team", "scoped-current-team", "phase.json"), {
|
|
9862
|
+
current_phase: "team-exec",
|
|
9863
|
+
max_fix_attempts: 3,
|
|
9864
|
+
current_fix_attempt: 0,
|
|
9865
|
+
transitions: [],
|
|
9866
|
+
updated_at: new Date().toISOString(),
|
|
9867
|
+
});
|
|
9868
|
+
const result = await dispatchCodexNativeHook({
|
|
9869
|
+
hook_event_name: "Stop",
|
|
9870
|
+
cwd,
|
|
9871
|
+
session_id: "sess-current",
|
|
9872
|
+
thread_id: "thread-current",
|
|
9873
|
+
}, { cwd });
|
|
9874
|
+
assert.equal(result.omxEventName, "stop");
|
|
9875
|
+
assert.deepEqual(result.outputJson, {
|
|
9876
|
+
decision: "block",
|
|
9877
|
+
reason: `OMX team pipeline is still active (scoped-current-team) at phase team-exec; continue coordinating until the team reaches a terminal phase.${TEAM_STOP_COMMIT_GUIDANCE}`,
|
|
9878
|
+
stopReason: "team_team-exec",
|
|
9879
|
+
systemMessage: "OMX team pipeline is still active at phase team-exec.",
|
|
9880
|
+
});
|
|
9881
|
+
}
|
|
9882
|
+
finally {
|
|
9883
|
+
await rm(cwd, { recursive: true, force: true });
|
|
9884
|
+
}
|
|
9885
|
+
});
|
|
8722
9886
|
it("does not block Stop from another session's stale root team state when no scoped team state exists", async () => {
|
|
8723
9887
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-stale-root-team-"));
|
|
8724
9888
|
try {
|
|
@@ -8901,6 +10065,79 @@ describe("codex native hook triage integration", () => {
|
|
|
8901
10065
|
await rm(cwd, { recursive: true, force: true });
|
|
8902
10066
|
}
|
|
8903
10067
|
});
|
|
10068
|
+
it("does not activate workflow state for native subagent prompts even when canonical id is the child session", async () => {
|
|
10069
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-subagent-keyword-"));
|
|
10070
|
+
const boxedRoot = await mkdtemp(join(tmpdir(), "omx-native-subagent-keyword-boxed-"));
|
|
10071
|
+
const originalOmxRoot = process.env.OMX_ROOT;
|
|
10072
|
+
const originalOmxStateRoot = process.env.OMX_STATE_ROOT;
|
|
10073
|
+
const originalTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT;
|
|
10074
|
+
try {
|
|
10075
|
+
process.env.OMX_ROOT = boxedRoot;
|
|
10076
|
+
delete process.env.OMX_STATE_ROOT;
|
|
10077
|
+
delete process.env.OMX_TEAM_STATE_ROOT;
|
|
10078
|
+
const boxedStateDir = getBaseStateDir(cwd);
|
|
10079
|
+
await mkdir(boxedStateDir, { recursive: true });
|
|
10080
|
+
await writeJson(join(boxedStateDir, "subagent-tracking.json"), {
|
|
10081
|
+
schemaVersion: 1,
|
|
10082
|
+
sessions: {
|
|
10083
|
+
"omx-parent-session": {
|
|
10084
|
+
session_id: "omx-parent-session",
|
|
10085
|
+
leader_thread_id: "parent-native-thread",
|
|
10086
|
+
updated_at: "2026-05-21T19:04:40.000Z",
|
|
10087
|
+
threads: {
|
|
10088
|
+
"parent-native-thread": {
|
|
10089
|
+
thread_id: "parent-native-thread",
|
|
10090
|
+
kind: "leader",
|
|
10091
|
+
first_seen_at: "2026-05-21T19:04:40.000Z",
|
|
10092
|
+
last_seen_at: "2026-05-21T19:04:40.000Z",
|
|
10093
|
+
turn_count: 1,
|
|
10094
|
+
},
|
|
10095
|
+
"child-native-session": {
|
|
10096
|
+
thread_id: "child-native-session",
|
|
10097
|
+
kind: "subagent",
|
|
10098
|
+
first_seen_at: "2026-05-21T19:04:41.000Z",
|
|
10099
|
+
last_seen_at: "2026-05-21T19:04:41.000Z",
|
|
10100
|
+
turn_count: 1,
|
|
10101
|
+
mode: "review",
|
|
10102
|
+
},
|
|
10103
|
+
},
|
|
10104
|
+
},
|
|
10105
|
+
},
|
|
10106
|
+
});
|
|
10107
|
+
const result = await dispatchCodexNativeHook({
|
|
10108
|
+
hook_event_name: "UserPromptSubmit",
|
|
10109
|
+
cwd,
|
|
10110
|
+
session_id: "child-native-session",
|
|
10111
|
+
thread_id: "child-native-session",
|
|
10112
|
+
turn_id: "turn-subagent-review",
|
|
10113
|
+
prompt: [
|
|
10114
|
+
"Read-only review only. Do not edit files. Do not inspect/mutate OMX state/hooks.",
|
|
10115
|
+
"Context: The user asked for $autopilot, and this subagent must only review the patch.",
|
|
10116
|
+
].join("\n\n"),
|
|
10117
|
+
}, { cwd });
|
|
10118
|
+
const additionalContext = String(result.outputJson?.hookSpecificOutput?.additionalContext ?? "");
|
|
10119
|
+
assert.equal(additionalContext, "");
|
|
10120
|
+
assert.equal(existsSync(join(boxedStateDir, "sessions", "child-native-session", "skill-active-state.json")), false);
|
|
10121
|
+
assert.equal(existsSync(join(boxedStateDir, "sessions", "child-native-session", "autopilot-state.json")), false);
|
|
10122
|
+
assert.equal(existsSync(join(cwd, ".omx", "state", "subagent-tracking.json")), false, "subagent tracking must not leak into the source worktree when OMX_ROOT is boxed");
|
|
10123
|
+
}
|
|
10124
|
+
finally {
|
|
10125
|
+
if (originalOmxRoot === undefined)
|
|
10126
|
+
delete process.env.OMX_ROOT;
|
|
10127
|
+
else
|
|
10128
|
+
process.env.OMX_ROOT = originalOmxRoot;
|
|
10129
|
+
if (originalOmxStateRoot === undefined)
|
|
10130
|
+
delete process.env.OMX_STATE_ROOT;
|
|
10131
|
+
else
|
|
10132
|
+
process.env.OMX_STATE_ROOT = originalOmxStateRoot;
|
|
10133
|
+
if (originalTeamStateRoot === undefined)
|
|
10134
|
+
delete process.env.OMX_TEAM_STATE_ROOT;
|
|
10135
|
+
else
|
|
10136
|
+
process.env.OMX_TEAM_STATE_ROOT = originalTeamStateRoot;
|
|
10137
|
+
await rm(cwd, { recursive: true, force: true });
|
|
10138
|
+
await rm(boxedRoot, { recursive: true, force: true });
|
|
10139
|
+
}
|
|
10140
|
+
});
|
|
8904
10141
|
it("does not inject triage advisory for autopilot keyword prompts", async () => {
|
|
8905
10142
|
const cwd = await mkdtemp(join(tmpdir(), "omx-triage-keyword-autopilot-"));
|
|
8906
10143
|
try {
|
|
@@ -8925,6 +10162,48 @@ describe("codex native hook triage integration", () => {
|
|
|
8925
10162
|
await rm(cwd, { recursive: true, force: true });
|
|
8926
10163
|
}
|
|
8927
10164
|
});
|
|
10165
|
+
it("makes autopilot keyword activation observable in state, HUD context, and prompt guidance", async () => {
|
|
10166
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-autopilot-observable-"));
|
|
10167
|
+
try {
|
|
10168
|
+
await mkdir(join(cwd, ".omx", "state"), { recursive: true });
|
|
10169
|
+
await writeSessionStart(cwd, "sess-autopilot-observable");
|
|
10170
|
+
const result = await dispatchCodexNativeHook({
|
|
10171
|
+
hook_event_name: "UserPromptSubmit",
|
|
10172
|
+
cwd,
|
|
10173
|
+
session_id: "sess-autopilot-observable",
|
|
10174
|
+
thread_id: "thread-autopilot-observable",
|
|
10175
|
+
turn_id: "turn-autopilot-observable",
|
|
10176
|
+
prompt: "$autopilot implement issue #2430",
|
|
10177
|
+
}, { cwd });
|
|
10178
|
+
assert.equal(result.skillState?.skill, "autopilot");
|
|
10179
|
+
assert.equal(result.skillState?.phase, "deep-interview");
|
|
10180
|
+
assert.equal(result.skillState?.initialized_state_path, ".omx/state/sessions/sess-autopilot-observable/autopilot-state.json");
|
|
10181
|
+
const additionalContext = String(result.outputJson?.hookSpecificOutput?.additionalContext ?? "");
|
|
10182
|
+
assert.match(additionalContext, /detected workflow keyword "\$autopilot" -> autopilot/);
|
|
10183
|
+
assert.match(additionalContext, /\$deep-interview -> \$ralplan -> \$ultragoal \(\+ \$team if needed\) -> \$code-review -> \$ultraqa/);
|
|
10184
|
+
assert.match(additionalContext, /deep_interview_gate\.skip_reason/);
|
|
10185
|
+
assert.match(additionalContext, /Do not silently fall back to ordinary \$plan\/ralplan-only handling/);
|
|
10186
|
+
assert.match(additionalContext, /Codex goal-mode handoff guidance/);
|
|
10187
|
+
assert.doesNotMatch(additionalContext, /multi-step goal with no workflow keyword/);
|
|
10188
|
+
const statePath = join(cwd, ".omx", "state", "sessions", "sess-autopilot-observable", "autopilot-state.json");
|
|
10189
|
+
const modeState = JSON.parse(await readFile(statePath, "utf-8"));
|
|
10190
|
+
assert.equal(modeState.active, true);
|
|
10191
|
+
assert.equal(modeState.current_phase, "deep-interview");
|
|
10192
|
+
assert.deepEqual(modeState.state?.phase_cycle, ["deep-interview", "ralplan", "ultragoal", "code-review", "ultraqa"]);
|
|
10193
|
+
assert.deepEqual(modeState.state?.deep_interview_gate, {
|
|
10194
|
+
status: "required",
|
|
10195
|
+
skip_reason: null,
|
|
10196
|
+
rationale: "Autopilot starts at the deep-interview gate by default; clear bounded tasks may skip only with an explicit persisted skip reason.",
|
|
10197
|
+
});
|
|
10198
|
+
const hudState = await readAllState(cwd);
|
|
10199
|
+
assert.equal(hudState.autopilot?.active, true);
|
|
10200
|
+
assert.equal(hudState.autopilot?.current_phase, "deep-interview");
|
|
10201
|
+
assert.match(renderHud(hudState, "focused"), /autopilot:deep-interview/);
|
|
10202
|
+
}
|
|
10203
|
+
finally {
|
|
10204
|
+
await rm(cwd, { recursive: true, force: true });
|
|
10205
|
+
}
|
|
10206
|
+
});
|
|
8928
10207
|
// ── Group 2: HEAVY injection ─────────────────────────────────────────────
|
|
8929
10208
|
it("injects HEAVY advisory and writes prompt-routing-state for a multi-step goal prompt", async () => {
|
|
8930
10209
|
const cwd = await mkdtemp(join(tmpdir(), "omx-triage-heavy-"));
|