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
|
@@ -26,8 +26,11 @@ import { resetTriageConfigCache } from "../../hooks/triage-config.js";
|
|
|
26
26
|
import { executeStateOperation } from "../../state/operations.js";
|
|
27
27
|
import { OMX_TMUX_HUD_OWNER_ENV } from "../../hud/reconcile.js";
|
|
28
28
|
import { readAllState } from "../../hud/state.js";
|
|
29
|
+
import { renderHud } from "../../hud/render.js";
|
|
29
30
|
import { getLegacyWikiDir, serializePage, writePage } from "../../wiki/storage.js";
|
|
30
31
|
import { WIKI_SCHEMA_VERSION } from "../../wiki/types.js";
|
|
32
|
+
import { createUltragoalPlan, readUltragoalPlan } from "../../ultragoal/artifacts.js";
|
|
33
|
+
import { getBaseStateDir } from "../../state/paths.js";
|
|
31
34
|
|
|
32
35
|
function nativeHookScriptPath(): string {
|
|
33
36
|
return join(process.cwd(), "dist", "scripts", "codex-native-hook.js");
|
|
@@ -62,6 +65,40 @@ async function writeJson(path: string, value: unknown): Promise<void> {
|
|
|
62
65
|
await writeFile(path, JSON.stringify(value, null, 2));
|
|
63
66
|
}
|
|
64
67
|
|
|
68
|
+
async function withLoreGuardConfig<T>(
|
|
69
|
+
value: string,
|
|
70
|
+
prefix: string,
|
|
71
|
+
run: (cwd: string) => Promise<T>,
|
|
72
|
+
): Promise<T> {
|
|
73
|
+
const cwd = await mkdtemp(join(tmpdir(), `omx-native-hook-pretool-git-commit-lore-${prefix}-`));
|
|
74
|
+
const codexHome = await mkdtemp(join(tmpdir(), `omx-native-hook-codex-home-lore-${prefix}-`));
|
|
75
|
+
const defaultHome = await mkdtemp(join(tmpdir(), `omx-native-hook-home-lore-${prefix}-`));
|
|
76
|
+
const originalGuard = process.env.OMX_LORE_COMMIT_GUARD;
|
|
77
|
+
const originalCodexHome = process.env.CODEX_HOME;
|
|
78
|
+
const originalHome = process.env.HOME;
|
|
79
|
+
try {
|
|
80
|
+
delete process.env.OMX_LORE_COMMIT_GUARD;
|
|
81
|
+
process.env.CODEX_HOME = codexHome;
|
|
82
|
+
process.env.HOME = defaultHome;
|
|
83
|
+
await writeFile(
|
|
84
|
+
join(codexHome, "config.toml"),
|
|
85
|
+
`[shell_environment_policy.set]\nOMX_LORE_COMMIT_GUARD = "${value}"\n`,
|
|
86
|
+
"utf-8",
|
|
87
|
+
);
|
|
88
|
+
return await run(cwd);
|
|
89
|
+
} finally {
|
|
90
|
+
if (originalGuard === undefined) delete process.env.OMX_LORE_COMMIT_GUARD;
|
|
91
|
+
else process.env.OMX_LORE_COMMIT_GUARD = originalGuard;
|
|
92
|
+
if (originalCodexHome === undefined) delete process.env.CODEX_HOME;
|
|
93
|
+
else process.env.CODEX_HOME = originalCodexHome;
|
|
94
|
+
if (originalHome === undefined) delete process.env.HOME;
|
|
95
|
+
else process.env.HOME = originalHome;
|
|
96
|
+
await rm(cwd, { recursive: true, force: true });
|
|
97
|
+
await rm(codexHome, { recursive: true, force: true });
|
|
98
|
+
await rm(defaultHome, { recursive: true, force: true });
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
65
102
|
function buildWorkerStopFakeTmux(
|
|
66
103
|
tmuxLogPath: string,
|
|
67
104
|
options: { failSend?: boolean; busyLeader?: boolean } = {},
|
|
@@ -250,7 +287,7 @@ describe("codex native hook config", () => {
|
|
|
250
287
|
matcher?: string;
|
|
251
288
|
hooks?: Array<Record<string, unknown>>;
|
|
252
289
|
};
|
|
253
|
-
assert.equal(preToolUse.matcher,
|
|
290
|
+
assert.equal(preToolUse.matcher, undefined);
|
|
254
291
|
assert.match(
|
|
255
292
|
String(preToolUse.hooks?.[0]?.command || ""),
|
|
256
293
|
/codex-native-hook\.js"?$/,
|
|
@@ -729,7 +766,16 @@ describe("codex native hook dispatch", () => {
|
|
|
729
766
|
|
|
730
767
|
it("keeps subagent SessionStart from replacing the canonical leader session", async () => {
|
|
731
768
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-subagent-session-start-"));
|
|
769
|
+
const originalCodexHome = process.env.CODEX_HOME;
|
|
732
770
|
try {
|
|
771
|
+
process.env.CODEX_HOME = join(cwd, "codex-home");
|
|
772
|
+
await writeJson(join(process.env.CODEX_HOME, ".omx-config.json"), {
|
|
773
|
+
notifications: {
|
|
774
|
+
enabled: true,
|
|
775
|
+
verbosity: "session",
|
|
776
|
+
telegram: { enabled: true, botToken: "123:abc", chatId: "456" },
|
|
777
|
+
},
|
|
778
|
+
});
|
|
733
779
|
const stateDir = join(cwd, ".omx", "state");
|
|
734
780
|
const canonicalSessionId = "omx-leader-session";
|
|
735
781
|
const leaderNativeSessionId = "codex-leader-thread";
|
|
@@ -745,6 +791,16 @@ describe("codex native hook dispatch", () => {
|
|
|
745
791
|
iteration: 1,
|
|
746
792
|
max_iterations: 5,
|
|
747
793
|
});
|
|
794
|
+
await mkdir(join(cwd, ".omx", "hooks"), { recursive: true });
|
|
795
|
+
await writeFile(
|
|
796
|
+
join(cwd, ".omx", "hooks", "record-lifecycle.mjs"),
|
|
797
|
+
[
|
|
798
|
+
"import { appendFileSync } from 'node:fs';",
|
|
799
|
+
"export async function onHookEvent(event) {",
|
|
800
|
+
" appendFileSync('hook-events.jsonl', `${JSON.stringify({ event: event.event, context: event.context })}\\n`);",
|
|
801
|
+
"}",
|
|
802
|
+
].join("\n"),
|
|
803
|
+
);
|
|
748
804
|
const transcriptPath = join(cwd, "subagent-rollout.jsonl");
|
|
749
805
|
await writeFile(
|
|
750
806
|
transcriptPath,
|
|
@@ -794,6 +850,11 @@ describe("codex native hook dispatch", () => {
|
|
|
794
850
|
) as { active?: boolean; current_phase?: string };
|
|
795
851
|
assert.equal(leaderRalph.active, true);
|
|
796
852
|
assert.equal(leaderRalph.current_phase, "executing");
|
|
853
|
+
assert.equal(
|
|
854
|
+
existsSync(join(cwd, "hook-events.jsonl")),
|
|
855
|
+
false,
|
|
856
|
+
"subagent SessionStart must not independently dispatch session-start hook notifications",
|
|
857
|
+
);
|
|
797
858
|
|
|
798
859
|
const tracking = JSON.parse(
|
|
799
860
|
await readFile(join(stateDir, "subagent-tracking.json"), "utf-8"),
|
|
@@ -809,7 +870,257 @@ describe("codex native hook dispatch", () => {
|
|
|
809
870
|
assert.equal(tracking.sessions?.[leaderNativeSessionId]?.leader_thread_id, leaderNativeSessionId);
|
|
810
871
|
assert.equal(tracking.sessions?.[leaderNativeSessionId]?.threads?.[childNativeSessionId]?.kind, "subagent");
|
|
811
872
|
assert.equal(tracking.sessions?.[leaderNativeSessionId]?.threads?.[childNativeSessionId]?.mode, "critic");
|
|
873
|
+
|
|
874
|
+
await dispatchCodexNativeHook(
|
|
875
|
+
{
|
|
876
|
+
hook_event_name: "Stop",
|
|
877
|
+
cwd,
|
|
878
|
+
session_id: childNativeSessionId,
|
|
879
|
+
thread_id: childNativeSessionId,
|
|
880
|
+
turn_id: "child-stop-turn",
|
|
881
|
+
},
|
|
882
|
+
{ cwd },
|
|
883
|
+
);
|
|
884
|
+
assert.equal(
|
|
885
|
+
existsSync(join(cwd, "hook-events.jsonl")),
|
|
886
|
+
false,
|
|
887
|
+
"subagent Stop must not independently dispatch stop hook notifications",
|
|
888
|
+
);
|
|
889
|
+
} finally {
|
|
890
|
+
if (originalCodexHome === undefined) {
|
|
891
|
+
delete process.env.CODEX_HOME;
|
|
892
|
+
} else {
|
|
893
|
+
process.env.CODEX_HOME = originalCodexHome;
|
|
894
|
+
}
|
|
895
|
+
await rm(cwd, { recursive: true, force: true });
|
|
896
|
+
}
|
|
897
|
+
});
|
|
898
|
+
|
|
899
|
+
it("suppresses child-agent SessionStart hook dispatch at minimal verbosity", async () => {
|
|
900
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-subagent-session-minimal-"));
|
|
901
|
+
const originalCodexHome = process.env.CODEX_HOME;
|
|
902
|
+
try {
|
|
903
|
+
process.env.CODEX_HOME = join(cwd, "codex-home");
|
|
904
|
+
await writeJson(join(process.env.CODEX_HOME, ".omx-config.json"), {
|
|
905
|
+
notifications: {
|
|
906
|
+
enabled: true,
|
|
907
|
+
verbosity: "minimal",
|
|
908
|
+
telegram: { enabled: true, botToken: "123:abc", chatId: "456" },
|
|
909
|
+
},
|
|
910
|
+
});
|
|
911
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
912
|
+
const canonicalSessionId = "omx-leader-session-minimal";
|
|
913
|
+
const leaderNativeSessionId = "codex-leader-thread-minimal";
|
|
914
|
+
const childNativeSessionId = "codex-child-thread-minimal";
|
|
915
|
+
await mkdir(join(stateDir, "sessions", canonicalSessionId), { recursive: true });
|
|
916
|
+
await writeSessionStart(cwd, canonicalSessionId, {
|
|
917
|
+
nativeSessionId: leaderNativeSessionId,
|
|
918
|
+
});
|
|
919
|
+
await mkdir(join(cwd, ".omx", "hooks"), { recursive: true });
|
|
920
|
+
await writeFile(
|
|
921
|
+
join(cwd, ".omx", "hooks", "record-lifecycle.mjs"),
|
|
922
|
+
[
|
|
923
|
+
"import { appendFileSync } from 'node:fs';",
|
|
924
|
+
"export async function onHookEvent(event) {",
|
|
925
|
+
" appendFileSync('hook-events.jsonl', `${JSON.stringify({ event: event.event })}\\n`);",
|
|
926
|
+
"}",
|
|
927
|
+
].join("\n"),
|
|
928
|
+
);
|
|
929
|
+
const transcriptPath = join(cwd, "minimal-subagent-rollout.jsonl");
|
|
930
|
+
await writeFile(
|
|
931
|
+
transcriptPath,
|
|
932
|
+
`${JSON.stringify({
|
|
933
|
+
type: "session_meta",
|
|
934
|
+
payload: {
|
|
935
|
+
id: childNativeSessionId,
|
|
936
|
+
source: {
|
|
937
|
+
subagent: {
|
|
938
|
+
thread_spawn: {
|
|
939
|
+
parent_thread_id: leaderNativeSessionId,
|
|
940
|
+
agent_role: "verifier",
|
|
941
|
+
},
|
|
942
|
+
},
|
|
943
|
+
},
|
|
944
|
+
},
|
|
945
|
+
})}\n`,
|
|
946
|
+
);
|
|
947
|
+
|
|
948
|
+
await dispatchCodexNativeHook(
|
|
949
|
+
{
|
|
950
|
+
hook_event_name: "SessionStart",
|
|
951
|
+
cwd,
|
|
952
|
+
session_id: childNativeSessionId,
|
|
953
|
+
transcript_path: transcriptPath,
|
|
954
|
+
},
|
|
955
|
+
{ cwd, sessionOwnerPid: process.pid },
|
|
956
|
+
);
|
|
957
|
+
|
|
958
|
+
assert.equal(
|
|
959
|
+
existsSync(join(cwd, "hook-events.jsonl")),
|
|
960
|
+
false,
|
|
961
|
+
"subagent SessionStart must be suppressed at minimal verbosity",
|
|
962
|
+
);
|
|
963
|
+
} finally {
|
|
964
|
+
if (originalCodexHome === undefined) {
|
|
965
|
+
delete process.env.CODEX_HOME;
|
|
966
|
+
} else {
|
|
967
|
+
process.env.CODEX_HOME = originalCodexHome;
|
|
968
|
+
}
|
|
969
|
+
await rm(cwd, { recursive: true, force: true });
|
|
970
|
+
}
|
|
971
|
+
});
|
|
972
|
+
|
|
973
|
+
it("allows explicit child-agent lifecycle hook dispatch when includeChildAgents is enabled", async () => {
|
|
974
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-subagent-session-include-"));
|
|
975
|
+
const originalCodexHome = process.env.CODEX_HOME;
|
|
976
|
+
try {
|
|
977
|
+
process.env.CODEX_HOME = join(cwd, "codex-home");
|
|
978
|
+
await writeJson(join(process.env.CODEX_HOME, ".omx-config.json"), {
|
|
979
|
+
notifications: {
|
|
980
|
+
enabled: true,
|
|
981
|
+
verbosity: "session",
|
|
982
|
+
includeChildAgents: true,
|
|
983
|
+
telegram: { enabled: true, botToken: "123:abc", chatId: "456" },
|
|
984
|
+
},
|
|
985
|
+
});
|
|
986
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
987
|
+
const canonicalSessionId = "omx-leader-session-include";
|
|
988
|
+
const leaderNativeSessionId = "codex-leader-thread-include";
|
|
989
|
+
const childNativeSessionId = "codex-child-thread-include";
|
|
990
|
+
await mkdir(join(stateDir, "sessions", canonicalSessionId), { recursive: true });
|
|
991
|
+
await writeSessionStart(cwd, canonicalSessionId, {
|
|
992
|
+
nativeSessionId: leaderNativeSessionId,
|
|
993
|
+
});
|
|
994
|
+
await mkdir(join(cwd, ".omx", "hooks"), { recursive: true });
|
|
995
|
+
await writeFile(
|
|
996
|
+
join(cwd, ".omx", "hooks", "record-lifecycle.mjs"),
|
|
997
|
+
[
|
|
998
|
+
"import { appendFileSync } from 'node:fs';",
|
|
999
|
+
"export async function onHookEvent(event) {",
|
|
1000
|
+
" appendFileSync('hook-events.jsonl', `${JSON.stringify({ event: event.event })}\\n`);",
|
|
1001
|
+
"}",
|
|
1002
|
+
].join("\n"),
|
|
1003
|
+
);
|
|
1004
|
+
const transcriptPath = join(cwd, "included-subagent-rollout.jsonl");
|
|
1005
|
+
await writeFile(
|
|
1006
|
+
transcriptPath,
|
|
1007
|
+
`${JSON.stringify({
|
|
1008
|
+
type: "session_meta",
|
|
1009
|
+
payload: {
|
|
1010
|
+
id: childNativeSessionId,
|
|
1011
|
+
source: {
|
|
1012
|
+
subagent: {
|
|
1013
|
+
thread_spawn: {
|
|
1014
|
+
parent_thread_id: leaderNativeSessionId,
|
|
1015
|
+
agent_role: "verifier",
|
|
1016
|
+
},
|
|
1017
|
+
},
|
|
1018
|
+
},
|
|
1019
|
+
},
|
|
1020
|
+
})}\n`,
|
|
1021
|
+
);
|
|
1022
|
+
|
|
1023
|
+
await dispatchCodexNativeHook(
|
|
1024
|
+
{
|
|
1025
|
+
hook_event_name: "SessionStart",
|
|
1026
|
+
cwd,
|
|
1027
|
+
session_id: childNativeSessionId,
|
|
1028
|
+
transcript_path: transcriptPath,
|
|
1029
|
+
},
|
|
1030
|
+
{ cwd, sessionOwnerPid: process.pid },
|
|
1031
|
+
);
|
|
1032
|
+
|
|
1033
|
+
await dispatchCodexNativeHook(
|
|
1034
|
+
{
|
|
1035
|
+
hook_event_name: "Stop",
|
|
1036
|
+
cwd,
|
|
1037
|
+
session_id: childNativeSessionId,
|
|
1038
|
+
thread_id: childNativeSessionId,
|
|
1039
|
+
turn_id: "included-child-stop-turn",
|
|
1040
|
+
},
|
|
1041
|
+
{ cwd },
|
|
1042
|
+
);
|
|
1043
|
+
|
|
1044
|
+
const hookEvents = await readFile(join(cwd, "hook-events.jsonl"), "utf-8");
|
|
1045
|
+
assert.match(hookEvents, /"event":"session-start"/);
|
|
1046
|
+
assert.match(hookEvents, /"event":"stop"/);
|
|
1047
|
+
} finally {
|
|
1048
|
+
if (originalCodexHome === undefined) {
|
|
1049
|
+
delete process.env.CODEX_HOME;
|
|
1050
|
+
} else {
|
|
1051
|
+
process.env.CODEX_HOME = originalCodexHome;
|
|
1052
|
+
}
|
|
1053
|
+
await rm(cwd, { recursive: true, force: true });
|
|
1054
|
+
}
|
|
1055
|
+
});
|
|
1056
|
+
|
|
1057
|
+
it("allows child-agent lifecycle hook dispatch at agent verbosity", async () => {
|
|
1058
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-subagent-session-agent-"));
|
|
1059
|
+
const originalCodexHome = process.env.CODEX_HOME;
|
|
1060
|
+
try {
|
|
1061
|
+
process.env.CODEX_HOME = join(cwd, "codex-home");
|
|
1062
|
+
await writeJson(join(process.env.CODEX_HOME, ".omx-config.json"), {
|
|
1063
|
+
notifications: {
|
|
1064
|
+
enabled: true,
|
|
1065
|
+
verbosity: "agent",
|
|
1066
|
+
telegram: { enabled: true, botToken: "123:abc", chatId: "456" },
|
|
1067
|
+
},
|
|
1068
|
+
});
|
|
1069
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
1070
|
+
const canonicalSessionId = "omx-leader-session-agent";
|
|
1071
|
+
const leaderNativeSessionId = "codex-leader-thread-agent";
|
|
1072
|
+
const childNativeSessionId = "codex-child-thread-agent";
|
|
1073
|
+
await mkdir(join(stateDir, "sessions", canonicalSessionId), { recursive: true });
|
|
1074
|
+
await writeSessionStart(cwd, canonicalSessionId, {
|
|
1075
|
+
nativeSessionId: leaderNativeSessionId,
|
|
1076
|
+
});
|
|
1077
|
+
await mkdir(join(cwd, ".omx", "hooks"), { recursive: true });
|
|
1078
|
+
await writeFile(
|
|
1079
|
+
join(cwd, ".omx", "hooks", "record-lifecycle.mjs"),
|
|
1080
|
+
[
|
|
1081
|
+
"import { appendFileSync } from 'node:fs';",
|
|
1082
|
+
"export async function onHookEvent(event) {",
|
|
1083
|
+
" appendFileSync('hook-events.jsonl', `${JSON.stringify({ event: event.event })}\\n`);",
|
|
1084
|
+
"}",
|
|
1085
|
+
].join("\n"),
|
|
1086
|
+
);
|
|
1087
|
+
const transcriptPath = join(cwd, "agent-verbosity-subagent-rollout.jsonl");
|
|
1088
|
+
await writeFile(
|
|
1089
|
+
transcriptPath,
|
|
1090
|
+
`${JSON.stringify({
|
|
1091
|
+
type: "session_meta",
|
|
1092
|
+
payload: {
|
|
1093
|
+
id: childNativeSessionId,
|
|
1094
|
+
source: {
|
|
1095
|
+
subagent: {
|
|
1096
|
+
thread_spawn: {
|
|
1097
|
+
parent_thread_id: leaderNativeSessionId,
|
|
1098
|
+
agent_role: "verifier",
|
|
1099
|
+
},
|
|
1100
|
+
},
|
|
1101
|
+
},
|
|
1102
|
+
},
|
|
1103
|
+
})}\n`,
|
|
1104
|
+
);
|
|
1105
|
+
|
|
1106
|
+
await dispatchCodexNativeHook(
|
|
1107
|
+
{
|
|
1108
|
+
hook_event_name: "SessionStart",
|
|
1109
|
+
cwd,
|
|
1110
|
+
session_id: childNativeSessionId,
|
|
1111
|
+
transcript_path: transcriptPath,
|
|
1112
|
+
},
|
|
1113
|
+
{ cwd, sessionOwnerPid: process.pid },
|
|
1114
|
+
);
|
|
1115
|
+
|
|
1116
|
+
const hookEvents = await readFile(join(cwd, "hook-events.jsonl"), "utf-8");
|
|
1117
|
+
assert.match(hookEvents, /"event":"session-start"/);
|
|
812
1118
|
} finally {
|
|
1119
|
+
if (originalCodexHome === undefined) {
|
|
1120
|
+
delete process.env.CODEX_HOME;
|
|
1121
|
+
} else {
|
|
1122
|
+
process.env.CODEX_HOME = originalCodexHome;
|
|
1123
|
+
}
|
|
813
1124
|
await rm(cwd, { recursive: true, force: true });
|
|
814
1125
|
}
|
|
815
1126
|
});
|
|
@@ -1498,6 +1809,66 @@ describe("codex native hook dispatch", () => {
|
|
|
1498
1809
|
}
|
|
1499
1810
|
});
|
|
1500
1811
|
|
|
1812
|
+
it("does not repeat performance-goal reconciliation after a recorded objective mismatch blocker", async () => {
|
|
1813
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-performance-mismatch-blocked-stop-"));
|
|
1814
|
+
try {
|
|
1815
|
+
await writeJson(join(cwd, ".omx", "goals", "performance", "latency", "state.json"), {
|
|
1816
|
+
version: 1,
|
|
1817
|
+
workflow: "performance-goal",
|
|
1818
|
+
slug: "latency",
|
|
1819
|
+
objective: "Reduce latency",
|
|
1820
|
+
status: "blocked",
|
|
1821
|
+
lastValidation: {
|
|
1822
|
+
status: "blocked",
|
|
1823
|
+
evidence: "omx performance-goal complete rejected the fresh get_goal snapshot: Codex goal objective mismatch: expected \"reduce latency\", got \"legacy objective\".",
|
|
1824
|
+
recordedAt: "2026-05-20T00:00:00.000Z",
|
|
1825
|
+
},
|
|
1826
|
+
});
|
|
1827
|
+
|
|
1828
|
+
const result = await dispatchCodexNativeHook({
|
|
1829
|
+
hook_event_name: "Stop",
|
|
1830
|
+
cwd,
|
|
1831
|
+
session_id: "sess-performance-mismatch-blocked-stop",
|
|
1832
|
+
thread_id: "thread-performance-mismatch-blocked-stop",
|
|
1833
|
+
last_assistant_message: "Performance goal complete; next call update_goal({status: \"complete\"}).",
|
|
1834
|
+
}, { cwd });
|
|
1835
|
+
|
|
1836
|
+
assert.notEqual(result.outputJson?.decision, "block");
|
|
1837
|
+
assert.doesNotMatch(JSON.stringify(result.outputJson), /omx performance-goal complete --slug latency/);
|
|
1838
|
+
assert.doesNotMatch(JSON.stringify(result.outputJson), /get_goal snapshot reconciliation/);
|
|
1839
|
+
} finally {
|
|
1840
|
+
await rm(cwd, { recursive: true, force: true });
|
|
1841
|
+
}
|
|
1842
|
+
});
|
|
1843
|
+
|
|
1844
|
+
it("does not block Stop for an already complete performance-goal state", async () => {
|
|
1845
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-performance-complete-stop-"));
|
|
1846
|
+
try {
|
|
1847
|
+
await writeJson(join(cwd, ".omx", "goals", "performance", "latency", "state.json"), {
|
|
1848
|
+
version: 1,
|
|
1849
|
+
workflow: "performance-goal",
|
|
1850
|
+
slug: "latency",
|
|
1851
|
+
objective: "Reduce latency",
|
|
1852
|
+
status: "complete",
|
|
1853
|
+
completedAt: "2026-05-20T00:00:00.000Z",
|
|
1854
|
+
});
|
|
1855
|
+
|
|
1856
|
+
const result = await dispatchCodexNativeHook({
|
|
1857
|
+
hook_event_name: "Stop",
|
|
1858
|
+
cwd,
|
|
1859
|
+
session_id: "sess-performance-complete-stop",
|
|
1860
|
+
thread_id: "thread-performance-complete-stop",
|
|
1861
|
+
last_assistant_message: "Performance goal complete; next call update_goal({status: \"complete\"}).",
|
|
1862
|
+
}, { cwd });
|
|
1863
|
+
|
|
1864
|
+
assert.notEqual(result.outputJson?.decision, "block");
|
|
1865
|
+
assert.doesNotMatch(JSON.stringify(result.outputJson), /omx performance-goal complete --slug latency/);
|
|
1866
|
+
assert.doesNotMatch(JSON.stringify(result.outputJson), /get_goal snapshot reconciliation/);
|
|
1867
|
+
} finally {
|
|
1868
|
+
await rm(cwd, { recursive: true, force: true });
|
|
1869
|
+
}
|
|
1870
|
+
});
|
|
1871
|
+
|
|
1501
1872
|
it("blocks ultragoal Stop for concise generic goal completion claims", async () => {
|
|
1502
1873
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-generic-complete-stop-"));
|
|
1503
1874
|
try {
|
|
@@ -1546,7 +1917,7 @@ describe("codex native hook dispatch", () => {
|
|
|
1546
1917
|
}
|
|
1547
1918
|
});
|
|
1548
1919
|
|
|
1549
|
-
it("blocks ultragoal Stop with blocked checkpoint and
|
|
1920
|
+
it("blocks ultragoal Stop with blocked checkpoint and available-goal-context remediation for completed legacy snapshots", async () => {
|
|
1550
1921
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-legacy-stop-"));
|
|
1551
1922
|
try {
|
|
1552
1923
|
await writeJson(join(cwd, ".omx", "ultragoal", "goals.json"), {
|
|
@@ -1567,7 +1938,11 @@ describe("codex native hook dispatch", () => {
|
|
|
1567
1938
|
assert.equal(result.outputJson?.decision, "block");
|
|
1568
1939
|
assert.match(output, /omx ultragoal checkpoint --goal-id G001-demo --status complete/);
|
|
1569
1940
|
assert.match(output, /--status blocked/);
|
|
1570
|
-
assert.match(output, /
|
|
1941
|
+
assert.match(output, /Codex goal context/);
|
|
1942
|
+
assert.match(output, /no such table: thread_goals/);
|
|
1943
|
+
assert.match(output, /unavailable get_goal error JSON or path/);
|
|
1944
|
+
assert.match(output, /safe-recovery blocker/);
|
|
1945
|
+
assert.doesNotMatch(output, /fresh (?:Codex )?(?:thread|session)s?/i);
|
|
1571
1946
|
assert.match(output, /Hooks must not mutate Codex goal state/);
|
|
1572
1947
|
} finally {
|
|
1573
1948
|
await rm(cwd, { recursive: true, force: true });
|
|
@@ -1581,7 +1956,7 @@ describe("codex native hook dispatch", () => {
|
|
|
1581
1956
|
await writeJson(join(cwd, ".omx", "ultragoal", "goals.json"), {
|
|
1582
1957
|
version: 1,
|
|
1583
1958
|
codexGoalMode: "aggregate",
|
|
1584
|
-
codexObjective: "Complete
|
|
1959
|
+
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.",
|
|
1585
1960
|
activeGoalId: "G001-micro",
|
|
1586
1961
|
aggregateCompletion: {
|
|
1587
1962
|
status: "complete",
|
|
@@ -1863,51 +2238,335 @@ describe("codex native hook dispatch", () => {
|
|
|
1863
2238
|
}
|
|
1864
2239
|
});
|
|
1865
2240
|
|
|
1866
|
-
it("
|
|
1867
|
-
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-
|
|
2241
|
+
it("injects autopilot ralplan consensus gate guidance on prompt activation", async () => {
|
|
2242
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autopilot-ralplan-gate-"));
|
|
1868
2243
|
try {
|
|
1869
2244
|
await mkdir(join(cwd, ".omx", "state"), { recursive: true });
|
|
1870
2245
|
const result = await dispatchCodexNativeHook(
|
|
1871
2246
|
{
|
|
1872
2247
|
hook_event_name: "UserPromptSubmit",
|
|
1873
2248
|
cwd,
|
|
1874
|
-
session_id: "sess-
|
|
1875
|
-
thread_id: "thread-
|
|
1876
|
-
turn_id: "turn-
|
|
1877
|
-
prompt: "$
|
|
2249
|
+
session_id: "sess-autopilot-ralplan-gate",
|
|
2250
|
+
thread_id: "thread-autopilot-ralplan-gate",
|
|
2251
|
+
turn_id: "turn-autopilot-ralplan-gate",
|
|
2252
|
+
prompt: "$autopilot implement issue #2430",
|
|
1878
2253
|
},
|
|
1879
2254
|
{ cwd },
|
|
1880
2255
|
);
|
|
1881
2256
|
|
|
1882
2257
|
assert.equal(result.omxEventName, "keyword-detector");
|
|
1883
|
-
assert.equal(result.skillState?.skill, "
|
|
1884
|
-
assert.equal(result.skillState?.initialized_mode, undefined);
|
|
2258
|
+
assert.equal(result.skillState?.skill, "autopilot");
|
|
1885
2259
|
const message = String(
|
|
1886
2260
|
(result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext || "",
|
|
1887
2261
|
);
|
|
1888
|
-
assert.match(message, /
|
|
1889
|
-
assert.match(message, /
|
|
1890
|
-
assert.match(message, /
|
|
1891
|
-
assert.match(message, /
|
|
1892
|
-
assert.match(message, /update_goal/);
|
|
1893
|
-
assert.equal(existsSync(join(cwd, ".omx", "state", "sessions", "sess-ultragoal-1", "ultragoal-state.json")), false);
|
|
2262
|
+
assert.match(message, /Autopilot protocol:/);
|
|
2263
|
+
assert.match(message, /deep-interview -> ralplan -> ultragoal -> code-review -> ultraqa/);
|
|
2264
|
+
assert.match(message, /Planner output has been reviewed sequentially by Architect and then Critic/);
|
|
2265
|
+
assert.match(message, /do not hand off to Ultragoal or implementation until .*ralplan_architect_review.*ralplan_critic_review/);
|
|
1894
2266
|
} finally {
|
|
1895
2267
|
await rm(cwd, { recursive: true, force: true });
|
|
1896
2268
|
}
|
|
1897
2269
|
});
|
|
1898
2270
|
|
|
1899
|
-
it("
|
|
1900
|
-
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-
|
|
2271
|
+
it("records ultragoal prompt skill activation with goal-tool handoff guidance", async () => {
|
|
2272
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-"));
|
|
1901
2273
|
try {
|
|
1902
2274
|
await mkdir(join(cwd, ".omx", "state"), { recursive: true });
|
|
1903
2275
|
const result = await dispatchCodexNativeHook(
|
|
1904
2276
|
{
|
|
1905
2277
|
hook_event_name: "UserPromptSubmit",
|
|
1906
2278
|
cwd,
|
|
1907
|
-
session_id: "sess-
|
|
1908
|
-
thread_id: "thread-
|
|
1909
|
-
turn_id: "turn-
|
|
1910
|
-
prompt: "
|
|
2279
|
+
session_id: "sess-ultragoal-1",
|
|
2280
|
+
thread_id: "thread-ultragoal-1",
|
|
2281
|
+
turn_id: "turn-ultragoal-1",
|
|
2282
|
+
prompt: "$ultragoal split this launch into durable goals",
|
|
2283
|
+
},
|
|
2284
|
+
{ cwd },
|
|
2285
|
+
);
|
|
2286
|
+
|
|
2287
|
+
assert.equal(result.omxEventName, "keyword-detector");
|
|
2288
|
+
assert.equal(result.skillState?.skill, "ultragoal");
|
|
2289
|
+
assert.equal(result.skillState?.initialized_mode, "ultragoal");
|
|
2290
|
+
assert.equal(
|
|
2291
|
+
result.skillState?.initialized_state_path,
|
|
2292
|
+
".omx/state/sessions/sess-ultragoal-1/ultragoal-state.json",
|
|
2293
|
+
);
|
|
2294
|
+
const message = String(
|
|
2295
|
+
(result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext || "",
|
|
2296
|
+
);
|
|
2297
|
+
assert.match(message, /"\$ultragoal" -> ultragoal/);
|
|
2298
|
+
assert.match(message, /Ultragoal protocol:/);
|
|
2299
|
+
assert.match(message, /get_goal/);
|
|
2300
|
+
assert.match(message, /create_goal/);
|
|
2301
|
+
assert.match(message, /update_goal/);
|
|
2302
|
+
assert.match(message, /does not call `\/goal clear`/);
|
|
2303
|
+
assert.match(message, /multiple sequential ultragoal runs/);
|
|
2304
|
+
assert.equal(existsSync(join(cwd, ".omx", "state", "sessions", "sess-ultragoal-1", "ultragoal-state.json")), true);
|
|
2305
|
+
} finally {
|
|
2306
|
+
await rm(cwd, { recursive: true, force: true });
|
|
2307
|
+
}
|
|
2308
|
+
});
|
|
2309
|
+
|
|
2310
|
+
it("deactivates active deep-interview state on explicit ultragoal handoff", async () => {
|
|
2311
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-deep-interview-handoff-"));
|
|
2312
|
+
try {
|
|
2313
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
2314
|
+
const sessionDir = join(stateDir, "sessions", "sess-ultragoal-handoff");
|
|
2315
|
+
await mkdir(sessionDir, { recursive: true });
|
|
2316
|
+
await writeJson(join(sessionDir, "skill-active-state.json"), {
|
|
2317
|
+
version: 1,
|
|
2318
|
+
active: true,
|
|
2319
|
+
skill: "deep-interview",
|
|
2320
|
+
phase: "planning",
|
|
2321
|
+
session_id: "sess-ultragoal-handoff",
|
|
2322
|
+
active_skills: [{ skill: "deep-interview", phase: "planning", active: true, session_id: "sess-ultragoal-handoff" }],
|
|
2323
|
+
});
|
|
2324
|
+
await writeJson(join(sessionDir, "deep-interview-state.json"), {
|
|
2325
|
+
active: true,
|
|
2326
|
+
mode: "deep-interview",
|
|
2327
|
+
current_phase: "intent-first",
|
|
2328
|
+
session_id: "sess-ultragoal-handoff",
|
|
2329
|
+
question_enforcement: {
|
|
2330
|
+
obligation_id: "obligation-ultragoal-handoff",
|
|
2331
|
+
source: "omx-question",
|
|
2332
|
+
status: "pending",
|
|
2333
|
+
requested_at: "2026-05-21T03:00:00.000Z",
|
|
2334
|
+
},
|
|
2335
|
+
});
|
|
2336
|
+
|
|
2337
|
+
const result = await dispatchCodexNativeHook(
|
|
2338
|
+
{
|
|
2339
|
+
hook_event_name: "UserPromptSubmit",
|
|
2340
|
+
cwd,
|
|
2341
|
+
session_id: "sess-ultragoal-handoff",
|
|
2342
|
+
thread_id: "thread-ultragoal-handoff",
|
|
2343
|
+
turn_id: "turn-ultragoal-handoff",
|
|
2344
|
+
prompt: "$ultragoal turn the clarified spec into goals",
|
|
2345
|
+
},
|
|
2346
|
+
{ cwd },
|
|
2347
|
+
);
|
|
2348
|
+
|
|
2349
|
+
assert.equal(result.omxEventName, "keyword-detector");
|
|
2350
|
+
assert.equal(result.skillState?.skill, "ultragoal");
|
|
2351
|
+
assert.match(JSON.stringify(result.outputJson), /mode transiting: deep-interview -> ultragoal/);
|
|
2352
|
+
|
|
2353
|
+
const completed = JSON.parse(await readFile(join(sessionDir, "deep-interview-state.json"), "utf-8")) as {
|
|
2354
|
+
active?: boolean;
|
|
2355
|
+
current_phase?: string;
|
|
2356
|
+
question_enforcement?: { status?: string; clear_reason?: string };
|
|
2357
|
+
};
|
|
2358
|
+
assert.equal(completed.active, false);
|
|
2359
|
+
assert.equal(completed.current_phase, "completed");
|
|
2360
|
+
assert.equal(completed.question_enforcement?.status, "cleared");
|
|
2361
|
+
assert.equal(completed.question_enforcement?.clear_reason, "handoff");
|
|
2362
|
+
assert.equal(existsSync(join(sessionDir, "ultragoal-state.json")), true);
|
|
2363
|
+
|
|
2364
|
+
const edit = await dispatchCodexNativeHook(
|
|
2365
|
+
{
|
|
2366
|
+
hook_event_name: "PreToolUse",
|
|
2367
|
+
cwd,
|
|
2368
|
+
session_id: "sess-ultragoal-handoff",
|
|
2369
|
+
thread_id: "thread-ultragoal-handoff",
|
|
2370
|
+
tool_name: "Edit",
|
|
2371
|
+
tool_use_id: "tool-ultragoal-post-handoff-edit",
|
|
2372
|
+
tool_input: { file_path: "src/implementation.ts", old_string: "a", new_string: "b" },
|
|
2373
|
+
},
|
|
2374
|
+
{ cwd },
|
|
2375
|
+
);
|
|
2376
|
+
assert.equal(edit.outputJson, null);
|
|
2377
|
+
} finally {
|
|
2378
|
+
await rm(cwd, { recursive: true, force: true });
|
|
2379
|
+
}
|
|
2380
|
+
});
|
|
2381
|
+
|
|
2382
|
+
it("applies only explicit structured UserPromptSubmit ultragoal steering directives", async () => {
|
|
2383
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-steer-"));
|
|
2384
|
+
try {
|
|
2385
|
+
await createUltragoalPlan(cwd, {
|
|
2386
|
+
brief: "G002-cli-and-prompt-submit-bridge .omx/ultragoal hook steering fixture",
|
|
2387
|
+
goals: [{ title: "First", objective: "Complete first milestone with tests." }],
|
|
2388
|
+
});
|
|
2389
|
+
|
|
2390
|
+
const prose = await dispatchCodexNativeHook(
|
|
2391
|
+
{
|
|
2392
|
+
hook_event_name: "UserPromptSubmit",
|
|
2393
|
+
cwd,
|
|
2394
|
+
session_id: "sess-ultragoal-steer-1",
|
|
2395
|
+
prompt: "Please add a subgoal for docs later; this is normal prose, not a directive.",
|
|
2396
|
+
},
|
|
2397
|
+
{ cwd },
|
|
2398
|
+
);
|
|
2399
|
+
assert.equal(prose.outputJson, null);
|
|
2400
|
+
assert.equal((await readUltragoalPlan(cwd)).goals.length, 1);
|
|
2401
|
+
|
|
2402
|
+
const jsonExample = await dispatchCodexNativeHook(
|
|
2403
|
+
{
|
|
2404
|
+
hook_event_name: "UserPromptSubmit",
|
|
2405
|
+
cwd,
|
|
2406
|
+
session_id: "sess-ultragoal-steer-1",
|
|
2407
|
+
prompt: `Here is an inert example:\n\`\`\`json\n${JSON.stringify({
|
|
2408
|
+
kind: "add_subgoal",
|
|
2409
|
+
source: "user_prompt_submit",
|
|
2410
|
+
evidence: "Example JSON should not mutate .omx/ultragoal.",
|
|
2411
|
+
rationale: "Only explicit steering fences or labels are executable.",
|
|
2412
|
+
title: "Inert JSON example",
|
|
2413
|
+
objective: "This example must not be added.",
|
|
2414
|
+
})}\n\`\`\``,
|
|
2415
|
+
},
|
|
2416
|
+
{ cwd },
|
|
2417
|
+
);
|
|
2418
|
+
assert.equal(jsonExample.outputJson, null);
|
|
2419
|
+
assert.equal((await readUltragoalPlan(cwd)).goals.length, 1);
|
|
2420
|
+
|
|
2421
|
+
const result = await dispatchCodexNativeHook(
|
|
2422
|
+
{
|
|
2423
|
+
hook_event_name: "UserPromptSubmit",
|
|
2424
|
+
cwd,
|
|
2425
|
+
session_id: "sess-ultragoal-steer-1",
|
|
2426
|
+
prompt: `OMX_ULTRAGOAL_STEER: ${JSON.stringify({
|
|
2427
|
+
kind: "add_subgoal",
|
|
2428
|
+
source: "user_prompt_submit",
|
|
2429
|
+
evidence: "Prompt-submit supplied a structured .omx/ultragoal directive for G002-cli-and-prompt-submit-bridge.",
|
|
2430
|
+
rationale: "Add bounded hook regression work while preserving all completion gates.",
|
|
2431
|
+
title: "Prompt bridge regression",
|
|
2432
|
+
objective: "Verify UserPromptSubmit bounded steering bridge with tests.",
|
|
2433
|
+
})}`,
|
|
2434
|
+
},
|
|
2435
|
+
{ cwd },
|
|
2436
|
+
);
|
|
2437
|
+
|
|
2438
|
+
const message = String(
|
|
2439
|
+
(result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext || "",
|
|
2440
|
+
);
|
|
2441
|
+
assert.match(message, /bounded \.omx\/ultragoal steering/);
|
|
2442
|
+
assert.match(message, /G002-cli-and-prompt-submit-bridge/);
|
|
2443
|
+
assert.match(message, /accepted/);
|
|
2444
|
+
const plan = await readUltragoalPlan(cwd);
|
|
2445
|
+
assert.equal(plan.goals.length, 2);
|
|
2446
|
+
assert.equal(plan.goals[1]?.title, "Prompt bridge regression");
|
|
2447
|
+
} finally {
|
|
2448
|
+
await rm(cwd, { recursive: true, force: true });
|
|
2449
|
+
}
|
|
2450
|
+
});
|
|
2451
|
+
|
|
2452
|
+
it("does not apply UserPromptSubmit ultragoal steering from native subagent prompts", async () => {
|
|
2453
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-steer-subagent-"));
|
|
2454
|
+
try {
|
|
2455
|
+
await createUltragoalPlan(cwd, {
|
|
2456
|
+
brief: "G002-cli-and-prompt-submit-bridge .omx/ultragoal subagent steering fixture",
|
|
2457
|
+
goals: [{ title: "First", objective: "Complete first milestone with tests." }],
|
|
2458
|
+
});
|
|
2459
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
2460
|
+
const canonicalSessionId = "sess-ultragoal-parent";
|
|
2461
|
+
const leaderNativeSessionId = "native-ultragoal-parent";
|
|
2462
|
+
const childNativeSessionId = "native-ultragoal-child";
|
|
2463
|
+
const nowIso = new Date().toISOString();
|
|
2464
|
+
await writeJson(join(stateDir, "session.json"), {
|
|
2465
|
+
session_id: canonicalSessionId,
|
|
2466
|
+
native_session_id: leaderNativeSessionId,
|
|
2467
|
+
});
|
|
2468
|
+
await writeJson(join(stateDir, "subagent-tracking.json"), {
|
|
2469
|
+
schemaVersion: 1,
|
|
2470
|
+
sessions: {
|
|
2471
|
+
[canonicalSessionId]: {
|
|
2472
|
+
session_id: canonicalSessionId,
|
|
2473
|
+
leader_thread_id: leaderNativeSessionId,
|
|
2474
|
+
updated_at: nowIso,
|
|
2475
|
+
threads: {
|
|
2476
|
+
[leaderNativeSessionId]: {
|
|
2477
|
+
thread_id: leaderNativeSessionId,
|
|
2478
|
+
kind: "leader",
|
|
2479
|
+
first_seen_at: nowIso,
|
|
2480
|
+
last_seen_at: nowIso,
|
|
2481
|
+
turn_count: 1,
|
|
2482
|
+
},
|
|
2483
|
+
[childNativeSessionId]: {
|
|
2484
|
+
thread_id: childNativeSessionId,
|
|
2485
|
+
kind: "subagent",
|
|
2486
|
+
first_seen_at: nowIso,
|
|
2487
|
+
last_seen_at: nowIso,
|
|
2488
|
+
turn_count: 1,
|
|
2489
|
+
mode: "architect",
|
|
2490
|
+
},
|
|
2491
|
+
},
|
|
2492
|
+
},
|
|
2493
|
+
},
|
|
2494
|
+
});
|
|
2495
|
+
|
|
2496
|
+
const result = await dispatchCodexNativeHook(
|
|
2497
|
+
{
|
|
2498
|
+
hook_event_name: "UserPromptSubmit",
|
|
2499
|
+
cwd,
|
|
2500
|
+
session_id: childNativeSessionId,
|
|
2501
|
+
thread_id: childNativeSessionId,
|
|
2502
|
+
turn_id: "turn-ultragoal-child-1",
|
|
2503
|
+
prompt: `OMX_ULTRAGOAL_STEER: ${JSON.stringify({
|
|
2504
|
+
kind: "add_subgoal",
|
|
2505
|
+
source: "user_prompt_submit",
|
|
2506
|
+
evidence: "Subagent prompt text must be literal delegated context.",
|
|
2507
|
+
rationale: "Subagent prompts should not mutate the parent .omx/ultragoal ledger.",
|
|
2508
|
+
title: "Subagent should not add this",
|
|
2509
|
+
objective: "This must remain literal prompt text.",
|
|
2510
|
+
})}`,
|
|
2511
|
+
},
|
|
2512
|
+
{ cwd },
|
|
2513
|
+
);
|
|
2514
|
+
|
|
2515
|
+
assert.equal(result.outputJson, null);
|
|
2516
|
+
const plan = await readUltragoalPlan(cwd);
|
|
2517
|
+
assert.equal(plan.goals.length, 1);
|
|
2518
|
+
const ledger = await readFile(join(cwd, ".omx/ultragoal/ledger.jsonl"), "utf-8");
|
|
2519
|
+
assert.equal((ledger.match(/"event":"steering_accepted"/g) ?? []).length, 0);
|
|
2520
|
+
assert.equal((ledger.match(/"event":"steering_rejected"/g) ?? []).length, 0);
|
|
2521
|
+
} finally {
|
|
2522
|
+
await rm(cwd, { recursive: true, force: true });
|
|
2523
|
+
}
|
|
2524
|
+
});
|
|
2525
|
+
|
|
2526
|
+
it("dedupes repeated UserPromptSubmit ultragoal steering directives by prompt signature", async () => {
|
|
2527
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-steer-dedupe-"));
|
|
2528
|
+
try {
|
|
2529
|
+
await createUltragoalPlan(cwd, {
|
|
2530
|
+
brief: "G002-cli-and-prompt-submit-bridge .omx/ultragoal dedupe fixture",
|
|
2531
|
+
goals: [{ title: "First", objective: "Complete first milestone with tests." }],
|
|
2532
|
+
});
|
|
2533
|
+
const prompt = `\`\`\`omx-ultragoal-steer
|
|
2534
|
+
${JSON.stringify({
|
|
2535
|
+
kind: "add_subgoal",
|
|
2536
|
+
source: "user_prompt_submit",
|
|
2537
|
+
evidence: "Structured prompt-submit directive adds exactly one deduped goal.",
|
|
2538
|
+
rationale: "Use idempotent bridge semantics for repeated hook delivery.",
|
|
2539
|
+
title: "Deduped bridge regression",
|
|
2540
|
+
objective: "Verify repeated UserPromptSubmit steering does not duplicate goals.",
|
|
2541
|
+
})}
|
|
2542
|
+
\`\`\``;
|
|
2543
|
+
await dispatchCodexNativeHook({ hook_event_name: "UserPromptSubmit", cwd, session_id: "sess-dedupe", prompt }, { cwd });
|
|
2544
|
+
const second = await dispatchCodexNativeHook({ hook_event_name: "UserPromptSubmit", cwd, session_id: "sess-dedupe", prompt }, { cwd });
|
|
2545
|
+
const message = String(
|
|
2546
|
+
(second.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext || "",
|
|
2547
|
+
);
|
|
2548
|
+
assert.match(message, /deduped/);
|
|
2549
|
+
const plan = await readUltragoalPlan(cwd);
|
|
2550
|
+
assert.equal(plan.goals.filter((goal) => goal.title === "Deduped bridge regression").length, 1);
|
|
2551
|
+
const ledger = await readFile(join(cwd, ".omx/ultragoal/ledger.jsonl"), "utf-8");
|
|
2552
|
+
assert.equal((ledger.match(/"event":"steering_accepted"/g) ?? []).length, 1);
|
|
2553
|
+
} finally {
|
|
2554
|
+
await rm(cwd, { recursive: true, force: true });
|
|
2555
|
+
}
|
|
2556
|
+
});
|
|
2557
|
+
|
|
2558
|
+
it("normalizes the Korean keyboard typo for ulw during UserPromptSubmit activation", async () => {
|
|
2559
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ulw-ko-"));
|
|
2560
|
+
try {
|
|
2561
|
+
await mkdir(join(cwd, ".omx", "state"), { recursive: true });
|
|
2562
|
+
const result = await dispatchCodexNativeHook(
|
|
2563
|
+
{
|
|
2564
|
+
hook_event_name: "UserPromptSubmit",
|
|
2565
|
+
cwd,
|
|
2566
|
+
session_id: "sess-ulw-ko",
|
|
2567
|
+
thread_id: "thread-ulw-ko",
|
|
2568
|
+
turn_id: "turn-ulw-ko",
|
|
2569
|
+
prompt: "ㅕㅣㅈ로 병렬 처리해줘",
|
|
1911
2570
|
},
|
|
1912
2571
|
{ cwd },
|
|
1913
2572
|
);
|
|
@@ -3341,79 +4000,256 @@ exit 0
|
|
|
3341
4000
|
}
|
|
3342
4001
|
});
|
|
3343
4002
|
|
|
3344
|
-
it("
|
|
3345
|
-
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-
|
|
4003
|
+
it("blocks implementation file edits while deep-interview remains active after a clarified answer", async () => {
|
|
4004
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-deep-interview-edit-block-"));
|
|
3346
4005
|
try {
|
|
4006
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
4007
|
+
const sessionDir = join(stateDir, "sessions", "sess-di-edit-block");
|
|
4008
|
+
await mkdir(sessionDir, { recursive: true });
|
|
4009
|
+
await writeJson(join(stateDir, "session.json"), { session_id: "sess-di-edit-block", cwd });
|
|
4010
|
+
await writeJson(join(sessionDir, "skill-active-state.json"), {
|
|
4011
|
+
version: 1,
|
|
4012
|
+
active: true,
|
|
4013
|
+
skill: "deep-interview",
|
|
4014
|
+
phase: "planning",
|
|
4015
|
+
session_id: "sess-di-edit-block",
|
|
4016
|
+
thread_id: "thread-di-edit-block",
|
|
4017
|
+
active_skills: [{ skill: "deep-interview", phase: "planning", active: true, session_id: "sess-di-edit-block", thread_id: "thread-di-edit-block" }],
|
|
4018
|
+
});
|
|
4019
|
+
await writeJson(join(sessionDir, "deep-interview-state.json"), {
|
|
4020
|
+
active: true,
|
|
4021
|
+
mode: "deep-interview",
|
|
4022
|
+
current_phase: "intent-first",
|
|
4023
|
+
session_id: "sess-di-edit-block",
|
|
4024
|
+
thread_id: "thread-di-edit-block",
|
|
4025
|
+
rounds: [{ answer: "Implement by editing src/hooks/keyword-detector.ts and add tests." }],
|
|
4026
|
+
});
|
|
4027
|
+
|
|
3347
4028
|
const result = await dispatchCodexNativeHook(
|
|
3348
4029
|
{
|
|
3349
4030
|
hook_event_name: "PreToolUse",
|
|
3350
4031
|
cwd,
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
4032
|
+
session_id: "sess-di-edit-block",
|
|
4033
|
+
thread_id: "thread-di-edit-block",
|
|
4034
|
+
tool_name: "Edit",
|
|
4035
|
+
tool_use_id: "tool-di-edit-block",
|
|
4036
|
+
tool_input: { file_path: "src/hooks/keyword-detector.ts", old_string: "a", new_string: "b" },
|
|
3354
4037
|
},
|
|
3355
4038
|
{ cwd },
|
|
3356
4039
|
);
|
|
3357
4040
|
|
|
3358
4041
|
assert.equal(result.omxEventName, "pre-tool-use");
|
|
3359
|
-
assert.
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
systemMessage:
|
|
3364
|
-
"Destructive Bash command detected (`rm -rf dist`). Confirm the target and expected side effects before running it.",
|
|
3365
|
-
});
|
|
4042
|
+
assert.equal((result.outputJson as { decision?: string } | null)?.decision, "block");
|
|
4043
|
+
assert.match(String((result.outputJson as { reason?: string } | null)?.reason ?? ""), /Deep-interview is active/);
|
|
4044
|
+
assert.match(JSON.stringify(result.outputJson), /requirements\/spec mode/);
|
|
4045
|
+
assert.match(JSON.stringify(result.outputJson), /\$ralplan/);
|
|
3366
4046
|
} finally {
|
|
3367
4047
|
await rm(cwd, { recursive: true, force: true });
|
|
3368
4048
|
}
|
|
3369
4049
|
});
|
|
3370
4050
|
|
|
3371
|
-
it("
|
|
3372
|
-
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-
|
|
4051
|
+
it("allows deep-interview artifact and state writes while blocking implementation Bash writes", async () => {
|
|
4052
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-deep-interview-artifact-"));
|
|
3373
4053
|
try {
|
|
3374
|
-
const
|
|
4054
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
4055
|
+
const sessionDir = join(stateDir, "sessions", "sess-di-artifact");
|
|
4056
|
+
await mkdir(sessionDir, { recursive: true });
|
|
4057
|
+
await writeJson(join(stateDir, "session.json"), { session_id: "sess-di-artifact", cwd });
|
|
4058
|
+
await writeJson(join(sessionDir, "skill-active-state.json"), {
|
|
4059
|
+
version: 1,
|
|
4060
|
+
active: true,
|
|
4061
|
+
skill: "deep-interview",
|
|
4062
|
+
phase: "planning",
|
|
4063
|
+
session_id: "sess-di-artifact",
|
|
4064
|
+
active_skills: [{ skill: "deep-interview", phase: "planning", active: true, session_id: "sess-di-artifact" }],
|
|
4065
|
+
});
|
|
4066
|
+
await writeJson(join(sessionDir, "deep-interview-state.json"), {
|
|
4067
|
+
active: true,
|
|
4068
|
+
mode: "deep-interview",
|
|
4069
|
+
current_phase: "intent-first",
|
|
4070
|
+
session_id: "sess-di-artifact",
|
|
4071
|
+
});
|
|
4072
|
+
|
|
4073
|
+
const allowedWrite = await dispatchCodexNativeHook(
|
|
3375
4074
|
{
|
|
3376
4075
|
hook_event_name: "PreToolUse",
|
|
3377
4076
|
cwd,
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
4077
|
+
session_id: "sess-di-artifact",
|
|
4078
|
+
tool_name: "Write",
|
|
4079
|
+
tool_use_id: "tool-di-spec-write",
|
|
4080
|
+
tool_input: { file_path: ".omx/specs/deep-interview-demo.md", content: "# Spec" },
|
|
3381
4081
|
},
|
|
3382
4082
|
{ cwd },
|
|
3383
4083
|
);
|
|
4084
|
+
assert.equal(allowedWrite.outputJson, null);
|
|
3384
4085
|
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
4086
|
+
const allowedBash = await dispatchCodexNativeHook(
|
|
4087
|
+
{
|
|
4088
|
+
hook_event_name: "PreToolUse",
|
|
4089
|
+
cwd,
|
|
4090
|
+
session_id: "sess-di-artifact",
|
|
4091
|
+
tool_name: "Bash",
|
|
4092
|
+
tool_use_id: "tool-di-context-bash",
|
|
4093
|
+
tool_input: { command: "cat > .omx/context/demo.md <<'EOF'\n# Context\nEOF" },
|
|
4094
|
+
},
|
|
4095
|
+
{ cwd },
|
|
4096
|
+
);
|
|
4097
|
+
assert.equal(allowedBash.outputJson, null);
|
|
3391
4098
|
|
|
3392
|
-
|
|
3393
|
-
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-slop-warn-"));
|
|
3394
|
-
try {
|
|
3395
|
-
const result = await dispatchCodexNativeHook(
|
|
4099
|
+
const allowedAppendBash = await dispatchCodexNativeHook(
|
|
3396
4100
|
{
|
|
3397
4101
|
hook_event_name: "PreToolUse",
|
|
3398
4102
|
cwd,
|
|
4103
|
+
session_id: "sess-di-artifact",
|
|
3399
4104
|
tool_name: "Bash",
|
|
3400
|
-
tool_use_id: "tool-
|
|
3401
|
-
tool_input: {
|
|
3402
|
-
command: [
|
|
3403
|
-
"cat > src/runtime.ts <<'EOF'",
|
|
3404
|
-
"export function loadRuntime() {",
|
|
3405
|
-
" // implement a quick hack fallback if it fails",
|
|
3406
|
-
" return process.env.RUNTIME || 'local';",
|
|
3407
|
-
"}",
|
|
3408
|
-
"EOF",
|
|
3409
|
-
].join("\n"),
|
|
3410
|
-
},
|
|
4105
|
+
tool_use_id: "tool-di-context-append-bash",
|
|
4106
|
+
tool_input: { command: "echo more context >> .omx/context/demo.md" },
|
|
3411
4107
|
},
|
|
3412
4108
|
{ cwd },
|
|
3413
4109
|
);
|
|
4110
|
+
assert.equal(allowedAppendBash.outputJson, null);
|
|
3414
4111
|
|
|
3415
|
-
|
|
3416
|
-
|
|
4112
|
+
const blockedBash = await dispatchCodexNativeHook(
|
|
4113
|
+
{
|
|
4114
|
+
hook_event_name: "PreToolUse",
|
|
4115
|
+
cwd,
|
|
4116
|
+
session_id: "sess-di-artifact",
|
|
4117
|
+
tool_name: "Bash",
|
|
4118
|
+
tool_use_id: "tool-di-src-bash",
|
|
4119
|
+
tool_input: { command: "cat > src/implementation.ts <<'EOF'\nexport const x = 1;\nEOF" },
|
|
4120
|
+
},
|
|
4121
|
+
{ cwd },
|
|
4122
|
+
);
|
|
4123
|
+
assert.equal((blockedBash.outputJson as { decision?: string } | null)?.decision, "block");
|
|
4124
|
+
} finally {
|
|
4125
|
+
await rm(cwd, { recursive: true, force: true });
|
|
4126
|
+
}
|
|
4127
|
+
});
|
|
4128
|
+
|
|
4129
|
+
it("allows implementation tools after an explicit deep-interview handoff deactivates the mode", async () => {
|
|
4130
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-deep-interview-handoff-"));
|
|
4131
|
+
try {
|
|
4132
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
4133
|
+
const sessionDir = join(stateDir, "sessions", "sess-di-handoff");
|
|
4134
|
+
await mkdir(sessionDir, { recursive: true });
|
|
4135
|
+
await writeJson(join(sessionDir, "skill-active-state.json"), {
|
|
4136
|
+
version: 1,
|
|
4137
|
+
active: true,
|
|
4138
|
+
skill: "deep-interview",
|
|
4139
|
+
phase: "planning",
|
|
4140
|
+
session_id: "sess-di-handoff",
|
|
4141
|
+
active_skills: [{ skill: "deep-interview", phase: "planning", active: true, session_id: "sess-di-handoff" }],
|
|
4142
|
+
});
|
|
4143
|
+
await writeJson(join(sessionDir, "deep-interview-state.json"), {
|
|
4144
|
+
active: true,
|
|
4145
|
+
mode: "deep-interview",
|
|
4146
|
+
current_phase: "intent-first",
|
|
4147
|
+
session_id: "sess-di-handoff",
|
|
4148
|
+
});
|
|
4149
|
+
|
|
4150
|
+
await dispatchCodexNativeHook(
|
|
4151
|
+
{
|
|
4152
|
+
hook_event_name: "UserPromptSubmit",
|
|
4153
|
+
cwd,
|
|
4154
|
+
session_id: "sess-di-handoff",
|
|
4155
|
+
prompt: "$ralph implement the clarified spec in src/implementation.ts",
|
|
4156
|
+
},
|
|
4157
|
+
{ cwd },
|
|
4158
|
+
);
|
|
4159
|
+
|
|
4160
|
+
const result = await dispatchCodexNativeHook(
|
|
4161
|
+
{
|
|
4162
|
+
hook_event_name: "PreToolUse",
|
|
4163
|
+
cwd,
|
|
4164
|
+
session_id: "sess-di-handoff",
|
|
4165
|
+
tool_name: "Edit",
|
|
4166
|
+
tool_use_id: "tool-di-post-handoff-edit",
|
|
4167
|
+
tool_input: { file_path: "src/implementation.ts", old_string: "a", new_string: "b" },
|
|
4168
|
+
},
|
|
4169
|
+
{ cwd },
|
|
4170
|
+
);
|
|
4171
|
+
|
|
4172
|
+
assert.equal(result.outputJson, null);
|
|
4173
|
+
const completed = JSON.parse(await readFile(join(sessionDir, "deep-interview-state.json"), "utf-8")) as { active?: boolean };
|
|
4174
|
+
assert.equal(completed.active, false);
|
|
4175
|
+
} finally {
|
|
4176
|
+
await rm(cwd, { recursive: true, force: true });
|
|
4177
|
+
}
|
|
4178
|
+
});
|
|
4179
|
+
|
|
4180
|
+
it("returns a destructive-command caution on PreToolUse for rm -rf dist", async () => {
|
|
4181
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-danger-"));
|
|
4182
|
+
try {
|
|
4183
|
+
const result = await dispatchCodexNativeHook(
|
|
4184
|
+
{
|
|
4185
|
+
hook_event_name: "PreToolUse",
|
|
4186
|
+
cwd,
|
|
4187
|
+
tool_name: "Bash",
|
|
4188
|
+
tool_use_id: "tool-danger",
|
|
4189
|
+
tool_input: { command: "rm -rf dist" },
|
|
4190
|
+
},
|
|
4191
|
+
{ cwd },
|
|
4192
|
+
);
|
|
4193
|
+
|
|
4194
|
+
assert.equal(result.omxEventName, "pre-tool-use");
|
|
4195
|
+
assert.deepEqual(result.outputJson, {
|
|
4196
|
+
hookSpecificOutput: {
|
|
4197
|
+
hookEventName: "PreToolUse",
|
|
4198
|
+
},
|
|
4199
|
+
systemMessage:
|
|
4200
|
+
"Destructive Bash command detected (`rm -rf dist`). Confirm the target and expected side effects before running it.",
|
|
4201
|
+
});
|
|
4202
|
+
} finally {
|
|
4203
|
+
await rm(cwd, { recursive: true, force: true });
|
|
4204
|
+
}
|
|
4205
|
+
});
|
|
4206
|
+
|
|
4207
|
+
it("stays silent on PreToolUse for neutral pwd", async () => {
|
|
4208
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-neutral-"));
|
|
4209
|
+
try {
|
|
4210
|
+
const result = await dispatchCodexNativeHook(
|
|
4211
|
+
{
|
|
4212
|
+
hook_event_name: "PreToolUse",
|
|
4213
|
+
cwd,
|
|
4214
|
+
tool_name: "Bash",
|
|
4215
|
+
tool_use_id: "tool-neutral",
|
|
4216
|
+
tool_input: { command: "pwd" },
|
|
4217
|
+
},
|
|
4218
|
+
{ cwd },
|
|
4219
|
+
);
|
|
4220
|
+
|
|
4221
|
+
assert.equal(result.omxEventName, "pre-tool-use");
|
|
4222
|
+
assert.equal(result.outputJson, null);
|
|
4223
|
+
} finally {
|
|
4224
|
+
await rm(cwd, { recursive: true, force: true });
|
|
4225
|
+
}
|
|
4226
|
+
});
|
|
4227
|
+
|
|
4228
|
+
it("warns on PreToolUse for vague sloppy fallback implementation framing", async () => {
|
|
4229
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-slop-warn-"));
|
|
4230
|
+
try {
|
|
4231
|
+
const result = await dispatchCodexNativeHook(
|
|
4232
|
+
{
|
|
4233
|
+
hook_event_name: "PreToolUse",
|
|
4234
|
+
cwd,
|
|
4235
|
+
tool_name: "Bash",
|
|
4236
|
+
tool_use_id: "tool-slop-warn",
|
|
4237
|
+
tool_input: {
|
|
4238
|
+
command: [
|
|
4239
|
+
"cat > src/runtime.ts <<'EOF'",
|
|
4240
|
+
"export function loadRuntime() {",
|
|
4241
|
+
" // implement a quick hack fallback if it fails",
|
|
4242
|
+
" return process.env.RUNTIME || 'local';",
|
|
4243
|
+
"}",
|
|
4244
|
+
"EOF",
|
|
4245
|
+
].join("\n"),
|
|
4246
|
+
},
|
|
4247
|
+
},
|
|
4248
|
+
{ cwd },
|
|
4249
|
+
);
|
|
4250
|
+
|
|
4251
|
+
assert.equal(result.omxEventName, "pre-tool-use");
|
|
4252
|
+
assert.equal((result.outputJson as { decision?: string } | null)?.decision, undefined);
|
|
3417
4253
|
assert.equal((result.outputJson as { hookSpecificOutput?: { hookEventName?: string } } | null)?.hookSpecificOutput?.hookEventName, "PreToolUse");
|
|
3418
4254
|
assert.match(JSON.stringify(result.outputJson), /don't make potential slop/);
|
|
3419
4255
|
assert.match(JSON.stringify(result.outputJson), /architect/);
|
|
@@ -3768,7 +4604,7 @@ exit 0
|
|
|
3768
4604
|
cwd,
|
|
3769
4605
|
tool_name: "Bash",
|
|
3770
4606
|
tool_use_id: "tool-slop-git-priority",
|
|
3771
|
-
tool_input: { command: 'git commit -m "quick hack fallback if it fails"' },
|
|
4607
|
+
tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 git commit -m "quick hack fallback if it fails"' },
|
|
3772
4608
|
},
|
|
3773
4609
|
{ cwd },
|
|
3774
4610
|
);
|
|
@@ -3791,7 +4627,7 @@ exit 0
|
|
|
3791
4627
|
cwd,
|
|
3792
4628
|
tool_name: "Bash",
|
|
3793
4629
|
tool_use_id: "tool-git-commit-invalid",
|
|
3794
|
-
tool_input: { command: 'git commit -m "fix tests"' },
|
|
4630
|
+
tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 git commit -m "fix tests"' },
|
|
3795
4631
|
},
|
|
3796
4632
|
{ cwd },
|
|
3797
4633
|
);
|
|
@@ -3820,24 +4656,26 @@ exit 0
|
|
|
3820
4656
|
}
|
|
3821
4657
|
});
|
|
3822
4658
|
|
|
3823
|
-
|
|
3824
|
-
|
|
4659
|
+
|
|
4660
|
+
it("blocks PreToolUse git commit when process env explicitly enables the Lore commit guard", async () => {
|
|
4661
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-env-enabled-"));
|
|
3825
4662
|
const original = process.env.OMX_LORE_COMMIT_GUARD;
|
|
3826
4663
|
try {
|
|
3827
|
-
process.env.OMX_LORE_COMMIT_GUARD = "
|
|
4664
|
+
process.env.OMX_LORE_COMMIT_GUARD = "1";
|
|
3828
4665
|
const result = await dispatchCodexNativeHook(
|
|
3829
4666
|
{
|
|
3830
4667
|
hook_event_name: "PreToolUse",
|
|
3831
4668
|
cwd,
|
|
3832
4669
|
tool_name: "Bash",
|
|
3833
|
-
tool_use_id: "tool-git-commit-lore-
|
|
3834
|
-
tool_input: { command: 'git commit -m "fix
|
|
4670
|
+
tool_use_id: "tool-git-commit-lore-env-enabled",
|
|
4671
|
+
tool_input: { command: 'git commit -m "fix tests"' },
|
|
3835
4672
|
},
|
|
3836
4673
|
{ cwd },
|
|
3837
4674
|
);
|
|
3838
4675
|
|
|
3839
4676
|
assert.equal(result.omxEventName, "pre-tool-use");
|
|
3840
|
-
assert.equal(result.outputJson
|
|
4677
|
+
assert.equal((result.outputJson as { decision?: string } | null)?.decision, "block");
|
|
4678
|
+
assert.match(JSON.stringify(result.outputJson), /Lore protocol/);
|
|
3841
4679
|
} finally {
|
|
3842
4680
|
if (original === undefined) delete process.env.OMX_LORE_COMMIT_GUARD;
|
|
3843
4681
|
else process.env.OMX_LORE_COMMIT_GUARD = original;
|
|
@@ -3845,16 +4683,18 @@ exit 0
|
|
|
3845
4683
|
}
|
|
3846
4684
|
});
|
|
3847
4685
|
|
|
3848
|
-
it("allows non-Lore git commit messages when the Lore commit guard is disabled
|
|
3849
|
-
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-
|
|
4686
|
+
it("allows non-Lore git commit messages when the Lore commit guard is disabled by default", async () => {
|
|
4687
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-disabled-"));
|
|
4688
|
+
const original = process.env.OMX_LORE_COMMIT_GUARD;
|
|
3850
4689
|
try {
|
|
4690
|
+
delete process.env.OMX_LORE_COMMIT_GUARD;
|
|
3851
4691
|
const result = await dispatchCodexNativeHook(
|
|
3852
4692
|
{
|
|
3853
4693
|
hook_event_name: "PreToolUse",
|
|
3854
4694
|
cwd,
|
|
3855
4695
|
tool_name: "Bash",
|
|
3856
|
-
tool_use_id: "tool-git-commit-lore-
|
|
3857
|
-
tool_input: { command: '
|
|
4696
|
+
tool_use_id: "tool-git-commit-lore-disabled",
|
|
4697
|
+
tool_input: { command: 'git commit -m "fix: use conventional commit"' },
|
|
3858
4698
|
},
|
|
3859
4699
|
{ cwd },
|
|
3860
4700
|
);
|
|
@@ -3862,20 +4702,21 @@ exit 0
|
|
|
3862
4702
|
assert.equal(result.omxEventName, "pre-tool-use");
|
|
3863
4703
|
assert.equal(result.outputJson, null);
|
|
3864
4704
|
} finally {
|
|
4705
|
+
if (original === undefined) delete process.env.OMX_LORE_COMMIT_GUARD;
|
|
4706
|
+
else process.env.OMX_LORE_COMMIT_GUARD = original;
|
|
3865
4707
|
await rm(cwd, { recursive: true, force: true });
|
|
3866
4708
|
}
|
|
3867
4709
|
});
|
|
3868
4710
|
|
|
3869
|
-
it("
|
|
3870
|
-
|
|
3871
|
-
try {
|
|
4711
|
+
it("blocks non-Lore git commit messages when the Lore commit guard is enabled in CODEX_HOME config.toml", async () => {
|
|
4712
|
+
await withLoreGuardConfig("1", "config-enabled", async (cwd) => {
|
|
3872
4713
|
const result = await dispatchCodexNativeHook(
|
|
3873
4714
|
{
|
|
3874
4715
|
hook_event_name: "PreToolUse",
|
|
3875
4716
|
cwd,
|
|
3876
4717
|
tool_name: "Bash",
|
|
3877
|
-
tool_use_id: "tool-git-commit-lore-
|
|
3878
|
-
tool_input: { command: '
|
|
4718
|
+
tool_use_id: "tool-git-commit-lore-config-enabled",
|
|
4719
|
+
tool_input: { command: 'git commit -m "fix: conventional"' },
|
|
3879
4720
|
},
|
|
3880
4721
|
{ cwd },
|
|
3881
4722
|
);
|
|
@@ -3883,49 +4724,36 @@ exit 0
|
|
|
3883
4724
|
assert.equal(result.omxEventName, "pre-tool-use");
|
|
3884
4725
|
assert.equal((result.outputJson as { decision?: string } | null)?.decision, "block");
|
|
3885
4726
|
assert.match(JSON.stringify(result.outputJson), /Lore protocol/);
|
|
3886
|
-
}
|
|
3887
|
-
await rm(cwd, { recursive: true, force: true });
|
|
3888
|
-
}
|
|
4727
|
+
});
|
|
3889
4728
|
});
|
|
3890
4729
|
|
|
3891
|
-
it("
|
|
3892
|
-
|
|
3893
|
-
const original = process.env.OMX_LORE_COMMIT_GUARD;
|
|
3894
|
-
try {
|
|
3895
|
-
process.env.OMX_LORE_COMMIT_GUARD = "0";
|
|
4730
|
+
it("allows non-Lore git commit messages when the Lore commit guard is disabled in CODEX_HOME config.toml", async () => {
|
|
4731
|
+
await withLoreGuardConfig("0", "config-disabled", async (cwd) => {
|
|
3896
4732
|
const result = await dispatchCodexNativeHook(
|
|
3897
4733
|
{
|
|
3898
4734
|
hook_event_name: "PreToolUse",
|
|
3899
4735
|
cwd,
|
|
3900
4736
|
tool_name: "Bash",
|
|
3901
|
-
tool_use_id: "tool-git-commit-lore-
|
|
3902
|
-
tool_input: { command: '
|
|
4737
|
+
tool_use_id: "tool-git-commit-lore-config-disabled",
|
|
4738
|
+
tool_input: { command: 'git commit -m "fix: use conventional commit"' },
|
|
3903
4739
|
},
|
|
3904
4740
|
{ cwd },
|
|
3905
4741
|
);
|
|
3906
4742
|
|
|
3907
4743
|
assert.equal(result.omxEventName, "pre-tool-use");
|
|
3908
|
-
assert.equal(
|
|
3909
|
-
|
|
3910
|
-
} finally {
|
|
3911
|
-
if (original === undefined) delete process.env.OMX_LORE_COMMIT_GUARD;
|
|
3912
|
-
else process.env.OMX_LORE_COMMIT_GUARD = original;
|
|
3913
|
-
await rm(cwd, { recursive: true, force: true });
|
|
3914
|
-
}
|
|
4744
|
+
assert.equal(result.outputJson, null);
|
|
4745
|
+
});
|
|
3915
4746
|
});
|
|
3916
4747
|
|
|
3917
|
-
it("
|
|
3918
|
-
|
|
3919
|
-
const original = process.env.OMX_LORE_COMMIT_GUARD;
|
|
3920
|
-
try {
|
|
3921
|
-
process.env.OMX_LORE_COMMIT_GUARD = "0";
|
|
4748
|
+
it("lets inline Lore commit guard values override a disabled CODEX_HOME config.toml", async () => {
|
|
4749
|
+
await withLoreGuardConfig("0", "config-inline-enabled", async (cwd) => {
|
|
3922
4750
|
const result = await dispatchCodexNativeHook(
|
|
3923
4751
|
{
|
|
3924
4752
|
hook_event_name: "PreToolUse",
|
|
3925
4753
|
cwd,
|
|
3926
4754
|
tool_name: "Bash",
|
|
3927
|
-
tool_use_id: "tool-git-commit-lore-
|
|
3928
|
-
tool_input: { command: '
|
|
4755
|
+
tool_use_id: "tool-git-commit-lore-config-inline-enabled",
|
|
4756
|
+
tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 git commit -m "fix: conventional"' },
|
|
3929
4757
|
},
|
|
3930
4758
|
{ cwd },
|
|
3931
4759
|
);
|
|
@@ -3933,47 +4761,37 @@ exit 0
|
|
|
3933
4761
|
assert.equal(result.omxEventName, "pre-tool-use");
|
|
3934
4762
|
assert.equal((result.outputJson as { decision?: string } | null)?.decision, "block");
|
|
3935
4763
|
assert.match(JSON.stringify(result.outputJson), /Lore protocol/);
|
|
3936
|
-
}
|
|
3937
|
-
if (original === undefined) delete process.env.OMX_LORE_COMMIT_GUARD;
|
|
3938
|
-
else process.env.OMX_LORE_COMMIT_GUARD = original;
|
|
3939
|
-
await rm(cwd, { recursive: true, force: true });
|
|
3940
|
-
}
|
|
4764
|
+
});
|
|
3941
4765
|
});
|
|
3942
4766
|
|
|
3943
|
-
it("
|
|
3944
|
-
|
|
3945
|
-
try {
|
|
4767
|
+
it("restores default-off Lore guard when env -u removes a disabled CODEX_HOME config source", async () => {
|
|
4768
|
+
await withLoreGuardConfig("0", "config-codex-home-unset", async (cwd) => {
|
|
3946
4769
|
const result = await dispatchCodexNativeHook(
|
|
3947
4770
|
{
|
|
3948
4771
|
hook_event_name: "PreToolUse",
|
|
3949
4772
|
cwd,
|
|
3950
4773
|
tool_name: "Bash",
|
|
3951
|
-
tool_use_id: "tool-git-commit-lore-
|
|
3952
|
-
tool_input: { command: '
|
|
4774
|
+
tool_use_id: "tool-git-commit-lore-config-codex-home-unset",
|
|
4775
|
+
tool_input: { command: 'env -u CODEX_HOME git commit -m "fix: conventional"' },
|
|
3953
4776
|
},
|
|
3954
4777
|
{ cwd },
|
|
3955
4778
|
);
|
|
3956
4779
|
|
|
3957
4780
|
assert.equal(result.omxEventName, "pre-tool-use");
|
|
3958
|
-
assert.equal(
|
|
3959
|
-
|
|
3960
|
-
} finally {
|
|
3961
|
-
await rm(cwd, { recursive: true, force: true });
|
|
3962
|
-
}
|
|
4781
|
+
assert.equal(result.outputJson, null);
|
|
4782
|
+
});
|
|
3963
4783
|
});
|
|
3964
4784
|
|
|
3965
|
-
it("
|
|
3966
|
-
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-
|
|
3967
|
-
const original = process.env.OMX_LORE_COMMIT_GUARD;
|
|
4785
|
+
it("allows non-Lore git commit messages when the Lore commit guard is disabled inline", async () => {
|
|
4786
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-inline-disabled-"));
|
|
3968
4787
|
try {
|
|
3969
|
-
process.env.OMX_LORE_COMMIT_GUARD = " OFF ";
|
|
3970
4788
|
const result = await dispatchCodexNativeHook(
|
|
3971
4789
|
{
|
|
3972
4790
|
hook_event_name: "PreToolUse",
|
|
3973
4791
|
cwd,
|
|
3974
4792
|
tool_name: "Bash",
|
|
3975
|
-
tool_use_id: "tool-git-commit-lore-
|
|
3976
|
-
tool_input: { command: 'git commit -m "
|
|
4793
|
+
tool_use_id: "tool-git-commit-lore-inline-disabled",
|
|
4794
|
+
tool_input: { command: 'OMX_LORE_COMMIT_GUARD=0 git commit -m "fix: conventional"' },
|
|
3977
4795
|
},
|
|
3978
4796
|
{ cwd },
|
|
3979
4797
|
);
|
|
@@ -3981,31 +4799,29 @@ exit 0
|
|
|
3981
4799
|
assert.equal(result.omxEventName, "pre-tool-use");
|
|
3982
4800
|
assert.equal(result.outputJson, null);
|
|
3983
4801
|
} finally {
|
|
3984
|
-
if (original === undefined) delete process.env.OMX_LORE_COMMIT_GUARD;
|
|
3985
|
-
else process.env.OMX_LORE_COMMIT_GUARD = original;
|
|
3986
4802
|
await rm(cwd, { recursive: true, force: true });
|
|
3987
4803
|
}
|
|
3988
4804
|
});
|
|
3989
4805
|
|
|
3990
|
-
|
|
3991
|
-
|
|
4806
|
+
|
|
4807
|
+
it("allows inline disabled guard to override an enabled process env", async () => {
|
|
4808
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-inline-override-disabled-"));
|
|
3992
4809
|
const original = process.env.OMX_LORE_COMMIT_GUARD;
|
|
3993
4810
|
try {
|
|
3994
|
-
process.env.OMX_LORE_COMMIT_GUARD = "
|
|
4811
|
+
process.env.OMX_LORE_COMMIT_GUARD = "1";
|
|
3995
4812
|
const result = await dispatchCodexNativeHook(
|
|
3996
4813
|
{
|
|
3997
4814
|
hook_event_name: "PreToolUse",
|
|
3998
4815
|
cwd,
|
|
3999
4816
|
tool_name: "Bash",
|
|
4000
|
-
tool_use_id: "tool-git-commit-lore-
|
|
4001
|
-
tool_input: { command: 'git commit -m "fix
|
|
4817
|
+
tool_use_id: "tool-git-commit-lore-inline-override-disabled",
|
|
4818
|
+
tool_input: { command: 'OMX_LORE_COMMIT_GUARD=0 git commit -m "fix: conventional"' },
|
|
4002
4819
|
},
|
|
4003
4820
|
{ cwd },
|
|
4004
4821
|
);
|
|
4005
4822
|
|
|
4006
4823
|
assert.equal(result.omxEventName, "pre-tool-use");
|
|
4007
|
-
assert.equal(
|
|
4008
|
-
assert.match(JSON.stringify(result.outputJson), /Lore protocol/);
|
|
4824
|
+
assert.equal(result.outputJson, null);
|
|
4009
4825
|
} finally {
|
|
4010
4826
|
if (original === undefined) delete process.env.OMX_LORE_COMMIT_GUARD;
|
|
4011
4827
|
else process.env.OMX_LORE_COMMIT_GUARD = original;
|
|
@@ -4013,63 +4829,57 @@ exit 0
|
|
|
4013
4829
|
}
|
|
4014
4830
|
});
|
|
4015
4831
|
|
|
4016
|
-
it("
|
|
4017
|
-
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-lore-
|
|
4018
|
-
const original = process.env.OMX_LORE_COMMIT_GUARD;
|
|
4832
|
+
it("does not treat newline-separated Lore guard assignment as inline git commit opt-in", async () => {
|
|
4833
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-newline-assignment-"));
|
|
4019
4834
|
try {
|
|
4020
|
-
process.env.OMX_LORE_COMMIT_GUARD = "false";
|
|
4021
4835
|
const result = await dispatchCodexNativeHook(
|
|
4022
4836
|
{
|
|
4023
4837
|
hook_event_name: "PreToolUse",
|
|
4024
4838
|
cwd,
|
|
4025
4839
|
tool_name: "Bash",
|
|
4026
|
-
tool_use_id: "tool-lore-
|
|
4027
|
-
tool_input: { command:
|
|
4840
|
+
tool_use_id: "tool-git-commit-lore-newline-assignment",
|
|
4841
|
+
tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1\ngit commit -m "fix: conventional"' },
|
|
4028
4842
|
},
|
|
4029
4843
|
{ cwd },
|
|
4030
4844
|
);
|
|
4031
4845
|
|
|
4032
4846
|
assert.equal(result.omxEventName, "pre-tool-use");
|
|
4033
|
-
assert.
|
|
4034
|
-
assert.match(JSON.stringify(result.outputJson), /Destructive Bash command detected/);
|
|
4847
|
+
assert.equal(result.outputJson, null);
|
|
4035
4848
|
} finally {
|
|
4036
|
-
if (original === undefined) delete process.env.OMX_LORE_COMMIT_GUARD;
|
|
4037
|
-
else process.env.OMX_LORE_COMMIT_GUARD = original;
|
|
4038
4849
|
await rm(cwd, { recursive: true, force: true });
|
|
4039
4850
|
}
|
|
4040
4851
|
});
|
|
4041
4852
|
|
|
4042
|
-
it("
|
|
4043
|
-
|
|
4044
|
-
try {
|
|
4853
|
+
it("restores default-off Lore guard when env -u unsets a config.toml fallback", async () => {
|
|
4854
|
+
await withLoreGuardConfig("1", "config-env-unset", async (cwd) => {
|
|
4045
4855
|
const result = await dispatchCodexNativeHook(
|
|
4046
4856
|
{
|
|
4047
4857
|
hook_event_name: "PreToolUse",
|
|
4048
4858
|
cwd,
|
|
4049
4859
|
tool_name: "Bash",
|
|
4050
|
-
tool_use_id: "tool-git-
|
|
4051
|
-
tool_input: { command:
|
|
4860
|
+
tool_use_id: "tool-git-commit-lore-config-env-unset",
|
|
4861
|
+
tool_input: { command: 'env -u OMX_LORE_COMMIT_GUARD git commit -m "fix: conventional"' },
|
|
4052
4862
|
},
|
|
4053
4863
|
{ cwd },
|
|
4054
4864
|
);
|
|
4055
4865
|
|
|
4056
4866
|
assert.equal(result.omxEventName, "pre-tool-use");
|
|
4057
4867
|
assert.equal(result.outputJson, null);
|
|
4058
|
-
}
|
|
4059
|
-
await rm(cwd, { recursive: true, force: true });
|
|
4060
|
-
}
|
|
4868
|
+
});
|
|
4061
4869
|
});
|
|
4062
4870
|
|
|
4063
|
-
it("
|
|
4064
|
-
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-
|
|
4871
|
+
it("restores default-off Lore guard when env -u unsets an enabled process env", async () => {
|
|
4872
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-env-unset-"));
|
|
4873
|
+
const original = process.env.OMX_LORE_COMMIT_GUARD;
|
|
4065
4874
|
try {
|
|
4875
|
+
process.env.OMX_LORE_COMMIT_GUARD = "1";
|
|
4066
4876
|
const result = await dispatchCodexNativeHook(
|
|
4067
4877
|
{
|
|
4068
4878
|
hook_event_name: "PreToolUse",
|
|
4069
4879
|
cwd,
|
|
4070
4880
|
tool_name: "Bash",
|
|
4071
|
-
tool_use_id: "tool-git-
|
|
4072
|
-
tool_input: { command:
|
|
4881
|
+
tool_use_id: "tool-git-commit-lore-env-unset",
|
|
4882
|
+
tool_input: { command: 'env -u OMX_LORE_COMMIT_GUARD git commit -m "fix: conventional"' },
|
|
4073
4883
|
},
|
|
4074
4884
|
{ cwd },
|
|
4075
4885
|
);
|
|
@@ -4077,20 +4887,24 @@ exit 0
|
|
|
4077
4887
|
assert.equal(result.omxEventName, "pre-tool-use");
|
|
4078
4888
|
assert.equal(result.outputJson, null);
|
|
4079
4889
|
} finally {
|
|
4890
|
+
if (original === undefined) delete process.env.OMX_LORE_COMMIT_GUARD;
|
|
4891
|
+
else process.env.OMX_LORE_COMMIT_GUARD = original;
|
|
4080
4892
|
await rm(cwd, { recursive: true, force: true });
|
|
4081
4893
|
}
|
|
4082
4894
|
});
|
|
4083
4895
|
|
|
4084
|
-
it("
|
|
4085
|
-
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-
|
|
4896
|
+
it("restores default-off Lore guard when env -i clears an enabled process env", async () => {
|
|
4897
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-env-ignore-"));
|
|
4898
|
+
const original = process.env.OMX_LORE_COMMIT_GUARD;
|
|
4086
4899
|
try {
|
|
4900
|
+
process.env.OMX_LORE_COMMIT_GUARD = "1";
|
|
4087
4901
|
const result = await dispatchCodexNativeHook(
|
|
4088
4902
|
{
|
|
4089
4903
|
hook_event_name: "PreToolUse",
|
|
4090
4904
|
cwd,
|
|
4091
4905
|
tool_name: "Bash",
|
|
4092
|
-
tool_use_id: "tool-git-
|
|
4093
|
-
tool_input: { command:
|
|
4906
|
+
tool_use_id: "tool-git-commit-lore-env-ignore",
|
|
4907
|
+
tool_input: { command: 'env -i PATH=/usr/bin git commit -m "fix: conventional"' },
|
|
4094
4908
|
},
|
|
4095
4909
|
{ cwd },
|
|
4096
4910
|
);
|
|
@@ -4098,20 +4912,182 @@ exit 0
|
|
|
4098
4912
|
assert.equal(result.omxEventName, "pre-tool-use");
|
|
4099
4913
|
assert.equal(result.outputJson, null);
|
|
4100
4914
|
} finally {
|
|
4915
|
+
if (original === undefined) delete process.env.OMX_LORE_COMMIT_GUARD;
|
|
4916
|
+
else process.env.OMX_LORE_COMMIT_GUARD = original;
|
|
4101
4917
|
await rm(cwd, { recursive: true, force: true });
|
|
4102
4918
|
}
|
|
4103
4919
|
});
|
|
4104
4920
|
|
|
4105
|
-
it("
|
|
4106
|
-
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-
|
|
4921
|
+
it("keeps Lore commit enforcement disabled for unknown inline guard values", async () => {
|
|
4922
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-inline-unknown-"));
|
|
4107
4923
|
try {
|
|
4108
4924
|
const result = await dispatchCodexNativeHook(
|
|
4109
4925
|
{
|
|
4110
4926
|
hook_event_name: "PreToolUse",
|
|
4111
4927
|
cwd,
|
|
4112
4928
|
tool_name: "Bash",
|
|
4113
|
-
tool_use_id: "tool-git-commit-
|
|
4114
|
-
tool_input: { command: '
|
|
4929
|
+
tool_use_id: "tool-git-commit-lore-inline-unknown",
|
|
4930
|
+
tool_input: { command: 'OMX_LORE_COMMIT_GUARD=maybe git commit -m "fix: conventional"' },
|
|
4931
|
+
},
|
|
4932
|
+
{ cwd },
|
|
4933
|
+
);
|
|
4934
|
+
|
|
4935
|
+
assert.equal(result.omxEventName, "pre-tool-use");
|
|
4936
|
+
assert.equal(result.outputJson, null);
|
|
4937
|
+
} finally {
|
|
4938
|
+
await rm(cwd, { recursive: true, force: true });
|
|
4939
|
+
}
|
|
4940
|
+
});
|
|
4941
|
+
|
|
4942
|
+
it("treats Lore commit guard disabled values as trim and case tolerant", async () => {
|
|
4943
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-off-"));
|
|
4944
|
+
const original = process.env.OMX_LORE_COMMIT_GUARD;
|
|
4945
|
+
try {
|
|
4946
|
+
process.env.OMX_LORE_COMMIT_GUARD = " OFF ";
|
|
4947
|
+
const result = await dispatchCodexNativeHook(
|
|
4948
|
+
{
|
|
4949
|
+
hook_event_name: "PreToolUse",
|
|
4950
|
+
cwd,
|
|
4951
|
+
tool_name: "Bash",
|
|
4952
|
+
tool_use_id: "tool-git-commit-lore-off",
|
|
4953
|
+
tool_input: { command: 'git commit -m "chore: conventional commit"' },
|
|
4954
|
+
},
|
|
4955
|
+
{ cwd },
|
|
4956
|
+
);
|
|
4957
|
+
|
|
4958
|
+
assert.equal(result.omxEventName, "pre-tool-use");
|
|
4959
|
+
assert.equal(result.outputJson, null);
|
|
4960
|
+
} finally {
|
|
4961
|
+
if (original === undefined) delete process.env.OMX_LORE_COMMIT_GUARD;
|
|
4962
|
+
else process.env.OMX_LORE_COMMIT_GUARD = original;
|
|
4963
|
+
await rm(cwd, { recursive: true, force: true });
|
|
4964
|
+
}
|
|
4965
|
+
});
|
|
4966
|
+
|
|
4967
|
+
it("keeps Lore commit enforcement disabled for unknown guard values", async () => {
|
|
4968
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-unknown-"));
|
|
4969
|
+
const original = process.env.OMX_LORE_COMMIT_GUARD;
|
|
4970
|
+
try {
|
|
4971
|
+
process.env.OMX_LORE_COMMIT_GUARD = "maybe";
|
|
4972
|
+
const result = await dispatchCodexNativeHook(
|
|
4973
|
+
{
|
|
4974
|
+
hook_event_name: "PreToolUse",
|
|
4975
|
+
cwd,
|
|
4976
|
+
tool_name: "Bash",
|
|
4977
|
+
tool_use_id: "tool-git-commit-lore-unknown",
|
|
4978
|
+
tool_input: { command: 'git commit -m "fix tests"' },
|
|
4979
|
+
},
|
|
4980
|
+
{ cwd },
|
|
4981
|
+
);
|
|
4982
|
+
|
|
4983
|
+
assert.equal(result.omxEventName, "pre-tool-use");
|
|
4984
|
+
assert.equal(result.outputJson, null);
|
|
4985
|
+
} finally {
|
|
4986
|
+
if (original === undefined) delete process.env.OMX_LORE_COMMIT_GUARD;
|
|
4987
|
+
else process.env.OMX_LORE_COMMIT_GUARD = original;
|
|
4988
|
+
await rm(cwd, { recursive: true, force: true });
|
|
4989
|
+
}
|
|
4990
|
+
});
|
|
4991
|
+
|
|
4992
|
+
it("continues to later PreToolUse checks when Lore commit guard is disabled", async () => {
|
|
4993
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-lore-disabled-destructive-"));
|
|
4994
|
+
const original = process.env.OMX_LORE_COMMIT_GUARD;
|
|
4995
|
+
try {
|
|
4996
|
+
process.env.OMX_LORE_COMMIT_GUARD = "false";
|
|
4997
|
+
const result = await dispatchCodexNativeHook(
|
|
4998
|
+
{
|
|
4999
|
+
hook_event_name: "PreToolUse",
|
|
5000
|
+
cwd,
|
|
5001
|
+
tool_name: "Bash",
|
|
5002
|
+
tool_use_id: "tool-lore-disabled-destructive",
|
|
5003
|
+
tool_input: { command: "rm -rf dist" },
|
|
5004
|
+
},
|
|
5005
|
+
{ cwd },
|
|
5006
|
+
);
|
|
5007
|
+
|
|
5008
|
+
assert.equal(result.omxEventName, "pre-tool-use");
|
|
5009
|
+
assert.doesNotMatch(JSON.stringify(result.outputJson), /Lore protocol/);
|
|
5010
|
+
assert.match(JSON.stringify(result.outputJson), /Destructive Bash command detected/);
|
|
5011
|
+
} finally {
|
|
5012
|
+
if (original === undefined) delete process.env.OMX_LORE_COMMIT_GUARD;
|
|
5013
|
+
else process.env.OMX_LORE_COMMIT_GUARD = original;
|
|
5014
|
+
await rm(cwd, { recursive: true, force: true });
|
|
5015
|
+
}
|
|
5016
|
+
});
|
|
5017
|
+
|
|
5018
|
+
it("stays silent on PreToolUse for `git help commit`", async () => {
|
|
5019
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-help-commit-"));
|
|
5020
|
+
try {
|
|
5021
|
+
const result = await dispatchCodexNativeHook(
|
|
5022
|
+
{
|
|
5023
|
+
hook_event_name: "PreToolUse",
|
|
5024
|
+
cwd,
|
|
5025
|
+
tool_name: "Bash",
|
|
5026
|
+
tool_use_id: "tool-git-help-commit",
|
|
5027
|
+
tool_input: { command: "git help commit" },
|
|
5028
|
+
},
|
|
5029
|
+
{ cwd },
|
|
5030
|
+
);
|
|
5031
|
+
|
|
5032
|
+
assert.equal(result.omxEventName, "pre-tool-use");
|
|
5033
|
+
assert.equal(result.outputJson, null);
|
|
5034
|
+
} finally {
|
|
5035
|
+
await rm(cwd, { recursive: true, force: true });
|
|
5036
|
+
}
|
|
5037
|
+
});
|
|
5038
|
+
|
|
5039
|
+
it("stays silent on PreToolUse for `git config alias.ci commit`", async () => {
|
|
5040
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-config-alias-commit-"));
|
|
5041
|
+
try {
|
|
5042
|
+
const result = await dispatchCodexNativeHook(
|
|
5043
|
+
{
|
|
5044
|
+
hook_event_name: "PreToolUse",
|
|
5045
|
+
cwd,
|
|
5046
|
+
tool_name: "Bash",
|
|
5047
|
+
tool_use_id: "tool-git-config-alias-commit",
|
|
5048
|
+
tool_input: { command: "git config alias.ci commit" },
|
|
5049
|
+
},
|
|
5050
|
+
{ cwd },
|
|
5051
|
+
);
|
|
5052
|
+
|
|
5053
|
+
assert.equal(result.omxEventName, "pre-tool-use");
|
|
5054
|
+
assert.equal(result.outputJson, null);
|
|
5055
|
+
} finally {
|
|
5056
|
+
await rm(cwd, { recursive: true, force: true });
|
|
5057
|
+
}
|
|
5058
|
+
});
|
|
5059
|
+
|
|
5060
|
+
it("stays silent on PreToolUse for `git tag commit`", async () => {
|
|
5061
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-tag-commit-"));
|
|
5062
|
+
try {
|
|
5063
|
+
const result = await dispatchCodexNativeHook(
|
|
5064
|
+
{
|
|
5065
|
+
hook_event_name: "PreToolUse",
|
|
5066
|
+
cwd,
|
|
5067
|
+
tool_name: "Bash",
|
|
5068
|
+
tool_use_id: "tool-git-tag-commit",
|
|
5069
|
+
tool_input: { command: "git tag commit" },
|
|
5070
|
+
},
|
|
5071
|
+
{ cwd },
|
|
5072
|
+
);
|
|
5073
|
+
|
|
5074
|
+
assert.equal(result.omxEventName, "pre-tool-use");
|
|
5075
|
+
assert.equal(result.outputJson, null);
|
|
5076
|
+
} finally {
|
|
5077
|
+
await rm(cwd, { recursive: true, force: true });
|
|
5078
|
+
}
|
|
5079
|
+
});
|
|
5080
|
+
|
|
5081
|
+
it("blocks PreToolUse env-prefixed git commit when the inline message is not Lore-compliant", async () => {
|
|
5082
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-env-invalid-"));
|
|
5083
|
+
try {
|
|
5084
|
+
const result = await dispatchCodexNativeHook(
|
|
5085
|
+
{
|
|
5086
|
+
hook_event_name: "PreToolUse",
|
|
5087
|
+
cwd,
|
|
5088
|
+
tool_name: "Bash",
|
|
5089
|
+
tool_use_id: "tool-git-commit-env-invalid",
|
|
5090
|
+
tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 HUSKY=0 git commit -m "fix tests"' },
|
|
4115
5091
|
},
|
|
4116
5092
|
{ cwd },
|
|
4117
5093
|
);
|
|
@@ -4146,7 +5122,7 @@ exit 0
|
|
|
4146
5122
|
cwd,
|
|
4147
5123
|
tool_name: "Bash",
|
|
4148
5124
|
tool_use_id: "tool-git-commit-option-invalid",
|
|
4149
|
-
tool_input: { command: 'git -c core.editor=true commit -m "fix tests"' },
|
|
5125
|
+
tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 git -c core.editor=true commit -m "fix tests"' },
|
|
4150
5126
|
},
|
|
4151
5127
|
{ cwd },
|
|
4152
5128
|
);
|
|
@@ -4181,7 +5157,7 @@ exit 0
|
|
|
4181
5157
|
cwd,
|
|
4182
5158
|
tool_name: "Bash",
|
|
4183
5159
|
tool_use_id: "tool-git-exe-commit-env-wrapper-invalid",
|
|
4184
|
-
tool_input: { command: 'env git.exe commit -m "fix tests"' },
|
|
5160
|
+
tool_input: { command: 'env OMX_LORE_COMMIT_GUARD=1 git.exe commit -m "fix tests"' },
|
|
4185
5161
|
},
|
|
4186
5162
|
{ cwd },
|
|
4187
5163
|
);
|
|
@@ -4216,7 +5192,7 @@ exit 0
|
|
|
4216
5192
|
cwd,
|
|
4217
5193
|
tool_name: "Bash",
|
|
4218
5194
|
tool_use_id: "tool-git-exe-commit-invalid",
|
|
4219
|
-
tool_input: { command: 'git.exe commit -m "fix tests"' },
|
|
5195
|
+
tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 git.exe commit -m "fix tests"' },
|
|
4220
5196
|
},
|
|
4221
5197
|
{ cwd },
|
|
4222
5198
|
);
|
|
@@ -4251,7 +5227,7 @@ exit 0
|
|
|
4251
5227
|
cwd,
|
|
4252
5228
|
tool_name: "Bash",
|
|
4253
5229
|
tool_use_id: "tool-git-exe-commit-env-flag-wrapper-invalid",
|
|
4254
|
-
tool_input: { command: 'env -i PATH=/usr/bin git.exe commit -m "fix tests"' },
|
|
5230
|
+
tool_input: { command: 'env -i PATH=/usr/bin OMX_LORE_COMMIT_GUARD=1 git.exe commit -m "fix tests"' },
|
|
4255
5231
|
},
|
|
4256
5232
|
{ cwd },
|
|
4257
5233
|
);
|
|
@@ -4286,7 +5262,7 @@ exit 0
|
|
|
4286
5262
|
cwd,
|
|
4287
5263
|
tool_name: "Bash",
|
|
4288
5264
|
tool_use_id: "tool-git-exe-commit-env-value-wrapper-invalid",
|
|
4289
|
-
tool_input: { command: 'env -u FOO git.exe commit -m "fix tests"' },
|
|
5265
|
+
tool_input: { command: 'env -u FOO OMX_LORE_COMMIT_GUARD=1 git.exe commit -m "fix tests"' },
|
|
4290
5266
|
},
|
|
4291
5267
|
{ cwd },
|
|
4292
5268
|
);
|
|
@@ -4321,7 +5297,7 @@ exit 0
|
|
|
4321
5297
|
cwd,
|
|
4322
5298
|
tool_name: "Bash",
|
|
4323
5299
|
tool_use_id: "tool-git-exe-commit-windows-path-invalid",
|
|
4324
|
-
tool_input: { command: '"C:/Program Files/Git/cmd/git.exe" commit -m "fix tests"' },
|
|
5300
|
+
tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 "C:/Program Files/Git/cmd/git.exe" commit -m "fix tests"' },
|
|
4325
5301
|
},
|
|
4326
5302
|
{ cwd },
|
|
4327
5303
|
);
|
|
@@ -4356,7 +5332,7 @@ exit 0
|
|
|
4356
5332
|
cwd,
|
|
4357
5333
|
tool_name: "Bash",
|
|
4358
5334
|
tool_use_id: "tool-git-exe-commit-windows-backslash-path-invalid",
|
|
4359
|
-
tool_input: { command: '"C:\\Program Files\\Git\\cmd\\git.exe" commit -m "fix tests"' },
|
|
5335
|
+
tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 "C:\\Program Files\\Git\\cmd\\git.exe" commit -m "fix tests"' },
|
|
4360
5336
|
},
|
|
4361
5337
|
{ cwd },
|
|
4362
5338
|
);
|
|
@@ -4391,7 +5367,7 @@ exit 0
|
|
|
4391
5367
|
cwd,
|
|
4392
5368
|
tool_name: "Bash",
|
|
4393
5369
|
tool_use_id: "tool-git-commit-path-invalid",
|
|
4394
|
-
tool_input: { command: '/usr/bin/git commit -m "fix tests"' },
|
|
5370
|
+
tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 /usr/bin/git commit -m "fix tests"' },
|
|
4395
5371
|
},
|
|
4396
5372
|
{ cwd },
|
|
4397
5373
|
);
|
|
@@ -4426,7 +5402,7 @@ exit 0
|
|
|
4426
5402
|
cwd,
|
|
4427
5403
|
tool_name: "Bash",
|
|
4428
5404
|
tool_use_id: "tool-git-commit-file",
|
|
4429
|
-
tool_input: { command: "git commit -F .git/COMMIT_EDITMSG" },
|
|
5405
|
+
tool_input: { command: "OMX_LORE_COMMIT_GUARD=1 git commit -F .git/COMMIT_EDITMSG" },
|
|
4430
5406
|
},
|
|
4431
5407
|
{ cwd },
|
|
4432
5408
|
);
|
|
@@ -4460,7 +5436,7 @@ exit 0
|
|
|
4460
5436
|
tool_use_id: "tool-git-commit-missing-omx-coauthor",
|
|
4461
5437
|
tool_input: {
|
|
4462
5438
|
command: [
|
|
4463
|
-
'git commit',
|
|
5439
|
+
'OMX_LORE_COMMIT_GUARD=1 git commit',
|
|
4464
5440
|
'-m "Prevent invalid history from bypassing Lore enforcement"',
|
|
4465
5441
|
'-m "The native pre-tool-use hook now blocks inline git commit messages that skip Lore trailers or the required OmX co-author trailer."',
|
|
4466
5442
|
'-m "Constraint: Native PreToolUse can only inspect the Bash command text"',
|
|
@@ -4500,7 +5476,7 @@ exit 0
|
|
|
4500
5476
|
tool_use_id: "tool-git-commit-valid",
|
|
4501
5477
|
tool_input: {
|
|
4502
5478
|
command: [
|
|
4503
|
-
'git commit',
|
|
5479
|
+
'OMX_LORE_COMMIT_GUARD=1 git commit',
|
|
4504
5480
|
'-m "Prevent invalid history from bypassing Lore enforcement"',
|
|
4505
5481
|
'-m "The native pre-tool-use hook now blocks inline git commit messages that skip Lore trailers or the required OmX co-author trailer."',
|
|
4506
5482
|
'-m "Constraint: Native PreToolUse can only inspect the Bash command text"',
|
|
@@ -4530,7 +5506,7 @@ exit 0
|
|
|
4530
5506
|
tool_use_id: "tool-git-commit-compact-coauthor",
|
|
4531
5507
|
tool_input: {
|
|
4532
5508
|
command: [
|
|
4533
|
-
'git commit',
|
|
5509
|
+
'OMX_LORE_COMMIT_GUARD=1 git commit',
|
|
4534
5510
|
'-m "Launch lvisai.xyz intro site"',
|
|
4535
5511
|
'-m "Co-authored-by: OmX <omx@oh-my-codex.dev>"',
|
|
4536
5512
|
].join(" "),
|
|
@@ -4557,7 +5533,7 @@ exit 0
|
|
|
4557
5533
|
tool_use_id: "tool-git-commit-compact-trailers",
|
|
4558
5534
|
tool_input: {
|
|
4559
5535
|
command: [
|
|
4560
|
-
'git commit',
|
|
5536
|
+
'OMX_LORE_COMMIT_GUARD=1 git commit',
|
|
4561
5537
|
'-m "Launch lvisai.xyz intro site"',
|
|
4562
5538
|
'-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>"',
|
|
4563
5539
|
].join(" "),
|
|
@@ -4584,7 +5560,7 @@ exit 0
|
|
|
4584
5560
|
tool_use_id: "tool-git-commit-compact-no-separator",
|
|
4585
5561
|
tool_input: {
|
|
4586
5562
|
command: [
|
|
4587
|
-
'git commit',
|
|
5563
|
+
'OMX_LORE_COMMIT_GUARD=1 git commit',
|
|
4588
5564
|
'--message="Launch lvisai.xyz intro site\nCo-authored-by: OmX <omx@oh-my-codex.dev>"',
|
|
4589
5565
|
].join(" "),
|
|
4590
5566
|
},
|
|
@@ -5617,6 +6593,91 @@ exit 0
|
|
|
5617
6593
|
}
|
|
5618
6594
|
});
|
|
5619
6595
|
|
|
6596
|
+
it("allows Stop when terminal Autopilot run-state shadows stale session ralplan state", async () => {
|
|
6597
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-autopilot-terminal-run-state-"));
|
|
6598
|
+
try {
|
|
6599
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
6600
|
+
const sessionId = "sess-stop-autopilot-terminal-run-state";
|
|
6601
|
+
await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
|
|
6602
|
+
await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), {
|
|
6603
|
+
active: true,
|
|
6604
|
+
mode: "autopilot",
|
|
6605
|
+
current_phase: "ralplan",
|
|
6606
|
+
});
|
|
6607
|
+
await writeJson(join(stateDir, "sessions", sessionId, "run-state.json"), {
|
|
6608
|
+
version: 1,
|
|
6609
|
+
active: false,
|
|
6610
|
+
mode: "autopilot",
|
|
6611
|
+
outcome: "finish",
|
|
6612
|
+
lifecycle_outcome: "finished",
|
|
6613
|
+
current_phase: "complete",
|
|
6614
|
+
completed_at: "2026-05-20T11:00:00.000Z",
|
|
6615
|
+
updated_at: "2026-05-20T11:00:00.000Z",
|
|
6616
|
+
});
|
|
6617
|
+
|
|
6618
|
+
const result = await dispatchCodexNativeHook(
|
|
6619
|
+
{
|
|
6620
|
+
hook_event_name: "Stop",
|
|
6621
|
+
cwd,
|
|
6622
|
+
session_id: sessionId,
|
|
6623
|
+
thread_id: "thread-stop-autopilot-terminal-run-state",
|
|
6624
|
+
turn_id: "turn-stop-autopilot-terminal-run-state-1",
|
|
6625
|
+
last_assistant_message: "Done. Verification passed.",
|
|
6626
|
+
},
|
|
6627
|
+
{ cwd },
|
|
6628
|
+
);
|
|
6629
|
+
|
|
6630
|
+
assert.equal(result.omxEventName, "stop");
|
|
6631
|
+
assert.equal(result.outputJson, null);
|
|
6632
|
+
} finally {
|
|
6633
|
+
await rm(cwd, { recursive: true, force: true });
|
|
6634
|
+
}
|
|
6635
|
+
});
|
|
6636
|
+
|
|
6637
|
+
it("still blocks Stop while Autopilot ralplan state is genuinely non-terminal", async () => {
|
|
6638
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-autopilot-active-ralplan-"));
|
|
6639
|
+
try {
|
|
6640
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
6641
|
+
const sessionId = "sess-stop-autopilot-active-ralplan";
|
|
6642
|
+
await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
|
|
6643
|
+
await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), {
|
|
6644
|
+
active: true,
|
|
6645
|
+
mode: "autopilot",
|
|
6646
|
+
current_phase: "ralplan",
|
|
6647
|
+
});
|
|
6648
|
+
await writeJson(join(stateDir, "sessions", sessionId, "run-state.json"), {
|
|
6649
|
+
version: 1,
|
|
6650
|
+
active: true,
|
|
6651
|
+
mode: "autopilot",
|
|
6652
|
+
outcome: "continue",
|
|
6653
|
+
current_phase: "ralplan",
|
|
6654
|
+
updated_at: "2026-05-20T11:00:00.000Z",
|
|
6655
|
+
});
|
|
6656
|
+
|
|
6657
|
+
const result = await dispatchCodexNativeHook(
|
|
6658
|
+
{
|
|
6659
|
+
hook_event_name: "Stop",
|
|
6660
|
+
cwd,
|
|
6661
|
+
session_id: sessionId,
|
|
6662
|
+
thread_id: "thread-stop-autopilot-active-ralplan",
|
|
6663
|
+
turn_id: "turn-stop-autopilot-active-ralplan-1",
|
|
6664
|
+
},
|
|
6665
|
+
{ cwd },
|
|
6666
|
+
);
|
|
6667
|
+
|
|
6668
|
+
assert.equal(result.omxEventName, "stop");
|
|
6669
|
+
assert.deepEqual(result.outputJson, {
|
|
6670
|
+
decision: "block",
|
|
6671
|
+
reason:
|
|
6672
|
+
"OMX autopilot is still active (phase: ralplan); continue the task and gather fresh verification evidence before stopping.",
|
|
6673
|
+
stopReason: "autopilot_ralplan",
|
|
6674
|
+
systemMessage: "OMX autopilot is still active (phase: ralplan).",
|
|
6675
|
+
});
|
|
6676
|
+
} finally {
|
|
6677
|
+
await rm(cwd, { recursive: true, force: true });
|
|
6678
|
+
}
|
|
6679
|
+
});
|
|
6680
|
+
|
|
5620
6681
|
it("does not block Stop from stale root Autopilot planning state when the explicit session has no scoped state", async () => {
|
|
5621
6682
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-stale-root-autopilot-planning-"));
|
|
5622
6683
|
try {
|
|
@@ -5808,6 +6869,7 @@ exit 0
|
|
|
5808
6869
|
active: true,
|
|
5809
6870
|
current_phase: "team-exec",
|
|
5810
6871
|
team_name: "review-team",
|
|
6872
|
+
session_id: "sess-stop-team",
|
|
5811
6873
|
});
|
|
5812
6874
|
await writeJson(join(stateDir, "team", "review-team", "phase.json"), {
|
|
5813
6875
|
current_phase: "team-verify",
|
|
@@ -6695,45 +7757,129 @@ exit 0
|
|
|
6695
7757
|
}
|
|
6696
7758
|
});
|
|
6697
7759
|
|
|
6698
|
-
it("
|
|
6699
|
-
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-
|
|
7760
|
+
it("does not block Stop from canonical team state owned by another thread", async () => {
|
|
7761
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-canonical-other-thread-"));
|
|
6700
7762
|
try {
|
|
6701
7763
|
await initTeamState(
|
|
6702
|
-
"
|
|
6703
|
-
"
|
|
7764
|
+
"canonical-other-thread-team",
|
|
7765
|
+
"canonical other-thread stop fallback",
|
|
6704
7766
|
"executor",
|
|
6705
7767
|
1,
|
|
6706
7768
|
cwd,
|
|
6707
7769
|
undefined,
|
|
6708
|
-
{ ...process.env, OMX_SESSION_ID: "sess-stop-
|
|
6709
|
-
);
|
|
6710
|
-
await writeReleaseReadinessLeaderAttention(
|
|
6711
|
-
"release-ready-team",
|
|
6712
|
-
"sess-stop-release-ready",
|
|
6713
|
-
cwd,
|
|
6714
|
-
{ workRemaining: false },
|
|
6715
|
-
);
|
|
6716
|
-
await writeReleaseReadinessStateMarker(
|
|
6717
|
-
"sess-stop-release-ready",
|
|
6718
|
-
"release-ready-team",
|
|
6719
|
-
cwd,
|
|
7770
|
+
{ ...process.env, OMX_SESSION_ID: "sess-stop-team-canonical-thread" },
|
|
6720
7771
|
);
|
|
7772
|
+
const manifestPath = join(cwd, ".omx", "state", "team", "canonical-other-thread-team", "manifest.v2.json");
|
|
7773
|
+
const manifest = JSON.parse(await readFile(manifestPath, "utf-8")) as Record<string, unknown>;
|
|
7774
|
+
await writeJson(manifestPath, {
|
|
7775
|
+
...manifest,
|
|
7776
|
+
leader: {
|
|
7777
|
+
...(manifest.leader as Record<string, unknown> | undefined),
|
|
7778
|
+
thread_id: "thread-other",
|
|
7779
|
+
},
|
|
7780
|
+
});
|
|
6721
7781
|
|
|
6722
7782
|
const result = await dispatchCodexNativeHook(
|
|
6723
7783
|
{
|
|
6724
7784
|
hook_event_name: "Stop",
|
|
6725
7785
|
cwd,
|
|
6726
|
-
session_id: "sess-stop-
|
|
6727
|
-
thread_id: "thread-
|
|
6728
|
-
turn_id: "turn-stop-release-ready-1",
|
|
6729
|
-
mode: "release-readiness",
|
|
6730
|
-
last_assistant_message: "Launch-ready: yes",
|
|
7786
|
+
session_id: "sess-stop-team-canonical-thread",
|
|
7787
|
+
thread_id: "thread-current",
|
|
6731
7788
|
},
|
|
6732
7789
|
{ cwd },
|
|
6733
7790
|
);
|
|
6734
7791
|
|
|
6735
7792
|
assert.equal(result.omxEventName, "stop");
|
|
6736
|
-
assert.
|
|
7793
|
+
assert.equal(result.outputJson, null);
|
|
7794
|
+
} finally {
|
|
7795
|
+
await rm(cwd, { recursive: true, force: true });
|
|
7796
|
+
}
|
|
7797
|
+
});
|
|
7798
|
+
|
|
7799
|
+
it("blocks Stop from canonical team state owned by the current thread", async () => {
|
|
7800
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-canonical-current-thread-"));
|
|
7801
|
+
try {
|
|
7802
|
+
await initTeamState(
|
|
7803
|
+
"canonical-current-thread-team",
|
|
7804
|
+
"canonical current-thread stop fallback",
|
|
7805
|
+
"executor",
|
|
7806
|
+
1,
|
|
7807
|
+
cwd,
|
|
7808
|
+
undefined,
|
|
7809
|
+
{ ...process.env, OMX_SESSION_ID: "sess-stop-team-canonical-current-thread" },
|
|
7810
|
+
);
|
|
7811
|
+
const manifestPath = join(cwd, ".omx", "state", "team", "canonical-current-thread-team", "manifest.v2.json");
|
|
7812
|
+
const manifest = JSON.parse(await readFile(manifestPath, "utf-8")) as Record<string, unknown>;
|
|
7813
|
+
await writeJson(manifestPath, {
|
|
7814
|
+
...manifest,
|
|
7815
|
+
leader: {
|
|
7816
|
+
...(manifest.leader as Record<string, unknown> | undefined),
|
|
7817
|
+
thread_id: "thread-current",
|
|
7818
|
+
},
|
|
7819
|
+
});
|
|
7820
|
+
|
|
7821
|
+
const result = await dispatchCodexNativeHook(
|
|
7822
|
+
{
|
|
7823
|
+
hook_event_name: "Stop",
|
|
7824
|
+
cwd,
|
|
7825
|
+
session_id: "sess-stop-team-canonical-current-thread",
|
|
7826
|
+
thread_id: "thread-current",
|
|
7827
|
+
},
|
|
7828
|
+
{ cwd },
|
|
7829
|
+
);
|
|
7830
|
+
|
|
7831
|
+
assert.equal(result.omxEventName, "stop");
|
|
7832
|
+
assert.deepEqual(result.outputJson, {
|
|
7833
|
+
decision: "block",
|
|
7834
|
+
reason:
|
|
7835
|
+
`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}`,
|
|
7836
|
+
stopReason: "team_team-exec",
|
|
7837
|
+
systemMessage: "OMX team pipeline is still active at phase team-exec.",
|
|
7838
|
+
});
|
|
7839
|
+
} finally {
|
|
7840
|
+
await rm(cwd, { recursive: true, force: true });
|
|
7841
|
+
}
|
|
7842
|
+
});
|
|
7843
|
+
|
|
7844
|
+
it("emits one concise final decision summary and auto-finalize guidance when release-readiness already has a stable final recommendation and no active worker tasks", async () => {
|
|
7845
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-release-readiness-finalize-"));
|
|
7846
|
+
try {
|
|
7847
|
+
await initTeamState(
|
|
7848
|
+
"release-ready-team",
|
|
7849
|
+
"release readiness finalize",
|
|
7850
|
+
"executor",
|
|
7851
|
+
1,
|
|
7852
|
+
cwd,
|
|
7853
|
+
undefined,
|
|
7854
|
+
{ ...process.env, OMX_SESSION_ID: "sess-stop-release-ready" },
|
|
7855
|
+
);
|
|
7856
|
+
await writeReleaseReadinessLeaderAttention(
|
|
7857
|
+
"release-ready-team",
|
|
7858
|
+
"sess-stop-release-ready",
|
|
7859
|
+
cwd,
|
|
7860
|
+
{ workRemaining: false },
|
|
7861
|
+
);
|
|
7862
|
+
await writeReleaseReadinessStateMarker(
|
|
7863
|
+
"sess-stop-release-ready",
|
|
7864
|
+
"release-ready-team",
|
|
7865
|
+
cwd,
|
|
7866
|
+
);
|
|
7867
|
+
|
|
7868
|
+
const result = await dispatchCodexNativeHook(
|
|
7869
|
+
{
|
|
7870
|
+
hook_event_name: "Stop",
|
|
7871
|
+
cwd,
|
|
7872
|
+
session_id: "sess-stop-release-ready",
|
|
7873
|
+
thread_id: "thread-stop-release-ready",
|
|
7874
|
+
turn_id: "turn-stop-release-ready-1",
|
|
7875
|
+
mode: "release-readiness",
|
|
7876
|
+
last_assistant_message: "Launch-ready: yes",
|
|
7877
|
+
},
|
|
7878
|
+
{ cwd },
|
|
7879
|
+
);
|
|
7880
|
+
|
|
7881
|
+
assert.equal(result.omxEventName, "stop");
|
|
7882
|
+
assert.deepEqual(result.outputJson, {
|
|
6737
7883
|
decision: "block",
|
|
6738
7884
|
reason:
|
|
6739
7885
|
'Stable final recommendation already reached with no active worker tasks. Emit exactly one concise final decision summary aligned to "Launch-ready: yes." with no filler or residual acknowledgements (for example "yes"), then stop.',
|
|
@@ -6969,7 +8115,7 @@ exit 0
|
|
|
6969
8115
|
const sharedRoot = join(cwd, "shared-root");
|
|
6970
8116
|
const priorTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT;
|
|
6971
8117
|
try {
|
|
6972
|
-
process.env.OMX_TEAM_STATE_ROOT =
|
|
8118
|
+
process.env.OMX_TEAM_STATE_ROOT = sharedRoot;
|
|
6973
8119
|
await initTeamState(
|
|
6974
8120
|
"canonical-root-team",
|
|
6975
8121
|
"canonical stop root fallback",
|
|
@@ -6977,7 +8123,7 @@ exit 0
|
|
|
6977
8123
|
1,
|
|
6978
8124
|
cwd,
|
|
6979
8125
|
undefined,
|
|
6980
|
-
{ ...process.env, OMX_SESSION_ID: "sess-stop-team-root", OMX_TEAM_STATE_ROOT:
|
|
8126
|
+
{ ...process.env, OMX_SESSION_ID: "sess-stop-team-root", OMX_TEAM_STATE_ROOT: sharedRoot },
|
|
6981
8127
|
);
|
|
6982
8128
|
|
|
6983
8129
|
const result = await dispatchCodexNativeHook(
|
|
@@ -7042,9 +8188,10 @@ exit 0
|
|
|
7042
8188
|
|
|
7043
8189
|
it("returns Stop continuation output from canonical team state rooted via OMX_TEAM_STATE_ROOT", async () => {
|
|
7044
8190
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-env-root-"));
|
|
8191
|
+
const teamStateRoot = join(cwd, "shared-team-state");
|
|
7045
8192
|
const previousTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT;
|
|
7046
8193
|
try {
|
|
7047
|
-
process.env.OMX_TEAM_STATE_ROOT =
|
|
8194
|
+
process.env.OMX_TEAM_STATE_ROOT = teamStateRoot;
|
|
7048
8195
|
await initTeamState(
|
|
7049
8196
|
"env-root-team",
|
|
7050
8197
|
"env root stop fallback",
|
|
@@ -7055,7 +8202,7 @@ exit 0
|
|
|
7055
8202
|
{
|
|
7056
8203
|
...process.env,
|
|
7057
8204
|
OMX_SESSION_ID: "sess-stop-team-env-root",
|
|
7058
|
-
OMX_TEAM_STATE_ROOT:
|
|
8205
|
+
OMX_TEAM_STATE_ROOT: teamStateRoot,
|
|
7059
8206
|
},
|
|
7060
8207
|
);
|
|
7061
8208
|
|
|
@@ -8381,16 +9528,29 @@ exit 0
|
|
|
8381
9528
|
assert.equal(result.omxEventName, "stop");
|
|
8382
9529
|
const reason = String(result.outputJson?.reason);
|
|
8383
9530
|
assert.match(reason, /Ralph completion audit is missing required evidence/);
|
|
8384
|
-
assert.match(reason, /
|
|
9531
|
+
assert.match(reason, /set "completion_audit" on the Ralph state object/);
|
|
9532
|
+
assert.doesNotMatch(reason, /state\.completion_audit/);
|
|
8385
9533
|
assert.match(reason, /repo-relative JSON file/);
|
|
8386
9534
|
assert.match(reason, /Markdown artifacts and flat top-level checklist\/evidence fields are not accepted/);
|
|
8387
9535
|
assert.equal(result.outputJson?.stopReason, "ralph_completion_audit_missing_completion_audit");
|
|
8388
9536
|
const reopened = JSON.parse(await readFile(statePath, "utf-8")) as Record<string, unknown>;
|
|
8389
|
-
assert.equal(reopened.active,
|
|
8390
|
-
assert.equal(reopened.current_phase, "
|
|
9537
|
+
assert.equal(reopened.active, false);
|
|
9538
|
+
assert.equal(reopened.current_phase, "complete");
|
|
8391
9539
|
assert.equal(reopened.completion_audit_gate, "blocked");
|
|
8392
9540
|
assert.equal(reopened.completion_audit_missing_reason, "missing_completion_audit");
|
|
8393
|
-
assert.equal(
|
|
9541
|
+
assert.equal(reopened.completed_at, "2026-05-10T12:00:00.000Z");
|
|
9542
|
+
|
|
9543
|
+
const repeat = await dispatchCodexNativeHook(
|
|
9544
|
+
{
|
|
9545
|
+
hook_event_name: "Stop",
|
|
9546
|
+
cwd,
|
|
9547
|
+
session_id: sessionId,
|
|
9548
|
+
last_assistant_message: "Done. Ralph complete.",
|
|
9549
|
+
},
|
|
9550
|
+
{ cwd },
|
|
9551
|
+
);
|
|
9552
|
+
assert.equal(repeat.outputJson?.stopReason, "ralph_completion_audit_missing_completion_audit");
|
|
9553
|
+
assert.doesNotMatch(String(repeat.outputJson?.reason), /Ralph is still active/);
|
|
8394
9554
|
} finally {
|
|
8395
9555
|
await rm(cwd, { recursive: true, force: true });
|
|
8396
9556
|
}
|
|
@@ -10266,6 +11426,8 @@ exit 0
|
|
|
10266
11426
|
active: true,
|
|
10267
11427
|
current_phase: "team-exec",
|
|
10268
11428
|
team_name: "review-team",
|
|
11429
|
+
session_id: "sess-stop-team-refire",
|
|
11430
|
+
thread_id: "thread-stop-team-refire",
|
|
10269
11431
|
});
|
|
10270
11432
|
await writeJson(join(stateDir, "team", "review-team", "phase.json"), {
|
|
10271
11433
|
current_phase: "team-verify",
|
|
@@ -10505,6 +11667,250 @@ exit 0
|
|
|
10505
11667
|
}
|
|
10506
11668
|
});
|
|
10507
11669
|
|
|
11670
|
+
it("does not block Stop from root team state without team_name when no session is known", async () => {
|
|
11671
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-root-team-no-session-no-name-"));
|
|
11672
|
+
try {
|
|
11673
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
11674
|
+
await mkdir(stateDir, { recursive: true });
|
|
11675
|
+
await writeJson(join(stateDir, "team-state.json"), {
|
|
11676
|
+
active: true,
|
|
11677
|
+
mode: "team",
|
|
11678
|
+
current_phase: "starting",
|
|
11679
|
+
});
|
|
11680
|
+
|
|
11681
|
+
const result = await dispatchCodexNativeHook(
|
|
11682
|
+
{
|
|
11683
|
+
hook_event_name: "Stop",
|
|
11684
|
+
cwd,
|
|
11685
|
+
},
|
|
11686
|
+
{ cwd },
|
|
11687
|
+
);
|
|
11688
|
+
|
|
11689
|
+
assert.equal(result.omxEventName, "stop");
|
|
11690
|
+
assert.equal(result.outputJson, null);
|
|
11691
|
+
} finally {
|
|
11692
|
+
await rm(cwd, { recursive: true, force: true });
|
|
11693
|
+
}
|
|
11694
|
+
});
|
|
11695
|
+
|
|
11696
|
+
it("does not block Stop from root team state without team_name for a foreign session", async () => {
|
|
11697
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-root-team-foreign-no-name-"));
|
|
11698
|
+
try {
|
|
11699
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
11700
|
+
await mkdir(join(stateDir, "sessions", "sess-current"), { recursive: true });
|
|
11701
|
+
await writeJson(join(stateDir, "session.json"), { session_id: "sess-current" });
|
|
11702
|
+
await writeJson(join(stateDir, "team-state.json"), {
|
|
11703
|
+
active: true,
|
|
11704
|
+
mode: "team",
|
|
11705
|
+
current_phase: "starting",
|
|
11706
|
+
});
|
|
11707
|
+
|
|
11708
|
+
const result = await dispatchCodexNativeHook(
|
|
11709
|
+
{
|
|
11710
|
+
hook_event_name: "Stop",
|
|
11711
|
+
cwd,
|
|
11712
|
+
session_id: "sess-current",
|
|
11713
|
+
thread_id: "thread-current",
|
|
11714
|
+
},
|
|
11715
|
+
{ cwd },
|
|
11716
|
+
);
|
|
11717
|
+
|
|
11718
|
+
assert.equal(result.omxEventName, "stop");
|
|
11719
|
+
assert.equal(result.outputJson, null);
|
|
11720
|
+
} finally {
|
|
11721
|
+
await rm(cwd, { recursive: true, force: true });
|
|
11722
|
+
}
|
|
11723
|
+
});
|
|
11724
|
+
|
|
11725
|
+
it("does not block Stop from another thread's stale root team state when no scoped team state exists", async () => {
|
|
11726
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-stale-root-team-thread-"));
|
|
11727
|
+
try {
|
|
11728
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
11729
|
+
await mkdir(join(stateDir, "sessions", "sess-current"), { recursive: true });
|
|
11730
|
+
await writeJson(join(stateDir, "session.json"), { session_id: "sess-current" });
|
|
11731
|
+
await writeJson(join(stateDir, "team-state.json"), {
|
|
11732
|
+
active: true,
|
|
11733
|
+
current_phase: "starting",
|
|
11734
|
+
team_name: "stale-root-thread-team",
|
|
11735
|
+
session_id: "sess-current",
|
|
11736
|
+
thread_id: "thread-other",
|
|
11737
|
+
});
|
|
11738
|
+
await writeJson(join(stateDir, "team", "stale-root-thread-team", "phase.json"), {
|
|
11739
|
+
current_phase: "team-exec",
|
|
11740
|
+
max_fix_attempts: 3,
|
|
11741
|
+
current_fix_attempt: 0,
|
|
11742
|
+
transitions: [],
|
|
11743
|
+
updated_at: new Date().toISOString(),
|
|
11744
|
+
});
|
|
11745
|
+
|
|
11746
|
+
const result = await dispatchCodexNativeHook(
|
|
11747
|
+
{
|
|
11748
|
+
hook_event_name: "Stop",
|
|
11749
|
+
cwd,
|
|
11750
|
+
session_id: "sess-current",
|
|
11751
|
+
thread_id: "thread-current",
|
|
11752
|
+
},
|
|
11753
|
+
{ cwd },
|
|
11754
|
+
);
|
|
11755
|
+
|
|
11756
|
+
assert.equal(result.omxEventName, "stop");
|
|
11757
|
+
assert.equal(result.outputJson, null);
|
|
11758
|
+
} finally {
|
|
11759
|
+
await rm(cwd, { recursive: true, force: true });
|
|
11760
|
+
}
|
|
11761
|
+
});
|
|
11762
|
+
|
|
11763
|
+
it("does not block Stop from root team state with matching session but missing thread ownership", async () => {
|
|
11764
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-root-team-missing-thread-"));
|
|
11765
|
+
try {
|
|
11766
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
11767
|
+
await mkdir(join(stateDir, "sessions", "sess-current"), { recursive: true });
|
|
11768
|
+
await writeJson(join(stateDir, "session.json"), { session_id: "sess-current" });
|
|
11769
|
+
await writeJson(join(stateDir, "team-state.json"), {
|
|
11770
|
+
active: true,
|
|
11771
|
+
current_phase: "starting",
|
|
11772
|
+
team_name: "root-missing-thread-team",
|
|
11773
|
+
session_id: "sess-current",
|
|
11774
|
+
});
|
|
11775
|
+
await writeJson(join(stateDir, "team", "root-missing-thread-team", "phase.json"), {
|
|
11776
|
+
current_phase: "team-exec",
|
|
11777
|
+
max_fix_attempts: 3,
|
|
11778
|
+
current_fix_attempt: 0,
|
|
11779
|
+
transitions: [],
|
|
11780
|
+
updated_at: new Date().toISOString(),
|
|
11781
|
+
});
|
|
11782
|
+
|
|
11783
|
+
const result = await dispatchCodexNativeHook(
|
|
11784
|
+
{
|
|
11785
|
+
hook_event_name: "Stop",
|
|
11786
|
+
cwd,
|
|
11787
|
+
session_id: "sess-current",
|
|
11788
|
+
thread_id: "thread-current",
|
|
11789
|
+
},
|
|
11790
|
+
{ cwd },
|
|
11791
|
+
);
|
|
11792
|
+
|
|
11793
|
+
assert.equal(result.omxEventName, "stop");
|
|
11794
|
+
assert.equal(result.outputJson, null);
|
|
11795
|
+
} finally {
|
|
11796
|
+
await rm(cwd, { recursive: true, force: true });
|
|
11797
|
+
}
|
|
11798
|
+
});
|
|
11799
|
+
|
|
11800
|
+
it("does not block Stop from root team state when canonical phase is missing", async () => {
|
|
11801
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-root-team-missing-phase-"));
|
|
11802
|
+
try {
|
|
11803
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
11804
|
+
await mkdir(join(stateDir, "sessions", "sess-current"), { recursive: true });
|
|
11805
|
+
await mkdir(join(stateDir, "team", "root-missing-phase-team"), { recursive: true });
|
|
11806
|
+
await writeJson(join(stateDir, "session.json"), { session_id: "sess-current" });
|
|
11807
|
+
await writeJson(join(stateDir, "team-state.json"), {
|
|
11808
|
+
active: true,
|
|
11809
|
+
current_phase: "starting",
|
|
11810
|
+
team_name: "root-missing-phase-team",
|
|
11811
|
+
session_id: "sess-current",
|
|
11812
|
+
thread_id: "thread-current",
|
|
11813
|
+
});
|
|
11814
|
+
|
|
11815
|
+
const result = await dispatchCodexNativeHook(
|
|
11816
|
+
{
|
|
11817
|
+
hook_event_name: "Stop",
|
|
11818
|
+
cwd,
|
|
11819
|
+
session_id: "sess-current",
|
|
11820
|
+
thread_id: "thread-current",
|
|
11821
|
+
},
|
|
11822
|
+
{ cwd },
|
|
11823
|
+
);
|
|
11824
|
+
|
|
11825
|
+
assert.equal(result.omxEventName, "stop");
|
|
11826
|
+
assert.equal(result.outputJson, null);
|
|
11827
|
+
} finally {
|
|
11828
|
+
await rm(cwd, { recursive: true, force: true });
|
|
11829
|
+
}
|
|
11830
|
+
});
|
|
11831
|
+
|
|
11832
|
+
it("does not block Stop from session-scoped team state owned by another thread", async () => {
|
|
11833
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-scoped-team-other-thread-"));
|
|
11834
|
+
try {
|
|
11835
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
11836
|
+
await mkdir(join(stateDir, "sessions", "sess-current"), { recursive: true });
|
|
11837
|
+
await writeJson(join(stateDir, "session.json"), { session_id: "sess-current" });
|
|
11838
|
+
await writeJson(join(stateDir, "sessions", "sess-current", "team-state.json"), {
|
|
11839
|
+
active: true,
|
|
11840
|
+
current_phase: "starting",
|
|
11841
|
+
team_name: "scoped-other-thread-team",
|
|
11842
|
+
session_id: "sess-current",
|
|
11843
|
+
thread_id: "thread-other",
|
|
11844
|
+
});
|
|
11845
|
+
await writeJson(join(stateDir, "team", "scoped-other-thread-team", "phase.json"), {
|
|
11846
|
+
current_phase: "team-exec",
|
|
11847
|
+
max_fix_attempts: 3,
|
|
11848
|
+
current_fix_attempt: 0,
|
|
11849
|
+
transitions: [],
|
|
11850
|
+
updated_at: new Date().toISOString(),
|
|
11851
|
+
});
|
|
11852
|
+
|
|
11853
|
+
const result = await dispatchCodexNativeHook(
|
|
11854
|
+
{
|
|
11855
|
+
hook_event_name: "Stop",
|
|
11856
|
+
cwd,
|
|
11857
|
+
session_id: "sess-current",
|
|
11858
|
+
thread_id: "thread-current",
|
|
11859
|
+
},
|
|
11860
|
+
{ cwd },
|
|
11861
|
+
);
|
|
11862
|
+
|
|
11863
|
+
assert.equal(result.omxEventName, "stop");
|
|
11864
|
+
assert.equal(result.outputJson, null);
|
|
11865
|
+
} finally {
|
|
11866
|
+
await rm(cwd, { recursive: true, force: true });
|
|
11867
|
+
}
|
|
11868
|
+
});
|
|
11869
|
+
|
|
11870
|
+
it("blocks Stop from session-scoped team state owned by the current session and thread", async () => {
|
|
11871
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-scoped-team-current-thread-"));
|
|
11872
|
+
try {
|
|
11873
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
11874
|
+
await mkdir(join(stateDir, "sessions", "sess-current"), { recursive: true });
|
|
11875
|
+
await writeJson(join(stateDir, "session.json"), { session_id: "sess-current" });
|
|
11876
|
+
await writeJson(join(stateDir, "sessions", "sess-current", "team-state.json"), {
|
|
11877
|
+
active: true,
|
|
11878
|
+
current_phase: "starting",
|
|
11879
|
+
team_name: "scoped-current-team",
|
|
11880
|
+
session_id: "sess-current",
|
|
11881
|
+
thread_id: "thread-current",
|
|
11882
|
+
});
|
|
11883
|
+
await writeJson(join(stateDir, "team", "scoped-current-team", "phase.json"), {
|
|
11884
|
+
current_phase: "team-exec",
|
|
11885
|
+
max_fix_attempts: 3,
|
|
11886
|
+
current_fix_attempt: 0,
|
|
11887
|
+
transitions: [],
|
|
11888
|
+
updated_at: new Date().toISOString(),
|
|
11889
|
+
});
|
|
11890
|
+
|
|
11891
|
+
const result = await dispatchCodexNativeHook(
|
|
11892
|
+
{
|
|
11893
|
+
hook_event_name: "Stop",
|
|
11894
|
+
cwd,
|
|
11895
|
+
session_id: "sess-current",
|
|
11896
|
+
thread_id: "thread-current",
|
|
11897
|
+
},
|
|
11898
|
+
{ cwd },
|
|
11899
|
+
);
|
|
11900
|
+
|
|
11901
|
+
assert.equal(result.omxEventName, "stop");
|
|
11902
|
+
assert.deepEqual(result.outputJson, {
|
|
11903
|
+
decision: "block",
|
|
11904
|
+
reason:
|
|
11905
|
+
`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}`,
|
|
11906
|
+
stopReason: "team_team-exec",
|
|
11907
|
+
systemMessage: "OMX team pipeline is still active at phase team-exec.",
|
|
11908
|
+
});
|
|
11909
|
+
} finally {
|
|
11910
|
+
await rm(cwd, { recursive: true, force: true });
|
|
11911
|
+
}
|
|
11912
|
+
});
|
|
11913
|
+
|
|
10508
11914
|
it("does not block Stop from another session's stale root team state when no scoped team state exists", async () => {
|
|
10509
11915
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-stale-root-team-"));
|
|
10510
11916
|
try {
|
|
@@ -10718,6 +12124,91 @@ describe("codex native hook triage integration", () => {
|
|
|
10718
12124
|
}
|
|
10719
12125
|
});
|
|
10720
12126
|
|
|
12127
|
+
|
|
12128
|
+
it("does not activate workflow state for native subagent prompts even when canonical id is the child session", async () => {
|
|
12129
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-subagent-keyword-"));
|
|
12130
|
+
const boxedRoot = await mkdtemp(join(tmpdir(), "omx-native-subagent-keyword-boxed-"));
|
|
12131
|
+
const originalOmxRoot = process.env.OMX_ROOT;
|
|
12132
|
+
const originalOmxStateRoot = process.env.OMX_STATE_ROOT;
|
|
12133
|
+
const originalTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT;
|
|
12134
|
+
try {
|
|
12135
|
+
process.env.OMX_ROOT = boxedRoot;
|
|
12136
|
+
delete process.env.OMX_STATE_ROOT;
|
|
12137
|
+
delete process.env.OMX_TEAM_STATE_ROOT;
|
|
12138
|
+
const boxedStateDir = getBaseStateDir(cwd);
|
|
12139
|
+
await mkdir(boxedStateDir, { recursive: true });
|
|
12140
|
+
await writeJson(join(boxedStateDir, "subagent-tracking.json"), {
|
|
12141
|
+
schemaVersion: 1,
|
|
12142
|
+
sessions: {
|
|
12143
|
+
"omx-parent-session": {
|
|
12144
|
+
session_id: "omx-parent-session",
|
|
12145
|
+
leader_thread_id: "parent-native-thread",
|
|
12146
|
+
updated_at: "2026-05-21T19:04:40.000Z",
|
|
12147
|
+
threads: {
|
|
12148
|
+
"parent-native-thread": {
|
|
12149
|
+
thread_id: "parent-native-thread",
|
|
12150
|
+
kind: "leader",
|
|
12151
|
+
first_seen_at: "2026-05-21T19:04:40.000Z",
|
|
12152
|
+
last_seen_at: "2026-05-21T19:04:40.000Z",
|
|
12153
|
+
turn_count: 1,
|
|
12154
|
+
},
|
|
12155
|
+
"child-native-session": {
|
|
12156
|
+
thread_id: "child-native-session",
|
|
12157
|
+
kind: "subagent",
|
|
12158
|
+
first_seen_at: "2026-05-21T19:04:41.000Z",
|
|
12159
|
+
last_seen_at: "2026-05-21T19:04:41.000Z",
|
|
12160
|
+
turn_count: 1,
|
|
12161
|
+
mode: "review",
|
|
12162
|
+
},
|
|
12163
|
+
},
|
|
12164
|
+
},
|
|
12165
|
+
},
|
|
12166
|
+
});
|
|
12167
|
+
|
|
12168
|
+
const result = await dispatchCodexNativeHook(
|
|
12169
|
+
{
|
|
12170
|
+
hook_event_name: "UserPromptSubmit",
|
|
12171
|
+
cwd,
|
|
12172
|
+
session_id: "child-native-session",
|
|
12173
|
+
thread_id: "child-native-session",
|
|
12174
|
+
turn_id: "turn-subagent-review",
|
|
12175
|
+
prompt: [
|
|
12176
|
+
"Read-only review only. Do not edit files. Do not inspect/mutate OMX state/hooks.",
|
|
12177
|
+
"Context: The user asked for $autopilot, and this subagent must only review the patch.",
|
|
12178
|
+
].join("\n\n"),
|
|
12179
|
+
},
|
|
12180
|
+
{ cwd },
|
|
12181
|
+
);
|
|
12182
|
+
|
|
12183
|
+
const additionalContext = String(
|
|
12184
|
+
(result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext ?? "",
|
|
12185
|
+
);
|
|
12186
|
+
assert.equal(additionalContext, "");
|
|
12187
|
+
assert.equal(
|
|
12188
|
+
existsSync(join(boxedStateDir, "sessions", "child-native-session", "skill-active-state.json")),
|
|
12189
|
+
false,
|
|
12190
|
+
);
|
|
12191
|
+
assert.equal(
|
|
12192
|
+
existsSync(join(boxedStateDir, "sessions", "child-native-session", "autopilot-state.json")),
|
|
12193
|
+
false,
|
|
12194
|
+
);
|
|
12195
|
+
assert.equal(
|
|
12196
|
+
existsSync(join(cwd, ".omx", "state", "subagent-tracking.json")),
|
|
12197
|
+
false,
|
|
12198
|
+
"subagent tracking must not leak into the source worktree when OMX_ROOT is boxed",
|
|
12199
|
+
);
|
|
12200
|
+
} finally {
|
|
12201
|
+
if (originalOmxRoot === undefined) delete process.env.OMX_ROOT;
|
|
12202
|
+
else process.env.OMX_ROOT = originalOmxRoot;
|
|
12203
|
+
if (originalOmxStateRoot === undefined) delete process.env.OMX_STATE_ROOT;
|
|
12204
|
+
else process.env.OMX_STATE_ROOT = originalOmxStateRoot;
|
|
12205
|
+
if (originalTeamStateRoot === undefined) delete process.env.OMX_TEAM_STATE_ROOT;
|
|
12206
|
+
else process.env.OMX_TEAM_STATE_ROOT = originalTeamStateRoot;
|
|
12207
|
+
await rm(cwd, { recursive: true, force: true });
|
|
12208
|
+
await rm(boxedRoot, { recursive: true, force: true });
|
|
12209
|
+
}
|
|
12210
|
+
});
|
|
12211
|
+
|
|
10721
12212
|
it("does not inject triage advisory for autopilot keyword prompts", async () => {
|
|
10722
12213
|
const cwd = await mkdtemp(join(tmpdir(), "omx-triage-keyword-autopilot-"));
|
|
10723
12214
|
try {
|
|
@@ -10749,6 +12240,62 @@ describe("codex native hook triage integration", () => {
|
|
|
10749
12240
|
}
|
|
10750
12241
|
});
|
|
10751
12242
|
|
|
12243
|
+
it("makes autopilot keyword activation observable in state, HUD context, and prompt guidance", async () => {
|
|
12244
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-autopilot-observable-"));
|
|
12245
|
+
try {
|
|
12246
|
+
await mkdir(join(cwd, ".omx", "state"), { recursive: true });
|
|
12247
|
+
await writeSessionStart(cwd, "sess-autopilot-observable");
|
|
12248
|
+
|
|
12249
|
+
const result = await dispatchCodexNativeHook(
|
|
12250
|
+
{
|
|
12251
|
+
hook_event_name: "UserPromptSubmit",
|
|
12252
|
+
cwd,
|
|
12253
|
+
session_id: "sess-autopilot-observable",
|
|
12254
|
+
thread_id: "thread-autopilot-observable",
|
|
12255
|
+
turn_id: "turn-autopilot-observable",
|
|
12256
|
+
prompt: "$autopilot implement issue #2430",
|
|
12257
|
+
},
|
|
12258
|
+
{ cwd },
|
|
12259
|
+
);
|
|
12260
|
+
|
|
12261
|
+
assert.equal(result.skillState?.skill, "autopilot");
|
|
12262
|
+
assert.equal(result.skillState?.phase, "deep-interview");
|
|
12263
|
+
assert.equal(result.skillState?.initialized_state_path, ".omx/state/sessions/sess-autopilot-observable/autopilot-state.json");
|
|
12264
|
+
|
|
12265
|
+
const additionalContext = String(
|
|
12266
|
+
(result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext ?? "",
|
|
12267
|
+
);
|
|
12268
|
+
assert.match(additionalContext, /detected workflow keyword "\$autopilot" -> autopilot/);
|
|
12269
|
+
assert.match(additionalContext, /\$deep-interview -> \$ralplan -> \$ultragoal \(\+ \$team if needed\) -> \$code-review -> \$ultraqa/);
|
|
12270
|
+
assert.match(additionalContext, /deep_interview_gate\.skip_reason/);
|
|
12271
|
+
assert.match(additionalContext, /Do not silently fall back to ordinary \$plan\/ralplan-only handling/);
|
|
12272
|
+
assert.match(additionalContext, /Codex goal-mode handoff guidance/);
|
|
12273
|
+
assert.doesNotMatch(additionalContext, /multi-step goal with no workflow keyword/);
|
|
12274
|
+
|
|
12275
|
+
const statePath = join(cwd, ".omx", "state", "sessions", "sess-autopilot-observable", "autopilot-state.json");
|
|
12276
|
+
const modeState = JSON.parse(await readFile(statePath, "utf-8")) as {
|
|
12277
|
+
active: boolean;
|
|
12278
|
+
current_phase: string;
|
|
12279
|
+
state?: { phase_cycle?: string[]; deep_interview_gate?: { status?: string; skip_reason?: string | null } };
|
|
12280
|
+
};
|
|
12281
|
+
assert.equal(modeState.active, true);
|
|
12282
|
+
assert.equal(modeState.current_phase, "deep-interview");
|
|
12283
|
+
assert.deepEqual(modeState.state?.phase_cycle, ["deep-interview", "ralplan", "ultragoal", "code-review", "ultraqa"]);
|
|
12284
|
+
assert.deepEqual(modeState.state?.deep_interview_gate, {
|
|
12285
|
+
status: "required",
|
|
12286
|
+
skip_reason: null,
|
|
12287
|
+
rationale: "Autopilot starts at the deep-interview gate by default; clear bounded tasks may skip only with an explicit persisted skip reason.",
|
|
12288
|
+
});
|
|
12289
|
+
|
|
12290
|
+
const hudState = await readAllState(cwd);
|
|
12291
|
+
assert.equal(hudState.autopilot?.active, true);
|
|
12292
|
+
assert.equal(hudState.autopilot?.current_phase, "deep-interview");
|
|
12293
|
+
assert.match(renderHud(hudState, "focused"), /autopilot:deep-interview/);
|
|
12294
|
+
} finally {
|
|
12295
|
+
await rm(cwd, { recursive: true, force: true });
|
|
12296
|
+
}
|
|
12297
|
+
});
|
|
12298
|
+
|
|
10752
12299
|
// ── Group 2: HEAVY injection ─────────────────────────────────────────────
|
|
10753
12300
|
|
|
10754
12301
|
it("injects HEAVY advisory and writes prompt-routing-state for a multi-step goal prompt", async () => {
|