oh-my-codex 0.15.3 → 0.16.0
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 +10 -7
- package/Cargo.toml +1 -1
- package/README.md +3 -0
- package/crates/omx-explore/Cargo.toml +3 -0
- package/crates/omx-explore/src/main.rs +517 -16
- package/dist/autoresearch/goal.d.ts +90 -0
- package/dist/autoresearch/goal.d.ts.map +1 -0
- package/dist/autoresearch/goal.js +237 -0
- package/dist/autoresearch/goal.js.map +1 -0
- package/dist/autoresearch/skill-validation.d.ts +1 -0
- package/dist/autoresearch/skill-validation.d.ts.map +1 -1
- package/dist/autoresearch/skill-validation.js +10 -3
- package/dist/autoresearch/skill-validation.js.map +1 -1
- package/dist/catalog/__tests__/generator.test.js +9 -4
- package/dist/catalog/__tests__/generator.test.js.map +1 -1
- package/dist/catalog/__tests__/plugin-bundle-ssot.test.js +20 -1
- package/dist/catalog/__tests__/plugin-bundle-ssot.test.js.map +1 -1
- package/dist/catalog/__tests__/schema.test.js +14 -3
- package/dist/catalog/__tests__/schema.test.js.map +1 -1
- package/dist/catalog/schema.js +1 -1
- package/dist/catalog/schema.js.map +1 -1
- package/dist/cli/__tests__/autoresearch-goal.test.d.ts +2 -0
- package/dist/cli/__tests__/autoresearch-goal.test.d.ts.map +1 -0
- package/dist/cli/__tests__/autoresearch-goal.test.js +194 -0
- package/dist/cli/__tests__/autoresearch-goal.test.js.map +1 -0
- package/dist/cli/__tests__/cleanup.test.js +82 -1
- package/dist/cli/__tests__/cleanup.test.js.map +1 -1
- package/dist/cli/__tests__/codex-plugin-layout.test.js +7 -4
- package/dist/cli/__tests__/codex-plugin-layout.test.js.map +1 -1
- package/dist/cli/__tests__/doctor-warning-copy.test.js +23 -0
- package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
- package/dist/cli/__tests__/explore.test.js +8 -1
- package/dist/cli/__tests__/explore.test.js.map +1 -1
- package/dist/cli/__tests__/index.test.js +82 -3
- package/dist/cli/__tests__/index.test.js.map +1 -1
- package/dist/cli/__tests__/launch-fallback.test.js +58 -0
- package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
- package/dist/cli/__tests__/native-assets.test.js +26 -1
- package/dist/cli/__tests__/native-assets.test.js.map +1 -1
- package/dist/cli/__tests__/package-bin-contract.test.js +2 -2
- package/dist/cli/__tests__/package-bin-contract.test.js.map +1 -1
- package/dist/cli/__tests__/performance-goal.test.d.ts +2 -0
- package/dist/cli/__tests__/performance-goal.test.d.ts.map +1 -0
- package/dist/cli/__tests__/performance-goal.test.js +144 -0
- package/dist/cli/__tests__/performance-goal.test.js.map +1 -0
- package/dist/cli/__tests__/question.test.js +8 -0
- package/dist/cli/__tests__/question.test.js.map +1 -1
- package/dist/cli/__tests__/ralph-goal-mode-contract.test.d.ts +2 -0
- package/dist/cli/__tests__/ralph-goal-mode-contract.test.d.ts.map +1 -0
- package/dist/cli/__tests__/ralph-goal-mode-contract.test.js +31 -0
- package/dist/cli/__tests__/ralph-goal-mode-contract.test.js.map +1 -0
- package/dist/cli/__tests__/ralph-prd-deep-interview.test.js +5 -4
- package/dist/cli/__tests__/ralph-prd-deep-interview.test.js.map +1 -1
- package/dist/cli/__tests__/ralph-prd-smoke.test.js +7 -0
- package/dist/cli/__tests__/ralph-prd-smoke.test.js.map +1 -1
- package/dist/cli/__tests__/setup-install-mode.test.js +57 -21
- package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
- package/dist/cli/__tests__/setup-refresh.test.js +27 -8
- package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
- package/dist/cli/__tests__/setup-scope.test.js +18 -9
- package/dist/cli/__tests__/setup-scope.test.js.map +1 -1
- package/dist/cli/__tests__/setup-skill-validation.test.js +11 -11
- package/dist/cli/__tests__/setup-skill-validation.test.js.map +1 -1
- package/dist/cli/__tests__/setup-skills-overwrite.test.js +12 -12
- package/dist/cli/__tests__/setup-skills-overwrite.test.js.map +1 -1
- package/dist/cli/__tests__/team.test.js +187 -0
- package/dist/cli/__tests__/team.test.js.map +1 -1
- package/dist/cli/__tests__/ultragoal.test.d.ts +2 -0
- package/dist/cli/__tests__/ultragoal.test.d.ts.map +1 -0
- package/dist/cli/__tests__/ultragoal.test.js +106 -0
- package/dist/cli/__tests__/ultragoal.test.js.map +1 -0
- package/dist/cli/__tests__/uninstall.test.js +11 -0
- package/dist/cli/__tests__/uninstall.test.js.map +1 -1
- package/dist/cli/autoresearch-goal.d.ts +3 -0
- package/dist/cli/autoresearch-goal.d.ts.map +1 -0
- package/dist/cli/autoresearch-goal.js +175 -0
- package/dist/cli/autoresearch-goal.js.map +1 -0
- package/dist/cli/cleanup.d.ts +3 -1
- package/dist/cli/cleanup.d.ts.map +1 -1
- package/dist/cli/cleanup.js +42 -2
- package/dist/cli/cleanup.js.map +1 -1
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +49 -0
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/explore.d.ts.map +1 -1
- package/dist/cli/explore.js +10 -2
- package/dist/cli/explore.js.map +1 -1
- package/dist/cli/index.d.ts +6 -2
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +145 -18
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/native-assets.js +1 -1
- package/dist/cli/native-assets.js.map +1 -1
- package/dist/cli/performance-goal.d.ts +3 -0
- package/dist/cli/performance-goal.d.ts.map +1 -0
- package/dist/cli/performance-goal.js +186 -0
- package/dist/cli/performance-goal.js.map +1 -0
- package/dist/cli/ralph.d.ts.map +1 -1
- package/dist/cli/ralph.js +8 -0
- package/dist/cli/ralph.js.map +1 -1
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +13 -6
- package/dist/cli/setup.js.map +1 -1
- package/dist/cli/team.d.ts +2 -0
- package/dist/cli/team.d.ts.map +1 -1
- package/dist/cli/team.js +72 -17
- package/dist/cli/team.js.map +1 -1
- package/dist/cli/tmux-hook.d.ts.map +1 -1
- package/dist/cli/tmux-hook.js +2 -1
- package/dist/cli/tmux-hook.js.map +1 -1
- package/dist/cli/ultragoal.d.ts +3 -0
- package/dist/cli/ultragoal.d.ts.map +1 -0
- package/dist/cli/ultragoal.js +191 -0
- package/dist/cli/ultragoal.js.map +1 -0
- package/dist/cli/uninstall.d.ts.map +1 -1
- package/dist/cli/uninstall.js +4 -2
- package/dist/cli/uninstall.js.map +1 -1
- package/dist/config/__tests__/generator-idempotent.test.js +12 -1
- package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
- package/dist/config/__tests__/generator-notify.test.js +5 -0
- package/dist/config/__tests__/generator-notify.test.js.map +1 -1
- package/dist/config/commit-lore-guard.d.ts +3 -0
- package/dist/config/commit-lore-guard.d.ts.map +1 -0
- package/dist/config/commit-lore-guard.js +9 -0
- package/dist/config/commit-lore-guard.js.map +1 -0
- package/dist/config/generator.d.ts +3 -2
- package/dist/config/generator.d.ts.map +1 -1
- package/dist/config/generator.js +52 -8
- package/dist/config/generator.js.map +1 -1
- package/dist/config/omx-first-party-mcp.d.ts +1 -0
- package/dist/config/omx-first-party-mcp.d.ts.map +1 -1
- package/dist/config/omx-first-party-mcp.js +4 -1
- package/dist/config/omx-first-party-mcp.js.map +1 -1
- package/dist/goal-workflows/__tests__/artifacts.test.d.ts +2 -0
- package/dist/goal-workflows/__tests__/artifacts.test.d.ts.map +1 -0
- package/dist/goal-workflows/__tests__/artifacts.test.js +96 -0
- package/dist/goal-workflows/__tests__/artifacts.test.js.map +1 -0
- package/dist/goal-workflows/__tests__/codex-goal-snapshot.test.d.ts +2 -0
- package/dist/goal-workflows/__tests__/codex-goal-snapshot.test.d.ts.map +1 -0
- package/dist/goal-workflows/__tests__/codex-goal-snapshot.test.js +54 -0
- package/dist/goal-workflows/__tests__/codex-goal-snapshot.test.js.map +1 -0
- package/dist/goal-workflows/artifacts.d.ts +62 -0
- package/dist/goal-workflows/artifacts.d.ts.map +1 -0
- package/dist/goal-workflows/artifacts.js +132 -0
- package/dist/goal-workflows/artifacts.js.map +1 -0
- package/dist/goal-workflows/codex-goal-snapshot.d.ts +28 -0
- package/dist/goal-workflows/codex-goal-snapshot.d.ts.map +1 -0
- package/dist/goal-workflows/codex-goal-snapshot.js +110 -0
- package/dist/goal-workflows/codex-goal-snapshot.js.map +1 -0
- package/dist/goal-workflows/handoff.d.ts +10 -0
- package/dist/goal-workflows/handoff.d.ts.map +1 -0
- package/dist/goal-workflows/handoff.js +31 -0
- package/dist/goal-workflows/handoff.js.map +1 -0
- package/dist/goal-workflows/validation.d.ts +13 -0
- package/dist/goal-workflows/validation.d.ts.map +1 -0
- package/dist/goal-workflows/validation.js +36 -0
- package/dist/goal-workflows/validation.js.map +1 -0
- package/dist/hooks/__tests__/anti-slop-workflow.test.js +3 -3
- package/dist/hooks/__tests__/anti-slop-workflow.test.js.map +1 -1
- package/dist/hooks/__tests__/keyword-detector.test.js +45 -32
- package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-fallback-watcher.test.js +3 -3
- package/dist/hooks/__tests__/notify-fallback-watcher.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js +2 -1
- package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js +17 -24
- package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js.map +1 -1
- package/dist/hooks/__tests__/prompt-guidance-wave-two.test.js +3 -3
- package/dist/hooks/__tests__/prompt-guidance-wave-two.test.js.map +1 -1
- package/dist/hooks/__tests__/task-size-detector.test.js +1 -1
- package/dist/hooks/__tests__/task-size-detector.test.js.map +1 -1
- package/dist/hooks/__tests__/visual-ralph-skill.test.js +3 -3
- package/dist/hooks/__tests__/visual-ralph-skill.test.js.map +1 -1
- package/dist/hooks/__tests__/visual-verdict-loop.test.js +7 -11
- package/dist/hooks/__tests__/visual-verdict-loop.test.js.map +1 -1
- package/dist/hooks/agents-overlay.d.ts.map +1 -1
- package/dist/hooks/agents-overlay.js +2 -2
- package/dist/hooks/agents-overlay.js.map +1 -1
- package/dist/hooks/keyword-detector.d.ts.map +1 -1
- package/dist/hooks/keyword-detector.js +12 -13
- 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 +2 -10
- 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 +0 -4
- package/dist/hooks/prompt-guidance-contract.js.map +1 -1
- package/dist/hooks/session.js +2 -2
- package/dist/hooks/session.js.map +1 -1
- package/dist/hooks/task-size-detector.d.ts.map +1 -1
- package/dist/hooks/task-size-detector.js +1 -0
- package/dist/hooks/task-size-detector.js.map +1 -1
- package/dist/hud/__tests__/reconcile.test.js +29 -7
- package/dist/hud/__tests__/reconcile.test.js.map +1 -1
- package/dist/hud/reconcile.d.ts +2 -1
- package/dist/hud/reconcile.d.ts.map +1 -1
- package/dist/hud/reconcile.js +12 -0
- package/dist/hud/reconcile.js.map +1 -1
- package/dist/mcp/__tests__/bootstrap.test.js +15 -2
- package/dist/mcp/__tests__/bootstrap.test.js.map +1 -1
- package/dist/mcp/__tests__/state-paths.test.js +54 -0
- package/dist/mcp/__tests__/state-paths.test.js.map +1 -1
- package/dist/mcp/__tests__/state-server.test.js +36 -0
- package/dist/mcp/__tests__/state-server.test.js.map +1 -1
- package/dist/mcp/bootstrap.d.ts +1 -1
- package/dist/mcp/bootstrap.d.ts.map +1 -1
- package/dist/mcp/bootstrap.js +9 -7
- package/dist/mcp/bootstrap.js.map +1 -1
- package/dist/mcp/state-paths.d.ts +17 -0
- package/dist/mcp/state-paths.d.ts.map +1 -1
- package/dist/mcp/state-paths.js +36 -2
- package/dist/mcp/state-paths.js.map +1 -1
- package/dist/modes/__tests__/base-session-scope.test.js +26 -0
- package/dist/modes/__tests__/base-session-scope.test.js.map +1 -1
- package/dist/modes/base.d.ts +1 -0
- package/dist/modes/base.d.ts.map +1 -1
- package/dist/modes/base.js +35 -5
- package/dist/modes/base.js.map +1 -1
- package/dist/notifications/__tests__/http-client.test.d.ts +2 -0
- package/dist/notifications/__tests__/http-client.test.d.ts.map +1 -0
- package/dist/notifications/__tests__/http-client.test.js +90 -0
- package/dist/notifications/__tests__/http-client.test.js.map +1 -0
- package/dist/notifications/__tests__/notifier.test.js +22 -60
- package/dist/notifications/__tests__/notifier.test.js.map +1 -1
- package/dist/notifications/dispatcher.d.ts.map +1 -1
- package/dist/notifications/dispatcher.js +35 -60
- package/dist/notifications/dispatcher.js.map +1 -1
- package/dist/notifications/http-client.d.ts +22 -0
- package/dist/notifications/http-client.d.ts.map +1 -0
- package/dist/notifications/http-client.js +298 -0
- package/dist/notifications/http-client.js.map +1 -0
- package/dist/notifications/notifier.d.ts +3 -2
- package/dist/notifications/notifier.d.ts.map +1 -1
- package/dist/notifications/notifier.js +17 -22
- package/dist/notifications/notifier.js.map +1 -1
- package/dist/openclaw/__tests__/dispatcher.test.js +62 -1
- package/dist/openclaw/__tests__/dispatcher.test.js.map +1 -1
- package/dist/openclaw/dispatcher.d.ts.map +1 -1
- package/dist/openclaw/dispatcher.js +3 -2
- package/dist/openclaw/dispatcher.js.map +1 -1
- package/dist/performance-goal/artifacts.d.ts +76 -0
- package/dist/performance-goal/artifacts.d.ts.map +1 -0
- package/dist/performance-goal/artifacts.js +221 -0
- package/dist/performance-goal/artifacts.js.map +1 -0
- package/dist/pipeline/__tests__/stages.test.js +30 -5
- package/dist/pipeline/__tests__/stages.test.js.map +1 -1
- package/dist/pipeline/stages/team-exec.d.ts.map +1 -1
- package/dist/pipeline/stages/team-exec.js +2 -19
- package/dist/pipeline/stages/team-exec.js.map +1 -1
- package/dist/planning/__tests__/artifacts.test.js +16 -1
- package/dist/planning/__tests__/artifacts.test.js.map +1 -1
- package/dist/planning/artifacts.d.ts +1 -0
- package/dist/planning/artifacts.d.ts.map +1 -1
- package/dist/planning/artifacts.js +9 -12
- package/dist/planning/artifacts.js.map +1 -1
- package/dist/ralplan/__tests__/runtime.test.js +2 -0
- package/dist/ralplan/__tests__/runtime.test.js.map +1 -1
- package/dist/ralplan/runtime.d.ts.map +1 -1
- package/dist/ralplan/runtime.js +6 -0
- package/dist/ralplan/runtime.js.map +1 -1
- package/dist/scripts/__tests__/codex-native-hook.test.js +1516 -205
- package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
- package/dist/scripts/__tests__/hook-derived-watcher.test.js +33 -1
- package/dist/scripts/__tests__/hook-derived-watcher.test.js.map +1 -1
- package/dist/scripts/__tests__/run-test-files.test.js +36 -0
- package/dist/scripts/__tests__/run-test-files.test.js.map +1 -1
- package/dist/scripts/codex-native-hook.d.ts.map +1 -1
- package/dist/scripts/codex-native-hook.js +497 -51
- package/dist/scripts/codex-native-hook.js.map +1 -1
- package/dist/scripts/codex-native-pre-post.d.ts +7 -0
- package/dist/scripts/codex-native-pre-post.d.ts.map +1 -1
- package/dist/scripts/codex-native-pre-post.js +222 -19
- package/dist/scripts/codex-native-pre-post.js.map +1 -1
- package/dist/scripts/hook-derived-watcher.js +2 -1
- package/dist/scripts/hook-derived-watcher.js.map +1 -1
- package/dist/scripts/notify-fallback-watcher.js +2 -1
- package/dist/scripts/notify-fallback-watcher.js.map +1 -1
- package/dist/scripts/notify-hook/orchestration-intent.d.ts +1 -2
- package/dist/scripts/notify-hook/orchestration-intent.d.ts.map +1 -1
- package/dist/scripts/notify-hook/orchestration-intent.js +2 -3
- package/dist/scripts/notify-hook/orchestration-intent.js.map +1 -1
- package/dist/scripts/notify-hook/team-leader-nudge.d.ts +0 -2
- package/dist/scripts/notify-hook/team-leader-nudge.d.ts.map +1 -1
- package/dist/scripts/notify-hook/team-leader-nudge.js +8 -60
- package/dist/scripts/notify-hook/team-leader-nudge.js.map +1 -1
- package/dist/scripts/notify-hook/team-worker-stop.d.ts +15 -0
- package/dist/scripts/notify-hook/team-worker-stop.d.ts.map +1 -0
- package/dist/scripts/notify-hook/team-worker-stop.js +224 -0
- package/dist/scripts/notify-hook/team-worker-stop.js.map +1 -0
- package/dist/scripts/notify-hook/team-worker.d.ts.map +1 -1
- package/dist/scripts/notify-hook/team-worker.js +26 -18
- package/dist/scripts/notify-hook/team-worker.js.map +1 -1
- package/dist/scripts/run-test-files.js +17 -1
- package/dist/scripts/run-test-files.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/state/__tests__/operations.test.js +26 -0
- package/dist/state/__tests__/operations.test.js.map +1 -1
- package/dist/state/__tests__/skill-active.test.js +35 -0
- package/dist/state/__tests__/skill-active.test.js.map +1 -1
- package/dist/state/operations.d.ts +3 -1
- package/dist/state/operations.d.ts.map +1 -1
- package/dist/state/operations.js +8 -4
- package/dist/state/operations.js.map +1 -1
- package/dist/state/skill-active.d.ts +1 -0
- package/dist/state/skill-active.d.ts.map +1 -1
- package/dist/state/skill-active.js +54 -13
- package/dist/state/skill-active.js.map +1 -1
- package/dist/team/__tests__/api-interop.test.js +59 -0
- package/dist/team/__tests__/api-interop.test.js.map +1 -1
- package/dist/team/__tests__/approved-execution.test.d.ts +2 -0
- package/dist/team/__tests__/approved-execution.test.d.ts.map +1 -0
- package/dist/team/__tests__/approved-execution.test.js +124 -0
- package/dist/team/__tests__/approved-execution.test.js.map +1 -0
- package/dist/team/__tests__/delivery-e2e-smoke.test.js +2 -4
- package/dist/team/__tests__/delivery-e2e-smoke.test.js.map +1 -1
- package/dist/team/__tests__/delivery-log.test.d.ts +2 -0
- package/dist/team/__tests__/delivery-log.test.d.ts.map +1 -0
- package/dist/team/__tests__/delivery-log.test.js +44 -0
- package/dist/team/__tests__/delivery-log.test.js.map +1 -0
- package/dist/team/__tests__/role-router.test.js +4 -4
- package/dist/team/__tests__/role-router.test.js.map +1 -1
- package/dist/team/__tests__/runtime-boxed-state.test.d.ts +2 -0
- package/dist/team/__tests__/runtime-boxed-state.test.d.ts.map +1 -0
- package/dist/team/__tests__/runtime-boxed-state.test.js +39 -0
- package/dist/team/__tests__/runtime-boxed-state.test.js.map +1 -0
- package/dist/team/__tests__/runtime.test.js +118 -6
- package/dist/team/__tests__/runtime.test.js.map +1 -1
- package/dist/team/__tests__/state-root.test.js +13 -0
- package/dist/team/__tests__/state-root.test.js.map +1 -1
- package/dist/team/__tests__/tmux-session.test.js +3 -0
- package/dist/team/__tests__/tmux-session.test.js.map +1 -1
- package/dist/team/__tests__/worker-bootstrap.test.js +50 -0
- package/dist/team/__tests__/worker-bootstrap.test.js.map +1 -1
- package/dist/team/api-interop.d.ts.map +1 -1
- package/dist/team/api-interop.js +4 -3
- package/dist/team/api-interop.js.map +1 -1
- package/dist/team/approved-execution.d.ts +37 -0
- package/dist/team/approved-execution.d.ts.map +1 -0
- package/dist/team/approved-execution.js +136 -0
- package/dist/team/approved-execution.js.map +1 -0
- package/dist/team/delivery-log.d.ts.map +1 -1
- package/dist/team/delivery-log.js +2 -1
- package/dist/team/delivery-log.js.map +1 -1
- package/dist/team/followup-planner.js +2 -2
- package/dist/team/followup-planner.js.map +1 -1
- package/dist/team/goal-workflow.d.ts +20 -0
- package/dist/team/goal-workflow.d.ts.map +1 -0
- package/dist/team/goal-workflow.js +57 -0
- package/dist/team/goal-workflow.js.map +1 -0
- package/dist/team/orchestrator.js +2 -2
- package/dist/team/orchestrator.js.map +1 -1
- package/dist/team/role-router.js +5 -5
- package/dist/team/role-router.js.map +1 -1
- package/dist/team/runtime.d.ts +6 -0
- package/dist/team/runtime.d.ts.map +1 -1
- package/dist/team/runtime.js +46 -6
- package/dist/team/runtime.js.map +1 -1
- package/dist/team/scaling.d.ts.map +1 -1
- package/dist/team/scaling.js +2 -0
- package/dist/team/scaling.js.map +1 -1
- package/dist/team/tmux-session.d.ts.map +1 -1
- package/dist/team/tmux-session.js +4 -2
- package/dist/team/tmux-session.js.map +1 -1
- package/dist/team/worker-bootstrap.d.ts +2 -0
- package/dist/team/worker-bootstrap.d.ts.map +1 -1
- package/dist/team/worker-bootstrap.js +19 -2
- package/dist/team/worker-bootstrap.js.map +1 -1
- package/dist/ultragoal/__tests__/artifacts.test.d.ts +2 -0
- package/dist/ultragoal/__tests__/artifacts.test.d.ts.map +1 -0
- package/dist/ultragoal/__tests__/artifacts.test.js +93 -0
- package/dist/ultragoal/__tests__/artifacts.test.js.map +1 -0
- package/dist/ultragoal/artifacts.d.ts +89 -0
- package/dist/ultragoal/artifacts.d.ts.map +1 -0
- package/dist/ultragoal/artifacts.js +233 -0
- package/dist/ultragoal/artifacts.js.map +1 -0
- package/dist/utils/__tests__/agents-model-table.test.js +3 -1
- package/dist/utils/__tests__/agents-model-table.test.js.map +1 -1
- package/dist/utils/__tests__/paths.test.js +31 -1
- package/dist/utils/__tests__/paths.test.js.map +1 -1
- package/dist/utils/agents-model-table.d.ts.map +1 -1
- package/dist/utils/agents-model-table.js +12 -1
- package/dist/utils/agents-model-table.js.map +1 -1
- package/dist/utils/paths.d.ts +2 -0
- package/dist/utils/paths.d.ts.map +1 -1
- package/dist/utils/paths.js +23 -7
- package/dist/utils/paths.js.map +1 -1
- package/dist/verification/__tests__/ci-rust-gates.test.js +30 -19
- package/dist/verification/__tests__/ci-rust-gates.test.js.map +1 -1
- package/package.json +5 -5
- package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
- package/plugins/oh-my-codex/skills/ask/SKILL.md +58 -0
- package/plugins/oh-my-codex/skills/autoresearch-goal/SKILL.md +36 -0
- package/plugins/oh-my-codex/skills/omx-setup/SKILL.md +2 -2
- package/plugins/oh-my-codex/skills/performance-goal/SKILL.md +65 -0
- package/plugins/oh-my-codex/skills/plan/SKILL.md +1 -1
- package/plugins/oh-my-codex/skills/ralph/SKILL.md +22 -3
- package/plugins/oh-my-codex/skills/team/SKILL.md +6 -2
- package/plugins/oh-my-codex/skills/ultragoal/SKILL.md +49 -0
- package/plugins/oh-my-codex/skills/visual-ralph/SKILL.md +9 -9
- package/prompts/api-reviewer.md +1 -1
- package/prompts/code-reviewer.md +2 -0
- package/prompts/performance-reviewer.md +1 -1
- package/prompts/quality-reviewer.md +1 -1
- package/prompts/quality-strategist.md +2 -2
- package/prompts/style-reviewer.md +1 -1
- package/prompts/test-engineer.md +1 -1
- package/skills/ask/SKILL.md +58 -0
- package/skills/ask-claude/SKILL.md +3 -54
- package/skills/ask-gemini/SKILL.md +3 -54
- package/skills/autoresearch-goal/SKILL.md +36 -0
- package/skills/build-fix/SKILL.md +4 -139
- package/skills/deepsearch/SKILL.md +4 -32
- package/skills/ecomode/SKILL.md +4 -108
- package/skills/help/SKILL.md +4 -196
- package/skills/note/SKILL.md +4 -56
- package/skills/omx-setup/SKILL.md +2 -2
- package/skills/performance-goal/SKILL.md +65 -0
- package/skills/plan/SKILL.md +1 -1
- package/skills/ralph/SKILL.md +22 -3
- package/skills/ralph-init/SKILL.md +4 -40
- package/skills/review/SKILL.md +4 -32
- package/skills/security-review/SKILL.md +4 -294
- package/skills/swarm/SKILL.md +4 -19
- package/skills/tdd/SKILL.md +4 -100
- package/skills/team/SKILL.md +6 -2
- package/skills/trace/SKILL.md +4 -27
- package/skills/ultragoal/SKILL.md +49 -0
- package/skills/visual-ralph/SKILL.md +9 -9
- package/skills/visual-verdict/SKILL.md +4 -70
- package/skills/web-clone/SKILL.md +4 -18
- package/src/scripts/__tests__/codex-native-hook.test.ts +1654 -157
- package/src/scripts/__tests__/hook-derived-watcher.test.ts +45 -1
- package/src/scripts/__tests__/run-test-files.test.ts +46 -0
- package/src/scripts/codex-native-hook.ts +592 -52
- package/src/scripts/codex-native-pre-post.ts +252 -20
- package/src/scripts/hook-derived-watcher.ts +2 -1
- package/src/scripts/notify-fallback-watcher.ts +2 -1
- package/src/scripts/notify-hook/orchestration-intent.ts +1 -3
- package/src/scripts/notify-hook/team-leader-nudge.ts +7 -63
- package/src/scripts/notify-hook/team-worker-stop.ts +246 -0
- package/src/scripts/notify-hook/team-worker.ts +23 -14
- package/src/scripts/run-test-files.ts +20 -1
- package/src/scripts/sync-plugin-mirror.ts +2 -2
- package/templates/catalog-manifest.json +45 -27
- package/plugins/oh-my-codex/skills/ask-claude/SKILL.md +0 -61
- package/plugins/oh-my-codex/skills/ask-gemini/SKILL.md +0 -61
- package/plugins/oh-my-codex/skills/help/SKILL.md +0 -202
- package/plugins/oh-my-codex/skills/note/SKILL.md +0 -62
- package/plugins/oh-my-codex/skills/security-review/SKILL.md +0 -300
- package/plugins/oh-my-codex/skills/trace/SKILL.md +0 -33
- package/plugins/oh-my-codex/skills/visual-verdict/SKILL.md +0 -76
|
@@ -13,6 +13,7 @@ import { dispatchCodexNativeHook, isCodexNativeHookMainModule, mapCodexHookEvent
|
|
|
13
13
|
import { writeSessionStart } from "../../hooks/session.js";
|
|
14
14
|
import { resetTriageConfigCache } from "../../hooks/triage-config.js";
|
|
15
15
|
import { executeStateOperation } from "../../state/operations.js";
|
|
16
|
+
import { OMX_TMUX_HUD_OWNER_ENV } from "../../hud/reconcile.js";
|
|
16
17
|
function nativeHookScriptPath() {
|
|
17
18
|
return join(process.cwd(), "dist", "scripts", "codex-native-hook.js");
|
|
18
19
|
}
|
|
@@ -35,6 +36,50 @@ async function writeJson(path, value) {
|
|
|
35
36
|
await mkdir(dirname(path), { recursive: true }).catch(() => { });
|
|
36
37
|
await writeFile(path, JSON.stringify(value, null, 2));
|
|
37
38
|
}
|
|
39
|
+
function buildWorkerStopFakeTmux(tmuxLogPath, options = {}) {
|
|
40
|
+
return `#!/usr/bin/env bash
|
|
41
|
+
set -eu
|
|
42
|
+
echo "$@" >> "${tmuxLogPath}"
|
|
43
|
+
cmd="$1"
|
|
44
|
+
shift || true
|
|
45
|
+
if [[ "$cmd" == "display-message" ]]; then
|
|
46
|
+
fmt=""
|
|
47
|
+
while [[ "$#" -gt 0 ]]; do
|
|
48
|
+
case "$1" in
|
|
49
|
+
-p) ;;
|
|
50
|
+
-t) shift ;;
|
|
51
|
+
*) fmt="$1" ;;
|
|
52
|
+
esac
|
|
53
|
+
shift || true
|
|
54
|
+
done
|
|
55
|
+
case "$fmt" in
|
|
56
|
+
"#{pane_in_mode}") echo "0" ;;
|
|
57
|
+
"#{pane_id}") echo "%42" ;;
|
|
58
|
+
"#{pane_current_path}") pwd ;;
|
|
59
|
+
"#{pane_start_command}") echo "codex" ;;
|
|
60
|
+
"#{pane_current_command}") echo "codex" ;;
|
|
61
|
+
"#S") echo "omx-team-worker-stop" ;;
|
|
62
|
+
*) ;;
|
|
63
|
+
esac
|
|
64
|
+
exit 0
|
|
65
|
+
fi
|
|
66
|
+
if [[ "$cmd" == "capture-pane" ]]; then
|
|
67
|
+
echo "› ready"
|
|
68
|
+
exit 0
|
|
69
|
+
fi
|
|
70
|
+
if [[ "$cmd" == "send-keys" ]]; then
|
|
71
|
+
${options.failSend ? "exit 1" : "exit 0"}
|
|
72
|
+
fi
|
|
73
|
+
exit 0
|
|
74
|
+
`;
|
|
75
|
+
}
|
|
76
|
+
async function initTempGitRepo(prefix) {
|
|
77
|
+
const cwd = await mkdtemp(join(tmpdir(), prefix));
|
|
78
|
+
execFileSync("git", ["init"], { cwd, stdio: "ignore" });
|
|
79
|
+
execFileSync("git", ["config", "user.email", "test@example.com"], { cwd, stdio: "ignore" });
|
|
80
|
+
execFileSync("git", ["config", "user.name", "Test User"], { cwd, stdio: "ignore" });
|
|
81
|
+
return cwd;
|
|
82
|
+
}
|
|
38
83
|
async function writeActiveAutopilotSession(cwd, sessionId) {
|
|
39
84
|
await writeJson(join(cwd, ".omx", "state", "session.json"), {
|
|
40
85
|
session_id: sessionId,
|
|
@@ -96,10 +141,12 @@ const TEAM_ENV_KEYS = [
|
|
|
96
141
|
"OMX_TEAM_STATE_ROOT",
|
|
97
142
|
"OMX_TEAM_LEADER_CWD",
|
|
98
143
|
"OMX_SESSION_ID",
|
|
144
|
+
"SESSION_ID",
|
|
99
145
|
"OMX_QUESTION_RETURN_PANE",
|
|
100
146
|
"OMX_LEADER_PANE_ID",
|
|
101
147
|
"TMUX",
|
|
102
148
|
"TMUX_PANE",
|
|
149
|
+
"OMX_TMUX_HUD_OWNER",
|
|
103
150
|
];
|
|
104
151
|
const priorTeamEnv = new Map();
|
|
105
152
|
beforeEach(() => {
|
|
@@ -763,6 +810,114 @@ describe("codex native hook dispatch", () => {
|
|
|
763
810
|
await rm(cwd, { recursive: true, force: true });
|
|
764
811
|
}
|
|
765
812
|
});
|
|
813
|
+
it("warns completion-like prompts when active goal workflows need Codex snapshot reconciliation", async () => {
|
|
814
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-goal-warning-"));
|
|
815
|
+
try {
|
|
816
|
+
await writeJson(join(cwd, ".omx", "ultragoal", "goals.json"), {
|
|
817
|
+
version: 1,
|
|
818
|
+
activeGoalId: "G001-demo",
|
|
819
|
+
goals: [{ id: "G001-demo", status: "in_progress", objective: "Demo goal" }],
|
|
820
|
+
});
|
|
821
|
+
const result = await dispatchCodexNativeHook({
|
|
822
|
+
hook_event_name: "UserPromptSubmit",
|
|
823
|
+
cwd,
|
|
824
|
+
session_id: "sess-goal-warning",
|
|
825
|
+
thread_id: "thread-goal-warning",
|
|
826
|
+
prompt: "complete this goal now",
|
|
827
|
+
}, { cwd });
|
|
828
|
+
assert.match(JSON.stringify(result.outputJson), /requires Codex goal snapshot reconciliation/);
|
|
829
|
+
assert.match(JSON.stringify(result.outputJson), /get_goal/);
|
|
830
|
+
assert.match(JSON.stringify(result.outputJson), /--codex-goal-json/);
|
|
831
|
+
}
|
|
832
|
+
finally {
|
|
833
|
+
await rm(cwd, { recursive: true, force: true });
|
|
834
|
+
}
|
|
835
|
+
});
|
|
836
|
+
it("blocks Stop when a completion-like final answer skips active goal snapshot reconciliation", async () => {
|
|
837
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-goal-stop-"));
|
|
838
|
+
try {
|
|
839
|
+
await writeJson(join(cwd, ".omx", "goals", "performance", "latency", "state.json"), {
|
|
840
|
+
version: 1,
|
|
841
|
+
workflow: "performance-goal",
|
|
842
|
+
slug: "latency",
|
|
843
|
+
objective: "Reduce latency",
|
|
844
|
+
status: "validation_passed",
|
|
845
|
+
});
|
|
846
|
+
const result = await dispatchCodexNativeHook({
|
|
847
|
+
hook_event_name: "Stop",
|
|
848
|
+
cwd,
|
|
849
|
+
session_id: "sess-goal-stop",
|
|
850
|
+
thread_id: "thread-goal-stop",
|
|
851
|
+
last_assistant_message: "Performance goal complete; next call update_goal({status: \"complete\"}).",
|
|
852
|
+
}, { cwd });
|
|
853
|
+
assert.equal(result.outputJson?.decision, "block");
|
|
854
|
+
assert.match(JSON.stringify(result.outputJson), /get_goal snapshot reconciliation/);
|
|
855
|
+
assert.match(JSON.stringify(result.outputJson), /omx performance-goal complete --slug latency/);
|
|
856
|
+
assert.match(JSON.stringify(result.outputJson), /Hooks must not mutate Codex goal state/);
|
|
857
|
+
}
|
|
858
|
+
finally {
|
|
859
|
+
await rm(cwd, { recursive: true, force: true });
|
|
860
|
+
}
|
|
861
|
+
});
|
|
862
|
+
it("treats workflow keywords in native subagent prompt text as literal delegation text", async () => {
|
|
863
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-subagent-keyword-literal-"));
|
|
864
|
+
try {
|
|
865
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
866
|
+
const canonicalSessionId = "sess-parent";
|
|
867
|
+
const leaderNativeSessionId = "native-parent-thread";
|
|
868
|
+
const childNativeSessionId = "native-child-thread";
|
|
869
|
+
const nowIso = new Date().toISOString();
|
|
870
|
+
await writeJson(join(stateDir, "session.json"), {
|
|
871
|
+
session_id: canonicalSessionId,
|
|
872
|
+
native_session_id: leaderNativeSessionId,
|
|
873
|
+
});
|
|
874
|
+
await writeJson(join(stateDir, "subagent-tracking.json"), {
|
|
875
|
+
schemaVersion: 1,
|
|
876
|
+
sessions: {
|
|
877
|
+
[canonicalSessionId]: {
|
|
878
|
+
session_id: canonicalSessionId,
|
|
879
|
+
leader_thread_id: leaderNativeSessionId,
|
|
880
|
+
updated_at: nowIso,
|
|
881
|
+
threads: {
|
|
882
|
+
[leaderNativeSessionId]: {
|
|
883
|
+
thread_id: leaderNativeSessionId,
|
|
884
|
+
kind: "leader",
|
|
885
|
+
first_seen_at: nowIso,
|
|
886
|
+
last_seen_at: nowIso,
|
|
887
|
+
turn_count: 1,
|
|
888
|
+
},
|
|
889
|
+
[childNativeSessionId]: {
|
|
890
|
+
thread_id: childNativeSessionId,
|
|
891
|
+
kind: "subagent",
|
|
892
|
+
first_seen_at: nowIso,
|
|
893
|
+
last_seen_at: nowIso,
|
|
894
|
+
turn_count: 1,
|
|
895
|
+
mode: "architect",
|
|
896
|
+
},
|
|
897
|
+
},
|
|
898
|
+
},
|
|
899
|
+
},
|
|
900
|
+
});
|
|
901
|
+
const result = await dispatchCodexNativeHook({
|
|
902
|
+
hook_event_name: "UserPromptSubmit",
|
|
903
|
+
cwd,
|
|
904
|
+
session_id: childNativeSessionId,
|
|
905
|
+
thread_id: childNativeSessionId,
|
|
906
|
+
turn_id: "turn-child-1",
|
|
907
|
+
prompt: "$ralplan Architect review step. Review the draft plan and return APPROVE or ITERATE.",
|
|
908
|
+
}, { cwd });
|
|
909
|
+
assert.equal(result.omxEventName, "keyword-detector");
|
|
910
|
+
assert.equal(result.skillState, null);
|
|
911
|
+
assert.equal(result.outputJson, null);
|
|
912
|
+
assert.equal(existsSync(join(stateDir, "skill-active-state.json")), false);
|
|
913
|
+
assert.equal(existsSync(join(stateDir, "sessions", canonicalSessionId, "skill-active-state.json")), false);
|
|
914
|
+
assert.equal(existsSync(join(stateDir, "sessions", canonicalSessionId, "ralplan-state.json")), false);
|
|
915
|
+
assert.equal(existsSync(join(stateDir, "sessions", childNativeSessionId, "ralplan-state.json")), false);
|
|
916
|
+
}
|
|
917
|
+
finally {
|
|
918
|
+
await rm(cwd, { recursive: true, force: true });
|
|
919
|
+
}
|
|
920
|
+
});
|
|
766
921
|
it("records plugin-prefixed keyword activation from UserPromptSubmit payloads", async () => {
|
|
767
922
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-plugin-prefixed-"));
|
|
768
923
|
try {
|
|
@@ -786,6 +941,33 @@ describe("codex native hook dispatch", () => {
|
|
|
786
941
|
await rm(cwd, { recursive: true, force: true });
|
|
787
942
|
}
|
|
788
943
|
});
|
|
944
|
+
it("records ultragoal prompt skill activation with goal-tool handoff guidance", async () => {
|
|
945
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-"));
|
|
946
|
+
try {
|
|
947
|
+
await mkdir(join(cwd, ".omx", "state"), { recursive: true });
|
|
948
|
+
const result = await dispatchCodexNativeHook({
|
|
949
|
+
hook_event_name: "UserPromptSubmit",
|
|
950
|
+
cwd,
|
|
951
|
+
session_id: "sess-ultragoal-1",
|
|
952
|
+
thread_id: "thread-ultragoal-1",
|
|
953
|
+
turn_id: "turn-ultragoal-1",
|
|
954
|
+
prompt: "$ultragoal split this launch into durable goals",
|
|
955
|
+
}, { cwd });
|
|
956
|
+
assert.equal(result.omxEventName, "keyword-detector");
|
|
957
|
+
assert.equal(result.skillState?.skill, "ultragoal");
|
|
958
|
+
assert.equal(result.skillState?.initialized_mode, undefined);
|
|
959
|
+
const message = String(result.outputJson?.hookSpecificOutput?.additionalContext || "");
|
|
960
|
+
assert.match(message, /"\$ultragoal" -> ultragoal/);
|
|
961
|
+
assert.match(message, /Ultragoal protocol:/);
|
|
962
|
+
assert.match(message, /get_goal/);
|
|
963
|
+
assert.match(message, /create_goal/);
|
|
964
|
+
assert.match(message, /update_goal/);
|
|
965
|
+
assert.equal(existsSync(join(cwd, ".omx", "state", "sessions", "sess-ultragoal-1", "ultragoal-state.json")), false);
|
|
966
|
+
}
|
|
967
|
+
finally {
|
|
968
|
+
await rm(cwd, { recursive: true, force: true });
|
|
969
|
+
}
|
|
970
|
+
});
|
|
789
971
|
it("normalizes the Korean keyboard typo for ulw during UserPromptSubmit activation", async () => {
|
|
790
972
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ulw-ko-"));
|
|
791
973
|
try {
|
|
@@ -1505,10 +1687,12 @@ export async function onHookEvent(event) {
|
|
|
1505
1687
|
const originalTmux = process.env.TMUX;
|
|
1506
1688
|
const originalTmuxPane = process.env.TMUX_PANE;
|
|
1507
1689
|
const originalPath = process.env.PATH;
|
|
1690
|
+
const originalHudOwner = process.env[OMX_TMUX_HUD_OWNER_ENV];
|
|
1508
1691
|
const originalArgv = process.argv;
|
|
1509
1692
|
try {
|
|
1510
1693
|
process.env.TMUX = "1";
|
|
1511
1694
|
process.env.TMUX_PANE = "%1";
|
|
1695
|
+
process.env[OMX_TMUX_HUD_OWNER_ENV] = "1";
|
|
1512
1696
|
await mkdir(join(cwd, ".omx", "state"), { recursive: true });
|
|
1513
1697
|
await writeFile(join(cwd, ".omx", "hud-config.json"), JSON.stringify({ preset: "focused", git: { display: "branch" } }, null, 2));
|
|
1514
1698
|
const binDir = await mkdtemp(join(tmpdir(), "omx-native-hook-hud-reconcile-bin-"));
|
|
@@ -1560,11 +1744,61 @@ esac
|
|
|
1560
1744
|
else {
|
|
1561
1745
|
process.env.TMUX_PANE = originalTmuxPane;
|
|
1562
1746
|
}
|
|
1747
|
+
if (originalHudOwner === undefined) {
|
|
1748
|
+
delete process.env[OMX_TMUX_HUD_OWNER_ENV];
|
|
1749
|
+
}
|
|
1750
|
+
else {
|
|
1751
|
+
process.env[OMX_TMUX_HUD_OWNER_ENV] = originalHudOwner;
|
|
1752
|
+
}
|
|
1563
1753
|
process.env.PATH = originalPath;
|
|
1564
1754
|
process.argv = originalArgv;
|
|
1565
1755
|
await rm(cwd, { recursive: true, force: true });
|
|
1566
1756
|
}
|
|
1567
1757
|
});
|
|
1758
|
+
it("skips prompt-submit HUD reconciliation inside unowned tmux panes", async () => {
|
|
1759
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-hud-unowned-"));
|
|
1760
|
+
const originalTmux = process.env.TMUX;
|
|
1761
|
+
const originalTmuxPane = process.env.TMUX_PANE;
|
|
1762
|
+
const originalPath = process.env.PATH;
|
|
1763
|
+
const originalHudOwner = process.env[OMX_TMUX_HUD_OWNER_ENV];
|
|
1764
|
+
try {
|
|
1765
|
+
process.env.TMUX = "1";
|
|
1766
|
+
process.env.TMUX_PANE = "%claude";
|
|
1767
|
+
delete process.env[OMX_TMUX_HUD_OWNER_ENV];
|
|
1768
|
+
const binDir = await mkdtemp(join(tmpdir(), "omx-native-hook-hud-unowned-bin-"));
|
|
1769
|
+
const tmuxLog = join(cwd, "tmux.log");
|
|
1770
|
+
await writeFile(join(binDir, "tmux"), `#!/usr/bin/env bash
|
|
1771
|
+
printf '%s\n' "$*" >> ${JSON.stringify(tmuxLog)}
|
|
1772
|
+
exit 0
|
|
1773
|
+
`);
|
|
1774
|
+
await chmod(join(binDir, "tmux"), 0o755);
|
|
1775
|
+
process.env.PATH = `${binDir}:${originalPath}`;
|
|
1776
|
+
const result = await dispatchCodexNativeHook({
|
|
1777
|
+
hook_event_name: "UserPromptSubmit",
|
|
1778
|
+
cwd,
|
|
1779
|
+
session_id: "sess-hud-unowned",
|
|
1780
|
+
prompt: "$ralplan prepare plan",
|
|
1781
|
+
}, { cwd });
|
|
1782
|
+
assert.equal(result.omxEventName, "keyword-detector");
|
|
1783
|
+
assert.equal(existsSync(tmuxLog), false);
|
|
1784
|
+
}
|
|
1785
|
+
finally {
|
|
1786
|
+
if (originalTmux === undefined)
|
|
1787
|
+
delete process.env.TMUX;
|
|
1788
|
+
else
|
|
1789
|
+
process.env.TMUX = originalTmux;
|
|
1790
|
+
if (originalTmuxPane === undefined)
|
|
1791
|
+
delete process.env.TMUX_PANE;
|
|
1792
|
+
else
|
|
1793
|
+
process.env.TMUX_PANE = originalTmuxPane;
|
|
1794
|
+
if (originalHudOwner === undefined)
|
|
1795
|
+
delete process.env[OMX_TMUX_HUD_OWNER_ENV];
|
|
1796
|
+
else
|
|
1797
|
+
process.env[OMX_TMUX_HUD_OWNER_ENV] = originalHudOwner;
|
|
1798
|
+
process.env.PATH = originalPath;
|
|
1799
|
+
await rm(cwd, { recursive: true, force: true });
|
|
1800
|
+
}
|
|
1801
|
+
});
|
|
1568
1802
|
it("blocks Bash omx question when no leader-pane return hint is preserved", async () => {
|
|
1569
1803
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-question-enforce-"));
|
|
1570
1804
|
try {
|
|
@@ -2035,6 +2269,186 @@ esac
|
|
|
2035
2269
|
await rm(cwd, { recursive: true, force: true });
|
|
2036
2270
|
}
|
|
2037
2271
|
});
|
|
2272
|
+
it("blocks Stop for untracked non-Bash-style sloppy fallback source edits", async () => {
|
|
2273
|
+
const cwd = await initTempGitRepo("omx-native-hook-stop-slop-untracked-");
|
|
2274
|
+
try {
|
|
2275
|
+
await mkdir(join(cwd, "src"), { recursive: true });
|
|
2276
|
+
await writeFile(join(cwd, "src", "runtime.ts"), [
|
|
2277
|
+
"export function loadRuntime() {",
|
|
2278
|
+
" // implement a quick hack fallback if it fails",
|
|
2279
|
+
" return process.env.RUNTIME || 'local';",
|
|
2280
|
+
"}",
|
|
2281
|
+
].join("\n"));
|
|
2282
|
+
const result = await dispatchCodexNativeHook({ hook_event_name: "Stop", cwd, session_id: "sess-stop-slop-untracked" }, { cwd });
|
|
2283
|
+
assert.equal(result.omxEventName, "stop");
|
|
2284
|
+
assert.equal(result.outputJson?.decision, "block");
|
|
2285
|
+
assert.equal(result.outputJson?.stopReason, "sloppy_fallback_diff_audit");
|
|
2286
|
+
assert.match(JSON.stringify(result.outputJson), /src\/runtime\.ts/);
|
|
2287
|
+
assert.match(JSON.stringify(result.outputJson), /grounded design/);
|
|
2288
|
+
}
|
|
2289
|
+
finally {
|
|
2290
|
+
await rm(cwd, { recursive: true, force: true });
|
|
2291
|
+
}
|
|
2292
|
+
});
|
|
2293
|
+
it("keeps blocking repeated Stop while sloppy fallback diff remains", async () => {
|
|
2294
|
+
const cwd = await initTempGitRepo("omx-native-hook-stop-slop-repeat-");
|
|
2295
|
+
try {
|
|
2296
|
+
await mkdir(join(cwd, "src"), { recursive: true });
|
|
2297
|
+
await writeFile(join(cwd, "src", "runtime.ts"), [
|
|
2298
|
+
"export function loadRuntime() {",
|
|
2299
|
+
" // implement a quick hack fallback if it fails",
|
|
2300
|
+
" return process.env.RUNTIME || 'local';",
|
|
2301
|
+
"}",
|
|
2302
|
+
].join("\n"));
|
|
2303
|
+
const payload = { hook_event_name: "Stop", cwd, session_id: "sess-stop-slop-repeat", turn_id: "turn-repeat" };
|
|
2304
|
+
const first = await dispatchCodexNativeHook(payload, { cwd });
|
|
2305
|
+
const repeated = await dispatchCodexNativeHook({ ...payload, stop_hook_active: true }, { cwd });
|
|
2306
|
+
assert.equal(first.outputJson?.decision, "block");
|
|
2307
|
+
assert.equal(repeated.outputJson?.decision, "block");
|
|
2308
|
+
assert.equal(repeated.outputJson?.stopReason, "sloppy_fallback_diff_audit");
|
|
2309
|
+
}
|
|
2310
|
+
finally {
|
|
2311
|
+
await rm(cwd, { recursive: true, force: true });
|
|
2312
|
+
}
|
|
2313
|
+
});
|
|
2314
|
+
it("blocks Stop for unstaged tracked sloppy fallback source edits", async () => {
|
|
2315
|
+
const cwd = await initTempGitRepo("omx-native-hook-stop-slop-unstaged-");
|
|
2316
|
+
try {
|
|
2317
|
+
await mkdir(join(cwd, "src"), { recursive: true });
|
|
2318
|
+
await writeFile(join(cwd, "src", "runtime.ts"), "export const runtime = 'base';\n");
|
|
2319
|
+
execFileSync("git", ["add", "src/runtime.ts"], { cwd, stdio: "ignore" });
|
|
2320
|
+
execFileSync("git", ["commit", "-m", "initial"], { cwd, stdio: "ignore" });
|
|
2321
|
+
await writeFile(join(cwd, "src", "runtime.ts"), [
|
|
2322
|
+
"export function loadRuntime() {",
|
|
2323
|
+
" // just bypass fallback if it fails",
|
|
2324
|
+
" return process.env.RUNTIME || 'local';",
|
|
2325
|
+
"}",
|
|
2326
|
+
].join("\n"));
|
|
2327
|
+
const result = await dispatchCodexNativeHook({ hook_event_name: "Stop", cwd, session_id: "sess-stop-slop-unstaged" }, { cwd });
|
|
2328
|
+
assert.equal(result.omxEventName, "stop");
|
|
2329
|
+
assert.equal(result.outputJson?.decision, "block");
|
|
2330
|
+
assert.match(JSON.stringify(result.outputJson), /unstaged/);
|
|
2331
|
+
}
|
|
2332
|
+
finally {
|
|
2333
|
+
await rm(cwd, { recursive: true, force: true });
|
|
2334
|
+
}
|
|
2335
|
+
});
|
|
2336
|
+
it("blocks Stop from a subdirectory cwd for untracked sloppy source elsewhere", async () => {
|
|
2337
|
+
const cwd = await initTempGitRepo("omx-native-hook-stop-slop-subdir-");
|
|
2338
|
+
try {
|
|
2339
|
+
await mkdir(join(cwd, "src", "nested"), { recursive: true });
|
|
2340
|
+
await writeFile(join(cwd, "src", "nested", "anchor.ts"), "export const anchor = true;\n");
|
|
2341
|
+
await writeFile(join(cwd, "src", "runtime.ts"), [
|
|
2342
|
+
"export function loadRuntime() {",
|
|
2343
|
+
" // implement a quick hack fallback if it fails",
|
|
2344
|
+
" return process.env.RUNTIME || 'local';",
|
|
2345
|
+
"}",
|
|
2346
|
+
].join("\n"));
|
|
2347
|
+
const subdir = join(cwd, "src", "nested");
|
|
2348
|
+
const result = await dispatchCodexNativeHook({ hook_event_name: "Stop", cwd: subdir, session_id: "sess-stop-slop-subdir" }, { cwd: subdir });
|
|
2349
|
+
assert.equal(result.omxEventName, "stop");
|
|
2350
|
+
assert.equal(result.outputJson?.decision, "block");
|
|
2351
|
+
assert.match(JSON.stringify(result.outputJson), /src\/runtime\.ts/);
|
|
2352
|
+
}
|
|
2353
|
+
finally {
|
|
2354
|
+
await rm(cwd, { recursive: true, force: true });
|
|
2355
|
+
}
|
|
2356
|
+
});
|
|
2357
|
+
it("blocks Stop for staged sloppy fallback source edits", async () => {
|
|
2358
|
+
const cwd = await initTempGitRepo("omx-native-hook-stop-slop-staged-");
|
|
2359
|
+
try {
|
|
2360
|
+
await mkdir(join(cwd, "src"), { recursive: true });
|
|
2361
|
+
await writeFile(join(cwd, "src", "runtime.ts"), "export const runtime = 'base';\n");
|
|
2362
|
+
execFileSync("git", ["add", "src/runtime.ts"], { cwd, stdio: "ignore" });
|
|
2363
|
+
execFileSync("git", ["commit", "-m", "initial"], { cwd, stdio: "ignore" });
|
|
2364
|
+
await writeFile(join(cwd, "src", "runtime.ts"), [
|
|
2365
|
+
"export function loadRuntime() {",
|
|
2366
|
+
" // temporary workaround fallback if it fails",
|
|
2367
|
+
" return process.env.RUNTIME || 'local';",
|
|
2368
|
+
"}",
|
|
2369
|
+
].join("\n"));
|
|
2370
|
+
execFileSync("git", ["add", "src/runtime.ts"], { cwd, stdio: "ignore" });
|
|
2371
|
+
const result = await dispatchCodexNativeHook({ hook_event_name: "Stop", cwd, session_id: "sess-stop-slop-staged" }, { cwd });
|
|
2372
|
+
assert.equal(result.omxEventName, "stop");
|
|
2373
|
+
assert.equal(result.outputJson?.decision, "block");
|
|
2374
|
+
assert.match(JSON.stringify(result.outputJson), /staged/);
|
|
2375
|
+
}
|
|
2376
|
+
finally {
|
|
2377
|
+
await rm(cwd, { recursive: true, force: true });
|
|
2378
|
+
}
|
|
2379
|
+
});
|
|
2380
|
+
it("does not block Stop for grounded compatibility fallback source edits", async () => {
|
|
2381
|
+
const cwd = await initTempGitRepo("omx-native-hook-stop-slop-grounded-");
|
|
2382
|
+
try {
|
|
2383
|
+
await mkdir(join(cwd, "src"), { recursive: true });
|
|
2384
|
+
await writeFile(join(cwd, "src", "compat.ts"), [
|
|
2385
|
+
"export function resolveCompatMode() {",
|
|
2386
|
+
" // temporary fallback for legacy startup",
|
|
2387
|
+
" // compatibility fail-safe tested by regression coverage",
|
|
2388
|
+
" return 'legacy';",
|
|
2389
|
+
"}",
|
|
2390
|
+
].join("\n"));
|
|
2391
|
+
const result = await dispatchCodexNativeHook({ hook_event_name: "Stop", cwd, session_id: "sess-stop-slop-grounded" }, { cwd });
|
|
2392
|
+
assert.equal(result.omxEventName, "stop");
|
|
2393
|
+
assert.equal(result.outputJson, null);
|
|
2394
|
+
}
|
|
2395
|
+
finally {
|
|
2396
|
+
await rm(cwd, { recursive: true, force: true });
|
|
2397
|
+
}
|
|
2398
|
+
});
|
|
2399
|
+
it("does not block Stop when existing nearby source context grounds a new fallback line", async () => {
|
|
2400
|
+
const cwd = await initTempGitRepo("omx-native-hook-stop-slop-existing-ground-");
|
|
2401
|
+
try {
|
|
2402
|
+
await mkdir(join(cwd, "src"), { recursive: true });
|
|
2403
|
+
await writeFile(join(cwd, "src", "compat.ts"), [
|
|
2404
|
+
"export function resolveCompatMode() {",
|
|
2405
|
+
" // compatibility fail-safe tested by regression coverage",
|
|
2406
|
+
" return 'legacy';",
|
|
2407
|
+
"}",
|
|
2408
|
+
].join("\n"));
|
|
2409
|
+
execFileSync("git", ["add", "src/compat.ts"], { cwd, stdio: "ignore" });
|
|
2410
|
+
execFileSync("git", ["commit", "-m", "initial"], { cwd, stdio: "ignore" });
|
|
2411
|
+
await writeFile(join(cwd, "src", "compat.ts"), [
|
|
2412
|
+
"export function resolveCompatMode() {",
|
|
2413
|
+
" // compatibility fail-safe tested by regression coverage",
|
|
2414
|
+
" // temporary fallback if it fails",
|
|
2415
|
+
" return 'legacy';",
|
|
2416
|
+
"}",
|
|
2417
|
+
].join("\n"));
|
|
2418
|
+
const result = await dispatchCodexNativeHook({ hook_event_name: "Stop", cwd, session_id: "sess-stop-slop-existing-ground" }, { cwd });
|
|
2419
|
+
assert.equal(result.omxEventName, "stop");
|
|
2420
|
+
assert.equal(result.outputJson, null);
|
|
2421
|
+
}
|
|
2422
|
+
finally {
|
|
2423
|
+
await rm(cwd, { recursive: true, force: true });
|
|
2424
|
+
}
|
|
2425
|
+
});
|
|
2426
|
+
it("does not block Stop for source-adjacent test file fallback wording", async () => {
|
|
2427
|
+
const cwd = await initTempGitRepo("omx-native-hook-stop-slop-test-file-");
|
|
2428
|
+
try {
|
|
2429
|
+
await mkdir(join(cwd, "src"), { recursive: true });
|
|
2430
|
+
await writeFile(join(cwd, "src", "runtime.test.ts"), "it('documents no quick hack fallback if it fails', () => {});\n");
|
|
2431
|
+
const result = await dispatchCodexNativeHook({ hook_event_name: "Stop", cwd, session_id: "sess-stop-slop-test-file" }, { cwd });
|
|
2432
|
+
assert.equal(result.omxEventName, "stop");
|
|
2433
|
+
assert.equal(result.outputJson, null);
|
|
2434
|
+
}
|
|
2435
|
+
finally {
|
|
2436
|
+
await rm(cwd, { recursive: true, force: true });
|
|
2437
|
+
}
|
|
2438
|
+
});
|
|
2439
|
+
it("does not block Stop for docs-only fallback wording", async () => {
|
|
2440
|
+
const cwd = await initTempGitRepo("omx-native-hook-stop-slop-docs-");
|
|
2441
|
+
try {
|
|
2442
|
+
await mkdir(join(cwd, "docs"), { recursive: true });
|
|
2443
|
+
await writeFile(join(cwd, "docs", "notes.md"), "Do not implement a quick hack fallback if it fails.\n");
|
|
2444
|
+
const result = await dispatchCodexNativeHook({ hook_event_name: "Stop", cwd, session_id: "sess-stop-slop-docs" }, { cwd });
|
|
2445
|
+
assert.equal(result.omxEventName, "stop");
|
|
2446
|
+
assert.equal(result.outputJson, null);
|
|
2447
|
+
}
|
|
2448
|
+
finally {
|
|
2449
|
+
await rm(cwd, { recursive: true, force: true });
|
|
2450
|
+
}
|
|
2451
|
+
});
|
|
2038
2452
|
it("keeps git commit Lore enforcement ahead of sloppy fallback advisory", async () => {
|
|
2039
2453
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-slop-git-priority-"));
|
|
2040
2454
|
try {
|
|
@@ -2087,32 +2501,38 @@ esac
|
|
|
2087
2501
|
await rm(cwd, { recursive: true, force: true });
|
|
2088
2502
|
}
|
|
2089
2503
|
});
|
|
2090
|
-
it("
|
|
2091
|
-
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-
|
|
2504
|
+
it("allows non-Lore git commit messages when the Lore commit guard is explicitly disabled", async () => {
|
|
2505
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-disabled-"));
|
|
2506
|
+
const original = process.env.OMX_LORE_COMMIT_GUARD;
|
|
2092
2507
|
try {
|
|
2508
|
+
process.env.OMX_LORE_COMMIT_GUARD = "0";
|
|
2093
2509
|
const result = await dispatchCodexNativeHook({
|
|
2094
2510
|
hook_event_name: "PreToolUse",
|
|
2095
2511
|
cwd,
|
|
2096
2512
|
tool_name: "Bash",
|
|
2097
|
-
tool_use_id: "tool-git-
|
|
2098
|
-
tool_input: { command:
|
|
2513
|
+
tool_use_id: "tool-git-commit-lore-disabled",
|
|
2514
|
+
tool_input: { command: 'git commit -m "fix: use conventional commit"' },
|
|
2099
2515
|
}, { cwd });
|
|
2100
2516
|
assert.equal(result.omxEventName, "pre-tool-use");
|
|
2101
2517
|
assert.equal(result.outputJson, null);
|
|
2102
2518
|
}
|
|
2103
2519
|
finally {
|
|
2520
|
+
if (original === undefined)
|
|
2521
|
+
delete process.env.OMX_LORE_COMMIT_GUARD;
|
|
2522
|
+
else
|
|
2523
|
+
process.env.OMX_LORE_COMMIT_GUARD = original;
|
|
2104
2524
|
await rm(cwd, { recursive: true, force: true });
|
|
2105
2525
|
}
|
|
2106
2526
|
});
|
|
2107
|
-
it("
|
|
2108
|
-
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-
|
|
2527
|
+
it("allows non-Lore git commit messages when the Lore commit guard is disabled inline", async () => {
|
|
2528
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-inline-disabled-"));
|
|
2109
2529
|
try {
|
|
2110
2530
|
const result = await dispatchCodexNativeHook({
|
|
2111
2531
|
hook_event_name: "PreToolUse",
|
|
2112
2532
|
cwd,
|
|
2113
2533
|
tool_name: "Bash",
|
|
2114
|
-
tool_use_id: "tool-git-
|
|
2115
|
-
tool_input: { command:
|
|
2534
|
+
tool_use_id: "tool-git-commit-lore-inline-disabled",
|
|
2535
|
+
tool_input: { command: 'OMX_LORE_COMMIT_GUARD=0 git commit -m "fix: conventional"' },
|
|
2116
2536
|
}, { cwd });
|
|
2117
2537
|
assert.equal(result.omxEventName, "pre-tool-use");
|
|
2118
2538
|
assert.equal(result.outputJson, null);
|
|
@@ -2121,105 +2541,294 @@ esac
|
|
|
2121
2541
|
await rm(cwd, { recursive: true, force: true });
|
|
2122
2542
|
}
|
|
2123
2543
|
});
|
|
2124
|
-
it("
|
|
2125
|
-
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-
|
|
2544
|
+
it("does not treat newline-separated Lore guard assignment as inline git commit env", async () => {
|
|
2545
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-newline-assignment-"));
|
|
2126
2546
|
try {
|
|
2127
2547
|
const result = await dispatchCodexNativeHook({
|
|
2128
2548
|
hook_event_name: "PreToolUse",
|
|
2129
2549
|
cwd,
|
|
2130
2550
|
tool_name: "Bash",
|
|
2131
|
-
tool_use_id: "tool-git-
|
|
2132
|
-
tool_input: { command:
|
|
2551
|
+
tool_use_id: "tool-git-commit-lore-newline-assignment",
|
|
2552
|
+
tool_input: { command: 'OMX_LORE_COMMIT_GUARD=0\ngit commit -m "fix: conventional"' },
|
|
2133
2553
|
}, { cwd });
|
|
2134
2554
|
assert.equal(result.omxEventName, "pre-tool-use");
|
|
2135
|
-
assert.equal(result.outputJson,
|
|
2555
|
+
assert.equal(result.outputJson?.decision, "block");
|
|
2556
|
+
assert.match(JSON.stringify(result.outputJson), /Lore protocol/);
|
|
2136
2557
|
}
|
|
2137
2558
|
finally {
|
|
2138
2559
|
await rm(cwd, { recursive: true, force: true });
|
|
2139
2560
|
}
|
|
2140
2561
|
});
|
|
2141
|
-
it("
|
|
2142
|
-
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-env-
|
|
2562
|
+
it("restores default-on Lore guard when env -u unsets a disabled process env", async () => {
|
|
2563
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-env-unset-"));
|
|
2564
|
+
const original = process.env.OMX_LORE_COMMIT_GUARD;
|
|
2143
2565
|
try {
|
|
2566
|
+
process.env.OMX_LORE_COMMIT_GUARD = "0";
|
|
2144
2567
|
const result = await dispatchCodexNativeHook({
|
|
2145
2568
|
hook_event_name: "PreToolUse",
|
|
2146
2569
|
cwd,
|
|
2147
2570
|
tool_name: "Bash",
|
|
2148
|
-
tool_use_id: "tool-git-commit-env-
|
|
2149
|
-
tool_input: { command: '
|
|
2571
|
+
tool_use_id: "tool-git-commit-lore-env-unset",
|
|
2572
|
+
tool_input: { command: 'env -u OMX_LORE_COMMIT_GUARD git commit -m "fix: conventional"' },
|
|
2150
2573
|
}, { cwd });
|
|
2151
2574
|
assert.equal(result.omxEventName, "pre-tool-use");
|
|
2152
|
-
assert.
|
|
2153
|
-
|
|
2154
|
-
reason: "git commit is blocked until the inline commit message satisfies the Lore format and includes the required OmX co-author trailer.",
|
|
2155
|
-
hookSpecificOutput: {
|
|
2156
|
-
hookEventName: "PreToolUse",
|
|
2157
|
-
},
|
|
2158
|
-
systemMessage: [
|
|
2159
|
-
"git commit is blocked until the inline commit message follows the Lore protocol and includes `Co-authored-by: OmX <omx@oh-my-codex.dev>`.",
|
|
2160
|
-
"- Add a blank line after the subject before the narrative body.",
|
|
2161
|
-
"- Add a narrative body paragraph explaining the decision context.",
|
|
2162
|
-
"- Add at least one Lore trailer such as `Constraint:`, `Confidence:`, or `Tested:`.",
|
|
2163
|
-
"- Add the required co-author trailer: `Co-authored-by: OmX <omx@oh-my-codex.dev>`.",
|
|
2164
|
-
].join("\n"),
|
|
2165
|
-
});
|
|
2575
|
+
assert.equal(result.outputJson?.decision, "block");
|
|
2576
|
+
assert.match(JSON.stringify(result.outputJson), /Lore protocol/);
|
|
2166
2577
|
}
|
|
2167
2578
|
finally {
|
|
2579
|
+
if (original === undefined)
|
|
2580
|
+
delete process.env.OMX_LORE_COMMIT_GUARD;
|
|
2581
|
+
else
|
|
2582
|
+
process.env.OMX_LORE_COMMIT_GUARD = original;
|
|
2168
2583
|
await rm(cwd, { recursive: true, force: true });
|
|
2169
2584
|
}
|
|
2170
2585
|
});
|
|
2171
|
-
it("
|
|
2172
|
-
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-
|
|
2586
|
+
it("restores default-on Lore guard when env -i clears a disabled process env", async () => {
|
|
2587
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-env-ignore-"));
|
|
2588
|
+
const original = process.env.OMX_LORE_COMMIT_GUARD;
|
|
2173
2589
|
try {
|
|
2590
|
+
process.env.OMX_LORE_COMMIT_GUARD = "0";
|
|
2174
2591
|
const result = await dispatchCodexNativeHook({
|
|
2175
2592
|
hook_event_name: "PreToolUse",
|
|
2176
2593
|
cwd,
|
|
2177
2594
|
tool_name: "Bash",
|
|
2178
|
-
tool_use_id: "tool-git-commit-
|
|
2179
|
-
tool_input: { command: '
|
|
2595
|
+
tool_use_id: "tool-git-commit-lore-env-ignore",
|
|
2596
|
+
tool_input: { command: 'env -i PATH=/usr/bin git commit -m "fix: conventional"' },
|
|
2180
2597
|
}, { cwd });
|
|
2181
2598
|
assert.equal(result.omxEventName, "pre-tool-use");
|
|
2182
|
-
assert.
|
|
2183
|
-
|
|
2184
|
-
reason: "git commit is blocked until the inline commit message satisfies the Lore format and includes the required OmX co-author trailer.",
|
|
2185
|
-
hookSpecificOutput: {
|
|
2186
|
-
hookEventName: "PreToolUse",
|
|
2187
|
-
},
|
|
2188
|
-
systemMessage: [
|
|
2189
|
-
"git commit is blocked until the inline commit message follows the Lore protocol and includes `Co-authored-by: OmX <omx@oh-my-codex.dev>`.",
|
|
2190
|
-
"- Add a blank line after the subject before the narrative body.",
|
|
2191
|
-
"- Add a narrative body paragraph explaining the decision context.",
|
|
2192
|
-
"- Add at least one Lore trailer such as `Constraint:`, `Confidence:`, or `Tested:`.",
|
|
2193
|
-
"- Add the required co-author trailer: `Co-authored-by: OmX <omx@oh-my-codex.dev>`.",
|
|
2194
|
-
].join("\n"),
|
|
2195
|
-
});
|
|
2599
|
+
assert.equal(result.outputJson?.decision, "block");
|
|
2600
|
+
assert.match(JSON.stringify(result.outputJson), /Lore protocol/);
|
|
2196
2601
|
}
|
|
2197
2602
|
finally {
|
|
2603
|
+
if (original === undefined)
|
|
2604
|
+
delete process.env.OMX_LORE_COMMIT_GUARD;
|
|
2605
|
+
else
|
|
2606
|
+
process.env.OMX_LORE_COMMIT_GUARD = original;
|
|
2198
2607
|
await rm(cwd, { recursive: true, force: true });
|
|
2199
2608
|
}
|
|
2200
2609
|
});
|
|
2201
|
-
it("
|
|
2202
|
-
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-
|
|
2610
|
+
it("keeps Lore commit enforcement enabled for unknown inline guard values", async () => {
|
|
2611
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-inline-unknown-"));
|
|
2203
2612
|
try {
|
|
2204
2613
|
const result = await dispatchCodexNativeHook({
|
|
2205
2614
|
hook_event_name: "PreToolUse",
|
|
2206
2615
|
cwd,
|
|
2207
2616
|
tool_name: "Bash",
|
|
2208
|
-
tool_use_id: "tool-git-
|
|
2209
|
-
tool_input: { command: '
|
|
2617
|
+
tool_use_id: "tool-git-commit-lore-inline-unknown",
|
|
2618
|
+
tool_input: { command: 'OMX_LORE_COMMIT_GUARD=maybe git commit -m "fix: conventional"' },
|
|
2210
2619
|
}, { cwd });
|
|
2211
2620
|
assert.equal(result.omxEventName, "pre-tool-use");
|
|
2212
|
-
assert.
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2621
|
+
assert.equal(result.outputJson?.decision, "block");
|
|
2622
|
+
assert.match(JSON.stringify(result.outputJson), /Lore protocol/);
|
|
2623
|
+
}
|
|
2624
|
+
finally {
|
|
2625
|
+
await rm(cwd, { recursive: true, force: true });
|
|
2626
|
+
}
|
|
2627
|
+
});
|
|
2628
|
+
it("treats Lore commit guard disabled values as trim and case tolerant", async () => {
|
|
2629
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-off-"));
|
|
2630
|
+
const original = process.env.OMX_LORE_COMMIT_GUARD;
|
|
2631
|
+
try {
|
|
2632
|
+
process.env.OMX_LORE_COMMIT_GUARD = " OFF ";
|
|
2633
|
+
const result = await dispatchCodexNativeHook({
|
|
2634
|
+
hook_event_name: "PreToolUse",
|
|
2635
|
+
cwd,
|
|
2636
|
+
tool_name: "Bash",
|
|
2637
|
+
tool_use_id: "tool-git-commit-lore-off",
|
|
2638
|
+
tool_input: { command: 'git commit -m "chore: conventional commit"' },
|
|
2639
|
+
}, { cwd });
|
|
2640
|
+
assert.equal(result.omxEventName, "pre-tool-use");
|
|
2641
|
+
assert.equal(result.outputJson, null);
|
|
2642
|
+
}
|
|
2643
|
+
finally {
|
|
2644
|
+
if (original === undefined)
|
|
2645
|
+
delete process.env.OMX_LORE_COMMIT_GUARD;
|
|
2646
|
+
else
|
|
2647
|
+
process.env.OMX_LORE_COMMIT_GUARD = original;
|
|
2648
|
+
await rm(cwd, { recursive: true, force: true });
|
|
2649
|
+
}
|
|
2650
|
+
});
|
|
2651
|
+
it("keeps Lore commit enforcement enabled for unknown guard values", async () => {
|
|
2652
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-unknown-"));
|
|
2653
|
+
const original = process.env.OMX_LORE_COMMIT_GUARD;
|
|
2654
|
+
try {
|
|
2655
|
+
process.env.OMX_LORE_COMMIT_GUARD = "maybe";
|
|
2656
|
+
const result = await dispatchCodexNativeHook({
|
|
2657
|
+
hook_event_name: "PreToolUse",
|
|
2658
|
+
cwd,
|
|
2659
|
+
tool_name: "Bash",
|
|
2660
|
+
tool_use_id: "tool-git-commit-lore-unknown",
|
|
2661
|
+
tool_input: { command: 'git commit -m "fix tests"' },
|
|
2662
|
+
}, { cwd });
|
|
2663
|
+
assert.equal(result.omxEventName, "pre-tool-use");
|
|
2664
|
+
assert.equal(result.outputJson?.decision, "block");
|
|
2665
|
+
assert.match(JSON.stringify(result.outputJson), /Lore protocol/);
|
|
2666
|
+
}
|
|
2667
|
+
finally {
|
|
2668
|
+
if (original === undefined)
|
|
2669
|
+
delete process.env.OMX_LORE_COMMIT_GUARD;
|
|
2670
|
+
else
|
|
2671
|
+
process.env.OMX_LORE_COMMIT_GUARD = original;
|
|
2672
|
+
await rm(cwd, { recursive: true, force: true });
|
|
2673
|
+
}
|
|
2674
|
+
});
|
|
2675
|
+
it("continues to later PreToolUse checks when Lore commit guard is disabled", async () => {
|
|
2676
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-lore-disabled-destructive-"));
|
|
2677
|
+
const original = process.env.OMX_LORE_COMMIT_GUARD;
|
|
2678
|
+
try {
|
|
2679
|
+
process.env.OMX_LORE_COMMIT_GUARD = "false";
|
|
2680
|
+
const result = await dispatchCodexNativeHook({
|
|
2681
|
+
hook_event_name: "PreToolUse",
|
|
2682
|
+
cwd,
|
|
2683
|
+
tool_name: "Bash",
|
|
2684
|
+
tool_use_id: "tool-lore-disabled-destructive",
|
|
2685
|
+
tool_input: { command: "rm -rf dist" },
|
|
2686
|
+
}, { cwd });
|
|
2687
|
+
assert.equal(result.omxEventName, "pre-tool-use");
|
|
2688
|
+
assert.doesNotMatch(JSON.stringify(result.outputJson), /Lore protocol/);
|
|
2689
|
+
assert.match(JSON.stringify(result.outputJson), /Destructive Bash command detected/);
|
|
2690
|
+
}
|
|
2691
|
+
finally {
|
|
2692
|
+
if (original === undefined)
|
|
2693
|
+
delete process.env.OMX_LORE_COMMIT_GUARD;
|
|
2694
|
+
else
|
|
2695
|
+
process.env.OMX_LORE_COMMIT_GUARD = original;
|
|
2696
|
+
await rm(cwd, { recursive: true, force: true });
|
|
2697
|
+
}
|
|
2698
|
+
});
|
|
2699
|
+
it("stays silent on PreToolUse for `git help commit`", async () => {
|
|
2700
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-help-commit-"));
|
|
2701
|
+
try {
|
|
2702
|
+
const result = await dispatchCodexNativeHook({
|
|
2703
|
+
hook_event_name: "PreToolUse",
|
|
2704
|
+
cwd,
|
|
2705
|
+
tool_name: "Bash",
|
|
2706
|
+
tool_use_id: "tool-git-help-commit",
|
|
2707
|
+
tool_input: { command: "git help commit" },
|
|
2708
|
+
}, { cwd });
|
|
2709
|
+
assert.equal(result.omxEventName, "pre-tool-use");
|
|
2710
|
+
assert.equal(result.outputJson, null);
|
|
2711
|
+
}
|
|
2712
|
+
finally {
|
|
2713
|
+
await rm(cwd, { recursive: true, force: true });
|
|
2714
|
+
}
|
|
2715
|
+
});
|
|
2716
|
+
it("stays silent on PreToolUse for `git config alias.ci commit`", async () => {
|
|
2717
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-config-alias-commit-"));
|
|
2718
|
+
try {
|
|
2719
|
+
const result = await dispatchCodexNativeHook({
|
|
2720
|
+
hook_event_name: "PreToolUse",
|
|
2721
|
+
cwd,
|
|
2722
|
+
tool_name: "Bash",
|
|
2723
|
+
tool_use_id: "tool-git-config-alias-commit",
|
|
2724
|
+
tool_input: { command: "git config alias.ci commit" },
|
|
2725
|
+
}, { cwd });
|
|
2726
|
+
assert.equal(result.omxEventName, "pre-tool-use");
|
|
2727
|
+
assert.equal(result.outputJson, null);
|
|
2728
|
+
}
|
|
2729
|
+
finally {
|
|
2730
|
+
await rm(cwd, { recursive: true, force: true });
|
|
2731
|
+
}
|
|
2732
|
+
});
|
|
2733
|
+
it("stays silent on PreToolUse for `git tag commit`", async () => {
|
|
2734
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-tag-commit-"));
|
|
2735
|
+
try {
|
|
2736
|
+
const result = await dispatchCodexNativeHook({
|
|
2737
|
+
hook_event_name: "PreToolUse",
|
|
2738
|
+
cwd,
|
|
2739
|
+
tool_name: "Bash",
|
|
2740
|
+
tool_use_id: "tool-git-tag-commit",
|
|
2741
|
+
tool_input: { command: "git tag commit" },
|
|
2742
|
+
}, { cwd });
|
|
2743
|
+
assert.equal(result.omxEventName, "pre-tool-use");
|
|
2744
|
+
assert.equal(result.outputJson, null);
|
|
2745
|
+
}
|
|
2746
|
+
finally {
|
|
2747
|
+
await rm(cwd, { recursive: true, force: true });
|
|
2748
|
+
}
|
|
2749
|
+
});
|
|
2750
|
+
it("blocks PreToolUse env-prefixed git commit when the inline message is not Lore-compliant", async () => {
|
|
2751
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-env-invalid-"));
|
|
2752
|
+
try {
|
|
2753
|
+
const result = await dispatchCodexNativeHook({
|
|
2754
|
+
hook_event_name: "PreToolUse",
|
|
2755
|
+
cwd,
|
|
2756
|
+
tool_name: "Bash",
|
|
2757
|
+
tool_use_id: "tool-git-commit-env-invalid",
|
|
2758
|
+
tool_input: { command: 'HUSKY=0 git commit -m "fix tests"' },
|
|
2759
|
+
}, { cwd });
|
|
2760
|
+
assert.equal(result.omxEventName, "pre-tool-use");
|
|
2761
|
+
assert.deepEqual(result.outputJson, {
|
|
2762
|
+
decision: "block",
|
|
2763
|
+
reason: "git commit is blocked until the inline commit message satisfies the Lore format and includes the required OmX co-author trailer.",
|
|
2764
|
+
hookSpecificOutput: {
|
|
2765
|
+
hookEventName: "PreToolUse",
|
|
2766
|
+
},
|
|
2767
|
+
systemMessage: [
|
|
2768
|
+
"git commit is blocked until the inline commit message follows the Lore protocol and includes `Co-authored-by: OmX <omx@oh-my-codex.dev>`.",
|
|
2769
|
+
"- Add a blank line after the subject before the narrative body.",
|
|
2770
|
+
"- Add a narrative body paragraph explaining the decision context.",
|
|
2771
|
+
"- Add at least one Lore trailer such as `Constraint:`, `Confidence:`, or `Tested:`.",
|
|
2772
|
+
"- Add the required co-author trailer: `Co-authored-by: OmX <omx@oh-my-codex.dev>`.",
|
|
2773
|
+
].join("\n"),
|
|
2774
|
+
});
|
|
2775
|
+
}
|
|
2776
|
+
finally {
|
|
2777
|
+
await rm(cwd, { recursive: true, force: true });
|
|
2778
|
+
}
|
|
2779
|
+
});
|
|
2780
|
+
it("blocks PreToolUse git commit when git options appear before the real commit subcommand", async () => {
|
|
2781
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-option-invalid-"));
|
|
2782
|
+
try {
|
|
2783
|
+
const result = await dispatchCodexNativeHook({
|
|
2784
|
+
hook_event_name: "PreToolUse",
|
|
2785
|
+
cwd,
|
|
2786
|
+
tool_name: "Bash",
|
|
2787
|
+
tool_use_id: "tool-git-commit-option-invalid",
|
|
2788
|
+
tool_input: { command: 'git -c core.editor=true commit -m "fix tests"' },
|
|
2789
|
+
}, { cwd });
|
|
2790
|
+
assert.equal(result.omxEventName, "pre-tool-use");
|
|
2791
|
+
assert.deepEqual(result.outputJson, {
|
|
2792
|
+
decision: "block",
|
|
2793
|
+
reason: "git commit is blocked until the inline commit message satisfies the Lore format and includes the required OmX co-author trailer.",
|
|
2794
|
+
hookSpecificOutput: {
|
|
2795
|
+
hookEventName: "PreToolUse",
|
|
2796
|
+
},
|
|
2797
|
+
systemMessage: [
|
|
2798
|
+
"git commit is blocked until the inline commit message follows the Lore protocol and includes `Co-authored-by: OmX <omx@oh-my-codex.dev>`.",
|
|
2799
|
+
"- Add a blank line after the subject before the narrative body.",
|
|
2800
|
+
"- Add a narrative body paragraph explaining the decision context.",
|
|
2801
|
+
"- Add at least one Lore trailer such as `Constraint:`, `Confidence:`, or `Tested:`.",
|
|
2802
|
+
"- Add the required co-author trailer: `Co-authored-by: OmX <omx@oh-my-codex.dev>`.",
|
|
2803
|
+
].join("\n"),
|
|
2804
|
+
});
|
|
2805
|
+
}
|
|
2806
|
+
finally {
|
|
2807
|
+
await rm(cwd, { recursive: true, force: true });
|
|
2808
|
+
}
|
|
2809
|
+
});
|
|
2810
|
+
it("blocks PreToolUse env wrapper-prefixed git.exe commit when the inline message is not Lore-compliant", async () => {
|
|
2811
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-exe-commit-env-wrapper-invalid-"));
|
|
2812
|
+
try {
|
|
2813
|
+
const result = await dispatchCodexNativeHook({
|
|
2814
|
+
hook_event_name: "PreToolUse",
|
|
2815
|
+
cwd,
|
|
2816
|
+
tool_name: "Bash",
|
|
2817
|
+
tool_use_id: "tool-git-exe-commit-env-wrapper-invalid",
|
|
2818
|
+
tool_input: { command: 'env git.exe commit -m "fix tests"' },
|
|
2819
|
+
}, { cwd });
|
|
2820
|
+
assert.equal(result.omxEventName, "pre-tool-use");
|
|
2821
|
+
assert.deepEqual(result.outputJson, {
|
|
2822
|
+
decision: "block",
|
|
2823
|
+
reason: "git commit is blocked until the inline commit message satisfies the Lore format and includes the required OmX co-author trailer.",
|
|
2824
|
+
hookSpecificOutput: {
|
|
2825
|
+
hookEventName: "PreToolUse",
|
|
2826
|
+
},
|
|
2827
|
+
systemMessage: [
|
|
2828
|
+
"git commit is blocked until the inline commit message follows the Lore protocol and includes `Co-authored-by: OmX <omx@oh-my-codex.dev>`.",
|
|
2829
|
+
"- Add a blank line after the subject before the narrative body.",
|
|
2830
|
+
"- Add a narrative body paragraph explaining the decision context.",
|
|
2831
|
+
"- Add at least one Lore trailer such as `Constraint:`, `Confidence:`, or `Tested:`.",
|
|
2223
2832
|
"- Add the required co-author trailer: `Co-authored-by: OmX <omx@oh-my-codex.dev>`.",
|
|
2224
2833
|
].join("\n"),
|
|
2225
2834
|
});
|
|
@@ -3077,12 +3686,44 @@ esac
|
|
|
3077
3686
|
await rm(cwd, { recursive: true, force: true });
|
|
3078
3687
|
}
|
|
3079
3688
|
});
|
|
3689
|
+
for (const rootActiveCase of [
|
|
3690
|
+
{ mode: "autopilot", phase: "execution" },
|
|
3691
|
+
{ mode: "ultrawork", phase: "executing" },
|
|
3692
|
+
{ mode: "ultraqa", phase: "diagnose" },
|
|
3693
|
+
]) {
|
|
3694
|
+
it(`returns Stop continuation output from root ${rootActiveCase.mode} state when no session is active`, async () => {
|
|
3695
|
+
const cwd = await mkdtemp(join(tmpdir(), `omx-native-hook-stop-root-${rootActiveCase.mode}-`));
|
|
3696
|
+
try {
|
|
3697
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
3698
|
+
await mkdir(stateDir, { recursive: true });
|
|
3699
|
+
await writeJson(join(stateDir, `${rootActiveCase.mode}-state.json`), {
|
|
3700
|
+
active: true,
|
|
3701
|
+
mode: rootActiveCase.mode,
|
|
3702
|
+
current_phase: rootActiveCase.phase,
|
|
3703
|
+
});
|
|
3704
|
+
const result = await dispatchCodexNativeHook({
|
|
3705
|
+
hook_event_name: "Stop",
|
|
3706
|
+
cwd,
|
|
3707
|
+
}, { cwd });
|
|
3708
|
+
assert.equal(result.omxEventName, "stop");
|
|
3709
|
+
assert.deepEqual(result.outputJson, {
|
|
3710
|
+
decision: "block",
|
|
3711
|
+
reason: `OMX ${rootActiveCase.mode} is still active (phase: ${rootActiveCase.phase}); continue the task and gather fresh verification evidence before stopping.`,
|
|
3712
|
+
stopReason: `${rootActiveCase.mode}_${rootActiveCase.phase}`,
|
|
3713
|
+
systemMessage: `OMX ${rootActiveCase.mode} is still active (phase: ${rootActiveCase.phase}).`,
|
|
3714
|
+
});
|
|
3715
|
+
}
|
|
3716
|
+
finally {
|
|
3717
|
+
await rm(cwd, { recursive: true, force: true });
|
|
3718
|
+
}
|
|
3719
|
+
});
|
|
3720
|
+
}
|
|
3080
3721
|
it("returns Stop continuation output while Autopilot is active", async () => {
|
|
3081
3722
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-autopilot-"));
|
|
3082
3723
|
try {
|
|
3083
3724
|
const stateDir = join(cwd, ".omx", "state");
|
|
3084
|
-
await mkdir(stateDir, { recursive: true });
|
|
3085
|
-
await writeJson(join(stateDir, "autopilot-state.json"), {
|
|
3725
|
+
await mkdir(join(stateDir, "sessions", "sess-stop-autopilot"), { recursive: true });
|
|
3726
|
+
await writeJson(join(stateDir, "sessions", "sess-stop-autopilot", "autopilot-state.json"), {
|
|
3086
3727
|
active: true,
|
|
3087
3728
|
current_phase: "execution",
|
|
3088
3729
|
});
|
|
@@ -3107,8 +3748,8 @@ esac
|
|
|
3107
3748
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-autopilot-planning-replay-"));
|
|
3108
3749
|
try {
|
|
3109
3750
|
const stateDir = join(cwd, ".omx", "state");
|
|
3110
|
-
await mkdir(stateDir, { recursive: true });
|
|
3111
|
-
await writeJson(join(stateDir, "autopilot-state.json"), {
|
|
3751
|
+
await mkdir(join(stateDir, "sessions", "sess-stop-autopilot-planning-replay"), { recursive: true });
|
|
3752
|
+
await writeJson(join(stateDir, "sessions", "sess-stop-autopilot-planning-replay", "autopilot-state.json"), {
|
|
3112
3753
|
active: true,
|
|
3113
3754
|
current_phase: "planning",
|
|
3114
3755
|
});
|
|
@@ -3162,12 +3803,40 @@ esac
|
|
|
3162
3803
|
await rm(cwd, { recursive: true, force: true });
|
|
3163
3804
|
}
|
|
3164
3805
|
});
|
|
3806
|
+
for (const staleRootCase of [
|
|
3807
|
+
{ mode: "autopilot", phase: "execution" },
|
|
3808
|
+
{ mode: "ultrawork", phase: "executing" },
|
|
3809
|
+
{ mode: "ultraqa", phase: "diagnose" },
|
|
3810
|
+
]) {
|
|
3811
|
+
it(`does not block Stop from stale root ${staleRootCase.mode} state when the explicit session directory is missing`, async () => {
|
|
3812
|
+
const cwd = await mkdtemp(join(tmpdir(), `omx-native-hook-stop-missing-session-${staleRootCase.mode}-`));
|
|
3813
|
+
try {
|
|
3814
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
3815
|
+
await mkdir(stateDir, { recursive: true });
|
|
3816
|
+
await writeJson(join(stateDir, `${staleRootCase.mode}-state.json`), {
|
|
3817
|
+
active: true,
|
|
3818
|
+
mode: staleRootCase.mode,
|
|
3819
|
+
current_phase: staleRootCase.phase,
|
|
3820
|
+
});
|
|
3821
|
+
const result = await dispatchCodexNativeHook({
|
|
3822
|
+
hook_event_name: "Stop",
|
|
3823
|
+
cwd,
|
|
3824
|
+
session_id: "missing-session",
|
|
3825
|
+
}, { cwd });
|
|
3826
|
+
assert.equal(result.omxEventName, "stop");
|
|
3827
|
+
assert.equal(result.outputJson, null);
|
|
3828
|
+
}
|
|
3829
|
+
finally {
|
|
3830
|
+
await rm(cwd, { recursive: true, force: true });
|
|
3831
|
+
}
|
|
3832
|
+
});
|
|
3833
|
+
}
|
|
3165
3834
|
it("does not block Stop when an explicit blocked_on_user run_outcome is present on a mode state", async () => {
|
|
3166
3835
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-autopilot-blocked-outcome-"));
|
|
3167
3836
|
try {
|
|
3168
3837
|
const stateDir = join(cwd, ".omx", "state");
|
|
3169
|
-
await mkdir(stateDir, { recursive: true });
|
|
3170
|
-
await writeJson(join(stateDir, "autopilot-state.json"), {
|
|
3838
|
+
await mkdir(join(stateDir, "sessions", "sess-stop-autopilot-blocked-outcome"), { recursive: true });
|
|
3839
|
+
await writeJson(join(stateDir, "sessions", "sess-stop-autopilot-blocked-outcome", "autopilot-state.json"), {
|
|
3171
3840
|
active: true,
|
|
3172
3841
|
current_phase: "execution",
|
|
3173
3842
|
run_outcome: "blocked_on_user",
|
|
@@ -3188,8 +3857,8 @@ esac
|
|
|
3188
3857
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-ultrawork-"));
|
|
3189
3858
|
try {
|
|
3190
3859
|
const stateDir = join(cwd, ".omx", "state");
|
|
3191
|
-
await mkdir(stateDir, { recursive: true });
|
|
3192
|
-
await writeJson(join(stateDir, "ultrawork-state.json"), {
|
|
3860
|
+
await mkdir(join(stateDir, "sessions", "sess-stop-ultrawork"), { recursive: true });
|
|
3861
|
+
await writeJson(join(stateDir, "sessions", "sess-stop-ultrawork", "ultrawork-state.json"), {
|
|
3193
3862
|
active: true,
|
|
3194
3863
|
current_phase: "executing",
|
|
3195
3864
|
});
|
|
@@ -3209,8 +3878,8 @@ esac
|
|
|
3209
3878
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-ultraqa-"));
|
|
3210
3879
|
try {
|
|
3211
3880
|
const stateDir = join(cwd, ".omx", "state");
|
|
3212
|
-
await mkdir(stateDir, { recursive: true });
|
|
3213
|
-
await writeJson(join(stateDir, "ultraqa-state.json"), {
|
|
3881
|
+
await mkdir(join(stateDir, "sessions", "sess-stop-ultraqa"), { recursive: true });
|
|
3882
|
+
await writeJson(join(stateDir, "sessions", "sess-stop-ultraqa", "ultraqa-state.json"), {
|
|
3214
3883
|
active: true,
|
|
3215
3884
|
current_phase: "diagnose",
|
|
3216
3885
|
});
|
|
@@ -3226,6 +3895,32 @@ esac
|
|
|
3226
3895
|
await rm(cwd, { recursive: true, force: true });
|
|
3227
3896
|
}
|
|
3228
3897
|
});
|
|
3898
|
+
it("marks leader-owned team attention during native Stop dispatch without a polling watcher", async () => {
|
|
3899
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-attention-"));
|
|
3900
|
+
try {
|
|
3901
|
+
await initTeamState("stop-attention-team", "native stop attention", "executor", 1, cwd, undefined, { ...process.env, OMX_SESSION_ID: "sess-stop-team-attention" });
|
|
3902
|
+
const result = await dispatchCodexNativeHook({
|
|
3903
|
+
hook_event_name: "Stop",
|
|
3904
|
+
cwd,
|
|
3905
|
+
session_id: "sess-stop-team-attention",
|
|
3906
|
+
}, { cwd });
|
|
3907
|
+
const attention = await readTeamLeaderAttention("stop-attention-team", cwd);
|
|
3908
|
+
assert.equal(result.omxEventName, "stop");
|
|
3909
|
+
assert.equal(attention?.source, "native_stop");
|
|
3910
|
+
assert.equal(attention?.leader_session_active, false);
|
|
3911
|
+
assert.equal(attention?.leader_session_id, "sess-stop-team-attention");
|
|
3912
|
+
assert.match(attention?.leader_session_stopped_at ?? "", /^\d{4}-\d{2}-\d{2}T/);
|
|
3913
|
+
assert.deepEqual(result.outputJson, {
|
|
3914
|
+
decision: "block",
|
|
3915
|
+
reason: `OMX team pipeline is still active (stop-attention-team) at phase team-exec; continue coordinating until the team reaches a terminal phase.${TEAM_STOP_COMMIT_GUIDANCE}`,
|
|
3916
|
+
stopReason: "team_team-exec",
|
|
3917
|
+
systemMessage: "OMX team pipeline is still active at phase team-exec.",
|
|
3918
|
+
});
|
|
3919
|
+
}
|
|
3920
|
+
finally {
|
|
3921
|
+
await rm(cwd, { recursive: true, force: true });
|
|
3922
|
+
}
|
|
3923
|
+
});
|
|
3229
3924
|
it("returns Stop continuation output while team phase is non-terminal", async () => {
|
|
3230
3925
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-"));
|
|
3231
3926
|
try {
|
|
@@ -3315,30 +4010,492 @@ esac
|
|
|
3315
4010
|
process.env.OMX_TEAM_STATE_ROOT = prevTeamStateRoot;
|
|
3316
4011
|
else
|
|
3317
4012
|
delete process.env.OMX_TEAM_STATE_ROOT;
|
|
3318
|
-
if (typeof prevLeaderCwd === "string")
|
|
3319
|
-
process.env.OMX_TEAM_LEADER_CWD = prevLeaderCwd;
|
|
4013
|
+
if (typeof prevLeaderCwd === "string")
|
|
4014
|
+
process.env.OMX_TEAM_LEADER_CWD = prevLeaderCwd;
|
|
4015
|
+
else
|
|
4016
|
+
delete process.env.OMX_TEAM_LEADER_CWD;
|
|
4017
|
+
await rm(cwd, { recursive: true, force: true });
|
|
4018
|
+
}
|
|
4019
|
+
});
|
|
4020
|
+
it("blocks Stop as a team-worker task failure when worker status is terminal but task evidence is not completed", async () => {
|
|
4021
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-worker-terminal-stale-"));
|
|
4022
|
+
const prevTeamWorker = process.env.OMX_TEAM_WORKER;
|
|
4023
|
+
const prevTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT;
|
|
4024
|
+
const prevLeaderCwd = process.env.OMX_TEAM_LEADER_CWD;
|
|
4025
|
+
try {
|
|
4026
|
+
await initTeamState("worker-stale-team", "worker stale stop fallback", "executor", 1, cwd, undefined, { ...process.env, OMX_SESSION_ID: "sess-stop-team-worker-stale" });
|
|
4027
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
4028
|
+
const workerCwd = join(cwd, ".omx", "team", "worker-stale-team", "worktrees", "worker-1");
|
|
4029
|
+
const workerDir = join(stateDir, "team", "worker-stale-team", "workers", "worker-1");
|
|
4030
|
+
await mkdir(workerCwd, { recursive: true });
|
|
4031
|
+
await writeJson(join(workerDir, "identity.json"), {
|
|
4032
|
+
name: "worker-1",
|
|
4033
|
+
index: 1,
|
|
4034
|
+
role: "executor",
|
|
4035
|
+
assigned_tasks: ["1"],
|
|
4036
|
+
worktree_path: workerCwd,
|
|
4037
|
+
team_state_root: stateDir,
|
|
4038
|
+
});
|
|
4039
|
+
await writeJson(join(workerDir, "status.json"), {
|
|
4040
|
+
state: "done",
|
|
4041
|
+
current_task_id: "1",
|
|
4042
|
+
updated_at: new Date().toISOString(),
|
|
4043
|
+
});
|
|
4044
|
+
await writeJson(join(stateDir, "team", "worker-stale-team", "tasks", "task-1.json"), {
|
|
4045
|
+
id: "1",
|
|
4046
|
+
subject: "stale hook task",
|
|
4047
|
+
description: "non-completed task should still block terminal worker Stop",
|
|
4048
|
+
status: "in_progress",
|
|
4049
|
+
owner: "worker-1",
|
|
4050
|
+
created_at: new Date().toISOString(),
|
|
4051
|
+
});
|
|
4052
|
+
process.env.OMX_TEAM_WORKER = "worker-stale-team/worker-1";
|
|
4053
|
+
process.env.OMX_TEAM_STATE_ROOT = stateDir;
|
|
4054
|
+
process.env.OMX_TEAM_LEADER_CWD = cwd;
|
|
4055
|
+
const payload = {
|
|
4056
|
+
hook_event_name: "Stop",
|
|
4057
|
+
cwd: workerCwd,
|
|
4058
|
+
session_id: "sess-stop-team-worker-stale",
|
|
4059
|
+
thread_id: "thread-stop-team-worker-stale",
|
|
4060
|
+
};
|
|
4061
|
+
const result = await dispatchCodexNativeHook(payload, { cwd: workerCwd });
|
|
4062
|
+
const replay = await dispatchCodexNativeHook({ ...payload, stop_hook_active: true }, { cwd: workerCwd });
|
|
4063
|
+
assert.equal(result.outputJson?.stopReason, "team_worker_worker-1_1_in_progress");
|
|
4064
|
+
assert.equal(replay.outputJson, null);
|
|
4065
|
+
}
|
|
4066
|
+
finally {
|
|
4067
|
+
if (typeof prevTeamWorker === "string")
|
|
4068
|
+
process.env.OMX_TEAM_WORKER = prevTeamWorker;
|
|
4069
|
+
else
|
|
4070
|
+
delete process.env.OMX_TEAM_WORKER;
|
|
4071
|
+
if (typeof prevTeamStateRoot === "string")
|
|
4072
|
+
process.env.OMX_TEAM_STATE_ROOT = prevTeamStateRoot;
|
|
4073
|
+
else
|
|
4074
|
+
delete process.env.OMX_TEAM_STATE_ROOT;
|
|
4075
|
+
if (typeof prevLeaderCwd === "string")
|
|
4076
|
+
process.env.OMX_TEAM_LEADER_CWD = prevLeaderCwd;
|
|
4077
|
+
else
|
|
4078
|
+
delete process.env.OMX_TEAM_LEADER_CWD;
|
|
4079
|
+
await rm(cwd, { recursive: true, force: true });
|
|
4080
|
+
}
|
|
4081
|
+
});
|
|
4082
|
+
it("re-blocks live team worker Stop replays but suppresses stale terminal worker repeats", async () => {
|
|
4083
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-worker-repeat-"));
|
|
4084
|
+
try {
|
|
4085
|
+
await initTeamState("worker-repeat-team", "worker stop repeat guard", "executor", 1, cwd, undefined, { ...process.env, OMX_SESSION_ID: "sess-stop-team-worker-repeat" });
|
|
4086
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
4087
|
+
const workerDir = join(stateDir, "team", "worker-repeat-team", "workers", "worker-1");
|
|
4088
|
+
const taskPath = join(stateDir, "team", "worker-repeat-team", "tasks", "task-1.json");
|
|
4089
|
+
const workerCwd = join(cwd, ".omx", "team", "worker-repeat-team", "worktrees", "worker-1");
|
|
4090
|
+
await mkdir(workerCwd, { recursive: true });
|
|
4091
|
+
await writeJson(join(workerDir, "identity.json"), {
|
|
4092
|
+
name: "worker-1",
|
|
4093
|
+
index: 1,
|
|
4094
|
+
role: "executor",
|
|
4095
|
+
assigned_tasks: ["1"],
|
|
4096
|
+
worktree_path: workerCwd,
|
|
4097
|
+
team_state_root: stateDir,
|
|
4098
|
+
});
|
|
4099
|
+
await writeJson(join(workerDir, "status.json"), {
|
|
4100
|
+
state: "working",
|
|
4101
|
+
current_task_id: "1",
|
|
4102
|
+
updated_at: new Date().toISOString(),
|
|
4103
|
+
});
|
|
4104
|
+
await writeJson(taskPath, {
|
|
4105
|
+
id: "1",
|
|
4106
|
+
subject: "hook task",
|
|
4107
|
+
description: "finish hook task",
|
|
4108
|
+
status: "in_progress",
|
|
4109
|
+
owner: "worker-1",
|
|
4110
|
+
created_at: new Date().toISOString(),
|
|
4111
|
+
});
|
|
4112
|
+
process.env.OMX_TEAM_WORKER = "worker-repeat-team/worker-1";
|
|
4113
|
+
process.env.OMX_TEAM_STATE_ROOT = stateDir;
|
|
4114
|
+
process.env.OMX_TEAM_LEADER_CWD = cwd;
|
|
4115
|
+
const basePayload = {
|
|
4116
|
+
hook_event_name: "Stop",
|
|
4117
|
+
cwd: workerCwd,
|
|
4118
|
+
session_id: "sess-stop-team-worker-repeat",
|
|
4119
|
+
thread_id: "thread-stop-team-worker-repeat",
|
|
4120
|
+
turn_id: "turn-stop-team-worker-repeat-1",
|
|
4121
|
+
last_assistant_message: "I need to stop before this task is done.",
|
|
4122
|
+
};
|
|
4123
|
+
const expectedInProgress = {
|
|
4124
|
+
decision: "block",
|
|
4125
|
+
reason: "OMX team worker worker-1 is still assigned non-terminal task 1 (in_progress); continue the current assigned task or report a concrete blocker before stopping.",
|
|
4126
|
+
stopReason: "team_worker_worker-1_1_in_progress",
|
|
4127
|
+
systemMessage: "OMX team worker worker-1 is still assigned task 1 (in_progress).",
|
|
4128
|
+
};
|
|
4129
|
+
const first = await dispatchCodexNativeHook(basePayload, { cwd: workerCwd });
|
|
4130
|
+
const replay = await dispatchCodexNativeHook({ ...basePayload, stop_hook_active: true }, { cwd: workerCwd });
|
|
4131
|
+
const freshTurn = await dispatchCodexNativeHook({ ...basePayload, turn_id: "turn-stop-team-worker-repeat-2", stop_hook_active: true }, { cwd: workerCwd });
|
|
4132
|
+
await writeJson(taskPath, {
|
|
4133
|
+
id: "1",
|
|
4134
|
+
subject: "hook task",
|
|
4135
|
+
description: "finish hook task",
|
|
4136
|
+
status: "blocked",
|
|
4137
|
+
owner: "worker-1",
|
|
4138
|
+
created_at: new Date().toISOString(),
|
|
4139
|
+
});
|
|
4140
|
+
const stateChanged = await dispatchCodexNativeHook({ ...basePayload, turn_id: "turn-stop-team-worker-repeat-3", stop_hook_active: true }, { cwd: workerCwd });
|
|
4141
|
+
assert.deepEqual(first.outputJson, expectedInProgress);
|
|
4142
|
+
assert.deepEqual(replay.outputJson, expectedInProgress);
|
|
4143
|
+
assert.deepEqual(freshTurn.outputJson, expectedInProgress);
|
|
4144
|
+
assert.deepEqual(stateChanged.outputJson, {
|
|
4145
|
+
decision: "block",
|
|
4146
|
+
reason: "OMX team worker worker-1 is still assigned non-terminal task 1 (blocked); continue the current assigned task or report a concrete blocker before stopping.",
|
|
4147
|
+
stopReason: "team_worker_worker-1_1_blocked",
|
|
4148
|
+
systemMessage: "OMX team worker worker-1 is still assigned task 1 (blocked).",
|
|
4149
|
+
});
|
|
4150
|
+
}
|
|
4151
|
+
finally {
|
|
4152
|
+
await rm(cwd, { recursive: true, force: true });
|
|
4153
|
+
}
|
|
4154
|
+
});
|
|
4155
|
+
it("allows Stop for a team worker when assigned task is terminal and bypasses generic team blocking", async () => {
|
|
4156
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-worker-terminal-"));
|
|
4157
|
+
const prevTeamWorker = process.env.OMX_TEAM_WORKER;
|
|
4158
|
+
const prevTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT;
|
|
4159
|
+
const prevPath = process.env.PATH;
|
|
4160
|
+
try {
|
|
4161
|
+
await initTeamState("worker-stop-team-terminal", "worker stop terminal fallback", "executor", 1, cwd, undefined, { ...process.env, OMX_SESSION_ID: "sess-stop-team-worker-terminal" });
|
|
4162
|
+
const fakeBinDir = join(cwd, "fake-bin");
|
|
4163
|
+
const tmuxLogPath = join(cwd, "tmux.log");
|
|
4164
|
+
await mkdir(fakeBinDir, { recursive: true });
|
|
4165
|
+
await writeFile(join(fakeBinDir, "tmux"), buildWorkerStopFakeTmux(tmuxLogPath));
|
|
4166
|
+
await chmod(join(fakeBinDir, "tmux"), 0o755);
|
|
4167
|
+
const workerDir = join(cwd, ".omx", "state", "team", "worker-stop-team-terminal", "workers", "worker-1");
|
|
4168
|
+
await writeJson(join(cwd, ".omx", "state", "team", "worker-stop-team-terminal", "config.json"), {
|
|
4169
|
+
name: "worker-stop-team-terminal",
|
|
4170
|
+
tmux_session: "omx-team-worker-stop",
|
|
4171
|
+
leader_pane_id: "%42",
|
|
4172
|
+
workers: [{ name: "worker-1", index: 1, pane_id: "%10" }],
|
|
4173
|
+
});
|
|
4174
|
+
await writeJson(join(cwd, ".omx", "state", "team", "worker-stop-team-terminal", "manifest.v2.json"), {
|
|
4175
|
+
name: "worker-stop-team-terminal",
|
|
4176
|
+
tmux_session: "omx-team-worker-stop",
|
|
4177
|
+
leader_pane_id: "%42",
|
|
4178
|
+
workers: [{ name: "worker-1", index: 1, pane_id: "%10" }],
|
|
4179
|
+
});
|
|
4180
|
+
await writeJson(join(workerDir, "identity.json"), {
|
|
4181
|
+
name: "worker-1",
|
|
4182
|
+
index: 1,
|
|
4183
|
+
role: "executor",
|
|
4184
|
+
assigned_tasks: ["1"],
|
|
4185
|
+
worktree_path: cwd,
|
|
4186
|
+
team_state_root: join(cwd, ".omx", "state"),
|
|
4187
|
+
});
|
|
4188
|
+
await writeJson(join(workerDir, "status.json"), {
|
|
4189
|
+
state: "done",
|
|
4190
|
+
current_task_id: "1",
|
|
4191
|
+
updated_at: new Date().toISOString(),
|
|
4192
|
+
});
|
|
4193
|
+
await writeJson(join(cwd, ".omx", "state", "team", "worker-stop-team-terminal", "tasks", "task-1.json"), {
|
|
4194
|
+
id: "1",
|
|
4195
|
+
subject: "hook task",
|
|
4196
|
+
description: "finish hook task",
|
|
4197
|
+
status: "completed",
|
|
4198
|
+
owner: "worker-1",
|
|
4199
|
+
created_at: new Date().toISOString(),
|
|
4200
|
+
});
|
|
4201
|
+
process.env.OMX_TEAM_WORKER = "worker-stop-team-terminal/worker-1";
|
|
4202
|
+
process.env.OMX_TEAM_STATE_ROOT = join(cwd, ".omx", "state");
|
|
4203
|
+
process.env.PATH = `${fakeBinDir}:${prevPath || ""}`;
|
|
4204
|
+
const result = await dispatchCodexNativeHook({
|
|
4205
|
+
hook_event_name: "Stop",
|
|
4206
|
+
cwd,
|
|
4207
|
+
session_id: "sess-stop-team-worker-terminal",
|
|
4208
|
+
}, { cwd });
|
|
4209
|
+
const replay = await dispatchCodexNativeHook({
|
|
4210
|
+
hook_event_name: "Stop",
|
|
4211
|
+
cwd,
|
|
4212
|
+
session_id: "sess-stop-team-worker-terminal",
|
|
4213
|
+
turn_id: "turn-worker-stop-terminal-replay",
|
|
4214
|
+
}, { cwd });
|
|
4215
|
+
assert.equal(result.outputJson, null);
|
|
4216
|
+
assert.equal(replay.outputJson, null);
|
|
4217
|
+
const tmuxLog = await readFile(tmuxLogPath, "utf-8");
|
|
4218
|
+
const stopNudges = tmuxLog.match(/send-keys -t %42 -l \[OMX\] worker-1 native Stop allowed/g) || [];
|
|
4219
|
+
assert.equal(stopNudges.length, 1, "allowed worker Stop should nudge leader exactly once inside cooldown");
|
|
4220
|
+
const nudgeState = JSON.parse(await readFile(join(workerDir, "worker-stop-nudge.json"), "utf-8"));
|
|
4221
|
+
assert.equal(nudgeState.delivery, "sent");
|
|
4222
|
+
}
|
|
4223
|
+
finally {
|
|
4224
|
+
if (typeof prevTeamWorker === "string")
|
|
4225
|
+
process.env.OMX_TEAM_WORKER = prevTeamWorker;
|
|
4226
|
+
else
|
|
4227
|
+
delete process.env.OMX_TEAM_WORKER;
|
|
4228
|
+
if (typeof prevTeamStateRoot === "string")
|
|
4229
|
+
process.env.OMX_TEAM_STATE_ROOT = prevTeamStateRoot;
|
|
4230
|
+
else
|
|
4231
|
+
delete process.env.OMX_TEAM_STATE_ROOT;
|
|
4232
|
+
if (typeof prevPath === "string")
|
|
4233
|
+
process.env.PATH = prevPath;
|
|
4234
|
+
else
|
|
4235
|
+
delete process.env.PATH;
|
|
4236
|
+
await rm(cwd, { recursive: true, force: true });
|
|
4237
|
+
}
|
|
4238
|
+
});
|
|
4239
|
+
it("allows worker Stop when the Stop nudge helper cannot deliver", async () => {
|
|
4240
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-worker-helper-fail-"));
|
|
4241
|
+
const prevTeamWorker = process.env.OMX_TEAM_WORKER;
|
|
4242
|
+
const prevTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT;
|
|
4243
|
+
const prevPath = process.env.PATH;
|
|
4244
|
+
try {
|
|
4245
|
+
await initTeamState("worker-stop-helper-fail", "worker stop helper failure", "executor", 1, cwd, undefined, { ...process.env, OMX_SESSION_ID: "sess-stop-team-worker-helper-fail" });
|
|
4246
|
+
const fakeBinDir = join(cwd, "fake-bin");
|
|
4247
|
+
await mkdir(fakeBinDir, { recursive: true });
|
|
4248
|
+
await writeFile(join(fakeBinDir, "tmux"), buildWorkerStopFakeTmux(join(cwd, "tmux.log"), { failSend: true }));
|
|
4249
|
+
await chmod(join(fakeBinDir, "tmux"), 0o755);
|
|
4250
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
4251
|
+
const workerDir = join(stateDir, "team", "worker-stop-helper-fail", "workers", "worker-1");
|
|
4252
|
+
await writeJson(join(stateDir, "team", "worker-stop-helper-fail", "config.json"), {
|
|
4253
|
+
name: "worker-stop-helper-fail",
|
|
4254
|
+
tmux_session: "omx-team-worker-stop",
|
|
4255
|
+
leader_pane_id: "%42",
|
|
4256
|
+
workers: [{ name: "worker-1", index: 1, pane_id: "%10" }],
|
|
4257
|
+
});
|
|
4258
|
+
await writeJson(join(stateDir, "team", "worker-stop-helper-fail", "manifest.v2.json"), {
|
|
4259
|
+
name: "worker-stop-helper-fail",
|
|
4260
|
+
tmux_session: "omx-team-worker-stop",
|
|
4261
|
+
leader_pane_id: "%42",
|
|
4262
|
+
workers: [{ name: "worker-1", index: 1, pane_id: "%10" }],
|
|
4263
|
+
});
|
|
4264
|
+
await writeJson(join(workerDir, "identity.json"), {
|
|
4265
|
+
name: "worker-1",
|
|
4266
|
+
assigned_tasks: ["1"],
|
|
4267
|
+
team_state_root: stateDir,
|
|
4268
|
+
});
|
|
4269
|
+
await writeJson(join(workerDir, "status.json"), {
|
|
4270
|
+
state: "done",
|
|
4271
|
+
current_task_id: "1",
|
|
4272
|
+
updated_at: new Date().toISOString(),
|
|
4273
|
+
});
|
|
4274
|
+
await writeJson(join(stateDir, "team", "worker-stop-helper-fail", "tasks", "task-1.json"), {
|
|
4275
|
+
id: "1",
|
|
4276
|
+
status: "completed",
|
|
4277
|
+
owner: "worker-1",
|
|
4278
|
+
});
|
|
4279
|
+
process.env.OMX_TEAM_WORKER = "worker-stop-helper-fail/worker-1";
|
|
4280
|
+
process.env.OMX_TEAM_STATE_ROOT = stateDir;
|
|
4281
|
+
process.env.PATH = `${fakeBinDir}:${prevPath || ""}`;
|
|
4282
|
+
const result = await dispatchCodexNativeHook({ hook_event_name: "Stop", cwd, session_id: "sess-stop-team-worker-helper-fail" }, { cwd });
|
|
4283
|
+
assert.equal(result.outputJson, null);
|
|
4284
|
+
const nudgeState = JSON.parse(await readFile(join(workerDir, "worker-stop-nudge.json"), "utf-8"));
|
|
4285
|
+
assert.equal(nudgeState.delivery, "deferred");
|
|
4286
|
+
}
|
|
4287
|
+
finally {
|
|
4288
|
+
if (typeof prevTeamWorker === "string")
|
|
4289
|
+
process.env.OMX_TEAM_WORKER = prevTeamWorker;
|
|
4290
|
+
else
|
|
4291
|
+
delete process.env.OMX_TEAM_WORKER;
|
|
4292
|
+
if (typeof prevTeamStateRoot === "string")
|
|
4293
|
+
process.env.OMX_TEAM_STATE_ROOT = prevTeamStateRoot;
|
|
4294
|
+
else
|
|
4295
|
+
delete process.env.OMX_TEAM_STATE_ROOT;
|
|
4296
|
+
if (typeof prevPath === "string")
|
|
4297
|
+
process.env.PATH = prevPath;
|
|
4298
|
+
else
|
|
4299
|
+
delete process.env.PATH;
|
|
4300
|
+
await rm(cwd, { recursive: true, force: true });
|
|
4301
|
+
}
|
|
4302
|
+
});
|
|
4303
|
+
it("does not treat failed or ambiguous worker task state as completed Stop evidence", async () => {
|
|
4304
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-worker-failed-"));
|
|
4305
|
+
const prevTeamWorker = process.env.OMX_TEAM_WORKER;
|
|
4306
|
+
const prevInternalTeamWorker = process.env.OMX_TEAM_INTERNAL_WORKER;
|
|
4307
|
+
const prevTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT;
|
|
4308
|
+
const prevPath = process.env.PATH;
|
|
4309
|
+
try {
|
|
4310
|
+
await initTeamState("worker-stop-failed-task", "worker stop failed task", "executor", 1, cwd, undefined, { ...process.env, OMX_SESSION_ID: "sess-stop-team-worker-failed" });
|
|
4311
|
+
const fakeBinDir = join(cwd, "fake-bin");
|
|
4312
|
+
const tmuxLogPath = join(cwd, "tmux.log");
|
|
4313
|
+
await mkdir(fakeBinDir, { recursive: true });
|
|
4314
|
+
await writeFile(join(fakeBinDir, "tmux"), buildWorkerStopFakeTmux(tmuxLogPath));
|
|
4315
|
+
await chmod(join(fakeBinDir, "tmux"), 0o755);
|
|
4316
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
4317
|
+
const workerDir = join(stateDir, "team", "worker-stop-failed-task", "workers", "worker-1");
|
|
4318
|
+
await writeJson(join(stateDir, "team", "worker-stop-failed-task", "config.json"), {
|
|
4319
|
+
name: "worker-stop-failed-task",
|
|
4320
|
+
tmux_session: "omx-team-worker-stop",
|
|
4321
|
+
leader_pane_id: "%42",
|
|
4322
|
+
workers: [{ name: "worker-1", index: 1, pane_id: "%10" }],
|
|
4323
|
+
});
|
|
4324
|
+
await writeJson(join(workerDir, "identity.json"), {
|
|
4325
|
+
name: "worker-1",
|
|
4326
|
+
assigned_tasks: ["1"],
|
|
4327
|
+
team_state_root: stateDir,
|
|
4328
|
+
});
|
|
4329
|
+
await writeJson(join(workerDir, "status.json"), {
|
|
4330
|
+
state: "failed",
|
|
4331
|
+
current_task_id: "1",
|
|
4332
|
+
updated_at: new Date().toISOString(),
|
|
4333
|
+
});
|
|
4334
|
+
await writeJson(join(stateDir, "team", "worker-stop-failed-task", "tasks", "task-1.json"), {
|
|
4335
|
+
id: "1",
|
|
4336
|
+
status: "failed",
|
|
4337
|
+
owner: "worker-1",
|
|
4338
|
+
});
|
|
4339
|
+
process.env.OMX_TEAM_WORKER = "worker-stop-failed-task/worker-1";
|
|
4340
|
+
delete process.env.OMX_TEAM_INTERNAL_WORKER;
|
|
4341
|
+
process.env.OMX_TEAM_STATE_ROOT = stateDir;
|
|
4342
|
+
process.env.PATH = `${fakeBinDir}:${prevPath || ""}`;
|
|
4343
|
+
const result = await dispatchCodexNativeHook({
|
|
4344
|
+
hook_event_name: "Stop",
|
|
4345
|
+
cwd,
|
|
4346
|
+
session_id: "sess-stop-team-worker-failed",
|
|
4347
|
+
thread_id: "thread-stop-team-worker-failed",
|
|
4348
|
+
turn_id: "turn-stop-team-worker-failed",
|
|
4349
|
+
}, { cwd });
|
|
4350
|
+
assert.equal(result.outputJson?.decision, "block");
|
|
4351
|
+
assert.match(String(result.outputJson?.stopReason || ""), /non_completed_task_1_failed/);
|
|
4352
|
+
assert.match(JSON.stringify(result.outputJson), /team/i);
|
|
4353
|
+
assert.equal(existsSync(join(workerDir, "worker-stop-nudge.json")), false);
|
|
4354
|
+
const tmuxLog = existsSync(tmuxLogPath) ? await readFile(tmuxLogPath, "utf-8") : "";
|
|
4355
|
+
assert.doesNotMatch(tmuxLog, /native Stop allowed/);
|
|
4356
|
+
}
|
|
4357
|
+
finally {
|
|
4358
|
+
if (typeof prevTeamWorker === "string")
|
|
4359
|
+
process.env.OMX_TEAM_WORKER = prevTeamWorker;
|
|
4360
|
+
else
|
|
4361
|
+
delete process.env.OMX_TEAM_WORKER;
|
|
4362
|
+
if (typeof prevInternalTeamWorker === "string")
|
|
4363
|
+
process.env.OMX_TEAM_INTERNAL_WORKER = prevInternalTeamWorker;
|
|
4364
|
+
else
|
|
4365
|
+
delete process.env.OMX_TEAM_INTERNAL_WORKER;
|
|
4366
|
+
if (typeof prevTeamStateRoot === "string")
|
|
4367
|
+
process.env.OMX_TEAM_STATE_ROOT = prevTeamStateRoot;
|
|
4368
|
+
else
|
|
4369
|
+
delete process.env.OMX_TEAM_STATE_ROOT;
|
|
4370
|
+
if (typeof prevPath === "string")
|
|
4371
|
+
process.env.PATH = prevPath;
|
|
4372
|
+
else
|
|
4373
|
+
delete process.env.PATH;
|
|
4374
|
+
await rm(cwd, { recursive: true, force: true });
|
|
4375
|
+
}
|
|
4376
|
+
});
|
|
4377
|
+
it("blocks worker Stop on missing task assignment without relying on generic team state", async () => {
|
|
4378
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-worker-missing-assignment-"));
|
|
4379
|
+
const prevTeamWorker = process.env.OMX_TEAM_WORKER;
|
|
4380
|
+
const prevInternalTeamWorker = process.env.OMX_TEAM_INTERNAL_WORKER;
|
|
4381
|
+
const prevTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT;
|
|
4382
|
+
try {
|
|
4383
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
4384
|
+
const workerDir = join(stateDir, "team", "worker-missing-assignment", "workers", "worker-1");
|
|
4385
|
+
await mkdir(workerDir, { recursive: true });
|
|
4386
|
+
await writeJson(join(workerDir, "identity.json"), {
|
|
4387
|
+
name: "worker-1",
|
|
4388
|
+
assigned_tasks: [],
|
|
4389
|
+
team_state_root: stateDir,
|
|
4390
|
+
});
|
|
4391
|
+
await writeJson(join(workerDir, "status.json"), {
|
|
4392
|
+
state: "idle",
|
|
4393
|
+
updated_at: new Date().toISOString(),
|
|
4394
|
+
});
|
|
4395
|
+
process.env.OMX_TEAM_WORKER = "worker-missing-assignment/worker-1";
|
|
4396
|
+
delete process.env.OMX_TEAM_INTERNAL_WORKER;
|
|
4397
|
+
process.env.OMX_TEAM_STATE_ROOT = stateDir;
|
|
4398
|
+
const result = await dispatchCodexNativeHook({
|
|
4399
|
+
hook_event_name: "Stop",
|
|
4400
|
+
cwd,
|
|
4401
|
+
session_id: "sess-stop-team-worker-missing-assignment",
|
|
4402
|
+
thread_id: "thread-stop-team-worker-missing-assignment",
|
|
4403
|
+
turn_id: "turn-stop-team-worker-missing-assignment",
|
|
4404
|
+
}, { cwd });
|
|
4405
|
+
assert.equal(result.outputJson?.decision, "block");
|
|
4406
|
+
assert.equal(result.outputJson?.stopReason, "team_worker_worker-1_missing_task_assignment");
|
|
4407
|
+
assert.equal(existsSync(join(workerDir, "worker-stop-nudge.json")), false);
|
|
4408
|
+
}
|
|
4409
|
+
finally {
|
|
4410
|
+
if (typeof prevTeamWorker === "string")
|
|
4411
|
+
process.env.OMX_TEAM_WORKER = prevTeamWorker;
|
|
4412
|
+
else
|
|
4413
|
+
delete process.env.OMX_TEAM_WORKER;
|
|
4414
|
+
if (typeof prevInternalTeamWorker === "string")
|
|
4415
|
+
process.env.OMX_TEAM_INTERNAL_WORKER = prevInternalTeamWorker;
|
|
4416
|
+
else
|
|
4417
|
+
delete process.env.OMX_TEAM_INTERNAL_WORKER;
|
|
4418
|
+
if (typeof prevTeamStateRoot === "string")
|
|
4419
|
+
process.env.OMX_TEAM_STATE_ROOT = prevTeamStateRoot;
|
|
4420
|
+
else
|
|
4421
|
+
delete process.env.OMX_TEAM_STATE_ROOT;
|
|
4422
|
+
await rm(cwd, { recursive: true, force: true });
|
|
4423
|
+
}
|
|
4424
|
+
});
|
|
4425
|
+
it("blocks unresolved worker Stop before generic auto-nudge can bypass it", async () => {
|
|
4426
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-worker-missing-state-"));
|
|
4427
|
+
const prevTeamWorker = process.env.OMX_TEAM_WORKER;
|
|
4428
|
+
const prevInternalTeamWorker = process.env.OMX_TEAM_INTERNAL_WORKER;
|
|
4429
|
+
const prevTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT;
|
|
4430
|
+
const prevPath = process.env.PATH;
|
|
4431
|
+
try {
|
|
4432
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
4433
|
+
const fakeBinDir = join(cwd, "fake-bin");
|
|
4434
|
+
const tmuxLogPath = join(cwd, "tmux.log");
|
|
4435
|
+
await mkdir(fakeBinDir, { recursive: true });
|
|
4436
|
+
await writeFile(join(fakeBinDir, "tmux"), buildWorkerStopFakeTmux(tmuxLogPath));
|
|
4437
|
+
await chmod(join(fakeBinDir, "tmux"), 0o755);
|
|
4438
|
+
process.env.OMX_TEAM_WORKER = "worker-missing-state/worker-1";
|
|
4439
|
+
delete process.env.OMX_TEAM_INTERNAL_WORKER;
|
|
4440
|
+
process.env.OMX_TEAM_STATE_ROOT = stateDir;
|
|
4441
|
+
process.env.PATH = `${fakeBinDir}:${prevPath || ""}`;
|
|
4442
|
+
const result = await dispatchCodexNativeHook({
|
|
4443
|
+
hook_event_name: "Stop",
|
|
4444
|
+
cwd,
|
|
4445
|
+
session_id: "sess-stop-team-worker-missing-state",
|
|
4446
|
+
thread_id: "thread-stop-team-worker-missing-state",
|
|
4447
|
+
turn_id: "turn-stop-team-worker-missing-state",
|
|
4448
|
+
last_assistant_message: "Should I proceed?",
|
|
4449
|
+
}, { cwd });
|
|
4450
|
+
assert.equal(result.outputJson?.decision, "block");
|
|
4451
|
+
assert.equal(result.outputJson?.stopReason, "team_worker_worker-1_missing_worker_state");
|
|
4452
|
+
assert.doesNotMatch(JSON.stringify(result.outputJson), /auto_nudge/);
|
|
4453
|
+
const tmuxLog = existsSync(tmuxLogPath) ? await readFile(tmuxLogPath, "utf-8") : "";
|
|
4454
|
+
assert.doesNotMatch(tmuxLog, /native Stop allowed/);
|
|
4455
|
+
}
|
|
4456
|
+
finally {
|
|
4457
|
+
if (typeof prevTeamWorker === "string")
|
|
4458
|
+
process.env.OMX_TEAM_WORKER = prevTeamWorker;
|
|
4459
|
+
else
|
|
4460
|
+
delete process.env.OMX_TEAM_WORKER;
|
|
4461
|
+
if (typeof prevInternalTeamWorker === "string")
|
|
4462
|
+
process.env.OMX_TEAM_INTERNAL_WORKER = prevInternalTeamWorker;
|
|
4463
|
+
else
|
|
4464
|
+
delete process.env.OMX_TEAM_INTERNAL_WORKER;
|
|
4465
|
+
if (typeof prevTeamStateRoot === "string")
|
|
4466
|
+
process.env.OMX_TEAM_STATE_ROOT = prevTeamStateRoot;
|
|
4467
|
+
else
|
|
4468
|
+
delete process.env.OMX_TEAM_STATE_ROOT;
|
|
4469
|
+
if (typeof prevPath === "string")
|
|
4470
|
+
process.env.PATH = prevPath;
|
|
3320
4471
|
else
|
|
3321
|
-
delete process.env.
|
|
4472
|
+
delete process.env.PATH;
|
|
3322
4473
|
await rm(cwd, { recursive: true, force: true });
|
|
3323
4474
|
}
|
|
3324
4475
|
});
|
|
3325
|
-
it("
|
|
3326
|
-
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-worker-
|
|
4476
|
+
it("prefers canonical internal worker identity over public worker identity for Stop nudges", async () => {
|
|
4477
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-worker-internal-env-"));
|
|
3327
4478
|
const prevTeamWorker = process.env.OMX_TEAM_WORKER;
|
|
4479
|
+
const prevInternalTeamWorker = process.env.OMX_TEAM_INTERNAL_WORKER;
|
|
3328
4480
|
const prevTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT;
|
|
3329
|
-
const
|
|
4481
|
+
const prevPath = process.env.PATH;
|
|
3330
4482
|
try {
|
|
3331
|
-
await initTeamState("worker-stale-team", "worker stale stop fallback", "executor", 1, cwd, undefined, { ...process.env, OMX_SESSION_ID: "sess-stop-team-worker-stale" });
|
|
3332
4483
|
const stateDir = join(cwd, ".omx", "state");
|
|
3333
|
-
const
|
|
3334
|
-
const
|
|
3335
|
-
await mkdir(
|
|
4484
|
+
const fakeBinDir = join(cwd, "fake-bin");
|
|
4485
|
+
const tmuxLogPath = join(cwd, "tmux.log");
|
|
4486
|
+
await mkdir(fakeBinDir, { recursive: true });
|
|
4487
|
+
await writeFile(join(fakeBinDir, "tmux"), buildWorkerStopFakeTmux(tmuxLogPath));
|
|
4488
|
+
await chmod(join(fakeBinDir, "tmux"), 0o755);
|
|
4489
|
+
const workerDir = join(stateDir, "team", "internal-stop-team", "workers", "worker-1");
|
|
4490
|
+
await writeJson(join(stateDir, "team", "internal-stop-team", "config.json"), {
|
|
4491
|
+
name: "internal-stop-team",
|
|
4492
|
+
tmux_session: "omx-team-worker-stop",
|
|
4493
|
+
leader_pane_id: "%42",
|
|
4494
|
+
workers: [{ name: "worker-1", index: 1, pane_id: "%10" }],
|
|
4495
|
+
});
|
|
3336
4496
|
await writeJson(join(workerDir, "identity.json"), {
|
|
3337
4497
|
name: "worker-1",
|
|
3338
|
-
index: 1,
|
|
3339
|
-
role: "executor",
|
|
3340
4498
|
assigned_tasks: ["1"],
|
|
3341
|
-
worktree_path: workerCwd,
|
|
3342
4499
|
team_state_root: stateDir,
|
|
3343
4500
|
});
|
|
3344
4501
|
await writeJson(join(workerDir, "status.json"), {
|
|
@@ -3346,156 +4503,121 @@ esac
|
|
|
3346
4503
|
current_task_id: "1",
|
|
3347
4504
|
updated_at: new Date().toISOString(),
|
|
3348
4505
|
});
|
|
3349
|
-
await writeJson(join(stateDir, "team", "
|
|
4506
|
+
await writeJson(join(stateDir, "team", "internal-stop-team", "tasks", "task-1.json"), {
|
|
3350
4507
|
id: "1",
|
|
3351
|
-
|
|
3352
|
-
description: "stale task should not trap terminal worker Stop",
|
|
3353
|
-
status: "in_progress",
|
|
4508
|
+
status: "completed",
|
|
3354
4509
|
owner: "worker-1",
|
|
3355
|
-
created_at: new Date().toISOString(),
|
|
3356
4510
|
});
|
|
3357
|
-
process.env.OMX_TEAM_WORKER = "
|
|
4511
|
+
process.env.OMX_TEAM_WORKER = "public-stop-team/worker-1";
|
|
4512
|
+
process.env.OMX_TEAM_INTERNAL_WORKER = "internal-stop-team/worker-1";
|
|
3358
4513
|
process.env.OMX_TEAM_STATE_ROOT = stateDir;
|
|
3359
|
-
process.env.
|
|
4514
|
+
process.env.PATH = `${fakeBinDir}:${prevPath || ""}`;
|
|
3360
4515
|
const result = await dispatchCodexNativeHook({
|
|
3361
4516
|
hook_event_name: "Stop",
|
|
3362
|
-
cwd
|
|
3363
|
-
session_id: "sess-stop-team-worker-
|
|
3364
|
-
|
|
3365
|
-
|
|
4517
|
+
cwd,
|
|
4518
|
+
session_id: "sess-stop-team-worker-internal-env",
|
|
4519
|
+
thread_id: "thread-stop-team-worker-internal-env",
|
|
4520
|
+
turn_id: "turn-stop-team-worker-internal-env",
|
|
4521
|
+
}, { cwd });
|
|
4522
|
+
assert.equal(result.outputJson, null);
|
|
4523
|
+
const tmuxLog = await readFile(tmuxLogPath, "utf-8");
|
|
4524
|
+
assert.match(tmuxLog, /send-keys -t %42 -l \[OMX\] worker-1 native Stop allowed/);
|
|
4525
|
+
assert.equal(existsSync(join(workerDir, "worker-stop-nudge.json")), true);
|
|
3366
4526
|
}
|
|
3367
4527
|
finally {
|
|
3368
4528
|
if (typeof prevTeamWorker === "string")
|
|
3369
4529
|
process.env.OMX_TEAM_WORKER = prevTeamWorker;
|
|
3370
4530
|
else
|
|
3371
4531
|
delete process.env.OMX_TEAM_WORKER;
|
|
4532
|
+
if (typeof prevInternalTeamWorker === "string")
|
|
4533
|
+
process.env.OMX_TEAM_INTERNAL_WORKER = prevInternalTeamWorker;
|
|
4534
|
+
else
|
|
4535
|
+
delete process.env.OMX_TEAM_INTERNAL_WORKER;
|
|
3372
4536
|
if (typeof prevTeamStateRoot === "string")
|
|
3373
4537
|
process.env.OMX_TEAM_STATE_ROOT = prevTeamStateRoot;
|
|
3374
4538
|
else
|
|
3375
4539
|
delete process.env.OMX_TEAM_STATE_ROOT;
|
|
3376
|
-
if (typeof
|
|
3377
|
-
process.env.
|
|
4540
|
+
if (typeof prevPath === "string")
|
|
4541
|
+
process.env.PATH = prevPath;
|
|
3378
4542
|
else
|
|
3379
|
-
delete process.env.
|
|
4543
|
+
delete process.env.PATH;
|
|
3380
4544
|
await rm(cwd, { recursive: true, force: true });
|
|
3381
4545
|
}
|
|
3382
4546
|
});
|
|
3383
|
-
it("
|
|
3384
|
-
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-worker-
|
|
4547
|
+
it("blocks worker Stop when canonical task ownership has a newer non-terminal task", async () => {
|
|
4548
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-worker-owned-task-"));
|
|
4549
|
+
const prevTeamWorker = process.env.OMX_TEAM_WORKER;
|
|
4550
|
+
const prevInternalTeamWorker = process.env.OMX_TEAM_INTERNAL_WORKER;
|
|
4551
|
+
const prevTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT;
|
|
4552
|
+
const prevPath = process.env.PATH;
|
|
3385
4553
|
try {
|
|
3386
|
-
await initTeamState("worker-repeat-team", "worker stop repeat guard", "executor", 1, cwd, undefined, { ...process.env, OMX_SESSION_ID: "sess-stop-team-worker-repeat" });
|
|
3387
4554
|
const stateDir = join(cwd, ".omx", "state");
|
|
3388
|
-
const
|
|
3389
|
-
const
|
|
3390
|
-
|
|
3391
|
-
await
|
|
4555
|
+
const fakeBinDir = join(cwd, "fake-bin");
|
|
4556
|
+
const tmuxLogPath = join(cwd, "tmux.log");
|
|
4557
|
+
await mkdir(fakeBinDir, { recursive: true });
|
|
4558
|
+
await writeFile(join(fakeBinDir, "tmux"), buildWorkerStopFakeTmux(tmuxLogPath));
|
|
4559
|
+
await chmod(join(fakeBinDir, "tmux"), 0o755);
|
|
4560
|
+
const workerDir = join(stateDir, "team", "worker-owned-task", "workers", "worker-1");
|
|
4561
|
+
await writeJson(join(stateDir, "team", "worker-owned-task", "config.json"), {
|
|
4562
|
+
name: "worker-owned-task",
|
|
4563
|
+
tmux_session: "omx-team-worker-stop",
|
|
4564
|
+
leader_pane_id: "%42",
|
|
4565
|
+
workers: [{ name: "worker-1", index: 1, pane_id: "%10" }],
|
|
4566
|
+
});
|
|
3392
4567
|
await writeJson(join(workerDir, "identity.json"), {
|
|
3393
4568
|
name: "worker-1",
|
|
3394
|
-
index: 1,
|
|
3395
|
-
role: "executor",
|
|
3396
4569
|
assigned_tasks: ["1"],
|
|
3397
|
-
worktree_path: workerCwd,
|
|
3398
4570
|
team_state_root: stateDir,
|
|
3399
4571
|
});
|
|
3400
|
-
await writeJson(join(workerDir, "status.json"), {
|
|
3401
|
-
state: "working",
|
|
3402
|
-
current_task_id: "1",
|
|
3403
|
-
updated_at: new Date().toISOString(),
|
|
3404
|
-
});
|
|
3405
|
-
await writeJson(taskPath, {
|
|
3406
|
-
id: "1",
|
|
3407
|
-
subject: "hook task",
|
|
3408
|
-
description: "finish hook task",
|
|
3409
|
-
status: "in_progress",
|
|
3410
|
-
owner: "worker-1",
|
|
3411
|
-
created_at: new Date().toISOString(),
|
|
3412
|
-
});
|
|
3413
|
-
process.env.OMX_TEAM_WORKER = "worker-repeat-team/worker-1";
|
|
3414
|
-
process.env.OMX_TEAM_STATE_ROOT = stateDir;
|
|
3415
|
-
process.env.OMX_TEAM_LEADER_CWD = cwd;
|
|
3416
|
-
const basePayload = {
|
|
3417
|
-
hook_event_name: "Stop",
|
|
3418
|
-
cwd: workerCwd,
|
|
3419
|
-
session_id: "sess-stop-team-worker-repeat",
|
|
3420
|
-
thread_id: "thread-stop-team-worker-repeat",
|
|
3421
|
-
turn_id: "turn-stop-team-worker-repeat-1",
|
|
3422
|
-
last_assistant_message: "I need to stop before this task is done.",
|
|
3423
|
-
};
|
|
3424
|
-
const expectedInProgress = {
|
|
3425
|
-
decision: "block",
|
|
3426
|
-
reason: "OMX team worker worker-1 is still assigned non-terminal task 1 (in_progress); continue the current assigned task or report a concrete blocker before stopping.",
|
|
3427
|
-
stopReason: "team_worker_worker-1_1_in_progress",
|
|
3428
|
-
systemMessage: "OMX team worker worker-1 is still assigned task 1 (in_progress).",
|
|
3429
|
-
};
|
|
3430
|
-
const first = await dispatchCodexNativeHook(basePayload, { cwd: workerCwd });
|
|
3431
|
-
const replay = await dispatchCodexNativeHook({ ...basePayload, stop_hook_active: true }, { cwd: workerCwd });
|
|
3432
|
-
const freshTurn = await dispatchCodexNativeHook({ ...basePayload, turn_id: "turn-stop-team-worker-repeat-2", stop_hook_active: true }, { cwd: workerCwd });
|
|
3433
|
-
await writeJson(taskPath, {
|
|
3434
|
-
id: "1",
|
|
3435
|
-
subject: "hook task",
|
|
3436
|
-
description: "finish hook task",
|
|
3437
|
-
status: "blocked",
|
|
3438
|
-
owner: "worker-1",
|
|
3439
|
-
created_at: new Date().toISOString(),
|
|
3440
|
-
});
|
|
3441
|
-
const stateChanged = await dispatchCodexNativeHook({ ...basePayload, turn_id: "turn-stop-team-worker-repeat-2", stop_hook_active: true }, { cwd: workerCwd });
|
|
3442
|
-
assert.deepEqual(first.outputJson, expectedInProgress);
|
|
3443
|
-
assert.deepEqual(replay.outputJson, null);
|
|
3444
|
-
assert.deepEqual(freshTurn.outputJson, expectedInProgress);
|
|
3445
|
-
assert.deepEqual(stateChanged.outputJson, {
|
|
3446
|
-
decision: "block",
|
|
3447
|
-
reason: "OMX team worker worker-1 is still assigned non-terminal task 1 (blocked); continue the current assigned task or report a concrete blocker before stopping.",
|
|
3448
|
-
stopReason: "team_worker_worker-1_1_blocked",
|
|
3449
|
-
systemMessage: "OMX team worker worker-1 is still assigned task 1 (blocked).",
|
|
3450
|
-
});
|
|
3451
|
-
}
|
|
3452
|
-
finally {
|
|
3453
|
-
await rm(cwd, { recursive: true, force: true });
|
|
3454
|
-
}
|
|
3455
|
-
});
|
|
3456
|
-
it("does not block Stop for a team worker when assigned task is terminal", async () => {
|
|
3457
|
-
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-worker-terminal-"));
|
|
3458
|
-
const prevTeamWorker = process.env.OMX_TEAM_WORKER;
|
|
3459
|
-
const prevTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT;
|
|
3460
|
-
try {
|
|
3461
|
-
await initTeamState("worker-stop-team-terminal", "worker stop terminal fallback", "executor", 1, cwd, undefined, { ...process.env, OMX_SESSION_ID: "sess-stop-team-worker-terminal" });
|
|
3462
|
-
const workerDir = join(cwd, ".omx", "state", "team", "worker-stop-team-terminal", "workers", "worker-1");
|
|
3463
4572
|
await writeJson(join(workerDir, "status.json"), {
|
|
3464
4573
|
state: "done",
|
|
3465
4574
|
current_task_id: "1",
|
|
3466
4575
|
updated_at: new Date().toISOString(),
|
|
3467
4576
|
});
|
|
3468
|
-
await writeJson(join(
|
|
4577
|
+
await writeJson(join(stateDir, "team", "worker-owned-task", "tasks", "task-1.json"), {
|
|
3469
4578
|
id: "1",
|
|
3470
|
-
subject: "hook task",
|
|
3471
|
-
description: "finish hook task",
|
|
3472
4579
|
status: "completed",
|
|
3473
4580
|
owner: "worker-1",
|
|
3474
|
-
created_at: new Date().toISOString(),
|
|
3475
4581
|
});
|
|
3476
|
-
|
|
3477
|
-
|
|
4582
|
+
await writeJson(join(stateDir, "team", "worker-owned-task", "tasks", "task-2.json"), {
|
|
4583
|
+
id: "2",
|
|
4584
|
+
status: "in_progress",
|
|
4585
|
+
owner: "worker-1",
|
|
4586
|
+
});
|
|
4587
|
+
process.env.OMX_TEAM_WORKER = "worker-owned-task/worker-1";
|
|
4588
|
+
delete process.env.OMX_TEAM_INTERNAL_WORKER;
|
|
4589
|
+
process.env.OMX_TEAM_STATE_ROOT = stateDir;
|
|
4590
|
+
process.env.PATH = `${fakeBinDir}:${prevPath || ""}`;
|
|
3478
4591
|
const result = await dispatchCodexNativeHook({
|
|
3479
4592
|
hook_event_name: "Stop",
|
|
3480
4593
|
cwd,
|
|
3481
|
-
session_id: "sess-stop-team-worker-
|
|
4594
|
+
session_id: "sess-stop-team-worker-owned-task",
|
|
4595
|
+
thread_id: "thread-stop-team-worker-owned-task",
|
|
4596
|
+
turn_id: "turn-stop-team-worker-owned-task",
|
|
3482
4597
|
}, { cwd });
|
|
3483
|
-
assert.
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
});
|
|
4598
|
+
assert.equal(result.outputJson?.decision, "block");
|
|
4599
|
+
assert.equal(result.outputJson?.stopReason, "team_worker_worker-1_2_in_progress");
|
|
4600
|
+
assert.equal(existsSync(join(workerDir, "worker-stop-nudge.json")), false);
|
|
4601
|
+
const tmuxLog = existsSync(tmuxLogPath) ? await readFile(tmuxLogPath, "utf-8") : "";
|
|
4602
|
+
assert.doesNotMatch(tmuxLog, /native Stop allowed/);
|
|
3489
4603
|
}
|
|
3490
4604
|
finally {
|
|
3491
4605
|
if (typeof prevTeamWorker === "string")
|
|
3492
4606
|
process.env.OMX_TEAM_WORKER = prevTeamWorker;
|
|
3493
4607
|
else
|
|
3494
4608
|
delete process.env.OMX_TEAM_WORKER;
|
|
4609
|
+
if (typeof prevInternalTeamWorker === "string")
|
|
4610
|
+
process.env.OMX_TEAM_INTERNAL_WORKER = prevInternalTeamWorker;
|
|
4611
|
+
else
|
|
4612
|
+
delete process.env.OMX_TEAM_INTERNAL_WORKER;
|
|
3495
4613
|
if (typeof prevTeamStateRoot === "string")
|
|
3496
4614
|
process.env.OMX_TEAM_STATE_ROOT = prevTeamStateRoot;
|
|
3497
4615
|
else
|
|
3498
4616
|
delete process.env.OMX_TEAM_STATE_ROOT;
|
|
4617
|
+
if (typeof prevPath === "string")
|
|
4618
|
+
process.env.PATH = prevPath;
|
|
4619
|
+
else
|
|
4620
|
+
delete process.env.PATH;
|
|
3499
4621
|
await rm(cwd, { recursive: true, force: true });
|
|
3500
4622
|
}
|
|
3501
4623
|
});
|
|
@@ -3805,12 +4927,12 @@ esac
|
|
|
3805
4927
|
session_id: "sess-stop-skill",
|
|
3806
4928
|
}, { cwd });
|
|
3807
4929
|
assert.equal(result.omxEventName, "stop");
|
|
3808
|
-
assert.
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
4930
|
+
assert.equal(result.outputJson?.decision, "block");
|
|
4931
|
+
assert.match(String(result.outputJson?.reason ?? ""), /Status: continue_from_artifact/);
|
|
4932
|
+
assert.match(String(result.outputJson?.reason ?? ""), /ralplan is still active \(phase: planning\)/);
|
|
4933
|
+
assert.match(String(result.outputJson?.reason ?? ""), /continue from the current ralplan artifact/i);
|
|
4934
|
+
assert.equal(result.outputJson?.stopReason, "skill_ralplan_planning_continue_artifact");
|
|
4935
|
+
assert.match(String(result.outputJson?.systemMessage ?? ""), /complete, paused for review, waiting for input, or still continuing/);
|
|
3814
4936
|
}
|
|
3815
4937
|
finally {
|
|
3816
4938
|
await rm(cwd, { recursive: true, force: true });
|
|
@@ -3930,7 +5052,7 @@ esac
|
|
|
3930
5052
|
await rm(cwd, { recursive: true, force: true });
|
|
3931
5053
|
}
|
|
3932
5054
|
});
|
|
3933
|
-
it("
|
|
5055
|
+
it("returns an explicit ralplan waiting status while subagents are still active", async () => {
|
|
3934
5056
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-skill-subagent-"));
|
|
3935
5057
|
try {
|
|
3936
5058
|
const stateDir = join(cwd, ".omx", "state");
|
|
@@ -3977,7 +5099,11 @@ esac
|
|
|
3977
5099
|
session_id: "sess-stop-skill-subagent",
|
|
3978
5100
|
}, { cwd });
|
|
3979
5101
|
assert.equal(result.omxEventName, "stop");
|
|
3980
|
-
assert.equal(result.outputJson,
|
|
5102
|
+
assert.equal(result.outputJson?.decision, "block");
|
|
5103
|
+
assert.match(String(result.outputJson?.reason ?? ""), /Status: waiting/);
|
|
5104
|
+
assert.match(String(result.outputJson?.reason ?? ""), /waiting for 1 active native subagent thread/);
|
|
5105
|
+
assert.match(String(result.outputJson?.reason ?? ""), /then continue from the current ralplan artifact/i);
|
|
5106
|
+
assert.equal(result.outputJson?.stopReason, "skill_ralplan_planning_waiting_subagent");
|
|
3981
5107
|
}
|
|
3982
5108
|
finally {
|
|
3983
5109
|
await rm(cwd, { recursive: true, force: true });
|
|
@@ -4096,6 +5222,31 @@ esac
|
|
|
4096
5222
|
await rm(cwd, { recursive: true, force: true });
|
|
4097
5223
|
}
|
|
4098
5224
|
});
|
|
5225
|
+
it("does not block Stop from stale root autoresearch state when the explicit session directory is missing", async () => {
|
|
5226
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-missing-session-autoresearch-"));
|
|
5227
|
+
try {
|
|
5228
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
5229
|
+
await mkdir(stateDir, { recursive: true });
|
|
5230
|
+
await writeJson(join(stateDir, "autoresearch-state.json"), {
|
|
5231
|
+
active: true,
|
|
5232
|
+
mode: "autoresearch",
|
|
5233
|
+
current_phase: "executing",
|
|
5234
|
+
validation_mode: "mission-validator-script",
|
|
5235
|
+
mission_validator_command: "node scripts/validate.js",
|
|
5236
|
+
completion_artifact_path: ".omx/specs/autoresearch-demo/completion.json",
|
|
5237
|
+
});
|
|
5238
|
+
const result = await dispatchCodexNativeHook({
|
|
5239
|
+
hook_event_name: "Stop",
|
|
5240
|
+
cwd,
|
|
5241
|
+
session_id: "missing-session",
|
|
5242
|
+
}, { cwd });
|
|
5243
|
+
assert.equal(result.omxEventName, "stop");
|
|
5244
|
+
assert.equal(result.outputJson, null);
|
|
5245
|
+
}
|
|
5246
|
+
finally {
|
|
5247
|
+
await rm(cwd, { recursive: true, force: true });
|
|
5248
|
+
}
|
|
5249
|
+
});
|
|
4099
5250
|
it("does not block Stop solely because deep-interview is active", async () => {
|
|
4100
5251
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-deep-interview-"));
|
|
4101
5252
|
try {
|
|
@@ -4831,6 +5982,46 @@ esac
|
|
|
4831
5982
|
await rm(cwd, { recursive: true, force: true });
|
|
4832
5983
|
}
|
|
4833
5984
|
});
|
|
5985
|
+
it("does not block Stop when Ralph skill-active initialization points at another session", async () => {
|
|
5986
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-ralph-stale-skill-active-"));
|
|
5987
|
+
try {
|
|
5988
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
5989
|
+
const currentSessionId = "sess-current-ralph";
|
|
5990
|
+
await mkdir(join(stateDir, "sessions", currentSessionId), { recursive: true });
|
|
5991
|
+
await writeJson(join(stateDir, "session.json"), {
|
|
5992
|
+
session_id: currentSessionId,
|
|
5993
|
+
native_session_id: currentSessionId,
|
|
5994
|
+
cwd,
|
|
5995
|
+
});
|
|
5996
|
+
await writeJson(join(stateDir, "sessions", currentSessionId, "ralph-state.json"), {
|
|
5997
|
+
active: true,
|
|
5998
|
+
mode: "ralph",
|
|
5999
|
+
current_phase: "verifying",
|
|
6000
|
+
session_id: currentSessionId,
|
|
6001
|
+
owner_omx_session_id: currentSessionId,
|
|
6002
|
+
task_slug: "stale-rebound-task",
|
|
6003
|
+
});
|
|
6004
|
+
await writeJson(join(stateDir, "sessions", currentSessionId, "skill-active-state.json"), {
|
|
6005
|
+
active: true,
|
|
6006
|
+
skill: "ralph",
|
|
6007
|
+
phase: "verifying",
|
|
6008
|
+
session_id: currentSessionId,
|
|
6009
|
+
initialized_mode: "ralph",
|
|
6010
|
+
initialized_state_path: ".omx/state/sessions/sess-old-ralph/ralph-state.json",
|
|
6011
|
+
active_skills: [{ skill: "ralph", phase: "verifying", active: true, session_id: currentSessionId }],
|
|
6012
|
+
});
|
|
6013
|
+
const result = await dispatchCodexNativeHook({
|
|
6014
|
+
hook_event_name: "Stop",
|
|
6015
|
+
cwd,
|
|
6016
|
+
session_id: currentSessionId,
|
|
6017
|
+
}, { cwd });
|
|
6018
|
+
assert.equal(result.omxEventName, "stop");
|
|
6019
|
+
assert.equal(result.outputJson, null);
|
|
6020
|
+
}
|
|
6021
|
+
finally {
|
|
6022
|
+
await rm(cwd, { recursive: true, force: true });
|
|
6023
|
+
}
|
|
6024
|
+
});
|
|
4834
6025
|
it("blocks same-session Ralph Stop continuation when ownership identifiers match", async () => {
|
|
4835
6026
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-ralph-owned-session-"));
|
|
4836
6027
|
const previousTmuxPane = process.env.TMUX_PANE;
|
|
@@ -4877,6 +6068,78 @@ esac
|
|
|
4877
6068
|
await rm(cwd, { recursive: true, force: true });
|
|
4878
6069
|
}
|
|
4879
6070
|
});
|
|
6071
|
+
it("allows native verifier subagent Stop to complete while leader Ralph remains active", async () => {
|
|
6072
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-ralph-subagent-verdict-"));
|
|
6073
|
+
try {
|
|
6074
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
6075
|
+
const omxSessionId = "sess-ralph-leader-verifier";
|
|
6076
|
+
const leaderNativeSessionId = "codex-ralph-leader-verifier";
|
|
6077
|
+
const childNativeSessionId = "codex-verifier-child";
|
|
6078
|
+
await mkdir(join(stateDir, "sessions", omxSessionId), { recursive: true });
|
|
6079
|
+
await writeSessionStart(cwd, omxSessionId, {
|
|
6080
|
+
nativeSessionId: leaderNativeSessionId,
|
|
6081
|
+
});
|
|
6082
|
+
await writeJson(join(stateDir, "sessions", omxSessionId, "ralph-state.json"), {
|
|
6083
|
+
active: true,
|
|
6084
|
+
mode: "ralph",
|
|
6085
|
+
current_phase: "verifying",
|
|
6086
|
+
session_id: omxSessionId,
|
|
6087
|
+
owner_omx_session_id: omxSessionId,
|
|
6088
|
+
owner_codex_session_id: leaderNativeSessionId,
|
|
6089
|
+
});
|
|
6090
|
+
const transcriptPath = join(cwd, "verifier-subagent-rollout.jsonl");
|
|
6091
|
+
await writeFile(transcriptPath, `${JSON.stringify({
|
|
6092
|
+
type: "session_meta",
|
|
6093
|
+
payload: {
|
|
6094
|
+
id: childNativeSessionId,
|
|
6095
|
+
source: {
|
|
6096
|
+
subagent: {
|
|
6097
|
+
thread_spawn: {
|
|
6098
|
+
parent_thread_id: leaderNativeSessionId,
|
|
6099
|
+
depth: 1,
|
|
6100
|
+
agent_nickname: "Verifier",
|
|
6101
|
+
agent_role: "verifier",
|
|
6102
|
+
},
|
|
6103
|
+
},
|
|
6104
|
+
},
|
|
6105
|
+
agent_nickname: "Verifier",
|
|
6106
|
+
agent_role: "verifier",
|
|
6107
|
+
},
|
|
6108
|
+
})}\n`);
|
|
6109
|
+
await dispatchCodexNativeHook({
|
|
6110
|
+
hook_event_name: "SessionStart",
|
|
6111
|
+
cwd,
|
|
6112
|
+
session_id: childNativeSessionId,
|
|
6113
|
+
transcript_path: transcriptPath,
|
|
6114
|
+
}, { cwd, sessionOwnerPid: process.pid });
|
|
6115
|
+
const childStop = await dispatchCodexNativeHook({
|
|
6116
|
+
hook_event_name: "Stop",
|
|
6117
|
+
cwd,
|
|
6118
|
+
session_id: childNativeSessionId,
|
|
6119
|
+
thread_id: childNativeSessionId,
|
|
6120
|
+
last_assistant_message: "Verdict: APPROVED. Evidence is sufficient.",
|
|
6121
|
+
}, { cwd });
|
|
6122
|
+
assert.equal(childStop.omxEventName, "stop");
|
|
6123
|
+
assert.equal(childStop.outputJson, null);
|
|
6124
|
+
const leaderStop = await dispatchCodexNativeHook({
|
|
6125
|
+
hook_event_name: "Stop",
|
|
6126
|
+
cwd,
|
|
6127
|
+
session_id: leaderNativeSessionId,
|
|
6128
|
+
thread_id: leaderNativeSessionId,
|
|
6129
|
+
last_assistant_message: "Waiting on verification integration.",
|
|
6130
|
+
}, { cwd });
|
|
6131
|
+
assert.equal(leaderStop.omxEventName, "stop");
|
|
6132
|
+
assert.deepEqual(leaderStop.outputJson, {
|
|
6133
|
+
decision: "block",
|
|
6134
|
+
reason: "OMX Ralph is still active (phase: verifying; state: .omx/state/sessions/sess-ralph-leader-verifier/ralph-state.json); continue the task and gather fresh verification evidence before stopping.",
|
|
6135
|
+
stopReason: "ralph_verifying",
|
|
6136
|
+
systemMessage: "OMX Ralph is still active (phase: verifying; state: .omx/state/sessions/sess-ralph-leader-verifier/ralph-state.json); continue the task and gather fresh verification evidence before stopping.",
|
|
6137
|
+
});
|
|
6138
|
+
}
|
|
6139
|
+
finally {
|
|
6140
|
+
await rm(cwd, { recursive: true, force: true });
|
|
6141
|
+
}
|
|
6142
|
+
});
|
|
4880
6143
|
it("prefers canonical run-state terminal lifecycle before stale session Ralph state during Stop", async () => {
|
|
4881
6144
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-canonical-run-state-ralph-"));
|
|
4882
6145
|
try {
|
|
@@ -5412,12 +6675,11 @@ esac
|
|
|
5412
6675
|
await rm(cwd, { recursive: true, force: true });
|
|
5413
6676
|
}
|
|
5414
6677
|
});
|
|
5415
|
-
it("suppresses native auto-nudge when root deep-interview mode state is active
|
|
6678
|
+
it("suppresses native auto-nudge when root deep-interview mode state is active and no session is known", async () => {
|
|
5416
6679
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-auto-nudge-deep-interview-mode-"));
|
|
5417
6680
|
try {
|
|
5418
6681
|
const stateDir = join(cwd, ".omx", "state");
|
|
5419
6682
|
await mkdir(stateDir, { recursive: true });
|
|
5420
|
-
process.env.OMX_SESSION_ID = "sess-stop-auto-mode";
|
|
5421
6683
|
await writeJson(join(stateDir, "deep-interview-state.json"), {
|
|
5422
6684
|
active: true,
|
|
5423
6685
|
mode: "deep-interview",
|
|
@@ -5436,6 +6698,57 @@ esac
|
|
|
5436
6698
|
await rm(cwd, { recursive: true, force: true });
|
|
5437
6699
|
}
|
|
5438
6700
|
});
|
|
6701
|
+
it("treats inherited OMX_SESSION_ID as session-aware for native auto-nudge Stop checks", async () => {
|
|
6702
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-auto-nudge-env-session-"));
|
|
6703
|
+
try {
|
|
6704
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
6705
|
+
await mkdir(stateDir, { recursive: true });
|
|
6706
|
+
process.env.OMX_SESSION_ID = "sess-stop-auto-mode";
|
|
6707
|
+
const result = await dispatchCodexNativeHook({
|
|
6708
|
+
hook_event_name: "Stop",
|
|
6709
|
+
cwd,
|
|
6710
|
+
thread_id: "thread-stop-auto-env-session",
|
|
6711
|
+
turn_id: "turn-stop-auto-env-session-1",
|
|
6712
|
+
last_assistant_message: "Keep going and finish the cleanup.",
|
|
6713
|
+
}, { cwd });
|
|
6714
|
+
assert.equal(result.omxEventName, "stop");
|
|
6715
|
+
assert.deepEqual(result.outputJson, {
|
|
6716
|
+
decision: "block",
|
|
6717
|
+
reason: DEFAULT_AUTO_NUDGE_RESPONSE,
|
|
6718
|
+
stopReason: "auto_nudge",
|
|
6719
|
+
systemMessage: "OMX native Stop detected a stall/permission-style handoff and continued the turn automatically.",
|
|
6720
|
+
});
|
|
6721
|
+
const stopState = JSON.parse(await readFile(join(stateDir, "native-stop-state.json"), "utf-8"));
|
|
6722
|
+
assert.ok(stopState.sessions["sess-stop-auto-mode"]);
|
|
6723
|
+
}
|
|
6724
|
+
finally {
|
|
6725
|
+
await rm(cwd, { recursive: true, force: true });
|
|
6726
|
+
}
|
|
6727
|
+
});
|
|
6728
|
+
it("ignores generic SESSION_ID for native auto-nudge Stop session scoping", async () => {
|
|
6729
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-auto-nudge-generic-session-"));
|
|
6730
|
+
try {
|
|
6731
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
6732
|
+
await mkdir(stateDir, { recursive: true });
|
|
6733
|
+
process.env.SESSION_ID = "generic-shell-session";
|
|
6734
|
+
const result = await dispatchCodexNativeHook({
|
|
6735
|
+
hook_event_name: "Stop",
|
|
6736
|
+
cwd,
|
|
6737
|
+
thread_id: "thread-stop-auto-generic-session",
|
|
6738
|
+
turn_id: "turn-stop-auto-generic-session-1",
|
|
6739
|
+
last_assistant_message: "Keep going and finish the cleanup.",
|
|
6740
|
+
}, { cwd });
|
|
6741
|
+
assert.equal(result.omxEventName, "stop");
|
|
6742
|
+
assert.equal(result.outputJson?.decision, "block");
|
|
6743
|
+
const stopState = JSON.parse(await readFile(join(stateDir, "native-stop-state.json"), "utf-8"));
|
|
6744
|
+
const sessions = stopState.sessions;
|
|
6745
|
+
assert.equal(sessions["generic-shell-session"], undefined);
|
|
6746
|
+
assert.ok(sessions["thread-stop-auto-generic-session"]);
|
|
6747
|
+
}
|
|
6748
|
+
finally {
|
|
6749
|
+
await rm(cwd, { recursive: true, force: true });
|
|
6750
|
+
}
|
|
6751
|
+
});
|
|
5439
6752
|
it("does not suppress native auto-nudge from stale root deep-interview mode state when the explicit session-scoped mode state is absent", async () => {
|
|
5440
6753
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-auto-nudge-stale-root-mode-"));
|
|
5441
6754
|
try {
|
|
@@ -5718,8 +7031,8 @@ esac
|
|
|
5718
7031
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-ultrawork-repeat-"));
|
|
5719
7032
|
try {
|
|
5720
7033
|
const stateDir = join(cwd, ".omx", "state");
|
|
5721
|
-
await mkdir(stateDir, { recursive: true });
|
|
5722
|
-
await writeJson(join(stateDir, "ultrawork-state.json"), {
|
|
7034
|
+
await mkdir(join(stateDir, "sessions", "sess-stop-ultrawork-repeat"), { recursive: true });
|
|
7035
|
+
await writeJson(join(stateDir, "sessions", "sess-stop-ultrawork-repeat", "ultrawork-state.json"), {
|
|
5723
7036
|
active: true,
|
|
5724
7037
|
current_phase: "executing",
|
|
5725
7038
|
});
|
|
@@ -5791,12 +7104,10 @@ esac
|
|
|
5791
7104
|
stop_hook_active: true,
|
|
5792
7105
|
}, { cwd });
|
|
5793
7106
|
assert.equal(repeated.omxEventName, "stop");
|
|
5794
|
-
assert.
|
|
5795
|
-
|
|
5796
|
-
|
|
5797
|
-
|
|
5798
|
-
systemMessage: "OMX skill ralplan is still active (phase: planning).",
|
|
5799
|
-
});
|
|
7107
|
+
assert.equal(repeated.outputJson?.decision, "block");
|
|
7108
|
+
assert.match(String(repeated.outputJson?.reason ?? ""), /Status: continue_from_artifact/);
|
|
7109
|
+
assert.match(String(repeated.outputJson?.reason ?? ""), /continue from the current ralplan artifact/i);
|
|
7110
|
+
assert.equal(repeated.outputJson?.stopReason, "skill_ralplan_planning_continue_artifact");
|
|
5800
7111
|
}
|
|
5801
7112
|
finally {
|
|
5802
7113
|
await rm(cwd, { recursive: true, force: true });
|