oh-my-codex 0.20.3 → 0.20.5
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 +35 -18
- package/Cargo.toml +1 -1
- package/README.md +2 -2
- package/crates/omx-runtime/Cargo.toml +3 -0
- package/crates/omx-runtime/src/main.rs +390 -0
- package/crates/omx-runtime/tests/execution.rs +264 -0
- package/dist/adapt/__tests__/herdr-target.test.d.ts +2 -0
- package/dist/adapt/__tests__/herdr-target.test.d.ts.map +1 -0
- package/dist/adapt/__tests__/herdr-target.test.js +72 -0
- package/dist/adapt/__tests__/herdr-target.test.js.map +1 -0
- package/dist/adapt/contracts.d.ts +1 -1
- package/dist/adapt/contracts.d.ts.map +1 -1
- package/dist/adapt/contracts.js +1 -1
- package/dist/adapt/contracts.js.map +1 -1
- package/dist/adapt/herdr/__tests__/authority.test.d.ts +2 -0
- package/dist/adapt/herdr/__tests__/authority.test.d.ts.map +1 -0
- package/dist/adapt/herdr/__tests__/authority.test.js +49 -0
- package/dist/adapt/herdr/__tests__/authority.test.js.map +1 -0
- package/dist/adapt/herdr/__tests__/bridge.test.d.ts +2 -0
- package/dist/adapt/herdr/__tests__/bridge.test.d.ts.map +1 -0
- package/dist/adapt/herdr/__tests__/bridge.test.js +176 -0
- package/dist/adapt/herdr/__tests__/bridge.test.js.map +1 -0
- package/dist/adapt/herdr/__tests__/sanitize.test.d.ts +2 -0
- package/dist/adapt/herdr/__tests__/sanitize.test.d.ts.map +1 -0
- package/dist/adapt/herdr/__tests__/sanitize.test.js +45 -0
- package/dist/adapt/herdr/__tests__/sanitize.test.js.map +1 -0
- package/dist/adapt/herdr/__tests__/semantic.test.d.ts +2 -0
- package/dist/adapt/herdr/__tests__/semantic.test.d.ts.map +1 -0
- package/dist/adapt/herdr/__tests__/semantic.test.js +130 -0
- package/dist/adapt/herdr/__tests__/semantic.test.js.map +1 -0
- package/dist/adapt/herdr/__tests__/seq-store.test.d.ts +2 -0
- package/dist/adapt/herdr/__tests__/seq-store.test.d.ts.map +1 -0
- package/dist/adapt/herdr/__tests__/seq-store.test.js +54 -0
- package/dist/adapt/herdr/__tests__/seq-store.test.js.map +1 -0
- package/dist/adapt/herdr/__tests__/socket-live.test.d.ts +2 -0
- package/dist/adapt/herdr/__tests__/socket-live.test.d.ts.map +1 -0
- package/dist/adapt/herdr/__tests__/socket-live.test.js +104 -0
- package/dist/adapt/herdr/__tests__/socket-live.test.js.map +1 -0
- package/dist/adapt/herdr/__tests__/team-map.test.d.ts +2 -0
- package/dist/adapt/herdr/__tests__/team-map.test.d.ts.map +1 -0
- package/dist/adapt/herdr/__tests__/team-map.test.js +46 -0
- package/dist/adapt/herdr/__tests__/team-map.test.js.map +1 -0
- package/dist/adapt/herdr/__tests__/transport.test.d.ts +2 -0
- package/dist/adapt/herdr/__tests__/transport.test.d.ts.map +1 -0
- package/dist/adapt/herdr/__tests__/transport.test.js +160 -0
- package/dist/adapt/herdr/__tests__/transport.test.js.map +1 -0
- package/dist/adapt/herdr/__tests__/wiring.test.d.ts +2 -0
- package/dist/adapt/herdr/__tests__/wiring.test.d.ts.map +1 -0
- package/dist/adapt/herdr/__tests__/wiring.test.js +118 -0
- package/dist/adapt/herdr/__tests__/wiring.test.js.map +1 -0
- package/dist/adapt/herdr/authority.d.ts +34 -0
- package/dist/adapt/herdr/authority.d.ts.map +1 -0
- package/dist/adapt/herdr/authority.js +71 -0
- package/dist/adapt/herdr/authority.js.map +1 -0
- package/dist/adapt/herdr/bridge.d.ts +84 -0
- package/dist/adapt/herdr/bridge.d.ts.map +1 -0
- package/dist/adapt/herdr/bridge.js +222 -0
- package/dist/adapt/herdr/bridge.js.map +1 -0
- package/dist/adapt/herdr/sanitize.d.ts +20 -0
- package/dist/adapt/herdr/sanitize.d.ts.map +1 -0
- package/dist/adapt/herdr/sanitize.js +70 -0
- package/dist/adapt/herdr/sanitize.js.map +1 -0
- package/dist/adapt/herdr/semantic.d.ts +67 -0
- package/dist/adapt/herdr/semantic.d.ts.map +1 -0
- package/dist/adapt/herdr/semantic.js +170 -0
- package/dist/adapt/herdr/semantic.js.map +1 -0
- package/dist/adapt/herdr/seq-store.d.ts +32 -0
- package/dist/adapt/herdr/seq-store.d.ts.map +1 -0
- package/dist/adapt/herdr/seq-store.js +126 -0
- package/dist/adapt/herdr/seq-store.js.map +1 -0
- package/dist/adapt/herdr/team-map.d.ts +30 -0
- package/dist/adapt/herdr/team-map.d.ts.map +1 -0
- package/dist/adapt/herdr/team-map.js +37 -0
- package/dist/adapt/herdr/team-map.js.map +1 -0
- package/dist/adapt/herdr/transport.d.ts +115 -0
- package/dist/adapt/herdr/transport.d.ts.map +1 -0
- package/dist/adapt/herdr/transport.js +260 -0
- package/dist/adapt/herdr/transport.js.map +1 -0
- package/dist/adapt/herdr/wiring.d.ts +24 -0
- package/dist/adapt/herdr/wiring.d.ts.map +1 -0
- package/dist/adapt/herdr/wiring.js +62 -0
- package/dist/adapt/herdr/wiring.js.map +1 -0
- package/dist/adapt/herdr.d.ts +19 -0
- package/dist/adapt/herdr.d.ts.map +1 -0
- package/dist/adapt/herdr.js +115 -0
- package/dist/adapt/herdr.js.map +1 -0
- package/dist/adapt/index.d.ts +4 -0
- package/dist/adapt/index.d.ts.map +1 -1
- package/dist/adapt/index.js +48 -0
- package/dist/adapt/index.js.map +1 -1
- package/dist/adapt/registry.d.ts.map +1 -1
- package/dist/adapt/registry.js +11 -0
- package/dist/adapt/registry.js.map +1 -1
- package/dist/auth/__tests__/hotswap.test.js +60 -1
- package/dist/auth/__tests__/hotswap.test.js.map +1 -1
- package/dist/auth/__tests__/storage.test.js +33 -1
- package/dist/auth/__tests__/storage.test.js.map +1 -1
- package/dist/auth/hotswap.d.ts +16 -2
- package/dist/auth/hotswap.d.ts.map +1 -1
- package/dist/auth/hotswap.js +22 -7
- package/dist/auth/hotswap.js.map +1 -1
- package/dist/auth/storage.js +1 -1
- package/dist/auth/storage.js.map +1 -1
- package/dist/autopilot/__tests__/ralplan-gate.test.js +86 -1333
- package/dist/autopilot/__tests__/ralplan-gate.test.js.map +1 -1
- package/dist/autopilot/ralplan-gate.d.ts.map +1 -1
- package/dist/autopilot/ralplan-gate.js +7 -6
- package/dist/autopilot/ralplan-gate.js.map +1 -1
- package/dist/cli/__tests__/agents-init.test.js +4 -23
- package/dist/cli/__tests__/agents-init.test.js.map +1 -1
- package/dist/cli/__tests__/api.test.js +61 -24
- package/dist/cli/__tests__/api.test.js.map +1 -1
- package/dist/cli/__tests__/auth.test.js +3 -1
- package/dist/cli/__tests__/auth.test.js.map +1 -1
- package/dist/cli/__tests__/codex-feature-probe.test.js +66 -1
- package/dist/cli/__tests__/codex-feature-probe.test.js.map +1 -1
- package/dist/cli/__tests__/codex-plugin-layout.test.js +70 -2
- package/dist/cli/__tests__/codex-plugin-layout.test.js.map +1 -1
- package/dist/cli/__tests__/detached-leader-teardown.test.d.ts +2 -0
- package/dist/cli/__tests__/detached-leader-teardown.test.d.ts.map +1 -0
- package/dist/cli/__tests__/detached-leader-teardown.test.js +473 -0
- package/dist/cli/__tests__/detached-leader-teardown.test.js.map +1 -0
- package/dist/cli/__tests__/doctor-process-identity-readiness.test.d.ts +2 -0
- package/dist/cli/__tests__/doctor-process-identity-readiness.test.d.ts.map +1 -0
- package/dist/cli/__tests__/doctor-process-identity-readiness.test.js +103 -0
- package/dist/cli/__tests__/doctor-process-identity-readiness.test.js.map +1 -0
- package/dist/cli/__tests__/doctor-state-root-binding.test.d.ts +2 -0
- package/dist/cli/__tests__/doctor-state-root-binding.test.d.ts.map +1 -0
- package/dist/cli/__tests__/doctor-state-root-binding.test.js +139 -0
- package/dist/cli/__tests__/doctor-state-root-binding.test.js.map +1 -0
- package/dist/cli/__tests__/doctor-warning-copy.test.js +35 -3
- package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
- package/dist/cli/__tests__/exec.test.js +75 -0
- package/dist/cli/__tests__/exec.test.js.map +1 -1
- package/dist/cli/__tests__/index.test.js +621 -460
- package/dist/cli/__tests__/index.test.js.map +1 -1
- package/dist/cli/__tests__/launch-fallback.test.js +1020 -143
- package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
- package/dist/cli/__tests__/mcp-parity.test.js +4 -3
- package/dist/cli/__tests__/mcp-parity.test.js.map +1 -1
- package/dist/cli/__tests__/native-assets.test.js +398 -16
- package/dist/cli/__tests__/native-assets.test.js.map +1 -1
- package/dist/cli/__tests__/native-hook-claim-journal.test.js +53 -5
- package/dist/cli/__tests__/native-hook-claim-journal.test.js.map +1 -1
- package/dist/cli/__tests__/ralph.test.js +2 -2
- package/dist/cli/__tests__/ralph.test.js.map +1 -1
- package/dist/cli/__tests__/ralplan-bootstrap-3181.test.js +57 -57
- package/dist/cli/__tests__/ralplan-bootstrap-3181.test.js.map +1 -1
- package/dist/cli/__tests__/ralplan-child-binding-3212.test.d.ts +2 -0
- package/dist/cli/__tests__/ralplan-child-binding-3212.test.d.ts.map +1 -0
- package/dist/cli/__tests__/ralplan-child-binding-3212.test.js +56 -0
- package/dist/cli/__tests__/ralplan-child-binding-3212.test.js.map +1 -0
- package/dist/cli/__tests__/ralplan.test.js +84 -12
- package/dist/cli/__tests__/ralplan.test.js.map +1 -1
- package/dist/cli/__tests__/session-scoped-runtime.test.js +1118 -43
- package/dist/cli/__tests__/session-scoped-runtime.test.js.map +1 -1
- package/dist/cli/__tests__/setup-agents-overwrite.test.js +8 -55
- package/dist/cli/__tests__/setup-agents-overwrite.test.js.map +1 -1
- package/dist/cli/__tests__/setup-install-mode.test.js +183 -4
- package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
- package/dist/cli/__tests__/sparkshell-cli.test.js +93 -41
- package/dist/cli/__tests__/sparkshell-cli.test.js.map +1 -1
- package/dist/cli/__tests__/team.test.js +45 -1
- package/dist/cli/__tests__/team.test.js.map +1 -1
- package/dist/cli/__tests__/ultragoal.test.js +117 -2
- package/dist/cli/__tests__/ultragoal.test.js.map +1 -1
- package/dist/cli/__tests__/uninstall.test.js +50 -5
- package/dist/cli/__tests__/uninstall.test.js.map +1 -1
- package/dist/cli/__tests__/update.test.js +669 -177
- package/dist/cli/__tests__/update.test.js.map +1 -1
- package/dist/cli/__tests__/windows-popup-loop-contract.test.js +32 -2
- package/dist/cli/__tests__/windows-popup-loop-contract.test.js.map +1 -1
- package/dist/cli/api.d.ts.map +1 -1
- package/dist/cli/api.js +9 -10
- package/dist/cli/api.js.map +1 -1
- package/dist/cli/codex-feature-probe.d.ts +16 -0
- package/dist/cli/codex-feature-probe.d.ts.map +1 -1
- package/dist/cli/codex-feature-probe.js +44 -0
- package/dist/cli/codex-feature-probe.js.map +1 -1
- package/dist/cli/codex-home.d.ts.map +1 -1
- package/dist/cli/codex-home.js +7 -4
- package/dist/cli/codex-home.js.map +1 -1
- package/dist/cli/doctor.d.ts +17 -0
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +277 -3
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/index.d.ts +231 -12
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +2298 -735
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/native-assets.d.ts +25 -0
- package/dist/cli/native-assets.d.ts.map +1 -1
- package/dist/cli/native-assets.js +448 -51
- package/dist/cli/native-assets.js.map +1 -1
- package/dist/cli/native-hook-claim-journal.d.ts +8 -4
- package/dist/cli/native-hook-claim-journal.d.ts.map +1 -1
- package/dist/cli/native-hook-claim-journal.js +30 -8
- package/dist/cli/native-hook-claim-journal.js.map +1 -1
- package/dist/cli/package-manager-ownership.d.ts +63 -0
- package/dist/cli/package-manager-ownership.d.ts.map +1 -0
- package/dist/cli/package-manager-ownership.js +323 -0
- package/dist/cli/package-manager-ownership.js.map +1 -0
- package/dist/cli/ralph.js +2 -2
- package/dist/cli/ralph.js.map +1 -1
- package/dist/cli/ralplan.d.ts +4 -2
- package/dist/cli/ralplan.d.ts.map +1 -1
- package/dist/cli/ralplan.js +76 -34
- package/dist/cli/ralplan.js.map +1 -1
- package/dist/cli/setup-preferences.d.ts +15 -0
- package/dist/cli/setup-preferences.d.ts.map +1 -1
- package/dist/cli/setup-preferences.js +27 -1
- 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 +55 -32
- package/dist/cli/setup.js.map +1 -1
- package/dist/cli/sparkshell.d.ts.map +1 -1
- package/dist/cli/sparkshell.js +52 -9
- package/dist/cli/sparkshell.js.map +1 -1
- package/dist/cli/ultragoal.d.ts.map +1 -1
- package/dist/cli/ultragoal.js +2 -2
- package/dist/cli/ultragoal.js.map +1 -1
- package/dist/cli/uninstall.d.ts.map +1 -1
- package/dist/cli/uninstall.js +27 -28
- package/dist/cli/uninstall.js.map +1 -1
- package/dist/cli/update-worker.d.ts +2 -0
- package/dist/cli/update-worker.d.ts.map +1 -0
- package/dist/cli/update-worker.js +142 -0
- package/dist/cli/update-worker.js.map +1 -0
- package/dist/cli/update.d.ts +14 -22
- package/dist/cli/update.d.ts.map +1 -1
- package/dist/cli/update.js +192 -241
- package/dist/cli/update.js.map +1 -1
- package/dist/config/__tests__/generator-idempotent.test.js +173 -29
- package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
- package/dist/config/__tests__/generator-notify.test.js +3 -1
- package/dist/config/__tests__/generator-notify.test.js.map +1 -1
- package/dist/config/generator.d.ts +25 -3
- package/dist/config/generator.d.ts.map +1 -1
- package/dist/config/generator.js +113 -35
- package/dist/config/generator.js.map +1 -1
- package/dist/goal-workflows/__tests__/codex-goal-snapshot.test.js +25 -0
- package/dist/goal-workflows/__tests__/codex-goal-snapshot.test.js.map +1 -1
- package/dist/goal-workflows/codex-goal-snapshot.d.ts +1 -1
- package/dist/goal-workflows/codex-goal-snapshot.d.ts.map +1 -1
- package/dist/goal-workflows/codex-goal-snapshot.js +2 -0
- package/dist/goal-workflows/codex-goal-snapshot.js.map +1 -1
- package/dist/hooks/__tests__/agents-overlay.test.js +3 -1
- package/dist/hooks/__tests__/agents-overlay.test.js.map +1 -1
- package/dist/hooks/__tests__/autopilot-skill-contract.test.js +27 -4
- package/dist/hooks/__tests__/autopilot-skill-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/issue-3293-autopilot-handoff.test.d.ts +2 -0
- package/dist/hooks/__tests__/issue-3293-autopilot-handoff.test.d.ts.map +1 -0
- package/dist/hooks/__tests__/issue-3293-autopilot-handoff.test.js +172 -0
- package/dist/hooks/__tests__/issue-3293-autopilot-handoff.test.js.map +1 -0
- package/dist/hooks/__tests__/keyword-detector.test.js +172 -33
- package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-fallback-watcher.test.js +173 -11
- package/dist/hooks/__tests__/notify-fallback-watcher.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-all-workers-idle.test.js +1 -0
- package/dist/hooks/__tests__/notify-hook-all-workers-idle.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js +65 -17
- package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-cross-worktree-heartbeat.test.js +27 -0
- package/dist/hooks/__tests__/notify-hook-cross-worktree-heartbeat.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-managed-tmux.test.js +10 -118
- package/dist/hooks/__tests__/notify-hook-managed-tmux.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-regression-205.test.js +7 -0
- package/dist/hooks/__tests__/notify-hook-regression-205.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js +2 -2
- package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js +4 -1
- package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-team-worker-fail-closed.test.js +2 -1
- package/dist/hooks/__tests__/notify-hook-team-worker-fail-closed.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-worker-idle.test.js +1 -0
- package/dist/hooks/__tests__/notify-hook-worker-idle.test.js.map +1 -1
- package/dist/hooks/__tests__/prompt-guidance-contract.test.js +9 -0
- package/dist/hooks/__tests__/prompt-guidance-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/session.test.js +2976 -557
- package/dist/hooks/__tests__/session.test.js.map +1 -1
- package/dist/hooks/agents-overlay.js +1 -1
- package/dist/hooks/agents-overlay.js.map +1 -1
- package/dist/hooks/extensibility/__tests__/dispatcher.test.js +108 -2
- package/dist/hooks/extensibility/__tests__/dispatcher.test.js.map +1 -1
- package/dist/hooks/extensibility/__tests__/sdk.test.js +151 -1
- package/dist/hooks/extensibility/__tests__/sdk.test.js.map +1 -1
- package/dist/hooks/extensibility/dispatcher.d.ts.map +1 -1
- package/dist/hooks/extensibility/dispatcher.js +20 -3
- package/dist/hooks/extensibility/dispatcher.js.map +1 -1
- package/dist/hooks/extensibility/plugin-runner.js +1 -0
- package/dist/hooks/extensibility/plugin-runner.js.map +1 -1
- package/dist/hooks/extensibility/runtime.d.ts.map +1 -1
- package/dist/hooks/extensibility/runtime.js.map +1 -1
- package/dist/hooks/extensibility/sdk/paths.d.ts +1 -1
- package/dist/hooks/extensibility/sdk/paths.d.ts.map +1 -1
- package/dist/hooks/extensibility/sdk/paths.js +2 -2
- package/dist/hooks/extensibility/sdk/paths.js.map +1 -1
- package/dist/hooks/extensibility/sdk/runtime-state.d.ts +1 -1
- package/dist/hooks/extensibility/sdk/runtime-state.d.ts.map +1 -1
- package/dist/hooks/extensibility/sdk/runtime-state.js +55 -15
- package/dist/hooks/extensibility/sdk/runtime-state.js.map +1 -1
- package/dist/hooks/extensibility/sdk.d.ts +1 -0
- package/dist/hooks/extensibility/sdk.d.ts.map +1 -1
- package/dist/hooks/extensibility/sdk.js +1 -1
- package/dist/hooks/extensibility/sdk.js.map +1 -1
- package/dist/hooks/extensibility/types.d.ts +7 -0
- package/dist/hooks/extensibility/types.d.ts.map +1 -1
- package/dist/hooks/keyword-detector.d.ts +10 -3
- package/dist/hooks/keyword-detector.d.ts.map +1 -1
- package/dist/hooks/keyword-detector.js +116 -20
- package/dist/hooks/keyword-detector.js.map +1 -1
- package/dist/hooks/session.d.ts +180 -14
- package/dist/hooks/session.d.ts.map +1 -1
- package/dist/hooks/session.js +1402 -281
- package/dist/hooks/session.js.map +1 -1
- package/dist/hud/__tests__/hud-tmux-injection.test.js +6 -6
- package/dist/hud/__tests__/hud-tmux-injection.test.js.map +1 -1
- package/dist/hud/__tests__/index.test.js +26 -16
- package/dist/hud/__tests__/index.test.js.map +1 -1
- package/dist/hud/__tests__/reconcile.test.js +83 -78
- package/dist/hud/__tests__/reconcile.test.js.map +1 -1
- package/dist/hud/__tests__/state.test.js +36 -31
- package/dist/hud/__tests__/state.test.js.map +1 -1
- package/dist/hud/__tests__/tmux.test.js +155 -198
- package/dist/hud/__tests__/tmux.test.js.map +1 -1
- package/dist/hud/tmux.d.ts +42 -4
- package/dist/hud/tmux.d.ts.map +1 -1
- package/dist/hud/tmux.js +1017 -139
- package/dist/hud/tmux.js.map +1 -1
- package/dist/leader/__tests__/contract.test.js +71 -34
- package/dist/leader/__tests__/contract.test.js.map +1 -1
- package/dist/leader/contract.d.ts +1 -6
- package/dist/leader/contract.d.ts.map +1 -1
- package/dist/leader/contract.js +25 -24
- package/dist/leader/contract.js.map +1 -1
- package/dist/mcp/__tests__/hermes-bridge.test.js +17 -0
- package/dist/mcp/__tests__/hermes-bridge.test.js.map +1 -1
- package/dist/mcp/__tests__/server-lifecycle.test.js +4 -4
- package/dist/mcp/__tests__/state-paths.test.js +735 -4
- package/dist/mcp/__tests__/state-paths.test.js.map +1 -1
- package/dist/mcp/__tests__/state-server.test.js +1 -1
- package/dist/mcp/__tests__/state-server.test.js.map +1 -1
- package/dist/mcp/__tests__/trace-server.test.js +2 -2
- package/dist/mcp/__tests__/trace-server.test.js.map +1 -1
- package/dist/mcp/hermes-bridge.d.ts.map +1 -1
- package/dist/mcp/hermes-bridge.js +2 -2
- package/dist/mcp/hermes-bridge.js.map +1 -1
- package/dist/mcp/state-paths.d.ts +78 -4
- package/dist/mcp/state-paths.d.ts.map +1 -1
- package/dist/mcp/state-paths.js +543 -40
- package/dist/mcp/state-paths.js.map +1 -1
- package/dist/mcp/state-server.d.ts +4 -4
- package/dist/mcp/state-server.d.ts.map +1 -1
- package/dist/modes/__tests__/base-autopilot-gates.test.js +36 -7
- package/dist/modes/__tests__/base-autopilot-gates.test.js.map +1 -1
- package/dist/modes/__tests__/base-multi-state-compat.test.js +1 -1
- package/dist/modes/__tests__/base-multi-state-compat.test.js.map +1 -1
- package/dist/modes/__tests__/base-session-scope.test.js +17 -8
- package/dist/modes/__tests__/base-session-scope.test.js.map +1 -1
- package/dist/modes/base.d.ts +5 -5
- package/dist/modes/base.d.ts.map +1 -1
- package/dist/modes/base.js +76 -21
- package/dist/modes/base.js.map +1 -1
- package/dist/native-assets/archive.d.ts +17 -0
- package/dist/native-assets/archive.d.ts.map +1 -0
- package/dist/native-assets/archive.js +238 -0
- package/dist/native-assets/archive.js.map +1 -0
- package/dist/native-assets/policy.d.ts +42 -0
- package/dist/native-assets/policy.d.ts.map +1 -0
- package/dist/native-assets/policy.js +155 -0
- package/dist/native-assets/policy.js.map +1 -0
- package/dist/pipeline/__tests__/orchestrator.test.js +162 -40
- package/dist/pipeline/__tests__/orchestrator.test.js.map +1 -1
- package/dist/pipeline/__tests__/stages.test.js +66 -149
- package/dist/pipeline/__tests__/stages.test.js.map +1 -1
- package/dist/pipeline/orchestrator.d.ts.map +1 -1
- package/dist/pipeline/orchestrator.js +124 -18
- package/dist/pipeline/orchestrator.js.map +1 -1
- package/dist/pipeline/stages/ralplan.d.ts.map +1 -1
- package/dist/pipeline/stages/ralplan.js +36 -8
- package/dist/pipeline/stages/ralplan.js.map +1 -1
- package/dist/ralplan/__tests__/consensus-gate.test.js +70 -1437
- package/dist/ralplan/__tests__/consensus-gate.test.js.map +1 -1
- package/dist/ralplan/__tests__/documented-leader-preflight.test.js +328 -39
- package/dist/ralplan/__tests__/documented-leader-preflight.test.js.map +1 -1
- package/dist/ralplan/__tests__/runtime.test.js +82 -102
- 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 +40 -304
- package/dist/ralplan/consensus-gate.js.map +1 -1
- package/dist/ralplan/documented-leader-preflight.d.ts +15 -8
- package/dist/ralplan/documented-leader-preflight.d.ts.map +1 -1
- package/dist/ralplan/documented-leader-preflight.js +356 -54
- package/dist/ralplan/documented-leader-preflight.js.map +1 -1
- package/dist/ralplan/runtime.d.ts +1 -1
- package/dist/ralplan/runtime.d.ts.map +1 -1
- package/dist/ralplan/runtime.js +68 -173
- package/dist/ralplan/runtime.js.map +1 -1
- package/dist/runtime/run-state.d.ts +5 -1
- package/dist/runtime/run-state.d.ts.map +1 -1
- package/dist/runtime/run-state.js +15 -8
- package/dist/runtime/run-state.js.map +1 -1
- package/dist/scripts/__tests__/codex-native-hook.test.js +14071 -8859
- package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
- package/dist/scripts/__tests__/issue-3293-callsite-parity.test.d.ts +2 -0
- package/dist/scripts/__tests__/issue-3293-callsite-parity.test.d.ts.map +1 -0
- package/dist/scripts/__tests__/issue-3293-callsite-parity.test.js +225 -0
- package/dist/scripts/__tests__/issue-3293-callsite-parity.test.js.map +1 -0
- package/dist/scripts/__tests__/issue-3293-cancel-transaction.test.d.ts +2 -0
- package/dist/scripts/__tests__/issue-3293-cancel-transaction.test.d.ts.map +1 -0
- package/dist/scripts/__tests__/issue-3293-cancel-transaction.test.js +188 -0
- package/dist/scripts/__tests__/issue-3293-cancel-transaction.test.js.map +1 -0
- package/dist/scripts/__tests__/issue-3293-hook-owned-cancel.test.d.ts +2 -0
- package/dist/scripts/__tests__/issue-3293-hook-owned-cancel.test.d.ts.map +1 -0
- package/dist/scripts/__tests__/issue-3293-hook-owned-cancel.test.js +259 -0
- package/dist/scripts/__tests__/issue-3293-hook-owned-cancel.test.js.map +1 -0
- package/dist/scripts/__tests__/issue-3311-ultragoal-native-outside-tmux.test.d.ts +2 -0
- package/dist/scripts/__tests__/issue-3311-ultragoal-native-outside-tmux.test.d.ts.map +1 -0
- package/dist/scripts/__tests__/issue-3311-ultragoal-native-outside-tmux.test.js +491 -0
- package/dist/scripts/__tests__/issue-3311-ultragoal-native-outside-tmux.test.js.map +1 -0
- package/dist/scripts/__tests__/issue-3358-ultragoal-cancel-lifecycle.test.d.ts +2 -0
- package/dist/scripts/__tests__/issue-3358-ultragoal-cancel-lifecycle.test.d.ts.map +1 -0
- package/dist/scripts/__tests__/issue-3358-ultragoal-cancel-lifecycle.test.js +708 -0
- package/dist/scripts/__tests__/issue-3358-ultragoal-cancel-lifecycle.test.js.map +1 -0
- package/dist/scripts/__tests__/issue-3369-autopilot-ralplan-state-machine.test.d.ts +2 -0
- package/dist/scripts/__tests__/issue-3369-autopilot-ralplan-state-machine.test.d.ts.map +1 -0
- package/dist/scripts/__tests__/issue-3369-autopilot-ralplan-state-machine.test.js +208 -0
- package/dist/scripts/__tests__/issue-3369-autopilot-ralplan-state-machine.test.js.map +1 -0
- package/dist/scripts/__tests__/notify-state-io.test.js +26 -0
- package/dist/scripts/__tests__/notify-state-io.test.js.map +1 -1
- package/dist/scripts/__tests__/notify-tmux-injection.test.js +23 -1
- package/dist/scripts/__tests__/notify-tmux-injection.test.js.map +1 -1
- package/dist/scripts/__tests__/postinstall.test.js +19 -1
- package/dist/scripts/__tests__/postinstall.test.js.map +1 -1
- package/dist/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.js +25 -8
- package/dist/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.js.map +1 -1
- package/dist/scripts/__tests__/role-intent-durable-recovery-3181.test.js +60 -25
- package/dist/scripts/__tests__/role-intent-durable-recovery-3181.test.js.map +1 -1
- package/dist/scripts/__tests__/smoke-packed-install.test.js +155 -16
- package/dist/scripts/__tests__/smoke-packed-install.test.js.map +1 -1
- package/dist/scripts/__tests__/verify-native-release-assets.test.d.ts +2 -0
- package/dist/scripts/__tests__/verify-native-release-assets.test.d.ts.map +1 -0
- package/dist/scripts/__tests__/verify-native-release-assets.test.js +235 -0
- package/dist/scripts/__tests__/verify-native-release-assets.test.js.map +1 -0
- package/dist/scripts/codex-native-hook.d.ts +50 -1
- package/dist/scripts/codex-native-hook.d.ts.map +1 -1
- package/dist/scripts/codex-native-hook.js +3559 -451
- package/dist/scripts/codex-native-hook.js.map +1 -1
- package/dist/scripts/codex-native-pre-post.d.ts +9 -0
- package/dist/scripts/codex-native-pre-post.d.ts.map +1 -1
- package/dist/scripts/codex-native-pre-post.js +35 -12
- package/dist/scripts/codex-native-pre-post.js.map +1 -1
- package/dist/scripts/generate-native-release-manifest.js +80 -51
- package/dist/scripts/generate-native-release-manifest.js.map +1 -1
- package/dist/scripts/notify-fallback-watcher.js +28 -39
- package/dist/scripts/notify-fallback-watcher.js.map +1 -1
- package/dist/scripts/notify-hook/state-io.d.ts.map +1 -1
- package/dist/scripts/notify-hook/state-io.js +5 -0
- 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 +32 -3
- 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 +23 -3
- package/dist/scripts/notify-hook/team-worker-stop.js.map +1 -1
- package/dist/scripts/notify-hook/team-worker.d.ts.map +1 -1
- package/dist/scripts/notify-hook/team-worker.js +45 -5
- package/dist/scripts/notify-hook/team-worker.js.map +1 -1
- package/dist/scripts/notify-hook/tmux-injection.d.ts +1 -0
- package/dist/scripts/notify-hook/tmux-injection.d.ts.map +1 -1
- package/dist/scripts/notify-hook/tmux-injection.js +4 -1
- package/dist/scripts/notify-hook/tmux-injection.js.map +1 -1
- package/dist/scripts/notify-hook.js +78 -82
- package/dist/scripts/notify-hook.js.map +1 -1
- package/dist/scripts/postinstall-advisory.d.ts +14 -0
- package/dist/scripts/postinstall-advisory.d.ts.map +1 -0
- package/dist/scripts/postinstall-advisory.js +49 -0
- package/dist/scripts/postinstall-advisory.js.map +1 -0
- package/dist/scripts/postinstall.d.ts +1 -1
- package/dist/scripts/postinstall.d.ts.map +1 -1
- package/dist/scripts/postinstall.js +1 -1
- package/dist/scripts/postinstall.js.map +1 -1
- package/dist/scripts/smoke-packed-install.d.ts +32 -1
- package/dist/scripts/smoke-packed-install.d.ts.map +1 -1
- package/dist/scripts/smoke-packed-install.js +413 -60
- package/dist/scripts/smoke-packed-install.js.map +1 -1
- package/dist/scripts/sync-plugin-mirror.d.ts.map +1 -1
- package/dist/scripts/sync-plugin-mirror.js +16 -1
- package/dist/scripts/sync-plugin-mirror.js.map +1 -1
- package/dist/scripts/verify-native-release-assets.js +37 -23
- package/dist/scripts/verify-native-release-assets.js.map +1 -1
- package/dist/state/__tests__/mode-state-context.test.js +12 -5
- package/dist/state/__tests__/mode-state-context.test.js.map +1 -1
- package/dist/state/__tests__/operations.test.js +460 -850
- package/dist/state/__tests__/operations.test.js.map +1 -1
- package/dist/state/__tests__/skill-active.test.js +400 -2
- package/dist/state/__tests__/skill-active.test.js.map +1 -1
- package/dist/state/mode-state-context.d.ts +3 -0
- package/dist/state/mode-state-context.d.ts.map +1 -1
- package/dist/state/mode-state-context.js +27 -13
- package/dist/state/mode-state-context.js.map +1 -1
- package/dist/state/operations.d.ts +3 -0
- package/dist/state/operations.d.ts.map +1 -1
- package/dist/state/operations.js +144 -40
- package/dist/state/operations.js.map +1 -1
- package/dist/state/skill-active.d.ts +18 -1
- package/dist/state/skill-active.d.ts.map +1 -1
- package/dist/state/skill-active.js +420 -23
- package/dist/state/skill-active.js.map +1 -1
- package/dist/state/workflow-transition-reconcile.d.ts +3 -0
- package/dist/state/workflow-transition-reconcile.d.ts.map +1 -1
- package/dist/state/workflow-transition-reconcile.js +10 -4
- 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 +3 -1
- package/dist/state/workflow-transition.js.map +1 -1
- package/dist/subagents/__tests__/leader-bootstrap-3181.test.js +27 -0
- package/dist/subagents/__tests__/leader-bootstrap-3181.test.js.map +1 -1
- package/dist/subagents/__tests__/tracker.test.js +837 -1048
- package/dist/subagents/__tests__/tracker.test.js.map +1 -1
- package/dist/subagents/tracker.d.ts +64 -60
- package/dist/subagents/tracker.d.ts.map +1 -1
- package/dist/subagents/tracker.js +809 -358
- package/dist/subagents/tracker.js.map +1 -1
- package/dist/team/__tests__/delivery-e2e-smoke.test.js +6 -7
- package/dist/team/__tests__/delivery-e2e-smoke.test.js.map +1 -1
- package/dist/team/__tests__/mcp-comm.test.js +104 -3
- package/dist/team/__tests__/mcp-comm.test.js.map +1 -1
- package/dist/team/__tests__/notice-ledger.test.d.ts +2 -0
- package/dist/team/__tests__/notice-ledger.test.d.ts.map +1 -0
- package/dist/team/__tests__/notice-ledger.test.js +131 -0
- package/dist/team/__tests__/notice-ledger.test.js.map +1 -0
- package/dist/team/__tests__/pane-teardown-residue-e2e.test.d.ts +2 -0
- package/dist/team/__tests__/pane-teardown-residue-e2e.test.d.ts.map +1 -0
- package/dist/team/__tests__/pane-teardown-residue-e2e.test.js +410 -0
- package/dist/team/__tests__/pane-teardown-residue-e2e.test.js.map +1 -0
- package/dist/team/__tests__/phase-controller.test.js +20 -0
- package/dist/team/__tests__/phase-controller.test.js.map +1 -1
- package/dist/team/__tests__/runtime.test.js +725 -59
- package/dist/team/__tests__/runtime.test.js.map +1 -1
- package/dist/team/__tests__/scaling.test.js +222 -237
- package/dist/team/__tests__/scaling.test.js.map +1 -1
- package/dist/team/__tests__/state-root.test.js +117 -2
- package/dist/team/__tests__/state-root.test.js.map +1 -1
- package/dist/team/__tests__/tmux-session.test.js +887 -92
- package/dist/team/__tests__/tmux-session.test.js.map +1 -1
- package/dist/team/__tests__/tmux-source-authority-realtmux.test.d.ts +2 -0
- package/dist/team/__tests__/tmux-source-authority-realtmux.test.d.ts.map +1 -0
- package/dist/team/__tests__/tmux-source-authority-realtmux.test.js +149 -0
- package/dist/team/__tests__/tmux-source-authority-realtmux.test.js.map +1 -0
- package/dist/team/__tests__/tmux-test-fixture.d.ts +69 -0
- package/dist/team/__tests__/tmux-test-fixture.d.ts.map +1 -1
- package/dist/team/__tests__/tmux-test-fixture.js +242 -15
- package/dist/team/__tests__/tmux-test-fixture.js.map +1 -1
- package/dist/team/__tests__/tmux-test-fixture.test.js +177 -1
- package/dist/team/__tests__/tmux-test-fixture.test.js.map +1 -1
- package/dist/team/__tests__/worker-runtime-identity.test.js +45 -72
- package/dist/team/__tests__/worker-runtime-identity.test.js.map +1 -1
- package/dist/team/notice-ledger.d.ts +54 -0
- package/dist/team/notice-ledger.d.ts.map +1 -0
- package/dist/team/notice-ledger.js +259 -0
- package/dist/team/notice-ledger.js.map +1 -0
- package/dist/team/phase-controller.d.ts.map +1 -1
- package/dist/team/phase-controller.js +2 -0
- package/dist/team/phase-controller.js.map +1 -1
- package/dist/team/runtime.d.ts +3 -1
- package/dist/team/runtime.d.ts.map +1 -1
- package/dist/team/runtime.js +314 -142
- package/dist/team/runtime.js.map +1 -1
- package/dist/team/scaling.d.ts.map +1 -1
- package/dist/team/scaling.js +161 -91
- package/dist/team/scaling.js.map +1 -1
- package/dist/team/state/monitor.d.ts +10 -0
- package/dist/team/state/monitor.d.ts.map +1 -1
- package/dist/team/state/monitor.js +13 -0
- package/dist/team/state/monitor.js.map +1 -1
- package/dist/team/state/types.d.ts +15 -0
- package/dist/team/state/types.d.ts.map +1 -1
- package/dist/team/state/types.js.map +1 -1
- package/dist/team/state-root.d.ts +12 -0
- package/dist/team/state-root.d.ts.map +1 -1
- package/dist/team/state-root.js +177 -0
- package/dist/team/state-root.js.map +1 -1
- package/dist/team/state.d.ts +18 -0
- package/dist/team/state.d.ts.map +1 -1
- package/dist/team/state.js +36 -0
- package/dist/team/state.js.map +1 -1
- package/dist/team/tmux-session.d.ts +40 -4
- package/dist/team/tmux-session.d.ts.map +1 -1
- package/dist/team/tmux-session.js +513 -166
- package/dist/team/tmux-session.js.map +1 -1
- package/dist/ultragoal/__tests__/artifacts.test.js +950 -2
- package/dist/ultragoal/__tests__/artifacts.test.js.map +1 -1
- package/dist/ultragoal/__tests__/docs-contract.test.js +1 -0
- package/dist/ultragoal/__tests__/docs-contract.test.js.map +1 -1
- package/dist/ultragoal/artifacts.d.ts +35 -1
- package/dist/ultragoal/artifacts.d.ts.map +1 -1
- package/dist/ultragoal/artifacts.js +232 -46
- package/dist/ultragoal/artifacts.js.map +1 -1
- package/dist/utils/__tests__/file-durability.test.js +58 -1
- package/dist/utils/__tests__/file-durability.test.js.map +1 -1
- package/dist/utils/__tests__/paths.test.js +21 -1
- package/dist/utils/__tests__/paths.test.js.map +1 -1
- package/dist/utils/file-durability.d.ts +10 -0
- package/dist/utils/file-durability.d.ts.map +1 -1
- package/dist/utils/file-durability.js +34 -4
- package/dist/utils/file-durability.js.map +1 -1
- package/dist/utils/paths.d.ts.map +1 -1
- package/dist/utils/paths.js +14 -6
- package/dist/utils/paths.js.map +1 -1
- package/dist/verification/__tests__/ci-rust-gates.test.js +49 -4
- package/dist/verification/__tests__/ci-rust-gates.test.js.map +1 -1
- package/dist/verification/__tests__/explore-harness-release-workflow.test.js +60 -0
- package/dist/verification/__tests__/explore-harness-release-workflow.test.js.map +1 -1
- package/dist/verification/__tests__/native-release-manifest.test.js +157 -4
- package/dist/verification/__tests__/native-release-manifest.test.js.map +1 -1
- package/package.json +13 -5
- package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
- package/plugins/oh-my-codex/hooks/codex-native-hook.mjs +81 -17
- package/plugins/oh-my-codex/skills/autopilot/SKILL.md +13 -5
- package/plugins/oh-my-codex/skills/cancel/SKILL.md +30 -85
- package/plugins/oh-my-codex/skills/ralph/SKILL.md +5 -5
- package/plugins/oh-my-codex/skills/ralplan/SKILL.md +15 -17
- package/plugins/oh-my-codex/skills/ultragoal/SKILL.md +3 -2
- package/skills/autopilot/SKILL.md +13 -5
- package/skills/cancel/SKILL.md +30 -85
- package/skills/ralph/SKILL.md +5 -5
- package/skills/ralplan/SKILL.md +15 -17
- package/skills/ultragoal/SKILL.md +3 -2
- package/src/scripts/__tests__/codex-native-hook.test.ts +6091 -540
- package/src/scripts/__tests__/issue-3293-callsite-parity.test.ts +187 -0
- package/src/scripts/__tests__/issue-3293-cancel-transaction.test.ts +189 -0
- package/src/scripts/__tests__/issue-3293-hook-owned-cancel.test.ts +187 -0
- package/src/scripts/__tests__/issue-3311-ultragoal-native-outside-tmux.test.ts +591 -0
- package/src/scripts/__tests__/issue-3358-ultragoal-cancel-lifecycle.test.ts +701 -0
- package/src/scripts/__tests__/issue-3369-autopilot-ralplan-state-machine.test.ts +211 -0
- package/src/scripts/__tests__/notify-state-io.test.ts +29 -0
- package/src/scripts/__tests__/notify-tmux-injection.test.ts +26 -1
- package/src/scripts/__tests__/postinstall.test.ts +23 -1
- package/src/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.ts +27 -8
- package/src/scripts/__tests__/role-intent-durable-recovery-3181.test.ts +61 -27
- package/src/scripts/__tests__/smoke-packed-install.test.ts +209 -18
- package/src/scripts/__tests__/verify-native-release-assets.test.ts +240 -0
- package/src/scripts/codex-native-hook.ts +3638 -475
- package/src/scripts/codex-native-pre-post.ts +43 -12
- package/src/scripts/generate-native-release-manifest.ts +83 -79
- package/src/scripts/notify-fallback-watcher.ts +38 -38
- package/src/scripts/notify-hook/state-io.ts +11 -0
- package/src/scripts/notify-hook/team-leader-nudge.ts +32 -4
- package/src/scripts/notify-hook/team-worker-stop.ts +23 -4
- package/src/scripts/notify-hook/team-worker.ts +43 -5
- package/src/scripts/notify-hook/tmux-injection.ts +5 -1
- package/src/scripts/notify-hook.ts +83 -82
- package/src/scripts/postinstall-advisory.ts +67 -0
- package/src/scripts/postinstall.ts +1 -1
- package/src/scripts/smoke-packed-install.ts +446 -59
- package/src/scripts/sync-plugin-mirror.ts +17 -1
- package/src/scripts/verify-native-release-assets.ts +39 -31
- package/templates/AGENTS.md +1 -0
|
@@ -1,33 +1,109 @@
|
|
|
1
1
|
import { describe, it } from 'node:test';
|
|
2
2
|
import assert from 'node:assert/strict';
|
|
3
|
-
import { mkdir, mkdtemp, rm,
|
|
4
|
-
import { existsSync } from 'fs';
|
|
3
|
+
import { chmod, mkdir, mkdtemp, readFile, readdir, rm, symlink, writeFile } from 'fs/promises';
|
|
4
|
+
import { existsSync, realpathSync } from 'fs';
|
|
5
5
|
import { dirname, join } from 'path';
|
|
6
|
-
import { tmpdir } from 'os';
|
|
6
|
+
import { tmpdir as osTmpdir } from 'os';
|
|
7
7
|
import { spawnSync } from 'child_process';
|
|
8
8
|
import { fileURLToPath } from 'url';
|
|
9
|
+
import { readModeState } from '../../modes/base.js';
|
|
10
|
+
import { readSkillActiveState } from '../../state/skill-active.js';
|
|
11
|
+
import { recordSkillActivation } from '../../hooks/keyword-detector.js';
|
|
12
|
+
import { cancelModesForTest } from '../index.js';
|
|
13
|
+
const tmpdir = () => realpathSync(osTmpdir());
|
|
14
|
+
async function createFakeCodexBin(root, output) {
|
|
15
|
+
const binDir = join(root, 'bin');
|
|
16
|
+
await mkdir(binDir, { recursive: true });
|
|
17
|
+
await writeFile(join(binDir, 'codex'), `#!/bin/sh\nprintf '%s\\n' '${output}'\n`);
|
|
18
|
+
await chmod(join(binDir, 'codex'), 0o755);
|
|
19
|
+
return binDir;
|
|
20
|
+
}
|
|
9
21
|
const testDir = dirname(fileURLToPath(import.meta.url));
|
|
10
22
|
const repoRoot = join(testDir, '..', '..', '..');
|
|
11
23
|
const omxBin = join(repoRoot, 'dist', 'cli', 'omx.js');
|
|
24
|
+
function cleanOmxEnv(overrides = {}) {
|
|
25
|
+
const env = { ...process.env };
|
|
26
|
+
for (const name of [
|
|
27
|
+
'OMX_ROOT',
|
|
28
|
+
'OMX_STATE_ROOT',
|
|
29
|
+
'OMX_TEAM_STATE_ROOT',
|
|
30
|
+
'OMX_SESSION_ID',
|
|
31
|
+
'CODEX_SESSION_ID',
|
|
32
|
+
'SESSION_ID',
|
|
33
|
+
'OMX_RUNS_DIR',
|
|
34
|
+
]) {
|
|
35
|
+
delete env[name];
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
...env,
|
|
39
|
+
...(overrides.FAKE_TMUX_SESSION_NAME ? {
|
|
40
|
+
FAKE_TMUX_PANE_PID: '4242',
|
|
41
|
+
FAKE_TMUX_INTERNAL_SESSION_ID: '$1',
|
|
42
|
+
FAKE_TMUX_SESSION_CREATED: '100',
|
|
43
|
+
} : {}),
|
|
44
|
+
...overrides,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
12
47
|
function runOmx(cwd, ...args) {
|
|
13
48
|
return spawnSync(process.execPath, [omxBin, ...args], {
|
|
14
49
|
cwd,
|
|
15
50
|
encoding: 'utf-8',
|
|
51
|
+
env: cleanOmxEnv(),
|
|
16
52
|
});
|
|
17
53
|
}
|
|
18
54
|
function runOmxWithEnv(cwd, env, ...args) {
|
|
19
55
|
return spawnSync(process.execPath, [omxBin, ...args], {
|
|
20
56
|
cwd,
|
|
21
57
|
encoding: 'utf-8',
|
|
22
|
-
env:
|
|
58
|
+
env: cleanOmxEnv(env),
|
|
23
59
|
});
|
|
24
60
|
}
|
|
61
|
+
async function createFakeTmuxBin(root) {
|
|
62
|
+
const binDir = join(root, 'bin');
|
|
63
|
+
const tmuxPath = join(binDir, 'tmux');
|
|
64
|
+
await mkdir(binDir, { recursive: true });
|
|
65
|
+
await writeFile(tmuxPath, `#!/bin/sh
|
|
66
|
+
case "$1" in
|
|
67
|
+
list-panes)
|
|
68
|
+
count=0
|
|
69
|
+
if [ -n "$FAKE_TMUX_COUNTER_FILE" ] && [ -f "$FAKE_TMUX_COUNTER_FILE" ]; then count=$(cat "$FAKE_TMUX_COUNTER_FILE"); fi
|
|
70
|
+
count=$((count + 1))
|
|
71
|
+
if [ -n "$FAKE_TMUX_COUNTER_FILE" ]; then printf '%s' "$count" > "$FAKE_TMUX_COUNTER_FILE"; fi
|
|
72
|
+
created="$FAKE_TMUX_SESSION_CREATED"
|
|
73
|
+
if [ "$FAKE_TMUX_REPLACE_AFTER_FIRST" = "1" ] && [ "$count" -gt 1 ]; then created=$((created + 1)); fi
|
|
74
|
+
printf '%%42\\t0\\t%s\\t%s\\t%s\\t%s\\t%s\\n' "$FAKE_TMUX_PANE_PID" "$FAKE_TMUX_SESSION_NAME" "$FAKE_TMUX_INTERNAL_SESSION_ID" "$created" "$FAKE_OMX_SESSION_ID"
|
|
75
|
+
;;
|
|
76
|
+
*) exit 1 ;;
|
|
77
|
+
esac
|
|
78
|
+
`);
|
|
79
|
+
await chmod(tmuxPath, 0o755);
|
|
80
|
+
return binDir;
|
|
81
|
+
}
|
|
82
|
+
async function writeActiveRunRecord(options) {
|
|
83
|
+
const activeDir = join(options.runsRoot, 'active-detached');
|
|
84
|
+
await mkdir(activeDir, { recursive: true });
|
|
85
|
+
await writeFile(join(activeDir, `${options.contextKey}.json`), JSON.stringify({
|
|
86
|
+
version: 1,
|
|
87
|
+
context_key: options.contextKey,
|
|
88
|
+
created_at: '2026-07-19T00:00:00.000Z',
|
|
89
|
+
source_cwd: options.sourceCwd,
|
|
90
|
+
...(options.worktreeCwd ? { worktree_cwd: options.worktreeCwd } : {}),
|
|
91
|
+
argv: ['--madmax'],
|
|
92
|
+
run_dir: options.runDir,
|
|
93
|
+
tmux_session_name: options.tmuxSessionName,
|
|
94
|
+
session_id: options.sessionId,
|
|
95
|
+
tmux_pane_id: '%42',
|
|
96
|
+
tmux_internal_session_id: '$1',
|
|
97
|
+
tmux_session_created: '100',
|
|
98
|
+
tmux_pane_pid: 4242,
|
|
99
|
+
}, null, 2));
|
|
100
|
+
}
|
|
25
101
|
describe('CLI session-scoped state parity', () => {
|
|
26
102
|
it('status and cancel include session-scoped states', async () => {
|
|
27
103
|
const wd = await mkdtemp(join(tmpdir(), 'omx-cli-session-scope-'));
|
|
28
104
|
try {
|
|
29
105
|
await mkdir(join(wd, '.omx', 'state'), { recursive: true });
|
|
30
|
-
await writeFile(join(wd, '.omx', 'state', 'session.json'), JSON.stringify({ session_id: 'sess1' }));
|
|
106
|
+
await writeFile(join(wd, '.omx', 'state', 'session.json'), JSON.stringify({ session_id: 'sess1', cwd: wd, state_root: join(wd, '.omx', 'state') }));
|
|
31
107
|
const scopedDir = join(wd, '.omx', 'state', 'sessions', 'sess1');
|
|
32
108
|
await mkdir(scopedDir, { recursive: true });
|
|
33
109
|
await writeFile(join(scopedDir, 'team-state.json'), JSON.stringify({
|
|
@@ -60,15 +136,14 @@ describe('CLI session-scoped state parity', () => {
|
|
|
60
136
|
const ralphPath = join(sessionDir, 'ralph-state.json');
|
|
61
137
|
const nativeStopPath = join(sessionDir, 'native-stop-state.json');
|
|
62
138
|
await mkdir(sessionDir, { recursive: true });
|
|
63
|
-
await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: sessionId }));
|
|
139
|
+
await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: sessionId, cwd: wd, state_root: stateDir }));
|
|
64
140
|
await writeFile(ralphPath, JSON.stringify({ active: true, current_phase: 'executing' }));
|
|
65
141
|
await writeFile(nativeStopPath, JSON.stringify({
|
|
66
142
|
sessions: { [sessionId]: { last_signature: 'ralph-stop|pending' } },
|
|
67
143
|
}));
|
|
68
144
|
const cancelResult = runOmxWithEnv(wd, { OMX_SESSION_ID: 'unmatched-session' }, 'cancel', '--force');
|
|
69
|
-
assert.
|
|
70
|
-
assert.match(cancelResult.stderr, /OMX_SESSION_ID
|
|
71
|
-
assert.match(cancelResult.stdout, /No active modes to cancel\./);
|
|
145
|
+
assert.notEqual(cancelResult.status, 0);
|
|
146
|
+
assert.match(cancelResult.stderr, /OMX_SESSION_ID does not match the live session recorded in session\.json/);
|
|
72
147
|
assert.deepEqual(JSON.parse(await readFile(ralphPath, 'utf-8')), { active: true, current_phase: 'executing' });
|
|
73
148
|
assert.deepEqual(JSON.parse(await readFile(nativeStopPath, 'utf-8')), { sessions: { [sessionId]: { last_signature: 'ralph-stop|pending' } } });
|
|
74
149
|
}
|
|
@@ -76,6 +151,660 @@ describe('CLI session-scoped state parity', () => {
|
|
|
76
151
|
await rm(wd, { recursive: true, force: true });
|
|
77
152
|
}
|
|
78
153
|
});
|
|
154
|
+
it('ignores ambient Codex session aliases that do not own writable cancellation', async () => {
|
|
155
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-cli-cancel-codex-session-mismatch-'));
|
|
156
|
+
try {
|
|
157
|
+
const stateDir = join(wd, '.omx', 'state');
|
|
158
|
+
const ownerSession = 'owner-session';
|
|
159
|
+
const foreignSession = 'foreign-codex-session';
|
|
160
|
+
const ownerPath = join(stateDir, 'sessions', ownerSession, 'ralplan-state.json');
|
|
161
|
+
const foreignPath = join(stateDir, 'sessions', foreignSession, 'team-state.json');
|
|
162
|
+
const ownerState = JSON.stringify({ active: true, mode: 'ralplan', current_phase: 'executing' }, null, 2);
|
|
163
|
+
const foreignState = JSON.stringify({ active: true, mode: 'team', current_phase: 'team-exec' }, null, 2);
|
|
164
|
+
await mkdir(dirname(ownerPath), { recursive: true });
|
|
165
|
+
await mkdir(dirname(foreignPath), { recursive: true });
|
|
166
|
+
await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: ownerSession, cwd: wd, state_root: stateDir }));
|
|
167
|
+
await writeFile(ownerPath, ownerState);
|
|
168
|
+
await writeFile(foreignPath, foreignState);
|
|
169
|
+
for (const environmentName of ['CODEX_SESSION_ID', 'SESSION_ID']) {
|
|
170
|
+
await writeFile(ownerPath, ownerState);
|
|
171
|
+
const cancelResult = runOmxWithEnv(wd, { [environmentName]: foreignSession }, 'cancel');
|
|
172
|
+
assert.equal(cancelResult.status, 0, cancelResult.stderr || cancelResult.stdout);
|
|
173
|
+
assert.match(cancelResult.stdout, /Cancelled: ralplan/);
|
|
174
|
+
assert.doesNotMatch(cancelResult.stdout, /Cancelled: team/);
|
|
175
|
+
assert.equal(JSON.parse(await readFile(ownerPath, 'utf-8')).active, false);
|
|
176
|
+
assert.equal(await readFile(foreignPath, 'utf-8'), foreignState);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
finally {
|
|
180
|
+
await rm(wd, { recursive: true, force: true });
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
it('keeps root cancellation authoritative despite ambient session aliases', async () => {
|
|
184
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-cli-cancel-root-ambient-session-'));
|
|
185
|
+
try {
|
|
186
|
+
const stateDir = join(wd, '.omx', 'state');
|
|
187
|
+
const foreignSession = 'foreign-ambient-session';
|
|
188
|
+
const rootPath = join(stateDir, 'team-state.json');
|
|
189
|
+
const foreignPath = join(stateDir, 'sessions', foreignSession, 'ralplan-state.json');
|
|
190
|
+
const rootState = JSON.stringify({ active: true, mode: 'team', current_phase: 'team-exec' }, null, 2);
|
|
191
|
+
const foreignState = JSON.stringify({ active: true, mode: 'ralplan', current_phase: 'executing' }, null, 2);
|
|
192
|
+
await mkdir(dirname(foreignPath), { recursive: true });
|
|
193
|
+
await writeFile(rootPath, rootState);
|
|
194
|
+
await writeFile(foreignPath, foreignState);
|
|
195
|
+
for (const environmentName of ['CODEX_SESSION_ID', 'SESSION_ID']) {
|
|
196
|
+
await writeFile(rootPath, rootState);
|
|
197
|
+
const cancelResult = runOmxWithEnv(wd, { [environmentName]: foreignSession }, 'cancel');
|
|
198
|
+
assert.equal(cancelResult.status, 0, cancelResult.stderr || cancelResult.stdout);
|
|
199
|
+
assert.match(cancelResult.stdout, /Cancelled: team/);
|
|
200
|
+
assert.doesNotMatch(cancelResult.stdout, /Cancelled: ralplan/);
|
|
201
|
+
assert.equal(JSON.parse(await readFile(rootPath, 'utf-8')).active, false);
|
|
202
|
+
assert.equal(await readFile(foreignPath, 'utf-8'), foreignState);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
finally {
|
|
206
|
+
await rm(wd, { recursive: true, force: true });
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
it('rejects unsupported cancellation flags before mutation', async () => {
|
|
210
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-cli-cancel-flags-'));
|
|
211
|
+
try {
|
|
212
|
+
const stateDir = join(wd, '.omx', 'state');
|
|
213
|
+
const statePath = join(stateDir, 'team-state.json');
|
|
214
|
+
const state = JSON.stringify({ active: true, mode: 'team', current_phase: 'team-exec' }, null, 2);
|
|
215
|
+
await mkdir(stateDir, { recursive: true });
|
|
216
|
+
await writeFile(statePath, state);
|
|
217
|
+
for (const args of [['--all'], ['--unknown'], ['--force', '--all']]) {
|
|
218
|
+
const result = runOmx(wd, 'cancel', ...args);
|
|
219
|
+
assert.notEqual(result.status, 0);
|
|
220
|
+
assert.equal(await readFile(statePath, 'utf-8'), state);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
finally {
|
|
224
|
+
await rm(wd, { recursive: true, force: true });
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
it('rejects symlinked ordinary-scope state targets', async () => {
|
|
228
|
+
if (process.platform === 'win32')
|
|
229
|
+
return;
|
|
230
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-cli-cancel-root-symlink-'));
|
|
231
|
+
const outside = await mkdtemp(join(tmpdir(), 'omx-cli-cancel-root-symlink-outside-'));
|
|
232
|
+
try {
|
|
233
|
+
const stateDir = join(wd, '.omx', 'state');
|
|
234
|
+
const outsidePath = join(outside, 'team-state.json');
|
|
235
|
+
const state = JSON.stringify({ active: true, mode: 'team', current_phase: 'team-exec' }, null, 2);
|
|
236
|
+
await mkdir(stateDir, { recursive: true });
|
|
237
|
+
await writeFile(outsidePath, state);
|
|
238
|
+
await symlink(outsidePath, join(stateDir, 'team-state.json'));
|
|
239
|
+
const result = runOmx(wd, 'cancel');
|
|
240
|
+
assert.notEqual(result.status, 0);
|
|
241
|
+
assert.match(result.stderr, /non-regular state target/);
|
|
242
|
+
assert.equal(await readFile(outsidePath, 'utf-8'), state);
|
|
243
|
+
}
|
|
244
|
+
finally {
|
|
245
|
+
await rm(wd, { recursive: true, force: true });
|
|
246
|
+
await rm(outside, { recursive: true, force: true });
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
it('rejects symlinked session authority directories', async () => {
|
|
250
|
+
if (process.platform === 'win32')
|
|
251
|
+
return;
|
|
252
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-cli-cancel-session-root-symlink-'));
|
|
253
|
+
const outside = await mkdtemp(join(tmpdir(), 'omx-cli-cancel-session-root-outside-'));
|
|
254
|
+
try {
|
|
255
|
+
const stateDir = join(wd, '.omx', 'state');
|
|
256
|
+
const sessionId = 'session-root-link';
|
|
257
|
+
const outsidePath = join(outside, 'ralplan-state.json');
|
|
258
|
+
const state = JSON.stringify({ active: true, mode: 'ralplan', session_id: sessionId, current_phase: 'executing' }, null, 2);
|
|
259
|
+
await mkdir(join(stateDir, 'sessions'), { recursive: true });
|
|
260
|
+
await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: sessionId, cwd: wd, state_root: stateDir }));
|
|
261
|
+
await writeFile(outsidePath, state);
|
|
262
|
+
await symlink(outside, join(stateDir, 'sessions', sessionId), 'dir');
|
|
263
|
+
const result = runOmx(wd, 'cancel');
|
|
264
|
+
assert.notEqual(result.status, 0);
|
|
265
|
+
assert.match(result.stderr, /symlinked authority component|unusable/);
|
|
266
|
+
assert.equal(await readFile(outsidePath, 'utf-8'), state);
|
|
267
|
+
}
|
|
268
|
+
finally {
|
|
269
|
+
await rm(wd, { recursive: true, force: true });
|
|
270
|
+
await rm(outside, { recursive: true, force: true });
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
it('denies contradictory selected session state transaction-wide', async () => {
|
|
274
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-cli-cancel-contradictory-'));
|
|
275
|
+
try {
|
|
276
|
+
const stateDir = join(wd, '.omx', 'state');
|
|
277
|
+
const sessionId = 'sess-contradictory';
|
|
278
|
+
const sessionDir = join(stateDir, 'sessions', sessionId);
|
|
279
|
+
const contradictoryPath = join(sessionDir, 'ralplan-state.json');
|
|
280
|
+
const validPath = join(sessionDir, 'autopilot-state.json');
|
|
281
|
+
const contradictory = JSON.stringify({ active: true, mode: 'team', session_id: 'foreign', current_phase: 'team-exec' }, null, 2);
|
|
282
|
+
const valid = JSON.stringify({ active: true, mode: 'autopilot', session_id: sessionId, current_phase: 'executing' }, null, 2);
|
|
283
|
+
await mkdir(sessionDir, { recursive: true });
|
|
284
|
+
await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: sessionId, cwd: wd, state_root: stateDir }));
|
|
285
|
+
await writeFile(contradictoryPath, contradictory);
|
|
286
|
+
await writeFile(validPath, valid);
|
|
287
|
+
const result = runOmx(wd, 'cancel');
|
|
288
|
+
assert.notEqual(result.status, 0);
|
|
289
|
+
assert.match(result.stderr, /contradictory mode state/);
|
|
290
|
+
assert.equal(await readFile(contradictoryPath, 'utf-8'), contradictory);
|
|
291
|
+
assert.equal(await readFile(validPath, 'utf-8'), valid);
|
|
292
|
+
}
|
|
293
|
+
finally {
|
|
294
|
+
await rm(wd, { recursive: true, force: true });
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
it('fails closed when owned session state is malformed', async () => {
|
|
298
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-cli-cancel-malformed-owner-'));
|
|
299
|
+
try {
|
|
300
|
+
const stateDir = join(wd, '.omx', 'state');
|
|
301
|
+
const sessionId = 'malformed-owner';
|
|
302
|
+
const sessionDir = join(stateDir, 'sessions', sessionId);
|
|
303
|
+
const malformedPath = join(sessionDir, 'ralplan-state.json');
|
|
304
|
+
const teamPath = join(stateDir, 'team-state.json');
|
|
305
|
+
const malformedState = '{"active":true';
|
|
306
|
+
const rootTeamState = JSON.stringify({ active: true, mode: 'team', current_phase: 'team-exec' }, null, 2);
|
|
307
|
+
await mkdir(sessionDir, { recursive: true });
|
|
308
|
+
await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: sessionId, cwd: wd, state_root: stateDir }));
|
|
309
|
+
await writeFile(malformedPath, malformedState);
|
|
310
|
+
await writeFile(teamPath, rootTeamState);
|
|
311
|
+
const cancelResult = runOmx(wd, 'cancel');
|
|
312
|
+
assert.notEqual(cancelResult.status, 0);
|
|
313
|
+
assert.match(cancelResult.stderr, /Refusing partial cancellation/);
|
|
314
|
+
assert.doesNotMatch(cancelResult.stdout, /Cancelled: team/);
|
|
315
|
+
assert.equal(await readFile(malformedPath, 'utf-8'), malformedState);
|
|
316
|
+
assert.equal(await readFile(teamPath, 'utf-8'), rootTeamState);
|
|
317
|
+
}
|
|
318
|
+
finally {
|
|
319
|
+
await rm(wd, { recursive: true, force: true });
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
it('denies wrong-typed and nested ownership contradictions transaction-wide', async () => {
|
|
323
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-cli-cancel-owner-evidence-'));
|
|
324
|
+
try {
|
|
325
|
+
const stateDir = join(wd, '.omx', 'state');
|
|
326
|
+
const sessionId = 'owner-evidence-session';
|
|
327
|
+
const sessionDir = join(stateDir, 'sessions', sessionId);
|
|
328
|
+
const skillPath = join(sessionDir, 'skill-active-state.json');
|
|
329
|
+
const modePath = join(sessionDir, 'autopilot-state.json');
|
|
330
|
+
const skillState = JSON.stringify({ active: true, skill: 'autopilot', owner_codex_session_id: 42, active_skills: [{ skill: 'autopilot', active: true, owner_codex_session_id: 'foreign-owner' }] }, null, 2);
|
|
331
|
+
const modeState = JSON.stringify({ active: true, mode: 'autopilot', session_id: sessionId }, null, 2);
|
|
332
|
+
await mkdir(sessionDir, { recursive: true });
|
|
333
|
+
await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: sessionId, cwd: wd, state_root: stateDir }));
|
|
334
|
+
await writeFile(skillPath, skillState);
|
|
335
|
+
await writeFile(modePath, modeState);
|
|
336
|
+
const result = runOmx(wd, 'cancel');
|
|
337
|
+
assert.notEqual(result.status, 0);
|
|
338
|
+
assert.match(result.stderr, /contradictory owner_codex_session_id/);
|
|
339
|
+
assert.equal(await readFile(skillPath, 'utf-8'), skillState);
|
|
340
|
+
assert.equal(await readFile(modePath, 'utf-8'), modeState);
|
|
341
|
+
}
|
|
342
|
+
finally {
|
|
343
|
+
await rm(wd, { recursive: true, force: true });
|
|
344
|
+
}
|
|
345
|
+
});
|
|
346
|
+
it('keeps root Team compatibility state read-only when current-session Ralplan owns cancellation', async () => {
|
|
347
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-cli-cancel-cross-mode-scope-'));
|
|
348
|
+
try {
|
|
349
|
+
const stateDir = join(wd, '.omx', 'state');
|
|
350
|
+
const sessionId = 'sess-ralplan-owner';
|
|
351
|
+
const sessionDir = join(stateDir, 'sessions', sessionId);
|
|
352
|
+
const ralplanPath = join(sessionDir, 'ralplan-state.json');
|
|
353
|
+
const teamPath = join(stateDir, 'team-state.json');
|
|
354
|
+
const skillActivePath = join(stateDir, 'skill-active-state.json');
|
|
355
|
+
const nativeStopPath = join(stateDir, 'native-stop-state.json');
|
|
356
|
+
const ralplanState = JSON.stringify({
|
|
357
|
+
active: true,
|
|
358
|
+
mode: 'ralplan',
|
|
359
|
+
current_phase: 'executing',
|
|
360
|
+
}, null, 2);
|
|
361
|
+
const rootTeamState = JSON.stringify({
|
|
362
|
+
active: true,
|
|
363
|
+
mode: 'team',
|
|
364
|
+
current_phase: 'team-exec',
|
|
365
|
+
}, null, 2);
|
|
366
|
+
const rootSkillActiveState = JSON.stringify({
|
|
367
|
+
version: 1,
|
|
368
|
+
active: true,
|
|
369
|
+
skill: 'team',
|
|
370
|
+
phase: 'team-exec',
|
|
371
|
+
active_skills: [{ skill: 'team', phase: 'team-exec', active: true }],
|
|
372
|
+
}, null, 2);
|
|
373
|
+
const rootNativeStopState = JSON.stringify({
|
|
374
|
+
sessions: { [sessionId]: { last_signature: 'team-stop|pending' } },
|
|
375
|
+
}, null, 2);
|
|
376
|
+
await mkdir(sessionDir, { recursive: true });
|
|
377
|
+
await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: sessionId, cwd: wd, state_root: stateDir }));
|
|
378
|
+
await writeFile(ralplanPath, ralplanState);
|
|
379
|
+
await writeFile(teamPath, rootTeamState);
|
|
380
|
+
await writeFile(skillActivePath, rootSkillActiveState);
|
|
381
|
+
await writeFile(nativeStopPath, rootNativeStopState);
|
|
382
|
+
const cancelResult = runOmx(wd, 'cancel');
|
|
383
|
+
assert.equal(cancelResult.status, 0, cancelResult.stderr || cancelResult.stdout);
|
|
384
|
+
assert.match(cancelResult.stdout, /Cancelled: ralplan/);
|
|
385
|
+
assert.doesNotMatch(cancelResult.stdout, /Cancelled: team/);
|
|
386
|
+
assert.equal(JSON.parse(await readFile(ralplanPath, 'utf-8')).active, false);
|
|
387
|
+
assert.equal(await readFile(teamPath, 'utf-8'), rootTeamState);
|
|
388
|
+
assert.equal(await readFile(skillActivePath, 'utf-8'), rootSkillActiveState);
|
|
389
|
+
assert.equal(await readFile(nativeStopPath, 'utf-8'), rootNativeStopState);
|
|
390
|
+
await writeFile(ralplanPath, ralplanState);
|
|
391
|
+
const forceResult = runOmx(wd, 'cancel', '--force');
|
|
392
|
+
assert.equal(forceResult.status, 0, forceResult.stderr || forceResult.stdout);
|
|
393
|
+
assert.match(forceResult.stdout, /Cancelled: ralplan/);
|
|
394
|
+
assert.doesNotMatch(forceResult.stdout, /Cancelled: team/);
|
|
395
|
+
assert.equal(JSON.parse(await readFile(ralplanPath, 'utf-8')).active, false);
|
|
396
|
+
assert.equal(await readFile(teamPath, 'utf-8'), rootTeamState);
|
|
397
|
+
assert.equal(await readFile(skillActivePath, 'utf-8'), rootSkillActiveState);
|
|
398
|
+
assert.equal(await readFile(nativeStopPath, 'utf-8'), rootNativeStopState);
|
|
399
|
+
}
|
|
400
|
+
finally {
|
|
401
|
+
await rm(wd, { recursive: true, force: true });
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
it('replaces only a stale top-level skill owner under complete current native authority', async () => {
|
|
405
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-cli-cancel-stale-skill-owner-'));
|
|
406
|
+
try {
|
|
407
|
+
const stateDir = join(wd, '.omx', 'state');
|
|
408
|
+
const sessionId = 'current-session';
|
|
409
|
+
const nativeSessionId = 'current-native-owner';
|
|
410
|
+
const sessionDir = join(stateDir, 'sessions', sessionId);
|
|
411
|
+
const pointer = JSON.stringify({
|
|
412
|
+
session_id: sessionId,
|
|
413
|
+
native_session_id: nativeSessionId,
|
|
414
|
+
cwd: wd,
|
|
415
|
+
state_root: stateDir,
|
|
416
|
+
platform: process.platform,
|
|
417
|
+
}, null, 2);
|
|
418
|
+
const skillPath = join(sessionDir, 'skill-active-state.json');
|
|
419
|
+
const ralplanPath = join(sessionDir, 'ralplan-state.json');
|
|
420
|
+
const rootSkillPath = join(stateDir, 'skill-active-state.json');
|
|
421
|
+
const rootSkill = JSON.stringify({ active: true, skill: 'team' }, null, 2);
|
|
422
|
+
await mkdir(sessionDir, { recursive: true });
|
|
423
|
+
await writeFile(join(stateDir, 'session.json'), pointer);
|
|
424
|
+
await mkdir(join(stateDir, 'sessions', nativeSessionId), { recursive: true });
|
|
425
|
+
await writeFile(join(stateDir, 'sessions', nativeSessionId, 'session-owner.json'), JSON.stringify({
|
|
426
|
+
session_id: nativeSessionId,
|
|
427
|
+
native_session_id: nativeSessionId,
|
|
428
|
+
cwd: wd,
|
|
429
|
+
platform: process.platform,
|
|
430
|
+
}, null, 2));
|
|
431
|
+
await writeFile(skillPath, JSON.stringify({
|
|
432
|
+
active: true,
|
|
433
|
+
skill: 'ralplan',
|
|
434
|
+
owner_codex_session_id: 'stale-native-owner',
|
|
435
|
+
active_skills: [{ skill: 'ralplan', active: true, owner_codex_session_id: nativeSessionId }],
|
|
436
|
+
}, null, 2));
|
|
437
|
+
await writeFile(ralplanPath, JSON.stringify({ active: true, mode: 'ralplan' }, null, 2));
|
|
438
|
+
await writeFile(rootSkillPath, rootSkill);
|
|
439
|
+
const descendantCwd = join(wd, 'packages', 'app');
|
|
440
|
+
await mkdir(descendantCwd, { recursive: true });
|
|
441
|
+
const result = runOmxWithEnv(descendantCwd, { OMX_ROOT: wd }, 'cancel');
|
|
442
|
+
assert.equal(result.status, 0, result.stderr || result.stdout);
|
|
443
|
+
const skill = JSON.parse(await readFile(skillPath, 'utf-8'));
|
|
444
|
+
assert.equal(skill.owner_codex_session_id, nativeSessionId);
|
|
445
|
+
assert.equal(skill.active_skills[0].owner_codex_session_id, nativeSessionId);
|
|
446
|
+
assert.equal(skill.active, false);
|
|
447
|
+
assert.equal(await readFile(rootSkillPath, 'utf-8'), rootSkill);
|
|
448
|
+
const repeat = runOmx(wd, 'cancel');
|
|
449
|
+
assert.equal(repeat.status, 0, repeat.stderr || repeat.stdout);
|
|
450
|
+
assert.equal(JSON.parse(await readFile(skillPath, 'utf-8')).owner_codex_session_id, nativeSessionId);
|
|
451
|
+
const malformedNestedSkill = JSON.stringify({
|
|
452
|
+
active: true,
|
|
453
|
+
skill: 'ralplan',
|
|
454
|
+
owner_codex_session_id: nativeSessionId,
|
|
455
|
+
active_skills: [{ skill: 'ralplan', active: 'true', owner_codex_session_id: nativeSessionId }],
|
|
456
|
+
}, null, 2);
|
|
457
|
+
await writeFile(skillPath, malformedNestedSkill);
|
|
458
|
+
const malformedNestedResult = runOmx(wd, 'cancel');
|
|
459
|
+
assert.notEqual(malformedNestedResult.status, 0);
|
|
460
|
+
assert.match(malformedNestedResult.stderr, /malformed active/);
|
|
461
|
+
assert.equal(await readFile(skillPath, 'utf-8'), malformedNestedSkill);
|
|
462
|
+
const fieldSwappedSkill = JSON.stringify({
|
|
463
|
+
active: true,
|
|
464
|
+
skill: 'ralplan',
|
|
465
|
+
session_id: nativeSessionId,
|
|
466
|
+
owner_codex_session_id: nativeSessionId,
|
|
467
|
+
active_skills: [{ skill: 'ralplan', active: true, session_id: nativeSessionId, owner_codex_session_id: nativeSessionId }],
|
|
468
|
+
}, null, 2);
|
|
469
|
+
await writeFile(skillPath, fieldSwappedSkill);
|
|
470
|
+
const fieldSwappedResult = runOmx(wd, 'cancel');
|
|
471
|
+
assert.notEqual(fieldSwappedResult.status, 0);
|
|
472
|
+
assert.match(fieldSwappedResult.stderr, /contradictory session_id/);
|
|
473
|
+
assert.equal(await readFile(skillPath, 'utf-8'), fieldSwappedSkill);
|
|
474
|
+
const inactiveStaleSkill = JSON.stringify({
|
|
475
|
+
active: false,
|
|
476
|
+
skill: 'ralplan',
|
|
477
|
+
owner_codex_session_id: 'stale-native-owner',
|
|
478
|
+
active_skills: [{ skill: 'ralplan', active: false, owner_codex_session_id: nativeSessionId }],
|
|
479
|
+
}, null, 2);
|
|
480
|
+
await writeFile(skillPath, inactiveStaleSkill);
|
|
481
|
+
const noActiveResult = runOmx(wd, 'cancel');
|
|
482
|
+
assert.equal(noActiveResult.status, 0, noActiveResult.stderr || noActiveResult.stdout);
|
|
483
|
+
assert.match(noActiveResult.stdout, /No active modes to cancel/);
|
|
484
|
+
assert.equal(await readFile(skillPath, 'utf-8'), inactiveStaleSkill);
|
|
485
|
+
const displacedDir = join(stateDir, 'sessions', 'stale-native-owner');
|
|
486
|
+
await mkdir(displacedDir, { recursive: true });
|
|
487
|
+
await writeFile(join(displacedDir, 'session-owner.json'), JSON.stringify({
|
|
488
|
+
session_id: 'stale-native-owner',
|
|
489
|
+
native_session_id: 'stale-native-owner',
|
|
490
|
+
cwd: wd,
|
|
491
|
+
platform: process.platform,
|
|
492
|
+
}, null, 2));
|
|
493
|
+
const liveDisplacedSkill = JSON.stringify({
|
|
494
|
+
active: true,
|
|
495
|
+
skill: 'ralplan',
|
|
496
|
+
owner_codex_session_id: 'stale-native-owner',
|
|
497
|
+
active_skills: [{ skill: 'ralplan', active: true, owner_codex_session_id: nativeSessionId }],
|
|
498
|
+
}, null, 2);
|
|
499
|
+
const activeRalplan = JSON.stringify({ active: true, mode: 'ralplan' }, null, 2);
|
|
500
|
+
await writeFile(skillPath, liveDisplacedSkill);
|
|
501
|
+
await writeFile(ralplanPath, activeRalplan);
|
|
502
|
+
const liveDisplaced = runOmx(wd, 'cancel');
|
|
503
|
+
assert.notEqual(liveDisplaced.status, 0);
|
|
504
|
+
assert.match(liveDisplaced.stderr, /non-stale displaced owner evidence/);
|
|
505
|
+
assert.equal(await readFile(skillPath, 'utf-8'), liveDisplacedSkill);
|
|
506
|
+
assert.equal(await readFile(ralplanPath, 'utf-8'), activeRalplan);
|
|
507
|
+
}
|
|
508
|
+
finally {
|
|
509
|
+
await rm(wd, { recursive: true, force: true });
|
|
510
|
+
}
|
|
511
|
+
});
|
|
512
|
+
it('preserves active Team skill entries while cancelling Ralph', async () => {
|
|
513
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-cli-cancel-ralph-team-peer-'));
|
|
514
|
+
try {
|
|
515
|
+
const stateDir = join(wd, '.omx', 'state');
|
|
516
|
+
const sessionId = 'canonical-session';
|
|
517
|
+
const nativeSessionId = 'native-session';
|
|
518
|
+
const sessionDir = join(stateDir, 'sessions', sessionId);
|
|
519
|
+
const skillPath = join(sessionDir, 'skill-active-state.json');
|
|
520
|
+
const ralphPath = join(sessionDir, 'ralph-state.json');
|
|
521
|
+
const teamPath = join(sessionDir, 'team-state.json');
|
|
522
|
+
const teamState = JSON.stringify({ active: true, mode: 'team', current_phase: 'team-exec' }, null, 2);
|
|
523
|
+
await mkdir(sessionDir, { recursive: true });
|
|
524
|
+
await writeFile(join(stateDir, 'session.json'), JSON.stringify({
|
|
525
|
+
session_id: sessionId,
|
|
526
|
+
native_session_id: nativeSessionId,
|
|
527
|
+
cwd: wd,
|
|
528
|
+
state_root: stateDir,
|
|
529
|
+
}, null, 2));
|
|
530
|
+
await mkdir(join(stateDir, 'sessions', nativeSessionId), { recursive: true });
|
|
531
|
+
await writeFile(join(stateDir, 'sessions', nativeSessionId, 'session-owner.json'), JSON.stringify({
|
|
532
|
+
session_id: nativeSessionId,
|
|
533
|
+
native_session_id: nativeSessionId,
|
|
534
|
+
cwd: wd,
|
|
535
|
+
}, null, 2));
|
|
536
|
+
await writeFile(skillPath, JSON.stringify({
|
|
537
|
+
active: true,
|
|
538
|
+
skill: 'ralph',
|
|
539
|
+
phase: 'executing',
|
|
540
|
+
session_id: sessionId,
|
|
541
|
+
owner_codex_session_id: nativeSessionId,
|
|
542
|
+
active_skills: [
|
|
543
|
+
{ skill: 'ralph', phase: 'executing', session_id: sessionId, owner_codex_session_id: nativeSessionId },
|
|
544
|
+
{ skill: 'team', phase: 'team-exec', session_id: sessionId, owner_codex_session_id: nativeSessionId },
|
|
545
|
+
],
|
|
546
|
+
}, null, 2));
|
|
547
|
+
await writeFile(ralphPath, JSON.stringify({ active: true, mode: 'ralph' }, null, 2));
|
|
548
|
+
await writeFile(teamPath, teamState);
|
|
549
|
+
const result = runOmx(wd, 'cancel');
|
|
550
|
+
assert.equal(result.status, 0, result.stderr || result.stdout);
|
|
551
|
+
const skill = JSON.parse(await readFile(skillPath, 'utf-8'));
|
|
552
|
+
assert.equal(skill.active, true);
|
|
553
|
+
assert.equal(skill.skill, 'team');
|
|
554
|
+
assert.equal(skill.phase, 'team-exec');
|
|
555
|
+
assert.equal(skill.current_phase, 'team-exec');
|
|
556
|
+
assert.deepEqual(skill.active_skills.map((entry) => [entry.skill, entry.active]), [
|
|
557
|
+
['ralph', false],
|
|
558
|
+
['team', undefined],
|
|
559
|
+
]);
|
|
560
|
+
assert.equal(JSON.parse(await readFile(ralphPath, 'utf-8')).active, false);
|
|
561
|
+
assert.equal(await readFile(teamPath, 'utf-8'), teamState);
|
|
562
|
+
}
|
|
563
|
+
finally {
|
|
564
|
+
await rm(wd, { recursive: true, force: true });
|
|
565
|
+
}
|
|
566
|
+
});
|
|
567
|
+
it('preserves a legacy top-level Team marker while cancelling Ralph', async () => {
|
|
568
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-cli-cancel-legacy-team-ralph-'));
|
|
569
|
+
try {
|
|
570
|
+
const stateDir = join(wd, '.omx', 'state');
|
|
571
|
+
const sessionId = 'legacy-team-ralph-session';
|
|
572
|
+
const sessionDir = join(stateDir, 'sessions', sessionId);
|
|
573
|
+
await mkdir(sessionDir, { recursive: true });
|
|
574
|
+
await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: sessionId, cwd: wd, state_root: stateDir }));
|
|
575
|
+
const skillPath = join(sessionDir, 'skill-active-state.json');
|
|
576
|
+
const teamPath = join(sessionDir, 'team-state.json');
|
|
577
|
+
const ralphPath = join(sessionDir, 'ralph-state.json');
|
|
578
|
+
const legacyTeamSkill = JSON.stringify({ active: true, skill: 'team', phase: 'team-exec', session_id: sessionId, active_skills: [{ skill: 'code-review', active: false }] }, null, 2);
|
|
579
|
+
const activeTeam = JSON.stringify({ active: true, mode: 'team', session_id: sessionId }, null, 2);
|
|
580
|
+
await writeFile(skillPath, legacyTeamSkill);
|
|
581
|
+
await writeFile(teamPath, activeTeam);
|
|
582
|
+
await writeFile(ralphPath, JSON.stringify({ active: true, mode: 'ralph', session_id: sessionId }, null, 2));
|
|
583
|
+
const result = runOmx(wd, 'cancel');
|
|
584
|
+
assert.equal(result.status, 0, result.stderr || result.stdout);
|
|
585
|
+
assert.equal(await readFile(skillPath, 'utf-8'), legacyTeamSkill);
|
|
586
|
+
assert.equal(await readFile(teamPath, 'utf-8'), activeTeam);
|
|
587
|
+
assert.equal(JSON.parse(await readFile(ralphPath, 'utf-8')).active, false);
|
|
588
|
+
}
|
|
589
|
+
finally {
|
|
590
|
+
await rm(wd, { recursive: true, force: true });
|
|
591
|
+
}
|
|
592
|
+
});
|
|
593
|
+
it('cancels valid skill-active-only workflow names without a duplicate allowlist', async () => {
|
|
594
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-cli-cancel-skill-only-name-'));
|
|
595
|
+
try {
|
|
596
|
+
const stateDir = join(wd, '.omx', 'state');
|
|
597
|
+
const sessionId = 'skill-only-name-session';
|
|
598
|
+
const nativeSessionId = 'skill-only-name-native';
|
|
599
|
+
const sessionDir = join(stateDir, 'sessions', sessionId);
|
|
600
|
+
await mkdir(sessionDir, { recursive: true });
|
|
601
|
+
await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: sessionId, native_session_id: nativeSessionId, cwd: wd, state_root: stateDir }));
|
|
602
|
+
const nativeOwnerDir = join(stateDir, 'sessions', nativeSessionId);
|
|
603
|
+
await mkdir(nativeOwnerDir, { recursive: true });
|
|
604
|
+
await writeFile(join(nativeOwnerDir, 'session-owner.json'), JSON.stringify({
|
|
605
|
+
session_id: nativeSessionId,
|
|
606
|
+
native_session_id: nativeSessionId,
|
|
607
|
+
cwd: wd,
|
|
608
|
+
platform: process.platform,
|
|
609
|
+
}));
|
|
610
|
+
const skillPath = join(sessionDir, 'skill-active-state.json');
|
|
611
|
+
await writeFile(skillPath, JSON.stringify({
|
|
612
|
+
active: true,
|
|
613
|
+
skill: 'code-review',
|
|
614
|
+
session_id: sessionId,
|
|
615
|
+
active_skills: [{ skill: 'code-review', active: true, session_id: sessionId, owner_codex_session_id: nativeSessionId }],
|
|
616
|
+
}, null, 2));
|
|
617
|
+
const result = runOmx(wd, 'cancel');
|
|
618
|
+
assert.equal(result.status, 0, result.stderr || result.stdout);
|
|
619
|
+
const skill = JSON.parse(await readFile(skillPath, 'utf-8'));
|
|
620
|
+
assert.equal(skill.active, false);
|
|
621
|
+
assert.equal(skill.active_skills[0].active, false);
|
|
622
|
+
}
|
|
623
|
+
finally {
|
|
624
|
+
await rm(wd, { recursive: true, force: true });
|
|
625
|
+
}
|
|
626
|
+
});
|
|
627
|
+
it('preserves mode-only cancellation without creating a skill marker', async () => {
|
|
628
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-cli-cancel-mode-only-'));
|
|
629
|
+
try {
|
|
630
|
+
const stateDir = join(wd, '.omx', 'state');
|
|
631
|
+
const sessionId = 'mode-only-session';
|
|
632
|
+
const sessionDir = join(stateDir, 'sessions', sessionId);
|
|
633
|
+
const skillPath = join(sessionDir, 'skill-active-state.json');
|
|
634
|
+
const modePath = join(sessionDir, 'ralplan-state.json');
|
|
635
|
+
await mkdir(sessionDir, { recursive: true });
|
|
636
|
+
await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: sessionId, cwd: wd, state_root: stateDir }));
|
|
637
|
+
await writeFile(modePath, JSON.stringify({ active: true, mode: 'ralplan', session_id: sessionId }, null, 2));
|
|
638
|
+
const result = runOmx(wd, 'cancel');
|
|
639
|
+
assert.equal(result.status, 0, result.stderr || result.stdout);
|
|
640
|
+
assert.match(result.stdout, /Cancelled: ralplan/);
|
|
641
|
+
assert.equal(existsSync(skillPath), false);
|
|
642
|
+
assert.equal(JSON.parse(await readFile(modePath, 'utf-8')).active, false);
|
|
643
|
+
const repeat = runOmx(wd, 'cancel');
|
|
644
|
+
assert.equal(repeat.status, 0, repeat.stderr || repeat.stdout);
|
|
645
|
+
assert.equal(existsSync(skillPath), false);
|
|
646
|
+
}
|
|
647
|
+
finally {
|
|
648
|
+
await rm(wd, { recursive: true, force: true });
|
|
649
|
+
}
|
|
650
|
+
});
|
|
651
|
+
it('reports rollback restoration failures without false cancellation success', async () => {
|
|
652
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-cli-cancel-rollback-failure-'));
|
|
653
|
+
try {
|
|
654
|
+
const stateDir = join(wd, '.omx', 'state');
|
|
655
|
+
const sessionId = 'rollback-session';
|
|
656
|
+
const nativeSessionId = 'rollback-native';
|
|
657
|
+
const sessionDir = join(stateDir, 'sessions', sessionId);
|
|
658
|
+
const modePath = join(sessionDir, 'ralplan-state.json');
|
|
659
|
+
const skillPath = join(sessionDir, 'skill-active-state.json');
|
|
660
|
+
const modeState = JSON.stringify({ active: true, mode: 'ralplan', session_id: sessionId }, null, 2);
|
|
661
|
+
const skillState = JSON.stringify({
|
|
662
|
+
active: true,
|
|
663
|
+
skill: 'ralplan',
|
|
664
|
+
session_id: sessionId,
|
|
665
|
+
owner_codex_session_id: nativeSessionId,
|
|
666
|
+
active_skills: [{ skill: 'ralplan', active: true, session_id: sessionId, owner_codex_session_id: nativeSessionId }],
|
|
667
|
+
}, null, 2);
|
|
668
|
+
await mkdir(sessionDir, { recursive: true });
|
|
669
|
+
await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: sessionId, native_session_id: nativeSessionId, cwd: wd, state_root: stateDir }));
|
|
670
|
+
await mkdir(join(stateDir, 'sessions', nativeSessionId), { recursive: true });
|
|
671
|
+
await writeFile(join(stateDir, 'sessions', nativeSessionId, 'session-owner.json'), JSON.stringify({
|
|
672
|
+
session_id: nativeSessionId,
|
|
673
|
+
native_session_id: nativeSessionId,
|
|
674
|
+
cwd: wd,
|
|
675
|
+
}));
|
|
676
|
+
await writeFile(modePath, modeState);
|
|
677
|
+
await writeFile(skillPath, skillState);
|
|
678
|
+
const errors = [];
|
|
679
|
+
const logs = [];
|
|
680
|
+
const previousError = console.error;
|
|
681
|
+
const previousLog = console.log;
|
|
682
|
+
const previousExitCode = process.exitCode;
|
|
683
|
+
const previousStderrWrite = process.stderr.write;
|
|
684
|
+
console.error = (...args) => { errors.push(args.map(String).join(' ')); };
|
|
685
|
+
console.log = (...args) => { logs.push(args.map(String).join(' ')); };
|
|
686
|
+
process.stderr.write = ((chunk) => {
|
|
687
|
+
errors.push(String(chunk));
|
|
688
|
+
return true;
|
|
689
|
+
});
|
|
690
|
+
try {
|
|
691
|
+
await cancelModesForTest(wd, [], {
|
|
692
|
+
writeFailureMode: 'skill-active',
|
|
693
|
+
rollbackFailureMode: 'ralplan',
|
|
694
|
+
});
|
|
695
|
+
assert.equal(process.exitCode, 1);
|
|
696
|
+
}
|
|
697
|
+
finally {
|
|
698
|
+
console.error = previousError;
|
|
699
|
+
console.log = previousLog;
|
|
700
|
+
process.stderr.write = previousStderrWrite;
|
|
701
|
+
process.exitCode = previousExitCode;
|
|
702
|
+
}
|
|
703
|
+
assert.match(errors.join('\n'), /cancellation_rollback_failed:1:ralplan/);
|
|
704
|
+
assert.doesNotMatch(logs.join('\n'), /Cancelled:/);
|
|
705
|
+
assert.equal(await readFile(skillPath, 'utf-8'), skillState);
|
|
706
|
+
assert.equal(JSON.parse(await readFile(modePath, 'utf-8')).active, false);
|
|
707
|
+
}
|
|
708
|
+
finally {
|
|
709
|
+
await rm(wd, { recursive: true, force: true });
|
|
710
|
+
}
|
|
711
|
+
});
|
|
712
|
+
it('fails Ralplan preflight without mutating unproven session state', async () => {
|
|
713
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-cli-ralplan-preflight-scope-'));
|
|
714
|
+
const codexBin = await createFakeCodexBin(wd, 'codex-cli 0.147.0');
|
|
715
|
+
try {
|
|
716
|
+
const stateDir = join(wd, '.omx', 'state');
|
|
717
|
+
const sessionId = 'sess-unproven-owner';
|
|
718
|
+
const sessionDir = join(stateDir, 'sessions', sessionId);
|
|
719
|
+
const ralplanPath = join(sessionDir, 'ralplan-state.json');
|
|
720
|
+
const ralplanState = JSON.stringify({
|
|
721
|
+
active: true,
|
|
722
|
+
mode: 'ralplan',
|
|
723
|
+
current_phase: 'starting',
|
|
724
|
+
}, null, 2);
|
|
725
|
+
await mkdir(sessionDir, { recursive: true });
|
|
726
|
+
await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: sessionId, cwd: wd, state_root: stateDir }));
|
|
727
|
+
await writeFile(ralplanPath, ralplanState);
|
|
728
|
+
const preflightResult = runOmxWithEnv(wd, { PATH: `${codexBin}:${process.env.PATH}` }, 'ralplan', 'preflight', '--json');
|
|
729
|
+
assert.equal(preflightResult.status, 1, preflightResult.stderr || preflightResult.stdout);
|
|
730
|
+
assert.deepEqual(JSON.parse(preflightResult.stdout), {
|
|
731
|
+
ok: false,
|
|
732
|
+
reason: 'unsupported_documented_leader_proof',
|
|
733
|
+
diagnostics: {
|
|
734
|
+
probe_status: 'ok',
|
|
735
|
+
detected_version: '0.147.0',
|
|
736
|
+
documented_root_identity: { status: 'unknown' },
|
|
737
|
+
},
|
|
738
|
+
});
|
|
739
|
+
assert.equal(await readFile(ralplanPath, 'utf-8'), ralplanState);
|
|
740
|
+
}
|
|
741
|
+
finally {
|
|
742
|
+
await rm(wd, { recursive: true, force: true });
|
|
743
|
+
}
|
|
744
|
+
});
|
|
745
|
+
it('keeps exact-owner routing-only Ralplan active after preflight denial', async () => {
|
|
746
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-cli-ralplan-preflight-owner-'));
|
|
747
|
+
const codexBin = await createFakeCodexBin(wd, 'codex-cli 0.147.0');
|
|
748
|
+
try {
|
|
749
|
+
const stateDir = join(wd, '.omx', 'state');
|
|
750
|
+
const sessionId = 'sess-proven-owner';
|
|
751
|
+
const sessionDir = join(stateDir, 'sessions', sessionId);
|
|
752
|
+
const ralplanPath = join(sessionDir, 'ralplan-state.json');
|
|
753
|
+
const sessionSkillPath = join(sessionDir, 'skill-active-state.json');
|
|
754
|
+
const rootTeamPath = join(stateDir, 'team-state.json');
|
|
755
|
+
const rootSkillPath = join(stateDir, 'skill-active-state.json');
|
|
756
|
+
const rootStopPath = join(stateDir, 'native-stop-state.json');
|
|
757
|
+
const rootTeam = JSON.stringify({ active: true, mode: 'team', current_phase: 'team-exec' }, null, 2);
|
|
758
|
+
const rootSkill = JSON.stringify({ active: true, skill: 'team', phase: 'team-exec' }, null, 2);
|
|
759
|
+
const rootStop = JSON.stringify({ sessions: { foreign: { pending: true } } }, null, 2);
|
|
760
|
+
await mkdir(sessionDir, { recursive: true });
|
|
761
|
+
await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: sessionId, cwd: wd, state_root: stateDir }));
|
|
762
|
+
const activated = await recordSkillActivation({
|
|
763
|
+
stateDir,
|
|
764
|
+
sourceCwd: wd,
|
|
765
|
+
text: '$ralplan continue issue #3212',
|
|
766
|
+
sessionId,
|
|
767
|
+
threadId: 'thread-preflight-owner',
|
|
768
|
+
turnId: 'turn-preflight-owner',
|
|
769
|
+
nowIso: '2026-07-19T00:00:00.000Z',
|
|
770
|
+
});
|
|
771
|
+
assert.ok(activated);
|
|
772
|
+
const ralplanState = await readFile(ralplanPath, 'utf-8');
|
|
773
|
+
const sessionSkillState = await readFile(sessionSkillPath, 'utf-8');
|
|
774
|
+
await writeFile(rootTeamPath, rootTeam);
|
|
775
|
+
await writeFile(rootSkillPath, rootSkill);
|
|
776
|
+
await writeFile(rootStopPath, rootStop);
|
|
777
|
+
const sessionEntries = await readdir(sessionDir);
|
|
778
|
+
const preflightResult = runOmxWithEnv(wd, { OMX_SESSION_ID: sessionId, PATH: `${codexBin}:${process.env.PATH}` }, 'ralplan', 'preflight', '--json');
|
|
779
|
+
assert.equal(preflightResult.status, 1, preflightResult.stderr || preflightResult.stdout);
|
|
780
|
+
assert.deepEqual(JSON.parse(preflightResult.stdout), {
|
|
781
|
+
ok: false,
|
|
782
|
+
reason: 'unsupported_documented_leader_proof',
|
|
783
|
+
diagnostics: {
|
|
784
|
+
probe_status: 'ok',
|
|
785
|
+
detected_version: '0.147.0',
|
|
786
|
+
documented_root_identity: { status: 'unknown' },
|
|
787
|
+
},
|
|
788
|
+
});
|
|
789
|
+
assert.equal(await readFile(ralplanPath, 'utf-8'), ralplanState);
|
|
790
|
+
assert.equal(await readFile(sessionSkillPath, 'utf-8'), sessionSkillState);
|
|
791
|
+
const [state, skillState] = await Promise.all([
|
|
792
|
+
readModeState('ralplan', wd),
|
|
793
|
+
readSkillActiveState(sessionSkillPath),
|
|
794
|
+
]);
|
|
795
|
+
assert.equal(state?.active, true);
|
|
796
|
+
assert.equal(skillState?.active, true);
|
|
797
|
+
const postPreflightEntries = await readdir(sessionDir);
|
|
798
|
+
assert.deepEqual(postPreflightEntries, sessionEntries);
|
|
799
|
+
assert.equal(postPreflightEntries.some((name) => name.startsWith('.ralplan-neutralization-')), false);
|
|
800
|
+
assert.equal(await readFile(rootTeamPath, 'utf-8'), rootTeam);
|
|
801
|
+
assert.equal(await readFile(rootSkillPath, 'utf-8'), rootSkill);
|
|
802
|
+
assert.equal(await readFile(rootStopPath, 'utf-8'), rootStop);
|
|
803
|
+
}
|
|
804
|
+
finally {
|
|
805
|
+
await rm(wd, { recursive: true, force: true });
|
|
806
|
+
}
|
|
807
|
+
});
|
|
79
808
|
it('status does not report a root fallback mode as active after current-session clear', async () => {
|
|
80
809
|
const wd = await mkdtemp(join(tmpdir(), 'omx-cli-session-clear-fallback-'));
|
|
81
810
|
try {
|
|
@@ -83,7 +812,7 @@ describe('CLI session-scoped state parity', () => {
|
|
|
83
812
|
const sessionId = 'sess-clear';
|
|
84
813
|
const sessionDir = join(stateDir, 'sessions', sessionId);
|
|
85
814
|
await mkdir(sessionDir, { recursive: true });
|
|
86
|
-
await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: sessionId }));
|
|
815
|
+
await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: sessionId, cwd: wd, state_root: stateDir }));
|
|
87
816
|
await writeFile(join(stateDir, 'deep-interview-state.json'), JSON.stringify({
|
|
88
817
|
active: true,
|
|
89
818
|
mode: 'deep-interview',
|
|
@@ -106,17 +835,332 @@ describe('CLI session-scoped state parity', () => {
|
|
|
106
835
|
await rm(wd, { recursive: true, force: true });
|
|
107
836
|
}
|
|
108
837
|
});
|
|
838
|
+
it('does not cancel a foreign hook-visible run-dir from session-owned scope', async () => {
|
|
839
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-cli-run-dir-cancel-foreign-owner-'));
|
|
840
|
+
const runsRoot = await mkdtemp(join(tmpdir(), 'omx-cli-run-dir-cancel-foreign-runs-'));
|
|
841
|
+
try {
|
|
842
|
+
const ownerSessionId = 'sess-canonical-owner';
|
|
843
|
+
const foreignSessionId = 'sess-foreign-run';
|
|
844
|
+
const stateDir = join(wd, '.omx', 'state');
|
|
845
|
+
const runDir = join(runsRoot, 'run-foreign-session');
|
|
846
|
+
const runStateDir = join(runDir, '.omx', 'state');
|
|
847
|
+
const foreignSessionDir = join(runStateDir, 'sessions', foreignSessionId);
|
|
848
|
+
const foreignStatePath = join(foreignSessionDir, 'autopilot-state.json');
|
|
849
|
+
const foreignState = JSON.stringify({
|
|
850
|
+
active: true,
|
|
851
|
+
mode: 'autopilot',
|
|
852
|
+
current_phase: 'deep-interview',
|
|
853
|
+
}, null, 2);
|
|
854
|
+
await mkdir(join(stateDir, 'sessions', ownerSessionId), { recursive: true });
|
|
855
|
+
await mkdir(foreignSessionDir, { recursive: true });
|
|
856
|
+
await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: ownerSessionId }));
|
|
857
|
+
await writeFile(join(runStateDir, 'session.json'), JSON.stringify({ session_id: foreignSessionId }));
|
|
858
|
+
await writeFile(foreignStatePath, foreignState);
|
|
859
|
+
await writeFile(join(runsRoot, 'registry.jsonl'), `${JSON.stringify({
|
|
860
|
+
source_cwd: wd,
|
|
861
|
+
run_dir: runDir,
|
|
862
|
+
})}\n`);
|
|
863
|
+
const cancelResult = runOmxWithEnv(wd, { OMX_RUNS_DIR: runsRoot }, 'cancel');
|
|
864
|
+
assert.notEqual(cancelResult.status, 0);
|
|
865
|
+
assert.match(cancelResult.stderr, /session\.json is present but unusable/);
|
|
866
|
+
assert.doesNotMatch(cancelResult.stdout, /Cancelled: autopilot/);
|
|
867
|
+
assert.equal(await readFile(foreignStatePath, 'utf-8'), foreignState);
|
|
868
|
+
}
|
|
869
|
+
finally {
|
|
870
|
+
await rm(wd, { recursive: true, force: true });
|
|
871
|
+
await rm(runsRoot, { recursive: true, force: true });
|
|
872
|
+
}
|
|
873
|
+
});
|
|
874
|
+
it('keeps stale registry-only run state read-only during root cancellation', async () => {
|
|
875
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-cli-run-dir-stale-registry-'));
|
|
876
|
+
const runsRoot = await mkdtemp(join(tmpdir(), 'omx-cli-run-dir-stale-runs-'));
|
|
877
|
+
try {
|
|
878
|
+
const sessionId = 'sess-stale-registry';
|
|
879
|
+
const runDir = join(runsRoot, 'run-stale-registry');
|
|
880
|
+
const runStateDir = join(runDir, '.omx', 'state');
|
|
881
|
+
const statePath = join(runStateDir, 'sessions', sessionId, 'autopilot-state.json');
|
|
882
|
+
const state = JSON.stringify({ active: true, mode: 'autopilot', current_phase: 'deep-interview' }, null, 2);
|
|
883
|
+
await mkdir(dirname(statePath), { recursive: true });
|
|
884
|
+
await mkdir(join(wd, '.omx', 'state'), { recursive: true });
|
|
885
|
+
await writeFile(join(runStateDir, 'session.json'), JSON.stringify({ session_id: sessionId }));
|
|
886
|
+
await writeFile(statePath, state);
|
|
887
|
+
await writeFile(join(runsRoot, 'registry.jsonl'), `${JSON.stringify({ source_cwd: wd, run_dir: runDir })}\n`);
|
|
888
|
+
const cancelResult = runOmxWithEnv(wd, { OMX_RUNS_DIR: runsRoot }, 'cancel');
|
|
889
|
+
assert.equal(cancelResult.status, 0, cancelResult.stderr || cancelResult.stdout);
|
|
890
|
+
assert.match(cancelResult.stdout, /No active modes to cancel\./);
|
|
891
|
+
assert.equal(await readFile(statePath, 'utf-8'), state);
|
|
892
|
+
}
|
|
893
|
+
finally {
|
|
894
|
+
await rm(wd, { recursive: true, force: true });
|
|
895
|
+
await rm(runsRoot, { recursive: true, force: true });
|
|
896
|
+
}
|
|
897
|
+
});
|
|
898
|
+
it('rejects a live run record whose run directory escapes through a symlink', async () => {
|
|
899
|
+
if (process.platform === 'win32')
|
|
900
|
+
return;
|
|
901
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-cli-run-dir-symlink-owner-'));
|
|
902
|
+
const runsRoot = await mkdtemp(join(tmpdir(), 'omx-cli-run-dir-symlink-runs-'));
|
|
903
|
+
const outsideRoot = await mkdtemp(join(tmpdir(), 'omx-cli-run-dir-symlink-outside-'));
|
|
904
|
+
try {
|
|
905
|
+
const sessionId = 'sess-symlink-escape';
|
|
906
|
+
const tmuxSessionName = 'omx-symlink-run';
|
|
907
|
+
const linkRunDir = join(runsRoot, 'linked-run');
|
|
908
|
+
const outsideStateDir = join(outsideRoot, '.omx', 'state');
|
|
909
|
+
const statePath = join(outsideStateDir, 'sessions', sessionId, 'autopilot-state.json');
|
|
910
|
+
const state = JSON.stringify({ active: true, mode: 'autopilot', current_phase: 'deep-interview' }, null, 2);
|
|
911
|
+
const fakeBin = await createFakeTmuxBin(wd);
|
|
912
|
+
await mkdir(dirname(statePath), { recursive: true });
|
|
913
|
+
await mkdir(join(wd, '.omx', 'state'), { recursive: true });
|
|
914
|
+
await writeFile(join(outsideStateDir, 'session.json'), JSON.stringify({ session_id: sessionId, cwd: wd, state_root: outsideStateDir }));
|
|
915
|
+
await writeFile(statePath, state);
|
|
916
|
+
await symlink(outsideRoot, linkRunDir, 'dir');
|
|
917
|
+
await writeActiveRunRecord({
|
|
918
|
+
runsRoot,
|
|
919
|
+
contextKey: 'symlink-run-context',
|
|
920
|
+
sourceCwd: wd,
|
|
921
|
+
runDir: linkRunDir,
|
|
922
|
+
sessionId,
|
|
923
|
+
tmuxSessionName,
|
|
924
|
+
});
|
|
925
|
+
const cancelResult = runOmxWithEnv(wd, {
|
|
926
|
+
OMX_RUNS_DIR: runsRoot,
|
|
927
|
+
PATH: `${fakeBin}:${process.env.PATH ?? ''}`,
|
|
928
|
+
FAKE_OMX_SESSION_ID: sessionId,
|
|
929
|
+
FAKE_TMUX_SESSION_NAME: tmuxSessionName,
|
|
930
|
+
}, 'cancel');
|
|
931
|
+
assert.notEqual(cancelResult.status, 0);
|
|
932
|
+
assert.match(cancelResult.stderr, /detached run authority is invalid/);
|
|
933
|
+
assert.equal(await readFile(statePath, 'utf-8'), state);
|
|
934
|
+
}
|
|
935
|
+
finally {
|
|
936
|
+
await rm(wd, { recursive: true, force: true });
|
|
937
|
+
await rm(runsRoot, { recursive: true, force: true });
|
|
938
|
+
await rm(outsideRoot, { recursive: true, force: true });
|
|
939
|
+
}
|
|
940
|
+
});
|
|
941
|
+
it('rejects nested session-directory and state-file symlink escapes', async () => {
|
|
942
|
+
if (process.platform === 'win32')
|
|
943
|
+
return;
|
|
944
|
+
for (const kind of ['session-dir', 'state-file']) {
|
|
945
|
+
const wd = await mkdtemp(join(tmpdir(), `omx-cli-nested-${kind}-owner-`));
|
|
946
|
+
const runsRoot = await mkdtemp(join(tmpdir(), `omx-cli-nested-${kind}-runs-`));
|
|
947
|
+
const outsideRoot = await mkdtemp(join(tmpdir(), `omx-cli-nested-${kind}-outside-`));
|
|
948
|
+
try {
|
|
949
|
+
const sessionId = `sess-nested-${kind}`;
|
|
950
|
+
const tmuxSessionName = `omx-nested-${kind}`;
|
|
951
|
+
const runDir = join(runsRoot, `run-nested-${kind}`);
|
|
952
|
+
const stateDir = join(runDir, '.omx', 'state');
|
|
953
|
+
const sessionDir = join(stateDir, 'sessions', sessionId);
|
|
954
|
+
const outsideStatePath = join(outsideRoot, 'autopilot-state.json');
|
|
955
|
+
const state = JSON.stringify({ active: true, mode: 'autopilot', current_phase: 'executing' }, null, 2);
|
|
956
|
+
const fakeBin = await createFakeTmuxBin(wd);
|
|
957
|
+
await mkdir(join(stateDir, 'sessions'), { recursive: true });
|
|
958
|
+
await mkdir(join(wd, '.omx', 'state'), { recursive: true });
|
|
959
|
+
await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: sessionId, cwd: wd, state_root: stateDir }));
|
|
960
|
+
await writeFile(outsideStatePath, state);
|
|
961
|
+
if (kind === 'session-dir') {
|
|
962
|
+
await symlink(outsideRoot, sessionDir, 'dir');
|
|
963
|
+
}
|
|
964
|
+
else {
|
|
965
|
+
await mkdir(sessionDir, { recursive: true });
|
|
966
|
+
await symlink(outsideStatePath, join(sessionDir, 'autopilot-state.json'));
|
|
967
|
+
}
|
|
968
|
+
await writeActiveRunRecord({ runsRoot, contextKey: `nested-${kind}`, sourceCwd: wd, runDir, sessionId, tmuxSessionName });
|
|
969
|
+
const cancelResult = runOmxWithEnv(wd, {
|
|
970
|
+
OMX_RUNS_DIR: runsRoot,
|
|
971
|
+
PATH: `${fakeBin}:${process.env.PATH ?? ''}`,
|
|
972
|
+
FAKE_OMX_SESSION_ID: sessionId,
|
|
973
|
+
FAKE_TMUX_SESSION_NAME: tmuxSessionName,
|
|
974
|
+
}, 'cancel');
|
|
975
|
+
assert.notEqual(cancelResult.status, 0);
|
|
976
|
+
assert.match(cancelResult.stderr, /detached run (?:state )?authority is invalid/);
|
|
977
|
+
assert.equal(await readFile(outsideStatePath, 'utf-8'), state);
|
|
978
|
+
}
|
|
979
|
+
finally {
|
|
980
|
+
await rm(wd, { recursive: true, force: true });
|
|
981
|
+
await rm(runsRoot, { recursive: true, force: true });
|
|
982
|
+
await rm(outsideRoot, { recursive: true, force: true });
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
});
|
|
986
|
+
it('revalidates frozen tmux incarnation immediately before mutation', async () => {
|
|
987
|
+
if (process.platform === 'win32')
|
|
988
|
+
return;
|
|
989
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-cli-run-toctou-owner-'));
|
|
990
|
+
const runsRoot = await mkdtemp(join(tmpdir(), 'omx-cli-run-toctou-runs-'));
|
|
991
|
+
try {
|
|
992
|
+
const sessionId = 'sess-run-toctou';
|
|
993
|
+
const tmuxSessionName = 'omx-run-toctou';
|
|
994
|
+
const runDir = join(runsRoot, 'run-toctou');
|
|
995
|
+
const stateDir = join(runDir, '.omx', 'state');
|
|
996
|
+
const statePath = join(stateDir, 'sessions', sessionId, 'autopilot-state.json');
|
|
997
|
+
const state = JSON.stringify({ active: true, mode: 'autopilot', current_phase: 'executing' }, null, 2);
|
|
998
|
+
const counterPath = join(wd, 'tmux-count');
|
|
999
|
+
const fakeBin = await createFakeTmuxBin(wd);
|
|
1000
|
+
await mkdir(dirname(statePath), { recursive: true });
|
|
1001
|
+
await mkdir(join(wd, '.omx', 'state'), { recursive: true });
|
|
1002
|
+
await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: sessionId, cwd: wd, state_root: stateDir }));
|
|
1003
|
+
await writeFile(statePath, state);
|
|
1004
|
+
await writeActiveRunRecord({ runsRoot, contextKey: 'toctou-run', sourceCwd: wd, runDir, sessionId, tmuxSessionName });
|
|
1005
|
+
const cancelResult = runOmxWithEnv(wd, {
|
|
1006
|
+
OMX_RUNS_DIR: runsRoot,
|
|
1007
|
+
PATH: `${fakeBin}:${process.env.PATH ?? ''}`,
|
|
1008
|
+
FAKE_OMX_SESSION_ID: sessionId,
|
|
1009
|
+
FAKE_TMUX_SESSION_NAME: tmuxSessionName,
|
|
1010
|
+
FAKE_TMUX_COUNTER_FILE: counterPath,
|
|
1011
|
+
FAKE_TMUX_REPLACE_AFTER_FIRST: '1',
|
|
1012
|
+
}, 'cancel');
|
|
1013
|
+
assert.notEqual(cancelResult.status, 0);
|
|
1014
|
+
assert.match(cancelResult.stderr, /detached run authority changed/);
|
|
1015
|
+
assert.equal(await readFile(statePath, 'utf-8'), state);
|
|
1016
|
+
}
|
|
1017
|
+
finally {
|
|
1018
|
+
await rm(wd, { recursive: true, force: true });
|
|
1019
|
+
await rm(runsRoot, { recursive: true, force: true });
|
|
1020
|
+
}
|
|
1021
|
+
});
|
|
1022
|
+
it('denies the entire run transaction when one candidate state is malformed', async () => {
|
|
1023
|
+
if (process.platform === 'win32')
|
|
1024
|
+
return;
|
|
1025
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-cli-run-malformed-owner-'));
|
|
1026
|
+
const runsRoot = await mkdtemp(join(tmpdir(), 'omx-cli-run-malformed-runs-'));
|
|
1027
|
+
try {
|
|
1028
|
+
const sessionId = 'sess-run-malformed';
|
|
1029
|
+
const tmuxSessionName = 'omx-run-malformed';
|
|
1030
|
+
const runDir = join(runsRoot, 'run-malformed');
|
|
1031
|
+
const stateDir = join(runDir, '.omx', 'state');
|
|
1032
|
+
const sessionDir = join(stateDir, 'sessions', sessionId);
|
|
1033
|
+
const validPath = join(sessionDir, 'ralplan-state.json');
|
|
1034
|
+
const malformedPath = join(sessionDir, 'autopilot-state.json');
|
|
1035
|
+
const validState = JSON.stringify({ active: true, mode: 'ralplan', current_phase: 'executing' }, null, 2);
|
|
1036
|
+
const malformedState = '{"active":true';
|
|
1037
|
+
const fakeBin = await createFakeTmuxBin(wd);
|
|
1038
|
+
await mkdir(sessionDir, { recursive: true });
|
|
1039
|
+
await mkdir(join(wd, '.omx', 'state'), { recursive: true });
|
|
1040
|
+
await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: sessionId, cwd: wd, state_root: stateDir }));
|
|
1041
|
+
await writeFile(validPath, validState);
|
|
1042
|
+
await writeFile(malformedPath, malformedState);
|
|
1043
|
+
await writeActiveRunRecord({ runsRoot, contextKey: 'malformed-run', sourceCwd: wd, runDir, sessionId, tmuxSessionName });
|
|
1044
|
+
const cancelResult = runOmxWithEnv(wd, {
|
|
1045
|
+
OMX_RUNS_DIR: runsRoot,
|
|
1046
|
+
PATH: `${fakeBin}:${process.env.PATH ?? ''}`,
|
|
1047
|
+
FAKE_OMX_SESSION_ID: sessionId,
|
|
1048
|
+
FAKE_TMUX_SESSION_NAME: tmuxSessionName,
|
|
1049
|
+
}, 'cancel');
|
|
1050
|
+
assert.notEqual(cancelResult.status, 0);
|
|
1051
|
+
assert.match(cancelResult.stderr, /Refusing partial cancellation/);
|
|
1052
|
+
assert.equal(await readFile(validPath, 'utf-8'), validState);
|
|
1053
|
+
assert.equal(await readFile(malformedPath, 'utf-8'), malformedState);
|
|
1054
|
+
}
|
|
1055
|
+
finally {
|
|
1056
|
+
await rm(wd, { recursive: true, force: true });
|
|
1057
|
+
await rm(runsRoot, { recursive: true, force: true });
|
|
1058
|
+
}
|
|
1059
|
+
});
|
|
1060
|
+
it('uses the authorized run session for force native-stop cleanup', async () => {
|
|
1061
|
+
if (process.platform === 'win32')
|
|
1062
|
+
return;
|
|
1063
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-cli-run-force-owner-'));
|
|
1064
|
+
const runsRoot = await mkdtemp(join(tmpdir(), 'omx-cli-run-force-runs-'));
|
|
1065
|
+
try {
|
|
1066
|
+
const sessionId = 'sess-run-force';
|
|
1067
|
+
const tmuxSessionName = 'omx-run-force';
|
|
1068
|
+
const runDir = join(runsRoot, 'run-force');
|
|
1069
|
+
const stateDir = join(runDir, '.omx', 'state');
|
|
1070
|
+
const sessionDir = join(stateDir, 'sessions', sessionId);
|
|
1071
|
+
const modePath = join(sessionDir, 'autopilot-state.json');
|
|
1072
|
+
const stopPath = join(sessionDir, 'native-stop-state.json');
|
|
1073
|
+
const fakeBin = await createFakeTmuxBin(wd);
|
|
1074
|
+
await mkdir(sessionDir, { recursive: true });
|
|
1075
|
+
await mkdir(join(wd, '.omx', 'state'), { recursive: true });
|
|
1076
|
+
await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: sessionId, cwd: wd, state_root: stateDir }));
|
|
1077
|
+
await writeFile(modePath, JSON.stringify({ active: true, mode: 'autopilot', current_phase: 'executing' }));
|
|
1078
|
+
await writeFile(stopPath, JSON.stringify({ sessions: { [sessionId]: { pending: true }, foreign: { pending: true } } }, null, 2));
|
|
1079
|
+
await writeActiveRunRecord({ runsRoot, contextKey: 'force-run', sourceCwd: wd, runDir, sessionId, tmuxSessionName });
|
|
1080
|
+
const cancelResult = runOmxWithEnv(wd, {
|
|
1081
|
+
OMX_RUNS_DIR: runsRoot,
|
|
1082
|
+
PATH: `${fakeBin}:${process.env.PATH ?? ''}`,
|
|
1083
|
+
FAKE_OMX_SESSION_ID: sessionId,
|
|
1084
|
+
FAKE_TMUX_SESSION_NAME: tmuxSessionName,
|
|
1085
|
+
}, 'cancel', '--force');
|
|
1086
|
+
assert.equal(cancelResult.status, 0, cancelResult.stderr || cancelResult.stdout);
|
|
1087
|
+
assert.match(cancelResult.stdout, /Cancelled: autopilot/);
|
|
1088
|
+
const stopState = JSON.parse(await readFile(stopPath, 'utf-8'));
|
|
1089
|
+
assert.deepEqual(stopState.sessions, { foreign: { pending: true } });
|
|
1090
|
+
}
|
|
1091
|
+
finally {
|
|
1092
|
+
await rm(wd, { recursive: true, force: true });
|
|
1093
|
+
await rm(runsRoot, { recursive: true, force: true });
|
|
1094
|
+
}
|
|
1095
|
+
});
|
|
1096
|
+
it('fails closed when multiple live run records match the same root', async () => {
|
|
1097
|
+
if (process.platform === 'win32')
|
|
1098
|
+
return;
|
|
1099
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-cli-run-dir-duplicate-owner-'));
|
|
1100
|
+
const runsRoot = await mkdtemp(join(tmpdir(), 'omx-cli-run-dir-duplicate-runs-'));
|
|
1101
|
+
try {
|
|
1102
|
+
const sessionId = 'sess-duplicate-live';
|
|
1103
|
+
const tmuxSessionName = 'omx-duplicate-run';
|
|
1104
|
+
const fakeBin = await createFakeTmuxBin(wd);
|
|
1105
|
+
const states = [];
|
|
1106
|
+
await mkdir(join(wd, '.omx', 'state'), { recursive: true });
|
|
1107
|
+
for (const [index, mode] of ['autopilot', 'ralplan'].entries()) {
|
|
1108
|
+
const runDir = join(runsRoot, `run-duplicate-${index}`);
|
|
1109
|
+
const runStateDir = join(runDir, '.omx', 'state');
|
|
1110
|
+
const statePath = join(runStateDir, 'sessions', sessionId, `${mode}-state.json`);
|
|
1111
|
+
const content = JSON.stringify({ active: true, mode, current_phase: 'executing' }, null, 2);
|
|
1112
|
+
await mkdir(dirname(statePath), { recursive: true });
|
|
1113
|
+
await writeFile(join(runStateDir, 'session.json'), JSON.stringify({ session_id: sessionId, cwd: wd, state_root: runStateDir }));
|
|
1114
|
+
await writeFile(statePath, content);
|
|
1115
|
+
await writeActiveRunRecord({
|
|
1116
|
+
runsRoot,
|
|
1117
|
+
contextKey: `duplicate-run-context-${index}`,
|
|
1118
|
+
sourceCwd: wd,
|
|
1119
|
+
runDir,
|
|
1120
|
+
sessionId,
|
|
1121
|
+
tmuxSessionName,
|
|
1122
|
+
});
|
|
1123
|
+
states.push({ path: statePath, content });
|
|
1124
|
+
}
|
|
1125
|
+
const cancelResult = runOmxWithEnv(wd, {
|
|
1126
|
+
OMX_RUNS_DIR: runsRoot,
|
|
1127
|
+
PATH: `${fakeBin}:${process.env.PATH ?? ''}`,
|
|
1128
|
+
FAKE_OMX_SESSION_ID: sessionId,
|
|
1129
|
+
FAKE_TMUX_SESSION_NAME: tmuxSessionName,
|
|
1130
|
+
}, 'cancel');
|
|
1131
|
+
assert.notEqual(cancelResult.status, 0);
|
|
1132
|
+
assert.match(cancelResult.stderr, /No active modes|multiple|authority/i);
|
|
1133
|
+
for (const state of states) {
|
|
1134
|
+
assert.equal(await readFile(state.path, 'utf-8'), state.content);
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
finally {
|
|
1138
|
+
await rm(wd, { recursive: true, force: true });
|
|
1139
|
+
await rm(runsRoot, { recursive: true, force: true });
|
|
1140
|
+
}
|
|
1141
|
+
});
|
|
109
1142
|
it('cancels hook-visible run-dir session state when worktree state list-active is empty', async () => {
|
|
110
1143
|
const wd = await mkdtemp(join(tmpdir(), 'omx-cli-run-dir-cancel-worktree-'));
|
|
111
1144
|
const runsRoot = await mkdtemp(join(tmpdir(), 'omx-cli-run-dir-cancel-runs-'));
|
|
112
1145
|
try {
|
|
1146
|
+
if (process.platform === 'win32')
|
|
1147
|
+
return;
|
|
113
1148
|
const sessionId = 'sess-run-dir-cancel';
|
|
1149
|
+
const tmuxSessionName = 'omx-live-run';
|
|
114
1150
|
const runDir = join(runsRoot, 'run-20260610121751-b6c4');
|
|
115
1151
|
const runStateDir = join(runDir, '.omx', 'state');
|
|
116
1152
|
const runSessionDir = join(runStateDir, 'sessions', sessionId);
|
|
1153
|
+
const fakeBin = await createFakeTmuxBin(wd);
|
|
1154
|
+
const rootAutopilotPath = join(runStateDir, 'autopilot-state.json');
|
|
1155
|
+
const rootAutopilotState = JSON.stringify({
|
|
1156
|
+
active: true,
|
|
1157
|
+
mode: 'autopilot',
|
|
1158
|
+
current_phase: 'stale-root-copy',
|
|
1159
|
+
}, null, 2);
|
|
117
1160
|
await mkdir(runSessionDir, { recursive: true });
|
|
118
1161
|
await mkdir(join(wd, '.omx', 'state'), { recursive: true });
|
|
119
|
-
await writeFile(join(runStateDir, 'session.json'), JSON.stringify({ session_id: sessionId }, null, 2));
|
|
1162
|
+
await writeFile(join(runStateDir, 'session.json'), JSON.stringify({ session_id: sessionId, cwd: wd, state_root: runStateDir }, null, 2));
|
|
1163
|
+
await writeFile(rootAutopilotPath, rootAutopilotState);
|
|
120
1164
|
await writeFile(join(runSessionDir, 'autopilot-state.json'), JSON.stringify({
|
|
121
1165
|
active: true,
|
|
122
1166
|
mode: 'autopilot',
|
|
@@ -130,18 +1174,23 @@ describe('CLI session-scoped state parity', () => {
|
|
|
130
1174
|
session_id: sessionId,
|
|
131
1175
|
active_skills: [{ skill: 'autopilot', phase: 'deep-interview', active: true, session_id: sessionId }],
|
|
132
1176
|
}, null, 2));
|
|
133
|
-
await
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
})
|
|
1177
|
+
await writeActiveRunRecord({
|
|
1178
|
+
runsRoot,
|
|
1179
|
+
contextKey: 'live-run-context',
|
|
1180
|
+
sourceCwd: wd,
|
|
1181
|
+
runDir,
|
|
1182
|
+
sessionId,
|
|
1183
|
+
tmuxSessionName,
|
|
1184
|
+
});
|
|
141
1185
|
const listResult = runOmx(wd, 'state', 'list-active', '--json');
|
|
142
1186
|
assert.equal(listResult.status, 0, listResult.stderr || listResult.stdout);
|
|
143
1187
|
assert.deepEqual(JSON.parse(listResult.stdout), { active_modes: [] });
|
|
144
|
-
const cancelResult = runOmxWithEnv(wd, {
|
|
1188
|
+
const cancelResult = runOmxWithEnv(wd, {
|
|
1189
|
+
OMX_RUNS_DIR: runsRoot,
|
|
1190
|
+
PATH: `${fakeBin}:${process.env.PATH ?? ''}`,
|
|
1191
|
+
FAKE_OMX_SESSION_ID: sessionId,
|
|
1192
|
+
FAKE_TMUX_SESSION_NAME: tmuxSessionName,
|
|
1193
|
+
}, 'cancel');
|
|
145
1194
|
assert.equal(cancelResult.status, 0, cancelResult.stderr || cancelResult.stdout);
|
|
146
1195
|
assert.match(cancelResult.stdout, /Cancelled: autopilot/);
|
|
147
1196
|
assert.doesNotMatch(cancelResult.stdout, /No active modes to cancel/);
|
|
@@ -149,6 +1198,7 @@ describe('CLI session-scoped state parity', () => {
|
|
|
149
1198
|
assert.equal(autopilot.active, false);
|
|
150
1199
|
assert.equal(autopilot.current_phase, 'cancelled');
|
|
151
1200
|
assert.ok(typeof autopilot.completed_at === 'string' && autopilot.completed_at.length > 0);
|
|
1201
|
+
assert.equal(await readFile(rootAutopilotPath, 'utf-8'), rootAutopilotState);
|
|
152
1202
|
const skillActive = JSON.parse(await readFile(join(runSessionDir, 'skill-active-state.json'), 'utf-8'));
|
|
153
1203
|
assert.equal(skillActive.active, false);
|
|
154
1204
|
assert.equal(skillActive.current_phase, 'cancelled');
|
|
@@ -173,7 +1223,7 @@ describe('CLI session-scoped state parity', () => {
|
|
|
173
1223
|
const runSessionDir = join(runStateDir, 'sessions', sessionId);
|
|
174
1224
|
await mkdir(runSessionDir, { recursive: true });
|
|
175
1225
|
await mkdir(join(wd, '.omx', 'state'), { recursive: true });
|
|
176
|
-
await writeFile(join(runStateDir, 'session.json'), JSON.stringify({ session_id: sessionId }, null, 2));
|
|
1226
|
+
await writeFile(join(runStateDir, 'session.json'), JSON.stringify({ session_id: sessionId, cwd: wd, state_root: runStateDir }, null, 2));
|
|
177
1227
|
await writeFile(join(runSessionDir, 'autopilot-state.json'), JSON.stringify({
|
|
178
1228
|
active: true,
|
|
179
1229
|
mode: 'autopilot',
|
|
@@ -205,35 +1255,39 @@ describe('CLI session-scoped state parity', () => {
|
|
|
205
1255
|
const worktree = await mkdtemp(join(tmpdir(), 'omx-cli-run-dir-worktree-alias-wt-'));
|
|
206
1256
|
const runsRoot = await mkdtemp(join(tmpdir(), 'omx-cli-run-dir-worktree-alias-runs-'));
|
|
207
1257
|
try {
|
|
1258
|
+
if (process.platform === 'win32')
|
|
1259
|
+
return;
|
|
208
1260
|
const sessionId = 'sess-run-dir-worktree-alias';
|
|
209
1261
|
const runDir = join(runsRoot, 'run-20260610121751-a11a');
|
|
210
1262
|
const runStateDir = join(runDir, '.omx', 'state');
|
|
211
1263
|
const runSessionDir = join(runStateDir, 'sessions', sessionId);
|
|
1264
|
+
const fakeBin = await createFakeTmuxBin(worktree);
|
|
212
1265
|
await mkdir(runSessionDir, { recursive: true });
|
|
213
1266
|
await mkdir(join(worktree, '.omx', 'state'), { recursive: true });
|
|
214
|
-
await
|
|
215
|
-
await writeFile(join(runStateDir, 'session.json'), JSON.stringify({ session_id: sessionId }, null, 2));
|
|
1267
|
+
await writeFile(join(runStateDir, 'session.json'), JSON.stringify({ session_id: sessionId, cwd: wd, state_root: runStateDir }, null, 2));
|
|
216
1268
|
await writeFile(join(runSessionDir, 'autopilot-state.json'), JSON.stringify({
|
|
217
1269
|
active: true,
|
|
218
1270
|
mode: 'autopilot',
|
|
219
1271
|
current_phase: 'deep-interview',
|
|
220
1272
|
}, null, 2));
|
|
221
|
-
await
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
session_id: sessionId,
|
|
231
|
-
tmux_pane_id: '%42',
|
|
232
|
-
})}\n`);
|
|
1273
|
+
await writeActiveRunRecord({
|
|
1274
|
+
runsRoot,
|
|
1275
|
+
contextKey: 'ctx-worktree-alias',
|
|
1276
|
+
sourceCwd: wd,
|
|
1277
|
+
worktreeCwd: worktree,
|
|
1278
|
+
runDir,
|
|
1279
|
+
sessionId,
|
|
1280
|
+
tmuxSessionName: 'omx-detached',
|
|
1281
|
+
});
|
|
233
1282
|
const statusResult = runOmxWithEnv(worktree, { OMX_RUNS_DIR: runsRoot }, 'status');
|
|
234
1283
|
assert.equal(statusResult.status, 0, statusResult.stderr || statusResult.stdout);
|
|
235
1284
|
assert.match(statusResult.stdout, /autopilot: ACTIVE \(phase: deep-interview\)/);
|
|
236
|
-
const cancelResult = runOmxWithEnv(worktree, {
|
|
1285
|
+
const cancelResult = runOmxWithEnv(worktree, {
|
|
1286
|
+
OMX_RUNS_DIR: runsRoot,
|
|
1287
|
+
PATH: `${fakeBin}:${process.env.PATH ?? ''}`,
|
|
1288
|
+
FAKE_OMX_SESSION_ID: sessionId,
|
|
1289
|
+
FAKE_TMUX_SESSION_NAME: 'omx-detached',
|
|
1290
|
+
}, 'cancel');
|
|
237
1291
|
assert.equal(cancelResult.status, 0, cancelResult.stderr || cancelResult.stdout);
|
|
238
1292
|
assert.match(cancelResult.stdout, /Cancelled: autopilot/);
|
|
239
1293
|
const autopilot = JSON.parse(await readFile(join(runSessionDir, 'autopilot-state.json'), 'utf-8'));
|
|
@@ -251,7 +1305,7 @@ describe('CLI session-scoped state parity', () => {
|
|
|
251
1305
|
try {
|
|
252
1306
|
const stateDir = join(wd, '.omx', 'state');
|
|
253
1307
|
await mkdir(stateDir, { recursive: true });
|
|
254
|
-
await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: 'sess-stale-autopilot' }, null, 2));
|
|
1308
|
+
await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: 'sess-stale-autopilot', cwd: wd, state_root: stateDir }, null, 2));
|
|
255
1309
|
const currentAutopilotPath = join(stateDir, 'current-autopilot.json');
|
|
256
1310
|
const currentAutopilot = {
|
|
257
1311
|
active: true,
|
|
@@ -281,7 +1335,7 @@ describe('CLI session-scoped state parity', () => {
|
|
|
281
1335
|
const sessionId = 'sess-stale-autopilot-inactive';
|
|
282
1336
|
const sessionDir = join(stateDir, 'sessions', sessionId);
|
|
283
1337
|
await mkdir(sessionDir, { recursive: true });
|
|
284
|
-
await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: sessionId }, null, 2));
|
|
1338
|
+
await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: sessionId, cwd: wd, state_root: stateDir }, null, 2));
|
|
285
1339
|
const currentAutopilotPath = join(stateDir, 'current-autopilot.json');
|
|
286
1340
|
const currentAutopilot = {
|
|
287
1341
|
active: true,
|
|
@@ -313,7 +1367,7 @@ describe('CLI session-scoped state parity', () => {
|
|
|
313
1367
|
const sessionId = 'sess-active-autopilot';
|
|
314
1368
|
const sessionDir = join(stateDir, 'sessions', sessionId);
|
|
315
1369
|
await mkdir(sessionDir, { recursive: true });
|
|
316
|
-
await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: sessionId }, null, 2));
|
|
1370
|
+
await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: sessionId, cwd: wd, state_root: stateDir }, null, 2));
|
|
317
1371
|
await writeFile(join(stateDir, 'current-autopilot.json'), JSON.stringify({
|
|
318
1372
|
active: true,
|
|
319
1373
|
current_phase: 'complete',
|
|
@@ -389,7 +1443,7 @@ describe('CLI session-scoped state parity', () => {
|
|
|
389
1443
|
const ultragoalDir = join(wd, '.omx', 'ultragoal');
|
|
390
1444
|
await mkdir(sessionDir, { recursive: true });
|
|
391
1445
|
await mkdir(ultragoalDir, { recursive: true });
|
|
392
|
-
await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: sessionId }));
|
|
1446
|
+
await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: sessionId, cwd: wd, state_root: stateDir }));
|
|
393
1447
|
await writeFile(join(sessionDir, 'ultragoal-state.json'), JSON.stringify({
|
|
394
1448
|
active: true,
|
|
395
1449
|
mode: 'ultragoal',
|
|
@@ -425,7 +1479,7 @@ describe('CLI session-scoped state parity', () => {
|
|
|
425
1479
|
const sessionId = 'sess-link';
|
|
426
1480
|
const sessionDir = join(stateDir, 'sessions', sessionId);
|
|
427
1481
|
await mkdir(sessionDir, { recursive: true });
|
|
428
|
-
await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: sessionId }));
|
|
1482
|
+
await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: sessionId, cwd: wd, state_root: stateDir }));
|
|
429
1483
|
await writeFile(join(sessionDir, 'ralph-state.json'), JSON.stringify({
|
|
430
1484
|
active: true,
|
|
431
1485
|
iteration: 2,
|
|
@@ -454,6 +1508,27 @@ describe('CLI session-scoped state parity', () => {
|
|
|
454
1508
|
await rm(wd, { recursive: true, force: true });
|
|
455
1509
|
}
|
|
456
1510
|
});
|
|
1511
|
+
it('cancels linked ecomode before Ralph', async () => {
|
|
1512
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-cli-ralph-ecomode-link-'));
|
|
1513
|
+
try {
|
|
1514
|
+
const stateDir = join(wd, '.omx', 'state');
|
|
1515
|
+
const sessionId = 'sess-ecomode-link';
|
|
1516
|
+
const sessionDir = join(stateDir, 'sessions', sessionId);
|
|
1517
|
+
await mkdir(sessionDir, { recursive: true });
|
|
1518
|
+
await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: sessionId, cwd: wd, state_root: stateDir }));
|
|
1519
|
+
await writeFile(join(sessionDir, 'ralph-state.json'), JSON.stringify({ active: true, mode: 'ralph', session_id: sessionId, current_phase: 'executing', linked_ecomode: true }));
|
|
1520
|
+
await writeFile(join(sessionDir, 'ecomode-state.json'), JSON.stringify({ active: true, mode: 'ecomode', session_id: sessionId, current_phase: 'executing' }));
|
|
1521
|
+
const result = runOmx(wd, 'cancel');
|
|
1522
|
+
assert.equal(result.status, 0, result.stderr || result.stdout);
|
|
1523
|
+
assert.match(result.stdout, /Cancelled: ecomode/);
|
|
1524
|
+
assert.match(result.stdout, /Cancelled: ralph/);
|
|
1525
|
+
assert.equal(JSON.parse(await readFile(join(sessionDir, 'ecomode-state.json'), 'utf-8')).active, false);
|
|
1526
|
+
assert.equal(JSON.parse(await readFile(join(sessionDir, 'ralph-state.json'), 'utf-8')).active, false);
|
|
1527
|
+
}
|
|
1528
|
+
finally {
|
|
1529
|
+
await rm(wd, { recursive: true, force: true });
|
|
1530
|
+
}
|
|
1531
|
+
});
|
|
457
1532
|
it('does not mutate unrelated sessions when cancelling current session mode', async () => {
|
|
458
1533
|
const wd = await mkdtemp(join(tmpdir(), 'omx-cli-cross-session-'));
|
|
459
1534
|
try {
|
|
@@ -462,7 +1537,7 @@ describe('CLI session-scoped state parity', () => {
|
|
|
462
1537
|
const sessionB = join(stateDir, 'sessions', 'sessB');
|
|
463
1538
|
await mkdir(sessionA, { recursive: true });
|
|
464
1539
|
await mkdir(sessionB, { recursive: true });
|
|
465
|
-
await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: 'sessA' }));
|
|
1540
|
+
await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: 'sessA', cwd: wd, state_root: stateDir }));
|
|
466
1541
|
await writeFile(join(sessionA, 'ralph-state.json'), JSON.stringify({
|
|
467
1542
|
active: true,
|
|
468
1543
|
current_phase: 'executing',
|
|
@@ -494,7 +1569,7 @@ describe('CLI session-scoped state parity', () => {
|
|
|
494
1569
|
const sessionId = 'sess-stale-autopilot-mirror';
|
|
495
1570
|
const sessionDir = join(stateDir, 'sessions', sessionId);
|
|
496
1571
|
await mkdir(sessionDir, { recursive: true });
|
|
497
|
-
await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: sessionId }, null, 2));
|
|
1572
|
+
await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: sessionId, cwd: wd, state_root: stateDir }, null, 2));
|
|
498
1573
|
await writeFile(join(stateDir, 'autopilot-state.json'), JSON.stringify({
|
|
499
1574
|
active: false,
|
|
500
1575
|
mode: 'autopilot',
|