oh-my-codex 0.18.6 → 0.18.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.lock +6 -6
- package/Cargo.toml +1 -1
- package/README.md +59 -10
- package/crates/omx-sparkshell/tests/execution.rs +1 -1
- package/dist/agents/__tests__/definitions.test.js +11 -0
- package/dist/agents/__tests__/definitions.test.js.map +1 -1
- package/dist/agents/__tests__/native-config.test.js +56 -6
- package/dist/agents/__tests__/native-config.test.js.map +1 -1
- package/dist/agents/definitions.d.ts +10 -0
- package/dist/agents/definitions.d.ts.map +1 -1
- package/dist/agents/definitions.js +5 -1
- package/dist/agents/definitions.js.map +1 -1
- package/dist/agents/native-config.d.ts +5 -1
- package/dist/agents/native-config.d.ts.map +1 -1
- package/dist/agents/native-config.js +19 -4
- package/dist/agents/native-config.js.map +1 -1
- package/dist/autopilot/__tests__/fsm.test.d.ts +2 -0
- package/dist/autopilot/__tests__/fsm.test.d.ts.map +1 -0
- package/dist/autopilot/__tests__/fsm.test.js +75 -0
- package/dist/autopilot/__tests__/fsm.test.js.map +1 -0
- package/dist/autopilot/__tests__/ralplan-gate.test.d.ts +2 -0
- package/dist/autopilot/__tests__/ralplan-gate.test.d.ts.map +1 -0
- package/dist/autopilot/__tests__/ralplan-gate.test.js +79 -0
- package/dist/autopilot/__tests__/ralplan-gate.test.js.map +1 -0
- package/dist/autopilot/deep-interview-gate.d.ts +18 -0
- package/dist/autopilot/deep-interview-gate.d.ts.map +1 -0
- package/dist/autopilot/deep-interview-gate.js +256 -0
- package/dist/autopilot/deep-interview-gate.js.map +1 -0
- package/dist/autopilot/fsm.d.ts +13 -0
- package/dist/autopilot/fsm.d.ts.map +1 -0
- package/dist/autopilot/fsm.js +70 -0
- package/dist/autopilot/fsm.js.map +1 -0
- package/dist/autopilot/ralplan-gate.d.ts +17 -0
- package/dist/autopilot/ralplan-gate.d.ts.map +1 -0
- package/dist/autopilot/ralplan-gate.js +61 -0
- package/dist/autopilot/ralplan-gate.js.map +1 -0
- package/dist/cli/__tests__/codex-plugin-layout.test.js +512 -1
- package/dist/cli/__tests__/codex-plugin-layout.test.js.map +1 -1
- package/dist/cli/__tests__/doctor-warning-copy.test.js +39 -0
- package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
- package/dist/cli/__tests__/index.test.js +83 -7
- package/dist/cli/__tests__/index.test.js.map +1 -1
- package/dist/cli/__tests__/launch-fallback.test.js +175 -6
- package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
- package/dist/cli/__tests__/package-bin-contract.test.js +8 -4
- package/dist/cli/__tests__/package-bin-contract.test.js.map +1 -1
- package/dist/cli/__tests__/question.test.js +100 -0
- package/dist/cli/__tests__/question.test.js.map +1 -1
- package/dist/cli/__tests__/ralph-goal-mode-contract.test.js +13 -0
- package/dist/cli/__tests__/ralph-goal-mode-contract.test.js.map +1 -1
- package/dist/cli/__tests__/ralph.test.js +14 -0
- package/dist/cli/__tests__/ralph.test.js.map +1 -1
- package/dist/cli/__tests__/setup-install-mode.test.js +89 -0
- package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
- package/dist/cli/__tests__/setup-refresh.test.js +83 -0
- package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
- package/dist/cli/__tests__/state.test.js +21 -0
- package/dist/cli/__tests__/state.test.js.map +1 -1
- package/dist/cli/__tests__/team.test.js +2 -2
- package/dist/cli/__tests__/team.test.js.map +1 -1
- package/dist/cli/__tests__/update.test.js +110 -2
- package/dist/cli/__tests__/update.test.js.map +1 -1
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +8 -1
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/index.d.ts +14 -3
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +298 -50
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/plugin-marketplace.d.ts +14 -2
- package/dist/cli/plugin-marketplace.d.ts.map +1 -1
- package/dist/cli/plugin-marketplace.js +62 -15
- package/dist/cli/plugin-marketplace.js.map +1 -1
- package/dist/cli/question.d.ts.map +1 -1
- package/dist/cli/question.js +36 -5
- package/dist/cli/question.js.map +1 -1
- package/dist/cli/ralph.d.ts.map +1 -1
- package/dist/cli/ralph.js +3 -1
- package/dist/cli/ralph.js.map +1 -1
- package/dist/cli/setup-preferences.d.ts +2 -0
- package/dist/cli/setup-preferences.d.ts.map +1 -1
- package/dist/cli/setup-preferences.js +4 -0
- package/dist/cli/setup-preferences.js.map +1 -1
- package/dist/cli/setup.d.ts +3 -0
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +166 -27
- package/dist/cli/setup.js.map +1 -1
- package/dist/cli/state.d.ts.map +1 -1
- package/dist/cli/state.js +8 -1
- package/dist/cli/state.js.map +1 -1
- package/dist/cli/tmux-hook.d.ts.map +1 -1
- package/dist/cli/tmux-hook.js +16 -0
- package/dist/cli/tmux-hook.js.map +1 -1
- package/dist/cli/update.d.ts +2 -0
- package/dist/cli/update.d.ts.map +1 -1
- package/dist/cli/update.js +47 -3
- package/dist/cli/update.js.map +1 -1
- package/dist/config/__tests__/deep-interview.test.js +7 -6
- package/dist/config/__tests__/deep-interview.test.js.map +1 -1
- package/dist/config/__tests__/generator-notify.test.js +1 -0
- package/dist/config/__tests__/generator-notify.test.js.map +1 -1
- package/dist/config/deep-interview.d.ts.map +1 -1
- package/dist/config/deep-interview.js +14 -4
- package/dist/config/deep-interview.js.map +1 -1
- package/dist/config/generator.d.ts +2 -2
- package/dist/config/generator.d.ts.map +1 -1
- package/dist/config/generator.js +2 -2
- package/dist/config/generator.js.map +1 -1
- package/dist/config/team-mode.d.ts +12 -0
- package/dist/config/team-mode.d.ts.map +1 -0
- package/dist/config/team-mode.js +91 -0
- package/dist/config/team-mode.js.map +1 -0
- package/dist/hooks/__tests__/agents-overlay.test.js +88 -0
- package/dist/hooks/__tests__/agents-overlay.test.js.map +1 -1
- package/dist/hooks/__tests__/autopilot-skill-contract.test.js +8 -0
- package/dist/hooks/__tests__/autopilot-skill-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/code-review-skill-contract.test.js +8 -0
- package/dist/hooks/__tests__/code-review-skill-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/deep-interview-contract.test.js +10 -0
- package/dist/hooks/__tests__/deep-interview-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/keyword-detector.test.js +1072 -14
- package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-fallback-watcher.test.js +64 -1
- package/dist/hooks/__tests__/notify-fallback-watcher.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js +189 -0
- package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js +35 -2
- package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js +3 -3
- package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js.map +1 -1
- package/dist/hooks/__tests__/session.test.js +25 -0
- package/dist/hooks/__tests__/session.test.js.map +1 -1
- package/dist/hooks/__tests__/skill-guidance-contract.test.js +21 -0
- package/dist/hooks/__tests__/skill-guidance-contract.test.js.map +1 -1
- package/dist/hooks/agents-overlay.d.ts.map +1 -1
- package/dist/hooks/agents-overlay.js +36 -50
- package/dist/hooks/agents-overlay.js.map +1 -1
- package/dist/hooks/deep-interview-config-instruction.js +1 -1
- package/dist/hooks/deep-interview-config-instruction.js.map +1 -1
- package/dist/hooks/extensibility/__tests__/plugin-runner.test.js +31 -0
- package/dist/hooks/extensibility/__tests__/plugin-runner.test.js.map +1 -1
- package/dist/hooks/extensibility/plugin-runner.js +17 -21
- package/dist/hooks/extensibility/plugin-runner.js.map +1 -1
- package/dist/hooks/keyword-detector.d.ts +1 -0
- package/dist/hooks/keyword-detector.d.ts.map +1 -1
- package/dist/hooks/keyword-detector.js +428 -32
- package/dist/hooks/keyword-detector.js.map +1 -1
- package/dist/hooks/keyword-registry.d.ts.map +1 -1
- package/dist/hooks/keyword-registry.js +1 -0
- package/dist/hooks/keyword-registry.js.map +1 -1
- package/dist/hooks/prompt-guidance-contract.d.ts.map +1 -1
- package/dist/hooks/prompt-guidance-contract.js +6 -0
- package/dist/hooks/prompt-guidance-contract.js.map +1 -1
- package/dist/hooks/session.d.ts +3 -0
- package/dist/hooks/session.d.ts.map +1 -1
- package/dist/hooks/session.js +13 -5
- package/dist/hooks/session.js.map +1 -1
- package/dist/hud/__tests__/authority.test.js +469 -31
- package/dist/hud/__tests__/authority.test.js.map +1 -1
- package/dist/hud/__tests__/hud-tmux-injection.test.js +2 -1
- package/dist/hud/__tests__/hud-tmux-injection.test.js.map +1 -1
- package/dist/hud/__tests__/index.test.js +210 -2
- package/dist/hud/__tests__/index.test.js.map +1 -1
- package/dist/hud/__tests__/reconcile.test.js +588 -28
- package/dist/hud/__tests__/reconcile.test.js.map +1 -1
- package/dist/hud/__tests__/render.test.js +61 -0
- package/dist/hud/__tests__/render.test.js.map +1 -1
- package/dist/hud/__tests__/state.test.js +208 -0
- package/dist/hud/__tests__/state.test.js.map +1 -1
- package/dist/hud/__tests__/tmux.test.js +314 -22
- package/dist/hud/__tests__/tmux.test.js.map +1 -1
- package/dist/hud/authority.d.ts +5 -0
- package/dist/hud/authority.d.ts.map +1 -1
- package/dist/hud/authority.js +337 -30
- package/dist/hud/authority.js.map +1 -1
- package/dist/hud/index.d.ts +20 -2
- package/dist/hud/index.d.ts.map +1 -1
- package/dist/hud/index.js +103 -26
- package/dist/hud/index.js.map +1 -1
- package/dist/hud/reconcile.d.ts +3 -3
- package/dist/hud/reconcile.d.ts.map +1 -1
- package/dist/hud/reconcile.js +129 -20
- package/dist/hud/reconcile.js.map +1 -1
- package/dist/hud/render.d.ts.map +1 -1
- package/dist/hud/render.js +35 -0
- package/dist/hud/render.js.map +1 -1
- package/dist/hud/state.d.ts.map +1 -1
- package/dist/hud/state.js +64 -50
- package/dist/hud/state.js.map +1 -1
- package/dist/hud/tmux.d.ts +26 -6
- package/dist/hud/tmux.d.ts.map +1 -1
- package/dist/hud/tmux.js +173 -38
- package/dist/hud/tmux.js.map +1 -1
- package/dist/hud/types.d.ts +11 -0
- package/dist/hud/types.d.ts.map +1 -1
- package/dist/hud/types.js.map +1 -1
- package/dist/mcp/__tests__/hermes-bridge.test.js +203 -7
- package/dist/mcp/__tests__/hermes-bridge.test.js.map +1 -1
- package/dist/mcp/__tests__/state-paths.test.js +71 -1
- package/dist/mcp/__tests__/state-paths.test.js.map +1 -1
- package/dist/mcp/__tests__/state-server.test.js +13 -1
- package/dist/mcp/__tests__/state-server.test.js.map +1 -1
- package/dist/mcp/hermes-bridge.d.ts +12 -2
- package/dist/mcp/hermes-bridge.d.ts.map +1 -1
- package/dist/mcp/hermes-bridge.js +83 -9
- package/dist/mcp/hermes-bridge.js.map +1 -1
- package/dist/mcp/state-paths.d.ts +32 -0
- package/dist/mcp/state-paths.d.ts.map +1 -1
- package/dist/mcp/state-paths.js +113 -17
- package/dist/mcp/state-paths.js.map +1 -1
- package/dist/mcp/state-server.d.ts +4 -4
- package/dist/modes/__tests__/base-autoresearch-contract.test.js +7 -1
- package/dist/modes/__tests__/base-autoresearch-contract.test.js.map +1 -1
- package/dist/pipeline/__tests__/stages.test.js +130 -0
- package/dist/pipeline/__tests__/stages.test.js.map +1 -1
- package/dist/pipeline/orchestrator.js +1 -1
- package/dist/pipeline/orchestrator.js.map +1 -1
- package/dist/pipeline/stages/ralplan.d.ts +1 -0
- package/dist/pipeline/stages/ralplan.d.ts.map +1 -1
- package/dist/pipeline/stages/ralplan.js +14 -5
- package/dist/pipeline/stages/ralplan.js.map +1 -1
- package/dist/question/__tests__/deep-interview.test.js +160 -2
- package/dist/question/__tests__/deep-interview.test.js.map +1 -1
- package/dist/question/__tests__/policy.test.js +63 -3
- package/dist/question/__tests__/policy.test.js.map +1 -1
- package/dist/question/__tests__/renderer.test.js +191 -2
- package/dist/question/__tests__/renderer.test.js.map +1 -1
- package/dist/question/__tests__/state.test.js +94 -3
- package/dist/question/__tests__/state.test.js.map +1 -1
- package/dist/question/__tests__/ui.test.js +4 -0
- package/dist/question/__tests__/ui.test.js.map +1 -1
- package/dist/question/autopilot-wait.d.ts +12 -2
- package/dist/question/autopilot-wait.d.ts.map +1 -1
- package/dist/question/autopilot-wait.js +158 -47
- package/dist/question/autopilot-wait.js.map +1 -1
- package/dist/question/deep-interview.d.ts.map +1 -1
- package/dist/question/deep-interview.js +22 -6
- package/dist/question/deep-interview.js.map +1 -1
- package/dist/question/policy.d.ts.map +1 -1
- package/dist/question/policy.js +2 -5
- package/dist/question/policy.js.map +1 -1
- package/dist/question/renderer.d.ts +12 -0
- package/dist/question/renderer.d.ts.map +1 -1
- package/dist/question/renderer.js +87 -3
- package/dist/question/renderer.js.map +1 -1
- package/dist/question/state.d.ts +8 -1
- package/dist/question/state.d.ts.map +1 -1
- package/dist/question/state.js +54 -14
- package/dist/question/state.js.map +1 -1
- package/dist/question/types.d.ts +1 -1
- package/dist/question/types.d.ts.map +1 -1
- package/dist/question/ui.d.ts +1 -0
- package/dist/question/ui.d.ts.map +1 -1
- package/dist/question/ui.js +1 -0
- package/dist/question/ui.js.map +1 -1
- package/dist/ralplan/__tests__/runtime.test.js +191 -0
- package/dist/ralplan/__tests__/runtime.test.js.map +1 -1
- package/dist/ralplan/consensus-gate.d.ts +9 -1
- package/dist/ralplan/consensus-gate.d.ts.map +1 -1
- package/dist/ralplan/consensus-gate.js +84 -2
- package/dist/ralplan/consensus-gate.js.map +1 -1
- package/dist/ralplan/runtime.d.ts +9 -0
- package/dist/ralplan/runtime.d.ts.map +1 -1
- package/dist/ralplan/runtime.js +32 -11
- package/dist/ralplan/runtime.js.map +1 -1
- package/dist/scripts/__tests__/codex-native-hook.test.js +2315 -280
- package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
- package/dist/scripts/__tests__/notify-state-io.test.js +72 -1
- package/dist/scripts/__tests__/notify-state-io.test.js.map +1 -1
- package/dist/scripts/__tests__/notify-tmux-injection.test.d.ts +2 -0
- package/dist/scripts/__tests__/notify-tmux-injection.test.d.ts.map +1 -0
- package/dist/scripts/__tests__/notify-tmux-injection.test.js +57 -0
- package/dist/scripts/__tests__/notify-tmux-injection.test.js.map +1 -0
- package/dist/scripts/__tests__/run-test-files.test.js +74 -0
- package/dist/scripts/__tests__/run-test-files.test.js.map +1 -1
- package/dist/scripts/__tests__/verify-native-agents.test.js +65 -0
- package/dist/scripts/__tests__/verify-native-agents.test.js.map +1 -1
- package/dist/scripts/codex-native-hook.d.ts.map +1 -1
- package/dist/scripts/codex-native-hook.js +431 -56
- package/dist/scripts/codex-native-hook.js.map +1 -1
- package/dist/scripts/codex-native-pre-post.d.ts.map +1 -1
- package/dist/scripts/codex-native-pre-post.js +79 -1
- package/dist/scripts/codex-native-pre-post.js.map +1 -1
- package/dist/scripts/eval/eval-parity-smoke.js +1 -1
- package/dist/scripts/eval/eval-parity-smoke.js.map +1 -1
- package/dist/scripts/hook-payload-guard.d.ts +9 -0
- package/dist/scripts/hook-payload-guard.d.ts.map +1 -0
- package/dist/scripts/hook-payload-guard.js +111 -0
- package/dist/scripts/hook-payload-guard.js.map +1 -0
- package/dist/scripts/notify-fallback-watcher.js +8 -1
- package/dist/scripts/notify-fallback-watcher.js.map +1 -1
- package/dist/scripts/notify-hook/__tests__/payload-guard.test.d.ts +2 -0
- package/dist/scripts/notify-hook/__tests__/payload-guard.test.d.ts.map +1 -0
- package/dist/scripts/notify-hook/__tests__/payload-guard.test.js +39 -0
- package/dist/scripts/notify-hook/__tests__/payload-guard.test.js.map +1 -0
- package/dist/scripts/notify-hook/auto-nudge.d.ts.map +1 -1
- package/dist/scripts/notify-hook/auto-nudge.js +3 -1
- package/dist/scripts/notify-hook/auto-nudge.js.map +1 -1
- package/dist/scripts/notify-hook/ralph-session-resume.d.ts.map +1 -1
- package/dist/scripts/notify-hook/ralph-session-resume.js +3 -10
- package/dist/scripts/notify-hook/ralph-session-resume.js.map +1 -1
- package/dist/scripts/notify-hook/state-io.d.ts.map +1 -1
- package/dist/scripts/notify-hook/state-io.js +62 -38
- package/dist/scripts/notify-hook/state-io.js.map +1 -1
- package/dist/scripts/notify-hook/team-leader-nudge.d.ts.map +1 -1
- package/dist/scripts/notify-hook/team-leader-nudge.js +7 -0
- package/dist/scripts/notify-hook/team-leader-nudge.js.map +1 -1
- package/dist/scripts/notify-hook/team-worker-stop.d.ts.map +1 -1
- package/dist/scripts/notify-hook/team-worker-stop.js +234 -86
- package/dist/scripts/notify-hook/team-worker-stop.js.map +1 -1
- package/dist/scripts/notify-hook/tmux-injection.d.ts +7 -0
- package/dist/scripts/notify-hook/tmux-injection.d.ts.map +1 -1
- package/dist/scripts/notify-hook/tmux-injection.js +24 -18
- package/dist/scripts/notify-hook/tmux-injection.js.map +1 -1
- package/dist/scripts/notify-hook.js +86 -13
- package/dist/scripts/notify-hook.js.map +1 -1
- package/dist/scripts/run-test-files.js +193 -22
- package/dist/scripts/run-test-files.js.map +1 -1
- package/dist/scripts/sync-plugin-mirror.d.ts.map +1 -1
- package/dist/scripts/sync-plugin-mirror.js +61 -3
- package/dist/scripts/sync-plugin-mirror.js.map +1 -1
- package/dist/scripts/verify-native-agents.d.ts.map +1 -1
- package/dist/scripts/verify-native-agents.js +58 -1
- package/dist/scripts/verify-native-agents.js.map +1 -1
- package/dist/state/__tests__/operations.test.js +1125 -1
- package/dist/state/__tests__/operations.test.js.map +1 -1
- package/dist/state/__tests__/skill-active.test.js +46 -1
- package/dist/state/__tests__/skill-active.test.js.map +1 -1
- package/dist/state/__tests__/workflow-transition.test.js +98 -7
- package/dist/state/__tests__/workflow-transition.test.js.map +1 -1
- package/dist/state/operations.d.ts.map +1 -1
- package/dist/state/operations.js +159 -2
- package/dist/state/operations.js.map +1 -1
- package/dist/state/skill-active.js +6 -8
- package/dist/state/skill-active.js.map +1 -1
- package/dist/state/workflow-transition-reconcile.d.ts +6 -0
- package/dist/state/workflow-transition-reconcile.d.ts.map +1 -1
- package/dist/state/workflow-transition-reconcile.js +38 -15
- package/dist/state/workflow-transition-reconcile.js.map +1 -1
- package/dist/state/workflow-transition.d.ts.map +1 -1
- package/dist/state/workflow-transition.js +10 -3
- package/dist/state/workflow-transition.js.map +1 -1
- package/dist/subagents/__tests__/tracker.test.js +139 -0
- package/dist/subagents/__tests__/tracker.test.js.map +1 -1
- package/dist/subagents/tracker.d.ts +3 -0
- package/dist/subagents/tracker.d.ts.map +1 -1
- package/dist/subagents/tracker.js +41 -4
- package/dist/subagents/tracker.js.map +1 -1
- package/dist/team/__tests__/coordination-protocol.test.d.ts +2 -0
- package/dist/team/__tests__/coordination-protocol.test.d.ts.map +1 -0
- package/dist/team/__tests__/coordination-protocol.test.js +173 -0
- package/dist/team/__tests__/coordination-protocol.test.js.map +1 -0
- package/dist/team/__tests__/runtime.test.js +52 -3
- package/dist/team/__tests__/runtime.test.js.map +1 -1
- package/dist/team/__tests__/scaling.test.js +9 -4
- package/dist/team/__tests__/scaling.test.js.map +1 -1
- package/dist/team/__tests__/state.test.js +83 -0
- package/dist/team/__tests__/state.test.js.map +1 -1
- package/dist/team/__tests__/tmux-session.test.js +240 -2
- package/dist/team/__tests__/tmux-session.test.js.map +1 -1
- package/dist/team/__tests__/worker-bootstrap.test.js +84 -0
- package/dist/team/__tests__/worker-bootstrap.test.js.map +1 -1
- package/dist/team/__tests__/worker-runtime-identity.test.js +4 -2
- package/dist/team/__tests__/worker-runtime-identity.test.js.map +1 -1
- package/dist/team/coordination-protocol.d.ts +14 -0
- package/dist/team/coordination-protocol.d.ts.map +1 -0
- package/dist/team/coordination-protocol.js +244 -0
- package/dist/team/coordination-protocol.js.map +1 -0
- package/dist/team/runtime.d.ts +1 -0
- package/dist/team/runtime.d.ts.map +1 -1
- package/dist/team/runtime.js +19 -3
- package/dist/team/runtime.js.map +1 -1
- package/dist/team/scaling.d.ts.map +1 -1
- package/dist/team/scaling.js +3 -2
- package/dist/team/scaling.js.map +1 -1
- package/dist/team/state/tasks.d.ts.map +1 -1
- package/dist/team/state/tasks.js +24 -0
- package/dist/team/state/tasks.js.map +1 -1
- package/dist/team/state/types.d.ts +21 -1
- package/dist/team/state/types.d.ts.map +1 -1
- package/dist/team/state/types.js.map +1 -1
- package/dist/team/state.d.ts +17 -1
- package/dist/team/state.d.ts.map +1 -1
- package/dist/team/state.js +12 -5
- package/dist/team/state.js.map +1 -1
- package/dist/team/team-ops.d.ts +1 -1
- package/dist/team/team-ops.d.ts.map +1 -1
- package/dist/team/team-ops.js.map +1 -1
- package/dist/team/tmux-session.d.ts +2 -0
- package/dist/team/tmux-session.d.ts.map +1 -1
- package/dist/team/tmux-session.js +161 -13
- package/dist/team/tmux-session.js.map +1 -1
- package/dist/team/worker-bootstrap.d.ts.map +1 -1
- package/dist/team/worker-bootstrap.js +63 -0
- package/dist/team/worker-bootstrap.js.map +1 -1
- package/dist/utils/__tests__/agents-model-table.test.js +4 -2
- package/dist/utils/__tests__/agents-model-table.test.js.map +1 -1
- package/dist/utils/agents-model-table.d.ts.map +1 -1
- package/dist/utils/agents-model-table.js +3 -0
- package/dist/utils/agents-model-table.js.map +1 -1
- package/dist/verification/__tests__/ci-rust-gates.test.js +81 -1
- package/dist/verification/__tests__/ci-rust-gates.test.js.map +1 -1
- package/package.json +8 -8
- package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
- package/plugins/oh-my-codex/hooks/codex-native-hook.mjs +334 -21
- package/plugins/oh-my-codex/hooks/hooks.json +1 -2
- package/plugins/oh-my-codex/skills/autopilot/SKILL.md +13 -6
- package/plugins/oh-my-codex/skills/code-review/SKILL.md +7 -7
- package/plugins/oh-my-codex/skills/deep-interview/SKILL.md +9 -4
- package/plugins/oh-my-codex/skills/ralph/SKILL.md +22 -22
- package/plugins/oh-my-codex/skills/ralplan/SKILL.md +12 -0
- package/plugins/oh-my-codex/skills/team/SKILL.md +16 -0
- package/plugins/oh-my-codex/skills/ultraqa/SKILL.md +9 -0
- package/plugins/oh-my-codex/skills/worker/SKILL.md +14 -0
- package/skills/autopilot/SKILL.md +13 -6
- package/skills/code-review/SKILL.md +7 -7
- package/skills/deep-interview/SKILL.md +9 -4
- package/skills/ralph/SKILL.md +22 -22
- package/skills/ralplan/SKILL.md +12 -0
- package/skills/team/SKILL.md +16 -0
- package/skills/ultraqa/SKILL.md +9 -0
- package/skills/worker/SKILL.md +14 -0
- package/src/scripts/__tests__/codex-native-hook.test.ts +4435 -2083
- package/src/scripts/__tests__/notify-state-io.test.ts +95 -0
- package/src/scripts/__tests__/notify-tmux-injection.test.ts +82 -0
- package/src/scripts/__tests__/run-test-files.test.ts +102 -0
- package/src/scripts/__tests__/verify-native-agents.test.ts +75 -0
- package/src/scripts/codex-native-hook.ts +536 -51
- package/src/scripts/codex-native-pre-post.ts +80 -0
- package/src/scripts/demo-team-e2e.sh +10 -7
- package/src/scripts/eval/eval-parity-smoke.ts +1 -1
- package/src/scripts/hook-payload-guard.ts +113 -0
- package/src/scripts/notify-fallback-watcher.ts +8 -1
- package/src/scripts/notify-hook/__tests__/payload-guard.test.ts +41 -0
- package/src/scripts/notify-hook/auto-nudge.ts +3 -1
- package/src/scripts/notify-hook/ralph-session-resume.ts +2 -8
- package/src/scripts/notify-hook/state-io.ts +75 -37
- package/src/scripts/notify-hook/team-leader-nudge.ts +7 -0
- package/src/scripts/notify-hook/team-worker-stop.ts +193 -52
- package/src/scripts/notify-hook/tmux-injection.ts +35 -19
- package/src/scripts/notify-hook.ts +105 -6
- package/src/scripts/run-test-files.ts +192 -22
- package/src/scripts/sync-plugin-mirror.ts +98 -9
- package/src/scripts/verify-native-agents.ts +65 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { describe, it, mock } from 'node:test';
|
|
2
2
|
import assert from 'node:assert/strict';
|
|
3
3
|
import { existsSync } from 'node:fs';
|
|
4
|
-
import { mkdtemp, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { mkdtemp, mkdir, readFile, rm, symlink, writeFile } from 'node:fs/promises';
|
|
5
5
|
import { tmpdir } from 'node:os';
|
|
6
6
|
import { join } from 'node:path';
|
|
7
7
|
import { detectKeywords, detectPrimaryKeyword, recordSkillActivation, DEEP_INTERVIEW_STATE_FILE, DEEP_INTERVIEW_BLOCKED_APPROVAL_INPUTS, DEEP_INTERVIEW_INPUT_LOCK_MESSAGE, persistDeepInterviewModeState, } from '../keyword-detector.js';
|
|
@@ -23,6 +23,54 @@ async function withIsolatedHome(prefix, run) {
|
|
|
23
23
|
await rm(homeDir, { recursive: true, force: true });
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
|
+
const AUTOPILOT_TEST_NOW = '2026-05-30T00:00:00.000Z';
|
|
27
|
+
const AUTOPILOT_TEST_STARTED_AT = '2026-05-29T00:00:00.000Z';
|
|
28
|
+
const AUTOPILOT_TEST_UPDATED_AT = '2026-05-29T00:10:00.000Z';
|
|
29
|
+
async function writeActiveAutopilotSkillState(stateDir, sessionId, phase = 'ralplan') {
|
|
30
|
+
await mkdir(join(stateDir, 'sessions', sessionId), { recursive: true });
|
|
31
|
+
await writeFile(join(stateDir, 'sessions', sessionId, SKILL_ACTIVE_STATE_FILE), JSON.stringify({
|
|
32
|
+
version: 1,
|
|
33
|
+
active: true,
|
|
34
|
+
skill: 'autopilot',
|
|
35
|
+
keyword: '$autopilot',
|
|
36
|
+
phase,
|
|
37
|
+
activated_at: AUTOPILOT_TEST_STARTED_AT,
|
|
38
|
+
updated_at: AUTOPILOT_TEST_UPDATED_AT,
|
|
39
|
+
session_id: sessionId,
|
|
40
|
+
active_skills: [{ skill: 'autopilot', active: true, phase, session_id: sessionId }],
|
|
41
|
+
}, null, 2));
|
|
42
|
+
}
|
|
43
|
+
async function readAutopilotModeState(stateDir, sessionId) {
|
|
44
|
+
return JSON.parse(await readFile(join(stateDir, 'sessions', sessionId, 'autopilot-state.json'), 'utf-8'));
|
|
45
|
+
}
|
|
46
|
+
async function continueAutopilotTestState(stateDir, cwd, sessionId, suffix, text = 'continue') {
|
|
47
|
+
await recordSkillActivation({
|
|
48
|
+
stateDir,
|
|
49
|
+
sourceCwd: cwd,
|
|
50
|
+
text,
|
|
51
|
+
sessionId,
|
|
52
|
+
threadId: `thread-${suffix}`,
|
|
53
|
+
turnId: `turn-${suffix}`,
|
|
54
|
+
nowIso: AUTOPILOT_TEST_NOW,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
async function assertAutopilotRecoverySnapshot(cwd, modeState, expectedPath, expectedReason) {
|
|
58
|
+
const snapshotPath = modeState.state?.handoff_artifacts?.context_snapshot_path ?? '';
|
|
59
|
+
if (typeof expectedPath === 'string')
|
|
60
|
+
assert.equal(snapshotPath, expectedPath);
|
|
61
|
+
else
|
|
62
|
+
assert.match(snapshotPath, expectedPath);
|
|
63
|
+
assert.equal(modeState.state?.handoff_artifacts?.context_snapshot?.kind, 'recovery');
|
|
64
|
+
assert.equal(modeState.state?.handoff_artifacts?.context_snapshot?.recovery?.reason, expectedReason);
|
|
65
|
+
assert.equal(modeState.state?.context_snapshot_recovery?.status, 'degraded');
|
|
66
|
+
assert.equal(modeState.state?.context_snapshot_recovery?.reason, expectedReason);
|
|
67
|
+
const recoverySnapshot = await readFile(join(cwd, snapshotPath), 'utf-8');
|
|
68
|
+
assert.match(recoverySnapshot, /recovery status: degraded/);
|
|
69
|
+
assert.match(recoverySnapshot, new RegExp(`recovery reason: ${expectedReason}`));
|
|
70
|
+
assert.match(recoverySnapshot, /do not treat the continuation input as the task seed/);
|
|
71
|
+
assert.doesNotMatch(recoverySnapshot, /task seed: continue/);
|
|
72
|
+
return snapshotPath;
|
|
73
|
+
}
|
|
26
74
|
describe('keyword detector team compatibility', () => {
|
|
27
75
|
it('keeps explicit $skill order in detectKeywords results (left-to-right)', () => {
|
|
28
76
|
const matches = detectKeywords('$analyze $ultraqa $code-review now');
|
|
@@ -133,6 +181,28 @@ describe('keyword detector team compatibility', () => {
|
|
|
133
181
|
const pathOnly = detectPrimaryKeyword('inspect .omx/ultragoal/goals.json');
|
|
134
182
|
assert.notEqual(pathOnly?.skill, 'ultragoal');
|
|
135
183
|
});
|
|
184
|
+
it('maps bare and command-style autopilot invocations to autopilot', () => {
|
|
185
|
+
for (const prompt of ['autopilot', 'run autopilot', 'autopilot this', 'autopilot mode']) {
|
|
186
|
+
const match = detectPrimaryKeyword(prompt);
|
|
187
|
+
assert.ok(match, `expected autopilot match for ${prompt}`);
|
|
188
|
+
assert.equal(match.skill, 'autopilot');
|
|
189
|
+
assert.equal(match.keyword.toLowerCase(), 'autopilot');
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
it('does not trigger autopilot from management/debug prose mentions', () => {
|
|
193
|
+
assert.equal(detectPrimaryKeyword('inspect autopilot state before continuing'), null);
|
|
194
|
+
assert.equal(detectPrimaryKeyword('fix the autopilot bug in the detector'), null);
|
|
195
|
+
assert.equal(detectPrimaryKeyword('why did autopilot fail?'), null);
|
|
196
|
+
assert.equal(detectPrimaryKeyword('run autopilot tests'), null);
|
|
197
|
+
assert.equal(detectPrimaryKeyword('run autopilot regression tests'), null);
|
|
198
|
+
assert.equal(detectPrimaryKeyword('continue autopilot debugging'), null);
|
|
199
|
+
assert.equal(detectPrimaryKeyword('start autopilot bug investigation'), null);
|
|
200
|
+
});
|
|
201
|
+
it('keeps higher-priority workflow keywords ahead of autopilot mentions', () => {
|
|
202
|
+
const match = detectPrimaryKeyword('autopilot this after consensus plan');
|
|
203
|
+
assert.ok(match);
|
|
204
|
+
assert.equal(match.skill, 'ralplan');
|
|
205
|
+
});
|
|
136
206
|
it('maps code-review keyword variants to code-review skill', () => {
|
|
137
207
|
const hyphen = detectPrimaryKeyword('run $code-review before merge');
|
|
138
208
|
assert.ok(hyphen);
|
|
@@ -333,6 +403,7 @@ describe('keyword registry coverage', () => {
|
|
|
333
403
|
assert.ok(registryKeywords.has('$ultragoal'));
|
|
334
404
|
assert.ok(registryKeywords.has('$prometheus-strict'));
|
|
335
405
|
assert.ok(registryKeywords.has('ultragoal'));
|
|
406
|
+
assert.ok(registryKeywords.has('autopilot'));
|
|
336
407
|
});
|
|
337
408
|
});
|
|
338
409
|
describe('keyword detector skill-active-state lifecycle', () => {
|
|
@@ -473,6 +544,12 @@ describe('keyword detector skill-active-state lifecycle', () => {
|
|
|
473
544
|
rationale: 'Autopilot starts at the deep-interview gate by default; clear bounded tasks may skip only with an explicit persisted skip reason.',
|
|
474
545
|
});
|
|
475
546
|
assert.deepEqual(modeState.state.handoff_artifacts, {
|
|
547
|
+
context_snapshot_path: '.omx/context/please-run-and-keep-going-20260225T000000Z.md',
|
|
548
|
+
context_snapshot: {
|
|
549
|
+
path: '.omx/context/please-run-and-keep-going-20260225T000000Z.md',
|
|
550
|
+
kind: 'canonical',
|
|
551
|
+
original_task_status: 'activation-prompt',
|
|
552
|
+
},
|
|
476
553
|
deep_interview: null,
|
|
477
554
|
ralplan: null,
|
|
478
555
|
ralplan_consensus_gate: {
|
|
@@ -491,11 +568,445 @@ describe('keyword detector skill-active-state lifecycle', () => {
|
|
|
491
568
|
assert.equal(modeState.state.review_verdict, null);
|
|
492
569
|
assert.equal(modeState.state.qa_verdict, null);
|
|
493
570
|
assert.equal(modeState.state.return_to_ralplan_reason, null);
|
|
571
|
+
const snapshot = await readFile(join(cwd, '.omx', 'context', 'please-run-and-keep-going-20260225T000000Z.md'), 'utf-8');
|
|
572
|
+
assert.match(snapshot, /activation prompt \/ task seed: please run \$autopilot and keep going/);
|
|
573
|
+
assert.match(snapshot, /scope note: this seed captures the Autopilot activation prompt/);
|
|
574
|
+
}
|
|
575
|
+
finally {
|
|
576
|
+
await rm(cwd, { recursive: true, force: true });
|
|
577
|
+
}
|
|
578
|
+
});
|
|
579
|
+
it('migrates legacy Autopilot context snapshot paths into handoff artifacts', async () => {
|
|
580
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-autopilot-legacy-context-'));
|
|
581
|
+
const stateDir = join(cwd, '.omx', 'state');
|
|
582
|
+
const sessionId = 'sess-autopilot-legacy-context';
|
|
583
|
+
try {
|
|
584
|
+
await writeActiveAutopilotSkillState(stateDir, sessionId, 'deep-interview');
|
|
585
|
+
await writeFile(join(stateDir, 'sessions', sessionId, 'autopilot-state.json'), JSON.stringify({
|
|
586
|
+
active: true,
|
|
587
|
+
mode: 'autopilot',
|
|
588
|
+
current_phase: 'deep-interview',
|
|
589
|
+
started_at: AUTOPILOT_TEST_STARTED_AT,
|
|
590
|
+
context_snapshot_path: '.omx/context/legacy-task-20260529T000000Z.md',
|
|
591
|
+
state: { handoff_artifacts: { deep_interview: null } },
|
|
592
|
+
}, null, 2));
|
|
593
|
+
await mkdir(join(cwd, '.omx', 'context'), { recursive: true });
|
|
594
|
+
await writeFile(join(cwd, '.omx', 'context', 'legacy-task-20260529T000000Z.md'), '# legacy task');
|
|
595
|
+
await continueAutopilotTestState(stateDir, cwd, sessionId, 'legacy');
|
|
596
|
+
const modeState = await readAutopilotModeState(stateDir, sessionId);
|
|
597
|
+
assert.equal(modeState.state?.handoff_artifacts?.context_snapshot_path, '.omx/context/legacy-task-20260529T000000Z.md');
|
|
598
|
+
assert.deepEqual(modeState.state?.handoff_artifacts?.context_snapshot, {
|
|
599
|
+
path: '.omx/context/legacy-task-20260529T000000Z.md',
|
|
600
|
+
kind: 'legacy',
|
|
601
|
+
original_task_status: 'legacy-unverified',
|
|
602
|
+
});
|
|
603
|
+
assert.equal(existsSync(join(cwd, '.omx', 'context', 'continue-20260530T000000Z.md')), false);
|
|
604
|
+
}
|
|
605
|
+
finally {
|
|
606
|
+
await rm(cwd, { recursive: true, force: true });
|
|
607
|
+
}
|
|
608
|
+
});
|
|
609
|
+
it('rejects unsafe legacy Autopilot context snapshot paths without writing outside .omx/context', async () => {
|
|
610
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-autopilot-unsafe-context-'));
|
|
611
|
+
const stateDir = join(cwd, '.omx', 'state');
|
|
612
|
+
const sessionId = 'sess-autopilot-unsafe-context';
|
|
613
|
+
try {
|
|
614
|
+
await writeActiveAutopilotSkillState(stateDir, sessionId, 'deep-interview');
|
|
615
|
+
await writeFile(join(stateDir, 'sessions', sessionId, 'autopilot-state.json'), JSON.stringify({
|
|
616
|
+
active: true,
|
|
617
|
+
mode: 'autopilot',
|
|
618
|
+
current_phase: 'deep-interview',
|
|
619
|
+
started_at: AUTOPILOT_TEST_STARTED_AT,
|
|
620
|
+
context_snapshot_path: '.omx/context/../../escape.md',
|
|
621
|
+
state: { handoff_artifacts: { deep_interview: null } },
|
|
622
|
+
}, null, 2));
|
|
623
|
+
const result = await recordSkillActivation({
|
|
624
|
+
stateDir,
|
|
625
|
+
text: 'continue',
|
|
626
|
+
sessionId,
|
|
627
|
+
threadId: 'thread-unsafe',
|
|
628
|
+
turnId: 'turn-unsafe',
|
|
629
|
+
nowIso: '2026-05-30T00:00:00.000Z',
|
|
630
|
+
});
|
|
631
|
+
assert.ok(result);
|
|
632
|
+
assert.equal(existsSync(join(cwd, '.omx', 'escape.md')), false);
|
|
633
|
+
const modeState = await readAutopilotModeState(stateDir, sessionId);
|
|
634
|
+
assert.equal(modeState.context_snapshot_path, undefined);
|
|
635
|
+
await assertAutopilotRecoverySnapshot(cwd, modeState, '.omx/context/autopilot-recovery-20260530T000000Z.md', 'missing-or-unsafe-legacy-context-snapshot');
|
|
636
|
+
}
|
|
637
|
+
finally {
|
|
638
|
+
await rm(cwd, { recursive: true, force: true });
|
|
639
|
+
}
|
|
640
|
+
});
|
|
641
|
+
it('does not snapshot bare continuation text when active Autopilot mode state is corrupt', async () => {
|
|
642
|
+
const expectedReasons = {
|
|
643
|
+
'missing-current-phase': 'nonpreservable-autopilot-mode-state-missing-current-phase',
|
|
644
|
+
'malformed-json': 'malformed-autopilot-mode-state',
|
|
645
|
+
'array-json': 'malformed-autopilot-mode-state',
|
|
646
|
+
};
|
|
647
|
+
for (const fixture of ['missing-current-phase', 'malformed-json', 'array-json']) {
|
|
648
|
+
const cwd = await mkdtemp(join(tmpdir(), `omx-keyword-autopilot-corrupt-continuation-${fixture}-`));
|
|
649
|
+
const stateDir = join(cwd, '.omx', 'state');
|
|
650
|
+
const sessionId = `sess-autopilot-corrupt-continuation-${fixture}`;
|
|
651
|
+
try {
|
|
652
|
+
await writeActiveAutopilotSkillState(stateDir, sessionId);
|
|
653
|
+
const modeStatePath = join(stateDir, 'sessions', sessionId, 'autopilot-state.json');
|
|
654
|
+
if (fixture === 'missing-current-phase') {
|
|
655
|
+
await writeFile(modeStatePath, JSON.stringify({
|
|
656
|
+
active: true,
|
|
657
|
+
mode: 'autopilot',
|
|
658
|
+
started_at: AUTOPILOT_TEST_STARTED_AT,
|
|
659
|
+
state: { handoff_artifacts: {} },
|
|
660
|
+
}, null, 2));
|
|
661
|
+
}
|
|
662
|
+
else if (fixture === 'malformed-json') {
|
|
663
|
+
await writeFile(modeStatePath, '{ "active": true, "mode": "autopilot",');
|
|
664
|
+
}
|
|
665
|
+
else {
|
|
666
|
+
await writeFile(modeStatePath, '[]');
|
|
667
|
+
}
|
|
668
|
+
await continueAutopilotTestState(stateDir, cwd, sessionId, fixture);
|
|
669
|
+
assert.equal(existsSync(join(cwd, '.omx', 'context', 'continue-20260530T000000Z.md')), false);
|
|
670
|
+
await assertAutopilotRecoverySnapshot(cwd, JSON.parse(await readFile(modeStatePath, 'utf-8')), /^\.omx\/context\/autopilot-recovery-20260530T000000Z(?:-\d+)?\.md$/, expectedReasons[fixture]);
|
|
671
|
+
}
|
|
672
|
+
finally {
|
|
673
|
+
await rm(cwd, { recursive: true, force: true });
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
});
|
|
677
|
+
it('rejects nested symlink Autopilot context snapshot candidates during reuse', async () => {
|
|
678
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-autopilot-nested-symlink-context-'));
|
|
679
|
+
const outside = await mkdtemp(join(tmpdir(), 'omx-keyword-autopilot-nested-symlink-outside-'));
|
|
680
|
+
const stateDir = join(cwd, '.omx', 'state');
|
|
681
|
+
const sessionId = 'sess-autopilot-nested-symlink-context';
|
|
682
|
+
try {
|
|
683
|
+
await mkdir(join(cwd, '.omx', 'context'), { recursive: true });
|
|
684
|
+
await symlink(outside, join(cwd, '.omx', 'context', 'link'));
|
|
685
|
+
await writeFile(join(outside, 'exfil.md'), '# outside context');
|
|
686
|
+
await writeActiveAutopilotSkillState(stateDir, sessionId);
|
|
687
|
+
await writeFile(join(stateDir, 'sessions', sessionId, 'autopilot-state.json'), JSON.stringify({
|
|
688
|
+
active: true,
|
|
689
|
+
mode: 'autopilot',
|
|
690
|
+
current_phase: 'ralplan',
|
|
691
|
+
started_at: AUTOPILOT_TEST_STARTED_AT,
|
|
692
|
+
state: { handoff_artifacts: { context_snapshot_path: '.omx/context/link/exfil.md' } },
|
|
693
|
+
}, null, 2));
|
|
694
|
+
await continueAutopilotTestState(stateDir, cwd, sessionId, 'nested-symlink');
|
|
695
|
+
await assertAutopilotRecoverySnapshot(cwd, await readAutopilotModeState(stateDir, sessionId), '.omx/context/autopilot-recovery-20260530T000000Z.md', 'missing-or-unsafe-legacy-context-snapshot');
|
|
696
|
+
assert.equal(existsSync(join(outside, 'exfil.md')), true);
|
|
697
|
+
}
|
|
698
|
+
finally {
|
|
699
|
+
await rm(cwd, { recursive: true, force: true });
|
|
700
|
+
await rm(outside, { recursive: true, force: true });
|
|
701
|
+
}
|
|
702
|
+
});
|
|
703
|
+
it('rejects typed canonical Autopilot recovery snapshot candidates during reuse', async () => {
|
|
704
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-autopilot-typed-recovery-context-'));
|
|
705
|
+
const stateDir = join(cwd, '.omx', 'state');
|
|
706
|
+
const sessionId = 'sess-autopilot-typed-recovery-context';
|
|
707
|
+
try {
|
|
708
|
+
await mkdir(join(cwd, '.omx', 'context'), { recursive: true });
|
|
709
|
+
await writeFile(join(cwd, '.omx', 'context', 'autopilot-recovery-20260529T000000Z.md'), '# stale degraded recovery');
|
|
710
|
+
await writeActiveAutopilotSkillState(stateDir, sessionId);
|
|
711
|
+
await writeFile(join(stateDir, 'sessions', sessionId, 'autopilot-state.json'), JSON.stringify({
|
|
712
|
+
active: true,
|
|
713
|
+
mode: 'autopilot',
|
|
714
|
+
current_phase: 'ralplan',
|
|
715
|
+
started_at: AUTOPILOT_TEST_STARTED_AT,
|
|
716
|
+
state: {
|
|
717
|
+
handoff_artifacts: {
|
|
718
|
+
context_snapshot: {
|
|
719
|
+
path: '.omx/context/autopilot-recovery-20260529T000000Z.md',
|
|
720
|
+
kind: 'canonical',
|
|
721
|
+
},
|
|
722
|
+
},
|
|
723
|
+
},
|
|
724
|
+
}, null, 2));
|
|
725
|
+
await continueAutopilotTestState(stateDir, cwd, sessionId, 'typed-recovery');
|
|
726
|
+
await assertAutopilotRecoverySnapshot(cwd, await readAutopilotModeState(stateDir, sessionId), '.omx/context/autopilot-recovery-20260530T000000Z.md', 'missing-or-unsafe-legacy-context-snapshot');
|
|
727
|
+
assert.equal(existsSync(join(cwd, '.omx', 'context', 'autopilot-recovery-20260529T000000Z.md')), true);
|
|
728
|
+
}
|
|
729
|
+
finally {
|
|
730
|
+
await rm(cwd, { recursive: true, force: true });
|
|
731
|
+
}
|
|
732
|
+
});
|
|
733
|
+
it('rejects oversized Autopilot context snapshot candidates during reuse', async () => {
|
|
734
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-autopilot-oversized-context-'));
|
|
735
|
+
const stateDir = join(cwd, '.omx', 'state');
|
|
736
|
+
const sessionId = 'sess-autopilot-oversized-context';
|
|
737
|
+
try {
|
|
738
|
+
await mkdir(join(cwd, '.omx', 'context'), { recursive: true });
|
|
739
|
+
await writeFile(join(cwd, '.omx', 'context', 'oversized-legacy-20260529T000000Z.md'), 'x'.repeat((1024 * 1024) + 1));
|
|
740
|
+
await writeActiveAutopilotSkillState(stateDir, sessionId);
|
|
741
|
+
await writeFile(join(stateDir, 'sessions', sessionId, 'autopilot-state.json'), JSON.stringify({
|
|
742
|
+
active: true,
|
|
743
|
+
mode: 'autopilot',
|
|
744
|
+
current_phase: 'ralplan',
|
|
745
|
+
started_at: AUTOPILOT_TEST_STARTED_AT,
|
|
746
|
+
state: {
|
|
747
|
+
handoff_artifacts: {
|
|
748
|
+
context_snapshot_path: '.omx/context/oversized-legacy-20260529T000000Z.md',
|
|
749
|
+
},
|
|
750
|
+
},
|
|
751
|
+
}, null, 2));
|
|
752
|
+
await continueAutopilotTestState(stateDir, cwd, sessionId, 'oversized-context');
|
|
753
|
+
await assertAutopilotRecoverySnapshot(cwd, await readAutopilotModeState(stateDir, sessionId), '.omx/context/autopilot-recovery-20260530T000000Z.md', 'missing-or-unsafe-legacy-context-snapshot');
|
|
754
|
+
assert.equal(existsSync(join(cwd, '.omx', 'context', 'oversized-legacy-20260529T000000Z.md')), true);
|
|
755
|
+
}
|
|
756
|
+
finally {
|
|
757
|
+
await rm(cwd, { recursive: true, force: true });
|
|
758
|
+
}
|
|
759
|
+
});
|
|
760
|
+
it('does not promote degraded recovery snapshots to canonical context on reactivation', async () => {
|
|
761
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-autopilot-recovery-reactivation-'));
|
|
762
|
+
const stateDir = join(cwd, '.omx', 'state');
|
|
763
|
+
const sessionId = 'sess-autopilot-recovery-reactivation';
|
|
764
|
+
try {
|
|
765
|
+
await mkdir(join(cwd, '.omx', 'context'), { recursive: true });
|
|
766
|
+
await writeFile(join(cwd, '.omx', 'context', 'autopilot-recovery-20260529T000000Z.md'), '# degraded recovery');
|
|
767
|
+
await writeActiveAutopilotSkillState(stateDir, sessionId, 'complete');
|
|
768
|
+
await writeFile(join(stateDir, 'sessions', sessionId, 'autopilot-state.json'), JSON.stringify({
|
|
769
|
+
active: true,
|
|
770
|
+
mode: 'autopilot',
|
|
771
|
+
current_phase: 'complete',
|
|
772
|
+
completed_at: AUTOPILOT_TEST_UPDATED_AT,
|
|
773
|
+
state: {
|
|
774
|
+
handoff_artifacts: {
|
|
775
|
+
context_snapshot_path: '.omx/context/autopilot-recovery-20260529T000000Z.md',
|
|
776
|
+
context_snapshot: {
|
|
777
|
+
path: '.omx/context/autopilot-recovery-20260529T000000Z.md',
|
|
778
|
+
kind: 'recovery',
|
|
779
|
+
recovery: { status: 'degraded', reason: 'missing-or-unsafe-legacy-context-snapshot' },
|
|
780
|
+
},
|
|
781
|
+
},
|
|
782
|
+
context_snapshot_recovery: { status: 'degraded', reason: 'missing-or-unsafe-legacy-context-snapshot' },
|
|
783
|
+
},
|
|
784
|
+
}, null, 2));
|
|
785
|
+
await continueAutopilotTestState(stateDir, cwd, sessionId, 'recovery-reactivation', '$autopilot implement the real task');
|
|
786
|
+
const modeState = await readAutopilotModeState(stateDir, sessionId);
|
|
787
|
+
assert.equal(modeState.state?.handoff_artifacts?.context_snapshot_path, '.omx/context/implement-the-real-task-20260530T000000Z.md');
|
|
788
|
+
assert.deepEqual(modeState.state?.handoff_artifacts?.context_snapshot, {
|
|
789
|
+
path: '.omx/context/implement-the-real-task-20260530T000000Z.md',
|
|
790
|
+
kind: 'canonical',
|
|
791
|
+
original_task_status: 'activation-prompt',
|
|
792
|
+
});
|
|
793
|
+
assert.equal(modeState.state?.context_snapshot_recovery, undefined);
|
|
794
|
+
const snapshot = await readFile(join(cwd, '.omx', 'context', 'implement-the-real-task-20260530T000000Z.md'), 'utf-8');
|
|
795
|
+
assert.match(snapshot, /activation prompt \/ task seed: \$autopilot implement the real task/);
|
|
494
796
|
}
|
|
495
797
|
finally {
|
|
496
798
|
await rm(cwd, { recursive: true, force: true });
|
|
497
799
|
}
|
|
498
800
|
});
|
|
801
|
+
it('does not follow symlinked Autopilot context directories when writing snapshots', async () => {
|
|
802
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-autopilot-symlink-context-'));
|
|
803
|
+
const outside = await mkdtemp(join(tmpdir(), 'omx-keyword-autopilot-symlink-outside-'));
|
|
804
|
+
const stateDir = join(cwd, '.omx', 'state');
|
|
805
|
+
try {
|
|
806
|
+
await mkdir(join(cwd, '.omx'), { recursive: true });
|
|
807
|
+
await symlink(outside, join(cwd, '.omx', 'context'));
|
|
808
|
+
await mkdir(stateDir, { recursive: true });
|
|
809
|
+
const warnings = [];
|
|
810
|
+
mock.method(console, 'warn', (...args) => {
|
|
811
|
+
warnings.push(args);
|
|
812
|
+
});
|
|
813
|
+
await recordSkillActivation({
|
|
814
|
+
stateDir,
|
|
815
|
+
sourceCwd: cwd,
|
|
816
|
+
text: '$autopilot symlink escape',
|
|
817
|
+
sessionId: 'sess-autopilot-symlink-context',
|
|
818
|
+
threadId: 'thread-symlink-context',
|
|
819
|
+
turnId: 'turn-symlink-context',
|
|
820
|
+
nowIso: '2026-05-30T00:00:00.000Z',
|
|
821
|
+
});
|
|
822
|
+
assert.equal(warnings.length, 1);
|
|
823
|
+
assert.match(String(warnings[0][1]), /symbolic link/);
|
|
824
|
+
assert.equal(existsSync(join(outside, 'symlink-escape-20260530T000000Z.md')), false);
|
|
825
|
+
assert.equal(existsSync(join(stateDir, 'sessions', 'sess-autopilot-symlink-context', 'autopilot-state.json')), false);
|
|
826
|
+
}
|
|
827
|
+
finally {
|
|
828
|
+
await rm(cwd, { recursive: true, force: true });
|
|
829
|
+
await rm(outside, { recursive: true, force: true });
|
|
830
|
+
}
|
|
831
|
+
});
|
|
832
|
+
it('allocates unique Autopilot context snapshot paths for same-second matching slugs', async () => {
|
|
833
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-autopilot-context-collision-'));
|
|
834
|
+
const stateDir = join(cwd, '.omx', 'state');
|
|
835
|
+
try {
|
|
836
|
+
await mkdir(stateDir, { recursive: true });
|
|
837
|
+
await recordSkillActivation({
|
|
838
|
+
stateDir,
|
|
839
|
+
sourceCwd: cwd,
|
|
840
|
+
text: '$autopilot same task',
|
|
841
|
+
sessionId: 'sess-autopilot-collision-a',
|
|
842
|
+
threadId: 'thread-collision',
|
|
843
|
+
turnId: 'turn-collision-a',
|
|
844
|
+
nowIso: '2026-05-30T00:00:00.000Z',
|
|
845
|
+
});
|
|
846
|
+
await recordSkillActivation({
|
|
847
|
+
stateDir,
|
|
848
|
+
sourceCwd: cwd,
|
|
849
|
+
text: '$autopilot same task',
|
|
850
|
+
sessionId: 'sess-autopilot-collision-b',
|
|
851
|
+
threadId: 'thread-collision',
|
|
852
|
+
turnId: 'turn-collision-b',
|
|
853
|
+
nowIso: '2026-05-30T00:00:00.000Z',
|
|
854
|
+
});
|
|
855
|
+
const first = JSON.parse(await readFile(join(stateDir, 'sessions', 'sess-autopilot-collision-a', 'autopilot-state.json'), 'utf-8'));
|
|
856
|
+
const second = JSON.parse(await readFile(join(stateDir, 'sessions', 'sess-autopilot-collision-b', 'autopilot-state.json'), 'utf-8'));
|
|
857
|
+
assert.equal(first.state?.handoff_artifacts?.context_snapshot_path, '.omx/context/same-task-20260530T000000Z.md');
|
|
858
|
+
assert.equal(second.state?.handoff_artifacts?.context_snapshot_path, '.omx/context/same-task-20260530T000000Z-2.md');
|
|
859
|
+
}
|
|
860
|
+
finally {
|
|
861
|
+
await rm(cwd, { recursive: true, force: true });
|
|
862
|
+
}
|
|
863
|
+
});
|
|
864
|
+
it('fully resets terminal Autopilot mode state when reactivated', async () => {
|
|
865
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-autopilot-terminal-reset-'));
|
|
866
|
+
const stateDir = join(cwd, '.omx', 'state');
|
|
867
|
+
const sessionId = 'sess-autopilot-terminal-reset';
|
|
868
|
+
try {
|
|
869
|
+
await mkdir(join(stateDir, 'sessions', sessionId), { recursive: true });
|
|
870
|
+
await writeFile(join(stateDir, 'sessions', sessionId, SKILL_ACTIVE_STATE_FILE), JSON.stringify({
|
|
871
|
+
version: 1,
|
|
872
|
+
active: true,
|
|
873
|
+
skill: 'autopilot',
|
|
874
|
+
keyword: '$autopilot',
|
|
875
|
+
phase: 'complete',
|
|
876
|
+
activated_at: '2026-05-29T00:00:00.000Z',
|
|
877
|
+
updated_at: '2026-05-29T00:00:00.000Z',
|
|
878
|
+
session_id: sessionId,
|
|
879
|
+
active_skills: [{ skill: 'autopilot', active: true, phase: 'complete', session_id: sessionId }],
|
|
880
|
+
}, null, 2));
|
|
881
|
+
await writeFile(join(stateDir, 'sessions', sessionId, 'autopilot-state.json'), JSON.stringify({
|
|
882
|
+
active: true,
|
|
883
|
+
mode: 'autopilot',
|
|
884
|
+
current_phase: 'complete',
|
|
885
|
+
started_at: '2026-05-29T00:00:00.000Z',
|
|
886
|
+
completed_at: '2026-05-29T00:10:00.000Z',
|
|
887
|
+
iteration: 10,
|
|
888
|
+
max_iterations: 10,
|
|
889
|
+
review_cycle: 3,
|
|
890
|
+
lifecycle_outcome: 'finished',
|
|
891
|
+
run_outcome: 'finish',
|
|
892
|
+
handoff_artifacts: {
|
|
893
|
+
code_review: { verdict: 'APPROVE / CLEAR' },
|
|
894
|
+
ultraqa: { verdict: 'pass' },
|
|
895
|
+
},
|
|
896
|
+
state: {
|
|
897
|
+
handoff_artifacts: {
|
|
898
|
+
ralplan_consensus_gate: { complete: false },
|
|
899
|
+
code_review: { verdict: 'stale' },
|
|
900
|
+
},
|
|
901
|
+
},
|
|
902
|
+
}, null, 2));
|
|
903
|
+
const result = await recordSkillActivation({
|
|
904
|
+
stateDir,
|
|
905
|
+
text: '$autopilot investigate the next issue',
|
|
906
|
+
sessionId,
|
|
907
|
+
threadId: 'thread-reactivated',
|
|
908
|
+
turnId: 'turn-reactivated',
|
|
909
|
+
nowIso: '2026-05-30T00:00:00.000Z',
|
|
910
|
+
});
|
|
911
|
+
assert.ok(result);
|
|
912
|
+
assert.equal(result.skill, 'autopilot');
|
|
913
|
+
assert.equal(result.phase, 'deep-interview');
|
|
914
|
+
assert.equal(result.activated_at, '2026-05-30T00:00:00.000Z');
|
|
915
|
+
assert.equal(result.active_skills?.[0]?.phase, 'deep-interview');
|
|
916
|
+
assert.equal(result.active_skills?.[0]?.activated_at, '2026-05-30T00:00:00.000Z');
|
|
917
|
+
const skillState = JSON.parse(await readFile(join(stateDir, 'sessions', sessionId, SKILL_ACTIVE_STATE_FILE), 'utf-8'));
|
|
918
|
+
assert.equal(skillState.phase, 'deep-interview');
|
|
919
|
+
assert.equal(skillState.activated_at, '2026-05-30T00:00:00.000Z');
|
|
920
|
+
assert.equal(skillState.active_skills?.[0]?.phase, 'deep-interview');
|
|
921
|
+
assert.equal(skillState.active_skills?.[0]?.activated_at, '2026-05-30T00:00:00.000Z');
|
|
922
|
+
const modeState = JSON.parse(await readFile(join(stateDir, 'sessions', sessionId, 'autopilot-state.json'), 'utf-8'));
|
|
923
|
+
assert.equal(modeState.active, true);
|
|
924
|
+
assert.equal(modeState.current_phase, 'deep-interview');
|
|
925
|
+
assert.equal(modeState.started_at, '2026-05-30T00:00:00.000Z');
|
|
926
|
+
assert.equal(modeState.completed_at, undefined);
|
|
927
|
+
assert.equal(modeState.iteration, 1);
|
|
928
|
+
assert.equal(modeState.max_iterations, 10);
|
|
929
|
+
assert.equal(modeState.review_cycle, 0);
|
|
930
|
+
assert.equal(modeState.lifecycle_outcome, undefined);
|
|
931
|
+
assert.equal(modeState.run_outcome, undefined);
|
|
932
|
+
assert.equal(modeState.handoff_artifacts, undefined);
|
|
933
|
+
assert.deepEqual(modeState.state?.handoff_artifacts, {
|
|
934
|
+
context_snapshot_path: '.omx/context/investigate-the-next-issue-20260530T000000Z.md',
|
|
935
|
+
context_snapshot: {
|
|
936
|
+
path: '.omx/context/investigate-the-next-issue-20260530T000000Z.md',
|
|
937
|
+
kind: 'canonical',
|
|
938
|
+
original_task_status: 'activation-prompt',
|
|
939
|
+
},
|
|
940
|
+
deep_interview: null,
|
|
941
|
+
ralplan: null,
|
|
942
|
+
ralplan_consensus_gate: {
|
|
943
|
+
required: true,
|
|
944
|
+
sequence: ['architect-review', 'critic-review'],
|
|
945
|
+
planning_artifacts_are_not_consensus: true,
|
|
946
|
+
required_review_roles: ['architect', 'critic'],
|
|
947
|
+
ralplan_architect_review: null,
|
|
948
|
+
ralplan_critic_review: null,
|
|
949
|
+
complete: false,
|
|
950
|
+
},
|
|
951
|
+
ultragoal: null,
|
|
952
|
+
code_review: null,
|
|
953
|
+
ultraqa: null,
|
|
954
|
+
});
|
|
955
|
+
}
|
|
956
|
+
finally {
|
|
957
|
+
await rm(cwd, { recursive: true, force: true });
|
|
958
|
+
}
|
|
959
|
+
});
|
|
960
|
+
it('resets stopped Autopilot mode state when reactivated', async () => {
|
|
961
|
+
for (const phase of ['stopped', 'user-stopped']) {
|
|
962
|
+
const cwd = await mkdtemp(join(tmpdir(), `omx-keyword-autopilot-${phase}-reset-`));
|
|
963
|
+
const stateDir = join(cwd, '.omx', 'state');
|
|
964
|
+
const sessionId = `sess-autopilot-${phase}-reset`;
|
|
965
|
+
try {
|
|
966
|
+
await mkdir(join(stateDir, 'sessions', sessionId), { recursive: true });
|
|
967
|
+
await writeFile(join(stateDir, 'sessions', sessionId, SKILL_ACTIVE_STATE_FILE), JSON.stringify({
|
|
968
|
+
version: 1,
|
|
969
|
+
active: true,
|
|
970
|
+
skill: 'autopilot',
|
|
971
|
+
keyword: '$autopilot',
|
|
972
|
+
phase,
|
|
973
|
+
activated_at: '2026-05-29T00:00:00.000Z',
|
|
974
|
+
updated_at: '2026-05-29T00:00:00.000Z',
|
|
975
|
+
source: 'keyword-detector',
|
|
976
|
+
session_id: sessionId,
|
|
977
|
+
active_skills: [{ skill: 'autopilot', active: true, phase, session_id: sessionId }],
|
|
978
|
+
}, null, 2));
|
|
979
|
+
await writeFile(join(stateDir, 'sessions', sessionId, 'autopilot-state.json'), JSON.stringify({
|
|
980
|
+
active: true,
|
|
981
|
+
mode: 'autopilot',
|
|
982
|
+
current_phase: phase,
|
|
983
|
+
started_at: '2026-05-29T00:00:00.000Z',
|
|
984
|
+
completed_at: '2026-05-29T00:10:00.000Z',
|
|
985
|
+
iteration: 10,
|
|
986
|
+
max_iterations: 10,
|
|
987
|
+
review_cycle: 3,
|
|
988
|
+
state: { handoff_artifacts: { code_review: { verdict: 'stale' } } },
|
|
989
|
+
}, null, 2));
|
|
990
|
+
const result = await recordSkillActivation({
|
|
991
|
+
stateDir,
|
|
992
|
+
text: '$autopilot new task after stop',
|
|
993
|
+
sessionId,
|
|
994
|
+
nowIso: '2026-05-30T00:00:00.000Z',
|
|
995
|
+
});
|
|
996
|
+
assert.ok(result);
|
|
997
|
+
assert.equal(result.phase, 'deep-interview');
|
|
998
|
+
assert.equal(result.activated_at, '2026-05-30T00:00:00.000Z');
|
|
999
|
+
const modeState = JSON.parse(await readFile(join(stateDir, 'sessions', sessionId, 'autopilot-state.json'), 'utf-8'));
|
|
1000
|
+
assert.equal(modeState.current_phase, 'deep-interview');
|
|
1001
|
+
assert.equal(modeState.iteration, 1);
|
|
1002
|
+
assert.equal(modeState.review_cycle, 0);
|
|
1003
|
+
assert.equal(modeState.state?.handoff_artifacts?.code_review, null);
|
|
1004
|
+
}
|
|
1005
|
+
finally {
|
|
1006
|
+
await rm(cwd, { recursive: true, force: true });
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
});
|
|
499
1010
|
it('adds approved workflow overlaps without deleting the existing canonical state', async () => {
|
|
500
1011
|
const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-state-overlap-'));
|
|
501
1012
|
const stateDir = join(cwd, '.omx', 'state');
|
|
@@ -864,7 +1375,7 @@ describe('keyword detector skill-active-state lifecycle', () => {
|
|
|
864
1375
|
await rm(cwd, { recursive: true, force: true });
|
|
865
1376
|
}
|
|
866
1377
|
});
|
|
867
|
-
it('
|
|
1378
|
+
it('denies ralplan handoff from deep-interview without completion or explicit skip evidence', async () => {
|
|
868
1379
|
const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-ralplan-handoff-'));
|
|
869
1380
|
const stateDir = join(cwd, '.omx', 'state');
|
|
870
1381
|
try {
|
|
@@ -884,10 +1395,49 @@ describe('keyword detector skill-active-state lifecycle', () => {
|
|
|
884
1395
|
sessionId: 'sess-ralplan-handoff',
|
|
885
1396
|
nowIso: '2026-04-10T00:00:00.000Z',
|
|
886
1397
|
});
|
|
1398
|
+
assert.equal(result?.skill, 'deep-interview');
|
|
1399
|
+
assert.match(String(result?.transition_error), /missing deep-interview completion\/skip gate/i);
|
|
1400
|
+
const preserved = JSON.parse(await readFile(join(stateDir, 'sessions', 'sess-ralplan-handoff', 'deep-interview-state.json'), 'utf-8'));
|
|
1401
|
+
assert.equal(preserved.active, true);
|
|
1402
|
+
assert.equal(preserved.current_phase, 'intent-first');
|
|
1403
|
+
assert.equal(existsSync(join(stateDir, 'sessions', 'sess-ralplan-handoff', 'ralplan-state.json')), false);
|
|
1404
|
+
}
|
|
1405
|
+
finally {
|
|
1406
|
+
await rm(cwd, { recursive: true, force: true });
|
|
1407
|
+
}
|
|
1408
|
+
});
|
|
1409
|
+
it('allows ralplan handoff from deep-interview with a durable completion gate', async () => {
|
|
1410
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-ralplan-handoff-complete-'));
|
|
1411
|
+
const stateDir = join(cwd, '.omx', 'state');
|
|
1412
|
+
try {
|
|
1413
|
+
await mkdir(join(stateDir, 'sessions', 'sess-ralplan-handoff-complete'), { recursive: true });
|
|
1414
|
+
await writeFile(join(stateDir, 'sessions', 'sess-ralplan-handoff-complete', SKILL_ACTIVE_STATE_FILE), JSON.stringify({
|
|
1415
|
+
version: 1,
|
|
1416
|
+
active: true,
|
|
1417
|
+
skill: 'deep-interview',
|
|
1418
|
+
phase: 'planning',
|
|
1419
|
+
session_id: 'sess-ralplan-handoff-complete',
|
|
1420
|
+
active_skills: [{ skill: 'deep-interview', phase: 'planning', active: true, session_id: 'sess-ralplan-handoff-complete' }],
|
|
1421
|
+
}, null, 2));
|
|
1422
|
+
await writeFile(join(stateDir, 'sessions', 'sess-ralplan-handoff-complete', 'deep-interview-state.json'), JSON.stringify({
|
|
1423
|
+
active: true,
|
|
1424
|
+
mode: 'deep-interview',
|
|
1425
|
+
current_phase: 'intent-first',
|
|
1426
|
+
deep_interview_gate: {
|
|
1427
|
+
status: 'complete',
|
|
1428
|
+
rationale: 'Requirements are clarified and ready for ralplan consensus.',
|
|
1429
|
+
},
|
|
1430
|
+
}, null, 2));
|
|
1431
|
+
const result = await recordSkillActivation({
|
|
1432
|
+
stateDir,
|
|
1433
|
+
text: '$ralplan implement the approved contract',
|
|
1434
|
+
sessionId: 'sess-ralplan-handoff-complete',
|
|
1435
|
+
nowIso: '2026-04-10T00:00:00.000Z',
|
|
1436
|
+
});
|
|
887
1437
|
assert.equal(result?.transition_error, undefined);
|
|
888
1438
|
assert.equal(result?.skill, 'ralplan');
|
|
889
1439
|
assert.equal(result?.transition_message, 'mode transiting: deep-interview -> ralplan');
|
|
890
|
-
const completed = JSON.parse(await readFile(join(stateDir, 'sessions', 'sess-ralplan-handoff', 'deep-interview-state.json'), 'utf-8'));
|
|
1440
|
+
const completed = JSON.parse(await readFile(join(stateDir, 'sessions', 'sess-ralplan-handoff-complete', 'deep-interview-state.json'), 'utf-8'));
|
|
891
1441
|
assert.equal(completed.active, false);
|
|
892
1442
|
assert.equal(completed.current_phase, 'completed');
|
|
893
1443
|
}
|
|
@@ -965,6 +1515,72 @@ describe('keyword detector skill-active-state lifecycle', () => {
|
|
|
965
1515
|
await rm(cwd, { recursive: true, force: true });
|
|
966
1516
|
}
|
|
967
1517
|
});
|
|
1518
|
+
it('does not activate team state when persisted Team mode is disabled', async () => {
|
|
1519
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-state-team-disabled-'));
|
|
1520
|
+
const stateDir = join(cwd, '.omx', 'state');
|
|
1521
|
+
try {
|
|
1522
|
+
await mkdir(join(cwd, '.omx'), { recursive: true });
|
|
1523
|
+
await mkdir(stateDir, { recursive: true });
|
|
1524
|
+
await writeFile(join(cwd, '.omx', 'setup-scope.json'), JSON.stringify({ scope: 'project', teamMode: 'disabled' }, null, 2));
|
|
1525
|
+
const result = await recordSkillActivation({
|
|
1526
|
+
stateDir,
|
|
1527
|
+
text: '$team coordinate the hotfix',
|
|
1528
|
+
sessionId: 'sess-team-disabled',
|
|
1529
|
+
nowIso: '2026-04-08T00:00:00.000Z',
|
|
1530
|
+
});
|
|
1531
|
+
assert.equal(result, null);
|
|
1532
|
+
assert.equal(existsSync(join(stateDir, 'team-state.json')), false);
|
|
1533
|
+
assert.equal(existsSync(join(stateDir, 'sessions', 'sess-team-disabled', SKILL_ACTIVE_STATE_FILE)), false);
|
|
1534
|
+
}
|
|
1535
|
+
finally {
|
|
1536
|
+
await rm(cwd, { recursive: true, force: true });
|
|
1537
|
+
}
|
|
1538
|
+
});
|
|
1539
|
+
it('ignores disabled Team when selecting the primary workflow', async () => {
|
|
1540
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-state-team-disabled-primary-'));
|
|
1541
|
+
const stateDir = join(cwd, '.omx', 'state');
|
|
1542
|
+
try {
|
|
1543
|
+
await mkdir(join(cwd, '.omx'), { recursive: true });
|
|
1544
|
+
await mkdir(stateDir, { recursive: true });
|
|
1545
|
+
await writeFile(join(cwd, '.omx', 'setup-scope.json'), JSON.stringify({ scope: 'project', teamMode: 'disabled' }, null, 2));
|
|
1546
|
+
const result = await recordSkillActivation({
|
|
1547
|
+
stateDir,
|
|
1548
|
+
text: '$team $ralph ship this fix',
|
|
1549
|
+
sessionId: 'sess-team-disabled-primary',
|
|
1550
|
+
nowIso: '2026-04-10T01:00:00.000Z',
|
|
1551
|
+
});
|
|
1552
|
+
assert.equal(result?.skill, 'ralph');
|
|
1553
|
+
assert.deepEqual(result?.active_skills?.map((entry) => entry.skill), ['ralph']);
|
|
1554
|
+
assert.equal(existsSync(join(stateDir, 'team-state.json')), false);
|
|
1555
|
+
assert.equal(existsSync(join(stateDir, 'sessions', 'sess-team-disabled-primary', 'ralph-state.json')), true);
|
|
1556
|
+
}
|
|
1557
|
+
finally {
|
|
1558
|
+
await rm(cwd, { recursive: true, force: true });
|
|
1559
|
+
}
|
|
1560
|
+
});
|
|
1561
|
+
it('filters deferred team handoffs when persisted Team mode is disabled', async () => {
|
|
1562
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-state-team-disabled-deferred-'));
|
|
1563
|
+
const stateDir = join(cwd, '.omx', 'state');
|
|
1564
|
+
try {
|
|
1565
|
+
await mkdir(join(cwd, '.omx'), { recursive: true });
|
|
1566
|
+
await mkdir(stateDir, { recursive: true });
|
|
1567
|
+
await writeFile(join(cwd, '.omx', 'setup-scope.json'), JSON.stringify({ scope: 'project', teamMode: 'disabled' }, null, 2));
|
|
1568
|
+
const result = await recordSkillActivation({
|
|
1569
|
+
stateDir,
|
|
1570
|
+
text: '$ralplan $team $ralph ship this fix',
|
|
1571
|
+
sessionId: 'sess-team-disabled-deferred',
|
|
1572
|
+
nowIso: '2026-04-10T00:00:00.000Z',
|
|
1573
|
+
});
|
|
1574
|
+
assert.equal(result?.skill, 'ralplan');
|
|
1575
|
+
assert.deepEqual(result?.active_skills?.map((entry) => entry.skill), ['ralplan']);
|
|
1576
|
+
assert.deepEqual(result?.deferred_skills, ['ralph']);
|
|
1577
|
+
assert.equal(existsSync(join(stateDir, 'team-state.json')), false);
|
|
1578
|
+
assert.equal(existsSync(join(stateDir, 'sessions', 'sess-team-disabled-deferred', 'team-state.json')), false);
|
|
1579
|
+
}
|
|
1580
|
+
finally {
|
|
1581
|
+
await rm(cwd, { recursive: true, force: true });
|
|
1582
|
+
}
|
|
1583
|
+
});
|
|
968
1584
|
it('preserves active team root state when $team is re-entered from prompt routing', async () => {
|
|
969
1585
|
const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-state-team-preserve-'));
|
|
970
1586
|
const stateDir = join(cwd, '.omx', 'state');
|
|
@@ -1034,6 +1650,57 @@ describe('keyword detector skill-active-state lifecycle', () => {
|
|
|
1034
1650
|
await rm(cwd, { recursive: true, force: true });
|
|
1035
1651
|
}
|
|
1036
1652
|
});
|
|
1653
|
+
it('emits terminal ralplan state before explicit ultragoal execution handoff', async () => {
|
|
1654
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-ralplan-ultragoal-handoff-'));
|
|
1655
|
+
const stateDir = join(cwd, '.omx', 'state');
|
|
1656
|
+
try {
|
|
1657
|
+
await mkdir(join(stateDir, 'sessions', 'sess-ralplan-ultragoal'), { recursive: true });
|
|
1658
|
+
await writeFile(join(stateDir, 'sessions', 'sess-ralplan-ultragoal', SKILL_ACTIVE_STATE_FILE), JSON.stringify({
|
|
1659
|
+
version: 1,
|
|
1660
|
+
active: true,
|
|
1661
|
+
skill: 'ralplan',
|
|
1662
|
+
keyword: '$ralplan',
|
|
1663
|
+
phase: 'planning',
|
|
1664
|
+
session_id: 'sess-ralplan-ultragoal',
|
|
1665
|
+
active_skills: [{ skill: 'ralplan', phase: 'planning', active: true, session_id: 'sess-ralplan-ultragoal' }],
|
|
1666
|
+
}, null, 2));
|
|
1667
|
+
await writeFile(join(stateDir, 'sessions', 'sess-ralplan-ultragoal', 'ralplan-state.json'), JSON.stringify({
|
|
1668
|
+
active: true,
|
|
1669
|
+
mode: 'ralplan',
|
|
1670
|
+
current_phase: 'complete',
|
|
1671
|
+
planning_complete: true,
|
|
1672
|
+
ralplan_consensus_gate: {
|
|
1673
|
+
complete: true,
|
|
1674
|
+
sequence: ['architect-review', 'critic-review'],
|
|
1675
|
+
ralplan_architect_review: { agent_role: 'architect', verdict: 'approve', approved: true },
|
|
1676
|
+
ralplan_critic_review: { agent_role: 'critic', verdict: 'approve', approved: true },
|
|
1677
|
+
},
|
|
1678
|
+
}, null, 2));
|
|
1679
|
+
const result = await recordSkillActivation({
|
|
1680
|
+
stateDir,
|
|
1681
|
+
sourceCwd: cwd,
|
|
1682
|
+
text: '$ultragoal execute the approved ralplan',
|
|
1683
|
+
sessionId: 'sess-ralplan-ultragoal',
|
|
1684
|
+
nowIso: '2026-04-10T00:20:00.000Z',
|
|
1685
|
+
});
|
|
1686
|
+
assert.equal(result?.transition_error, undefined);
|
|
1687
|
+
assert.equal(result?.skill, 'ultragoal');
|
|
1688
|
+
assert.equal(result?.transition_message, 'mode transiting: ralplan -> ultragoal');
|
|
1689
|
+
assert.deepEqual(result?.active_skills?.map((entry) => entry.skill), ['ultragoal']);
|
|
1690
|
+
const ralplan = JSON.parse(await readFile(join(stateDir, 'sessions', 'sess-ralplan-ultragoal', 'ralplan-state.json'), 'utf-8'));
|
|
1691
|
+
assert.equal(ralplan.active, false);
|
|
1692
|
+
assert.equal(ralplan.current_phase, 'completed');
|
|
1693
|
+
assert.equal(ralplan.auto_completed_reason, 'mode transiting: ralplan -> ultragoal');
|
|
1694
|
+
assert.ok(ralplan.completed_at);
|
|
1695
|
+
const ultragoal = JSON.parse(await readFile(join(stateDir, 'sessions', 'sess-ralplan-ultragoal', 'ultragoal-state.json'), 'utf-8'));
|
|
1696
|
+
assert.equal(ultragoal.active, true);
|
|
1697
|
+
assert.equal(ultragoal.mode, 'ultragoal');
|
|
1698
|
+
assert.equal(ultragoal.current_phase, 'planning');
|
|
1699
|
+
}
|
|
1700
|
+
finally {
|
|
1701
|
+
await rm(cwd, { recursive: true, force: true });
|
|
1702
|
+
}
|
|
1703
|
+
});
|
|
1037
1704
|
it('keeps root team state out of the session-scoped Ralph canonical state', async () => {
|
|
1038
1705
|
const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-state-team-ralph-'));
|
|
1039
1706
|
const stateDir = join(cwd, '.omx', 'state');
|
|
@@ -1523,6 +2190,211 @@ deepMaxRounds = 21
|
|
|
1523
2190
|
await rm(cwd, { recursive: true, force: true });
|
|
1524
2191
|
}
|
|
1525
2192
|
});
|
|
2193
|
+
it('keeps Autopilot visible when a supervised code-review child keyword appears', async () => {
|
|
2194
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-state-autopilot-child-code-review-'));
|
|
2195
|
+
const stateDir = join(cwd, '.omx', 'state');
|
|
2196
|
+
const sessionId = 'sess-autopilot-child-code-review';
|
|
2197
|
+
try {
|
|
2198
|
+
await mkdir(join(stateDir, 'sessions', sessionId), { recursive: true });
|
|
2199
|
+
await writeFile(join(stateDir, 'sessions', sessionId, SKILL_ACTIVE_STATE_FILE), JSON.stringify({
|
|
2200
|
+
version: 1,
|
|
2201
|
+
active: true,
|
|
2202
|
+
skill: 'autopilot',
|
|
2203
|
+
keyword: '$autopilot',
|
|
2204
|
+
phase: 'ralplan',
|
|
2205
|
+
activated_at: '2026-05-30T00:00:00.000Z',
|
|
2206
|
+
updated_at: '2026-05-30T00:01:00.000Z',
|
|
2207
|
+
source: 'keyword-detector',
|
|
2208
|
+
session_id: sessionId,
|
|
2209
|
+
active_skills: [{ skill: 'autopilot', phase: 'ralplan', active: true, session_id: sessionId }],
|
|
2210
|
+
}, null, 2));
|
|
2211
|
+
const result = await recordSkillActivation({
|
|
2212
|
+
stateDir,
|
|
2213
|
+
text: 'CODE REVIEW the current diff before continuing',
|
|
2214
|
+
sessionId,
|
|
2215
|
+
threadId: 'thread-autopilot-child-code-review',
|
|
2216
|
+
turnId: 'turn-autopilot-child-code-review',
|
|
2217
|
+
nowIso: '2026-05-30T00:02:00.000Z',
|
|
2218
|
+
});
|
|
2219
|
+
assert.ok(result);
|
|
2220
|
+
assert.equal(result.skill, 'autopilot');
|
|
2221
|
+
assert.equal(result.phase, 'ralplan');
|
|
2222
|
+
assert.equal(result.supervised_child_skill, 'code-review');
|
|
2223
|
+
const persisted = JSON.parse(await readFile(join(stateDir, 'sessions', sessionId, SKILL_ACTIVE_STATE_FILE), 'utf-8'));
|
|
2224
|
+
assert.equal(persisted.skill, 'autopilot');
|
|
2225
|
+
assert.equal(persisted.phase, 'ralplan');
|
|
2226
|
+
assert.deepEqual(persisted.active_skills?.map((entry) => entry.skill), ['autopilot']);
|
|
2227
|
+
assert.equal(existsSync(join(stateDir, 'sessions', sessionId, 'code-review-state.json')), false);
|
|
2228
|
+
}
|
|
2229
|
+
finally {
|
|
2230
|
+
await rm(cwd, { recursive: true, force: true });
|
|
2231
|
+
}
|
|
2232
|
+
});
|
|
2233
|
+
it('keeps tracked Autopilot child keywords supervised and completes stale child mode state', async () => {
|
|
2234
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-state-autopilot-child-ultraqa-'));
|
|
2235
|
+
const stateDir = join(cwd, '.omx', 'state');
|
|
2236
|
+
const sessionId = 'sess-autopilot-child-ultraqa';
|
|
2237
|
+
try {
|
|
2238
|
+
await mkdir(join(stateDir, 'sessions', sessionId), { recursive: true });
|
|
2239
|
+
await writeFile(join(stateDir, 'sessions', sessionId, SKILL_ACTIVE_STATE_FILE), JSON.stringify({
|
|
2240
|
+
version: 1,
|
|
2241
|
+
active: true,
|
|
2242
|
+
skill: 'autopilot',
|
|
2243
|
+
keyword: '$autopilot',
|
|
2244
|
+
phase: 'ultraqa',
|
|
2245
|
+
activated_at: '2026-05-30T00:00:00.000Z',
|
|
2246
|
+
updated_at: '2026-05-30T00:01:00.000Z',
|
|
2247
|
+
source: 'keyword-detector',
|
|
2248
|
+
session_id: sessionId,
|
|
2249
|
+
active_skills: [{ skill: 'autopilot', phase: 'ultraqa', active: true, session_id: sessionId }],
|
|
2250
|
+
}, null, 2));
|
|
2251
|
+
await writeFile(join(stateDir, 'sessions', sessionId, 'ultragoal-state.json'), JSON.stringify({
|
|
2252
|
+
active: true,
|
|
2253
|
+
mode: 'ultragoal',
|
|
2254
|
+
current_phase: 'planning',
|
|
2255
|
+
session_id: sessionId,
|
|
2256
|
+
started_at: '2026-05-29T23:00:00.000Z',
|
|
2257
|
+
updated_at: '2026-05-29T23:05:00.000Z',
|
|
2258
|
+
}, null, 2));
|
|
2259
|
+
const result = await recordSkillActivation({
|
|
2260
|
+
stateDir,
|
|
2261
|
+
text: '$ultraqa run adversarial checks',
|
|
2262
|
+
sessionId,
|
|
2263
|
+
threadId: 'thread-autopilot-child-ultraqa',
|
|
2264
|
+
turnId: 'turn-autopilot-child-ultraqa',
|
|
2265
|
+
nowIso: '2026-05-30T00:02:00.000Z',
|
|
2266
|
+
});
|
|
2267
|
+
assert.ok(result);
|
|
2268
|
+
assert.equal(result.skill, 'autopilot');
|
|
2269
|
+
assert.equal(result.phase, 'ultraqa');
|
|
2270
|
+
assert.equal(result.supervised_child_skill, 'ultraqa');
|
|
2271
|
+
assert.equal(result.transition_error, undefined);
|
|
2272
|
+
assert.equal(existsSync(join(stateDir, 'sessions', sessionId, 'ultraqa-state.json')), false);
|
|
2273
|
+
const ultragoal = JSON.parse(await readFile(join(stateDir, 'sessions', sessionId, 'ultragoal-state.json'), 'utf-8'));
|
|
2274
|
+
assert.equal(ultragoal.active, false);
|
|
2275
|
+
assert.equal(ultragoal.current_phase, 'completed');
|
|
2276
|
+
assert.match(ultragoal.auto_completed_reason || '', /mode transiting: ultragoal -> ultraqa/);
|
|
2277
|
+
}
|
|
2278
|
+
finally {
|
|
2279
|
+
await rm(cwd, { recursive: true, force: true });
|
|
2280
|
+
}
|
|
2281
|
+
});
|
|
2282
|
+
it('denies supervised Autopilot child rollback without clearing stale execution state', async () => {
|
|
2283
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-state-autopilot-child-rollback-'));
|
|
2284
|
+
const stateDir = join(cwd, '.omx', 'state');
|
|
2285
|
+
const sessionId = 'sess-autopilot-child-rollback';
|
|
2286
|
+
try {
|
|
2287
|
+
await mkdir(join(stateDir, 'sessions', sessionId), { recursive: true });
|
|
2288
|
+
await writeFile(join(stateDir, 'sessions', sessionId, SKILL_ACTIVE_STATE_FILE), JSON.stringify({
|
|
2289
|
+
version: 1,
|
|
2290
|
+
active: true,
|
|
2291
|
+
skill: 'autopilot',
|
|
2292
|
+
keyword: '$autopilot',
|
|
2293
|
+
phase: 'ultragoal',
|
|
2294
|
+
session_id: sessionId,
|
|
2295
|
+
active_skills: [{ skill: 'autopilot', phase: 'ultragoal', active: true, session_id: sessionId }],
|
|
2296
|
+
}, null, 2));
|
|
2297
|
+
await writeFile(join(stateDir, 'sessions', sessionId, 'ultragoal-state.json'), JSON.stringify({
|
|
2298
|
+
active: true,
|
|
2299
|
+
mode: 'ultragoal',
|
|
2300
|
+
current_phase: 'executing',
|
|
2301
|
+
session_id: sessionId,
|
|
2302
|
+
}, null, 2));
|
|
2303
|
+
const result = await recordSkillActivation({
|
|
2304
|
+
stateDir,
|
|
2305
|
+
text: '$deep-interview go back and re-plan',
|
|
2306
|
+
sessionId,
|
|
2307
|
+
nowIso: '2026-05-30T00:03:00.000Z',
|
|
2308
|
+
});
|
|
2309
|
+
assert.equal(result?.skill, 'autopilot');
|
|
2310
|
+
assert.match(String(result?.transition_error), /Execution-to-planning rollback auto-complete is not allowed/i);
|
|
2311
|
+
assert.equal(result?.supervised_child_skill, undefined);
|
|
2312
|
+
assert.equal(existsSync(join(stateDir, 'sessions', sessionId, 'deep-interview-state.json')), false);
|
|
2313
|
+
const ultragoal = JSON.parse(await readFile(join(stateDir, 'sessions', sessionId, 'ultragoal-state.json'), 'utf-8'));
|
|
2314
|
+
assert.equal(ultragoal.active, true);
|
|
2315
|
+
assert.equal(ultragoal.current_phase, 'executing');
|
|
2316
|
+
}
|
|
2317
|
+
finally {
|
|
2318
|
+
await rm(cwd, { recursive: true, force: true });
|
|
2319
|
+
}
|
|
2320
|
+
});
|
|
2321
|
+
it('surfaces supervised Autopilot deep-interview to ralplan gate failures', async () => {
|
|
2322
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-state-autopilot-child-gate-'));
|
|
2323
|
+
const stateDir = join(cwd, '.omx', 'state');
|
|
2324
|
+
const sessionId = 'sess-autopilot-child-gate';
|
|
2325
|
+
try {
|
|
2326
|
+
await mkdir(join(stateDir, 'sessions', sessionId), { recursive: true });
|
|
2327
|
+
await writeFile(join(stateDir, 'sessions', sessionId, SKILL_ACTIVE_STATE_FILE), JSON.stringify({
|
|
2328
|
+
version: 1,
|
|
2329
|
+
active: true,
|
|
2330
|
+
skill: 'autopilot',
|
|
2331
|
+
keyword: '$autopilot',
|
|
2332
|
+
phase: 'deep-interview',
|
|
2333
|
+
session_id: sessionId,
|
|
2334
|
+
active_skills: [{ skill: 'autopilot', phase: 'deep-interview', active: true, session_id: sessionId }],
|
|
2335
|
+
}, null, 2));
|
|
2336
|
+
await writeFile(join(stateDir, 'sessions', sessionId, 'deep-interview-state.json'), JSON.stringify({
|
|
2337
|
+
active: true,
|
|
2338
|
+
mode: 'deep-interview',
|
|
2339
|
+
current_phase: 'intent-first',
|
|
2340
|
+
session_id: sessionId,
|
|
2341
|
+
}, null, 2));
|
|
2342
|
+
const result = await recordSkillActivation({
|
|
2343
|
+
stateDir,
|
|
2344
|
+
text: '$ralplan continue without interview completion evidence',
|
|
2345
|
+
sessionId,
|
|
2346
|
+
nowIso: '2026-05-30T00:04:00.000Z',
|
|
2347
|
+
});
|
|
2348
|
+
assert.equal(result?.skill, 'autopilot');
|
|
2349
|
+
assert.match(String(result?.transition_error), /missing deep-interview completion\/skip gate/i);
|
|
2350
|
+
assert.equal(result?.supervised_child_skill, undefined);
|
|
2351
|
+
assert.equal(existsSync(join(stateDir, 'sessions', sessionId, 'ralplan-state.json')), false);
|
|
2352
|
+
const deepInterview = JSON.parse(await readFile(join(stateDir, 'sessions', sessionId, 'deep-interview-state.json'), 'utf-8'));
|
|
2353
|
+
assert.equal(deepInterview.active, true);
|
|
2354
|
+
assert.equal(deepInterview.current_phase, 'intent-first');
|
|
2355
|
+
}
|
|
2356
|
+
finally {
|
|
2357
|
+
await rm(cwd, { recursive: true, force: true });
|
|
2358
|
+
}
|
|
2359
|
+
});
|
|
2360
|
+
it('ignores stale root child mode state during session-scoped Autopilot child reconciliation', async () => {
|
|
2361
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-state-autopilot-child-session-root-'));
|
|
2362
|
+
const stateDir = join(cwd, '.omx', 'state');
|
|
2363
|
+
const sessionId = 'sess-autopilot-child-session-root';
|
|
2364
|
+
try {
|
|
2365
|
+
await mkdir(join(stateDir, 'sessions', sessionId), { recursive: true });
|
|
2366
|
+
await writeFile(join(stateDir, 'sessions', sessionId, SKILL_ACTIVE_STATE_FILE), JSON.stringify({
|
|
2367
|
+
version: 1,
|
|
2368
|
+
active: true,
|
|
2369
|
+
skill: 'autopilot',
|
|
2370
|
+
keyword: '$autopilot',
|
|
2371
|
+
phase: 'deep-interview',
|
|
2372
|
+
session_id: sessionId,
|
|
2373
|
+
active_skills: [{ skill: 'autopilot', phase: 'deep-interview', active: true, session_id: sessionId }],
|
|
2374
|
+
}, null, 2));
|
|
2375
|
+
await writeFile(join(stateDir, 'ultragoal-state.json'), JSON.stringify({
|
|
2376
|
+
active: true,
|
|
2377
|
+
mode: 'ultragoal',
|
|
2378
|
+
current_phase: 'executing',
|
|
2379
|
+
}, null, 2));
|
|
2380
|
+
const result = await recordSkillActivation({
|
|
2381
|
+
stateDir,
|
|
2382
|
+
text: '$deep-interview continue scoped interview',
|
|
2383
|
+
sessionId,
|
|
2384
|
+
nowIso: '2026-05-30T00:05:00.000Z',
|
|
2385
|
+
});
|
|
2386
|
+
assert.equal(result?.skill, 'autopilot');
|
|
2387
|
+
assert.equal(result?.supervised_child_skill, 'deep-interview');
|
|
2388
|
+
assert.equal(result?.transition_error, undefined);
|
|
2389
|
+
const rootUltragoal = JSON.parse(await readFile(join(stateDir, 'ultragoal-state.json'), 'utf-8'));
|
|
2390
|
+
assert.equal(rootUltragoal.active, true);
|
|
2391
|
+
assert.equal(rootUltragoal.current_phase, 'executing');
|
|
2392
|
+
assert.equal(existsSync(join(stateDir, 'sessions', sessionId, 'deep-interview-state.json')), false);
|
|
2393
|
+
}
|
|
2394
|
+
finally {
|
|
2395
|
+
await rm(cwd, { recursive: true, force: true });
|
|
2396
|
+
}
|
|
2397
|
+
});
|
|
1526
2398
|
it('records ultragoal as a prompt skill with first-class mode state', async () => {
|
|
1527
2399
|
const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-state-ultragoal-'));
|
|
1528
2400
|
const stateDir = join(cwd, '.omx', 'state');
|
|
@@ -1547,19 +2419,27 @@ deepMaxRounds = 21
|
|
|
1547
2419
|
}
|
|
1548
2420
|
});
|
|
1549
2421
|
it('emits a warning when skill-active-state persistence fails', async () => {
|
|
2422
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-state-persist-fail-'));
|
|
1550
2423
|
const warnings = [];
|
|
1551
2424
|
mock.method(console, 'warn', (...args) => {
|
|
1552
2425
|
warnings.push(args);
|
|
1553
2426
|
});
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
2427
|
+
try {
|
|
2428
|
+
const blockingFile = join(cwd, 'state-root-file');
|
|
2429
|
+
await writeFile(blockingFile, 'not a directory');
|
|
2430
|
+
const result = await recordSkillActivation({
|
|
2431
|
+
stateDir: join(blockingFile, 'nested', 'state-dir'),
|
|
2432
|
+
text: 'please run $autopilot',
|
|
2433
|
+
nowIso: '2026-02-25T00:00:00.000Z',
|
|
2434
|
+
});
|
|
2435
|
+
assert.ok(result);
|
|
2436
|
+
assert.equal(result.skill, 'autopilot');
|
|
2437
|
+
assert.equal(warnings.length, 1);
|
|
2438
|
+
assert.match(String(warnings[0][0]), /failed to persist keyword activation state/);
|
|
2439
|
+
}
|
|
2440
|
+
finally {
|
|
2441
|
+
await rm(cwd, { recursive: true, force: true });
|
|
2442
|
+
}
|
|
1563
2443
|
});
|
|
1564
2444
|
it('preserves activated_at for same-skill continuation', async () => {
|
|
1565
2445
|
const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-state-continuation-'));
|
|
@@ -1618,6 +2498,8 @@ deepMaxRounds = 21
|
|
|
1618
2498
|
session_id: 'sess-autopilot',
|
|
1619
2499
|
state: { context_snapshot_path: '.omx/context/existing.md' },
|
|
1620
2500
|
}));
|
|
2501
|
+
await mkdir(join(cwd, '.omx', 'context'), { recursive: true });
|
|
2502
|
+
await writeFile(join(cwd, '.omx', 'context', 'existing.md'), '# existing context');
|
|
1621
2503
|
const result = await recordSkillActivation({
|
|
1622
2504
|
stateDir,
|
|
1623
2505
|
text: 'autopilot keep going',
|
|
@@ -1631,7 +2513,8 @@ deepMaxRounds = 21
|
|
|
1631
2513
|
const modeState = JSON.parse(await readFile(join(stateDir, 'sessions', 'sess-autopilot', 'autopilot-state.json'), 'utf-8'));
|
|
1632
2514
|
assert.equal(modeState.current_phase, 'code-review');
|
|
1633
2515
|
assert.equal(modeState.started_at, '2026-02-25T00:00:00.000Z');
|
|
1634
|
-
assert.equal(modeState.state?.context_snapshot_path,
|
|
2516
|
+
assert.equal(modeState.state?.context_snapshot_path, undefined);
|
|
2517
|
+
assert.equal(modeState.state?.handoff_artifacts?.context_snapshot_path, '.omx/context/existing.md');
|
|
1635
2518
|
}
|
|
1636
2519
|
finally {
|
|
1637
2520
|
await rm(cwd, { recursive: true, force: true });
|
|
@@ -1801,6 +2684,8 @@ deepMaxRounds = 21
|
|
|
1801
2684
|
session_id: 'sess-autopilot-bare',
|
|
1802
2685
|
state: { context_snapshot_path: '.omx/context/autopilot.md' },
|
|
1803
2686
|
}, null, 2));
|
|
2687
|
+
await mkdir(join(cwd, '.omx', 'context'), { recursive: true });
|
|
2688
|
+
await writeFile(join(cwd, '.omx', 'context', 'autopilot.md'), '# autopilot context');
|
|
1804
2689
|
const result = await recordSkillActivation({
|
|
1805
2690
|
stateDir,
|
|
1806
2691
|
text: '\\ keep going now',
|
|
@@ -1813,13 +2698,149 @@ deepMaxRounds = 21
|
|
|
1813
2698
|
assert.equal(result.transition_error, undefined);
|
|
1814
2699
|
const modeState = JSON.parse(await readFile(join(stateDir, 'sessions', 'sess-autopilot-bare', 'autopilot-state.json'), 'utf-8'));
|
|
1815
2700
|
assert.equal(modeState.current_phase, 'code-review');
|
|
1816
|
-
assert.equal(modeState.state?.context_snapshot_path,
|
|
2701
|
+
assert.equal(modeState.state?.context_snapshot_path, undefined);
|
|
2702
|
+
assert.equal(modeState.state?.handoff_artifacts?.context_snapshot_path, '.omx/context/autopilot.md');
|
|
1817
2703
|
assert.equal(existsSync(join(stateDir, 'sessions', 'sess-autopilot-bare', 'ralph-state.json')), false);
|
|
1818
2704
|
}
|
|
1819
2705
|
finally {
|
|
1820
2706
|
await rm(cwd, { recursive: true, force: true });
|
|
1821
2707
|
}
|
|
1822
2708
|
});
|
|
2709
|
+
it('preserves active Autopilot question-wait state on bare continuation', async () => {
|
|
2710
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-state-autopilot-question-wait-'));
|
|
2711
|
+
const stateDir = join(cwd, '.omx', 'state');
|
|
2712
|
+
const sessionId = 'sess-autopilot-question-wait';
|
|
2713
|
+
try {
|
|
2714
|
+
await mkdir(join(stateDir, 'sessions', sessionId), { recursive: true });
|
|
2715
|
+
await writeFile(join(stateDir, 'sessions', sessionId, SKILL_ACTIVE_STATE_FILE), JSON.stringify({
|
|
2716
|
+
version: 1,
|
|
2717
|
+
active: true,
|
|
2718
|
+
skill: 'autopilot',
|
|
2719
|
+
keyword: '$autopilot',
|
|
2720
|
+
phase: 'waiting-for-user',
|
|
2721
|
+
activated_at: '2026-04-19T00:00:00.000Z',
|
|
2722
|
+
updated_at: '2026-04-19T00:10:00.000Z',
|
|
2723
|
+
source: 'keyword-detector',
|
|
2724
|
+
session_id: sessionId,
|
|
2725
|
+
active_skills: [
|
|
2726
|
+
{
|
|
2727
|
+
skill: 'autopilot',
|
|
2728
|
+
phase: 'waiting-for-user',
|
|
2729
|
+
active: true,
|
|
2730
|
+
activated_at: '2026-04-19T00:00:00.000Z',
|
|
2731
|
+
updated_at: '2026-04-19T00:10:00.000Z',
|
|
2732
|
+
session_id: sessionId,
|
|
2733
|
+
},
|
|
2734
|
+
],
|
|
2735
|
+
}, null, 2));
|
|
2736
|
+
await writeFile(join(stateDir, 'sessions', sessionId, 'autopilot-state.json'), JSON.stringify({
|
|
2737
|
+
active: true,
|
|
2738
|
+
mode: 'autopilot',
|
|
2739
|
+
current_phase: 'waiting-for-user',
|
|
2740
|
+
started_at: '2026-04-19T00:00:00.000Z',
|
|
2741
|
+
updated_at: '2026-04-19T00:10:00.000Z',
|
|
2742
|
+
session_id: sessionId,
|
|
2743
|
+
iteration: 4,
|
|
2744
|
+
max_iterations: 10,
|
|
2745
|
+
review_cycle: 2,
|
|
2746
|
+
run_outcome: 'blocked_on_user',
|
|
2747
|
+
lifecycle_outcome: 'askuserQuestion',
|
|
2748
|
+
state: {
|
|
2749
|
+
deep_interview_question: {
|
|
2750
|
+
status: 'waiting_for_user',
|
|
2751
|
+
obligation_id: 'obligation-question-wait',
|
|
2752
|
+
previous_phase: 'deep-interview',
|
|
2753
|
+
},
|
|
2754
|
+
},
|
|
2755
|
+
}, null, 2));
|
|
2756
|
+
const result = await recordSkillActivation({
|
|
2757
|
+
stateDir,
|
|
2758
|
+
text: '\\ keep going now',
|
|
2759
|
+
sessionId,
|
|
2760
|
+
nowIso: '2026-04-19T00:15:00.000Z',
|
|
2761
|
+
});
|
|
2762
|
+
assert.ok(result);
|
|
2763
|
+
assert.equal(result.skill, 'autopilot');
|
|
2764
|
+
const modeState = JSON.parse(await readFile(join(stateDir, 'sessions', sessionId, 'autopilot-state.json'), 'utf-8'));
|
|
2765
|
+
assert.equal(modeState.current_phase, 'waiting-for-user');
|
|
2766
|
+
assert.equal(modeState.iteration, 4);
|
|
2767
|
+
assert.equal(modeState.max_iterations, 10);
|
|
2768
|
+
assert.equal(modeState.review_cycle, 2);
|
|
2769
|
+
assert.equal(modeState.lifecycle_outcome, 'askuserQuestion');
|
|
2770
|
+
assert.equal(modeState.state?.deep_interview_question?.status, 'waiting_for_user');
|
|
2771
|
+
assert.equal(modeState.state?.deep_interview_question?.obligation_id, 'obligation-question-wait');
|
|
2772
|
+
}
|
|
2773
|
+
finally {
|
|
2774
|
+
await rm(cwd, { recursive: true, force: true });
|
|
2775
|
+
}
|
|
2776
|
+
});
|
|
2777
|
+
it('resets terminal Ralph blocked_on_user state when reactivated', async () => {
|
|
2778
|
+
const cases = [
|
|
2779
|
+
{ name: 'phase', phase: 'blocked_on_user', run_outcome: undefined },
|
|
2780
|
+
{ name: 'outcome', phase: 'executing', run_outcome: 'blocked_on_user' },
|
|
2781
|
+
];
|
|
2782
|
+
for (const testCase of cases) {
|
|
2783
|
+
const cwd = await mkdtemp(join(tmpdir(), `omx-keyword-state-ralph-terminal-${testCase.name}-reactivation-`));
|
|
2784
|
+
const stateDir = join(cwd, '.omx', 'state');
|
|
2785
|
+
const sessionId = `sess-ralph-terminal-${testCase.name}`;
|
|
2786
|
+
try {
|
|
2787
|
+
await mkdir(join(stateDir, 'sessions', sessionId), { recursive: true });
|
|
2788
|
+
await writeFile(join(stateDir, 'sessions', sessionId, SKILL_ACTIVE_STATE_FILE), JSON.stringify({
|
|
2789
|
+
version: 1,
|
|
2790
|
+
active: true,
|
|
2791
|
+
skill: 'ralph',
|
|
2792
|
+
keyword: '$ralph',
|
|
2793
|
+
phase: testCase.phase,
|
|
2794
|
+
activated_at: '2026-04-19T00:00:00.000Z',
|
|
2795
|
+
updated_at: '2026-04-19T00:10:00.000Z',
|
|
2796
|
+
source: 'keyword-detector',
|
|
2797
|
+
session_id: sessionId,
|
|
2798
|
+
active_skills: [
|
|
2799
|
+
{
|
|
2800
|
+
skill: 'ralph',
|
|
2801
|
+
phase: testCase.phase,
|
|
2802
|
+
active: true,
|
|
2803
|
+
activated_at: '2026-04-19T00:00:00.000Z',
|
|
2804
|
+
updated_at: '2026-04-19T00:10:00.000Z',
|
|
2805
|
+
session_id: sessionId,
|
|
2806
|
+
},
|
|
2807
|
+
],
|
|
2808
|
+
}, null, 2));
|
|
2809
|
+
await writeFile(join(stateDir, 'sessions', sessionId, 'ralph-state.json'), JSON.stringify({
|
|
2810
|
+
active: false,
|
|
2811
|
+
mode: 'ralph',
|
|
2812
|
+
current_phase: testCase.phase,
|
|
2813
|
+
started_at: '2026-04-19T00:00:00.000Z',
|
|
2814
|
+
completed_at: '2026-04-19T00:10:00.000Z',
|
|
2815
|
+
iteration: 50,
|
|
2816
|
+
max_iterations: 50,
|
|
2817
|
+
...(testCase.run_outcome ? { run_outcome: testCase.run_outcome } : {}),
|
|
2818
|
+
}, null, 2));
|
|
2819
|
+
const result = await recordSkillActivation({
|
|
2820
|
+
stateDir,
|
|
2821
|
+
text: '\\ keep going now',
|
|
2822
|
+
sessionId,
|
|
2823
|
+
nowIso: '2026-04-19T00:15:00.000Z',
|
|
2824
|
+
});
|
|
2825
|
+
assert.ok(result);
|
|
2826
|
+
assert.equal(result.skill, 'ralph');
|
|
2827
|
+
assert.equal(result.phase, 'planning');
|
|
2828
|
+
assert.equal(result.activated_at, '2026-04-19T00:15:00.000Z');
|
|
2829
|
+
assert.equal(result.active_skills?.[0]?.phase, 'planning');
|
|
2830
|
+
assert.equal(result.active_skills?.[0]?.activated_at, '2026-04-19T00:15:00.000Z');
|
|
2831
|
+
const modeState = JSON.parse(await readFile(join(stateDir, 'sessions', sessionId, 'ralph-state.json'), 'utf-8'));
|
|
2832
|
+
assert.equal(modeState.active, true);
|
|
2833
|
+
assert.equal(modeState.current_phase, 'starting');
|
|
2834
|
+
assert.equal(modeState.started_at, '2026-04-19T00:15:00.000Z');
|
|
2835
|
+
assert.equal(modeState.completed_at, undefined);
|
|
2836
|
+
assert.equal(modeState.iteration, 0);
|
|
2837
|
+
assert.equal(modeState.max_iterations, 50);
|
|
2838
|
+
}
|
|
2839
|
+
finally {
|
|
2840
|
+
await rm(cwd, { recursive: true, force: true });
|
|
2841
|
+
}
|
|
2842
|
+
}
|
|
2843
|
+
});
|
|
1823
2844
|
it('routes bare keep-going continuation to the active ralph skill instead of resetting through generic keep-going detection', async () => {
|
|
1824
2845
|
const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-state-ralph-bare-continuation-'));
|
|
1825
2846
|
const stateDir = join(cwd, '.omx', 'state');
|
|
@@ -2113,6 +3134,43 @@ describe('applyRalplanGate', () => {
|
|
|
2113
3134
|
await rm(cwd, { recursive: true, force: true });
|
|
2114
3135
|
}
|
|
2115
3136
|
});
|
|
3137
|
+
it('keeps native-proof execution follow-ups gated when consensus is artifact-only', async () => {
|
|
3138
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-gate-native-required-'));
|
|
3139
|
+
try {
|
|
3140
|
+
const plansDir = join(cwd, '.omx', 'plans');
|
|
3141
|
+
const stateDir = join(cwd, '.omx', 'state');
|
|
3142
|
+
await mkdir(plansDir, { recursive: true });
|
|
3143
|
+
await mkdir(stateDir, { recursive: true });
|
|
3144
|
+
await writeFile(join(plansDir, 'prd-issue-833.md'), '# Approved plan\n\nLaunch hint: omx team 3:executor "Execute approved issue 833 plan"\n');
|
|
3145
|
+
await writeFile(join(plansDir, 'test-spec-issue-833.md'), '# Test spec\n');
|
|
3146
|
+
await writeFile(join(stateDir, 'ralplan-state.json'), JSON.stringify({
|
|
3147
|
+
current_phase: 'complete',
|
|
3148
|
+
planning_complete: true,
|
|
3149
|
+
ralplan_consensus_gate: {
|
|
3150
|
+
complete: true,
|
|
3151
|
+
sequence: ['architect-review', 'critic-review'],
|
|
3152
|
+
ralplan_architect_review: {
|
|
3153
|
+
agent_role: 'architect',
|
|
3154
|
+
verdict: 'approve',
|
|
3155
|
+
iteration: 1,
|
|
3156
|
+
provenance_kind: 'codex_exec',
|
|
3157
|
+
},
|
|
3158
|
+
ralplan_critic_review: {
|
|
3159
|
+
agent_role: 'critic',
|
|
3160
|
+
verdict: 'approve',
|
|
3161
|
+
iteration: 1,
|
|
3162
|
+
provenance_kind: 'codex_exec',
|
|
3163
|
+
},
|
|
3164
|
+
},
|
|
3165
|
+
}));
|
|
3166
|
+
const result = applyRalplanGate(['team'], 'team', { cwd, requireNativeSubagents: true });
|
|
3167
|
+
assert.equal(result.gateApplied, true);
|
|
3168
|
+
assert.deepEqual(result.keywords, ['ralplan']);
|
|
3169
|
+
}
|
|
3170
|
+
finally {
|
|
3171
|
+
await rm(cwd, { recursive: true, force: true });
|
|
3172
|
+
}
|
|
3173
|
+
});
|
|
2116
3174
|
it('does not re-enter ralplan for a short approved ralph follow-up with durable consensus', async () => {
|
|
2117
3175
|
const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-gate-followup-ralph-'));
|
|
2118
3176
|
try {
|