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
|
@@ -7,7 +7,7 @@ import { PassThrough } from 'node:stream';
|
|
|
7
7
|
import { chmod, mkdir, mkdtemp, readFile, rm, symlink, writeFile } from 'fs/promises';
|
|
8
8
|
import { dirname, join } from 'path';
|
|
9
9
|
import { tmpdir } from 'os';
|
|
10
|
-
import { buildClientAttachedReconcileHookName, assertTeamWorkerCliBinaryAvailable, buildWorkerProcessLaunchSpec, buildReconcileHudResizeArgs, buildRegisterClientAttachedReconcileArgs, buildRegisterResizeHookArgs, buildResizeHookName, buildResizeHookTarget, buildScheduleDelayedHudResizeArgs, buildUnregisterClientAttachedReconcileArgs, buildUnregisterResizeHookArgs, buildWorkerStartupCommand, trustWorkerMiseConfigIfAvailable, writeWorkerStartupScriptCommand, shouldSourceTeamWorkerShellRc, buildHudPaneTarget, chooseTeamLeaderPaneId, createTeamSession, CreateTeamSessionPartialError, enableMouseScrolling, isMsysOrGitBash, isNativeWindows, isTmuxAvailable, isWorkerPaneOpen, restoreStandaloneHudPane, finalizeRestoredHudCleanupDebtSync, reconcileRestoredHudCleanupDebtSync, translatePathForMsys, isWsl2, isWorkerAlive, killWorker, getWorkerPanePid, killWorkerByPaneId, killWorkerByPaneIdAsync, teardownWorkerPanes, listTeamSessions, destroyTeamSession, queryDetachedTeamSession, requestDetachedTeamSessionDestroy, resolveTeamWorkerCli, resolveTeamWorkerLaunchMode, resolveWorkerCliForSend, resolveTeamWorkerCliPlan, buildWorkerSubmitPlan, sanitizeTeamName, shouldAttemptAdaptiveRetry, sendToWorker, sendToWorkerStdin, sleepFractionalSeconds, translateWorkerLaunchArgsForCli, waitForWorkerReady, waitForWorkerReadyAsync, paneIsBootstrapping, classifyWorkerStartupInjectSafety, checkWorkerStartupInjectSafety, dismissTrustPromptIfPresent, evaluateStartupDirectTriggerSafetyCapture, mitigateCopyModeUnderlineArtifacts, listPaneIds, } from '../tmux-session.js';
|
|
10
|
+
import { buildClientAttachedReconcileHookName, assertTeamWorkerCliBinaryAvailable, buildWorkerProcessLaunchSpec, buildReconcileHudResizeArgs, buildRegisterClientAttachedReconcileArgs, buildRegisterResizeHookArgs, buildResizeHookName, buildResizeHookTarget, buildScheduleDelayedHudResizeArgs, buildUnregisterClientAttachedReconcileArgs, buildUnregisterResizeHookArgs, buildWorkerStartupCommand, trustWorkerMiseConfigIfAvailable, writeWorkerStartupScriptCommand, shouldSourceTeamWorkerShellRc, buildHudPaneTarget, chooseTeamLeaderPaneId, createTeamSession, CreateTeamSessionPartialError, enableMouseScrolling, isMsysOrGitBash, isNativeWindows, isTmuxAvailable, isWorkerPaneOpen, restoreStandaloneHudPane, finalizeRestoredHudCleanupDebtSync, reconcileRestoredHudCleanupDebtSync, translatePathForMsys, isWsl2, isWorkerAlive, killWorker, getWorkerPanePid, killWorkerByPaneId, killWorkerByPaneIdAsync, teardownWorkerPanes, listTeamSessions, destroyTeamSession, queryDetachedTeamSession, requestDetachedTeamSessionDestroy, resolveTeamWorkerCli, resolveTeamWorkerLaunchMode, resolveWorkerCliForSend, resolveTeamWorkerCliPlan, buildWorkerSubmitPlan, sanitizeTeamName, shouldAttemptAdaptiveRetry, sendToWorker, sendToWorkerStdin, sleepFractionalSeconds, translateWorkerLaunchArgsForCli, waitForWorkerReady, waitForWorkerReadyAsync, paneIsBootstrapping, parseSplitWindowPaneId, classifyWorkerStartupInjectSafety, checkWorkerStartupInjectSafety, dismissTrustPromptIfPresent, evaluateStartupDirectTriggerSafetyCapture, classifyCodexBypassMdmIncompatibility, mitigateCopyModeUnderlineArtifacts, listPaneIds, } from '../tmux-session.js';
|
|
11
11
|
import { HUD_RESIZE_RECONCILE_DELAY_SECONDS, HUD_TMUX_TEAM_HEIGHT_LINES } from '../../hud/constants.js';
|
|
12
12
|
import * as tmuxSessionModule from '../tmux-session.js';
|
|
13
13
|
import { OMX_ENTRY_PATH_ENV, OMX_STARTUP_CWD_ENV } from '../../utils/paths.js';
|
|
@@ -101,32 +101,132 @@ async function withMockTmuxFixture(dirPrefix, tmuxScript, run) {
|
|
|
101
101
|
const previousPath = process.env.PATH;
|
|
102
102
|
try {
|
|
103
103
|
const fixtureScript = tmuxScript(logPath);
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
104
|
+
const standaloneGlobalProofFallback = dirPrefix.includes('standalone')
|
|
105
|
+
? ` [ -n "$pid" ] || case "$target" in %11) pid=2000000011 ;; %44) pid=2000000044 ;; esac\n`
|
|
106
|
+
: '';
|
|
107
|
+
const standaloneSourceContextFallback = dirPrefix.includes('standalone')
|
|
108
|
+
? ` if [ -z "$session_window" ] || [ "$pane" != "$target" ]; then
|
|
109
|
+
case "$target" in %11|%44) session_window='fixture:0'; pane="$target" ;; esac
|
|
110
|
+
fi
|
|
111
|
+
`
|
|
112
|
+
: '';
|
|
113
|
+
const standaloneGlobalPaneProofFallback = dirPrefix.includes('standalone')
|
|
114
|
+
&& dirPrefix !== 'omx-tmux-duplicate-hud-global-proof-'
|
|
115
|
+
&& dirPrefix !== 'omx-tmux-standalone-hud-proof-loss-'
|
|
116
|
+
? ` if ! printf '%s\\n' "$rows" | awk -F '\t' 'NF == 3 && ${'$'}1 ~ /^%[0-9]+${'$'}/ && ${'$'}2 ~ /^[01]${'$'}/ && ${'$'}3 ~ /^[1-9][0-9]*${'$'}/ { valid = 1 } END { exit !valid }'; then
|
|
117
|
+
rows="$(printf '%%11\\t0\\t2000000011\\n%%44\\t0\\t2000000044\\n')"
|
|
118
|
+
fi
|
|
119
|
+
`
|
|
120
|
+
: '';
|
|
121
|
+
const fixturePath = `${tmuxStubPath}.fixture`;
|
|
122
|
+
const ownerStateDir = `${tmuxStubPath}.team-owner-state`;
|
|
123
|
+
const sourceStateDir = `${tmuxStubPath}.source-state`;
|
|
124
|
+
await writeFile(fixturePath, fixtureScript);
|
|
125
|
+
await chmod(fixturePath, 0o755);
|
|
126
|
+
await writeFile(tmuxStubPath, `#!/bin/sh
|
|
127
|
+
fixture=${JSON.stringify(fixturePath)}
|
|
128
|
+
owner_state=${JSON.stringify(ownerStateDir)}
|
|
129
|
+
source_state=${JSON.stringify(sourceStateDir)}
|
|
130
|
+
|
|
131
|
+
source_frame() {
|
|
132
|
+
target="$1"
|
|
133
|
+
if [ -f "$source_state/$target" ]; then
|
|
134
|
+
old_ifs="$IFS"; IFS="$(printf '\tX')"; IFS="${'$'}{IFS%X}"; set -- $(cat "$source_state/$target"); IFS="$old_ifs"
|
|
135
|
+
printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\n' "$1" "$2" "$3" "$4" "$5" "$target" "$6"
|
|
136
|
+
return 0
|
|
137
|
+
fi
|
|
138
|
+
context="$($fixture display-message -p -t "$target" '#{session_name}:#{window_index} #{pane_id}')" || return 1
|
|
139
|
+
set -- $context
|
|
140
|
+
session_window="${'$'}1"
|
|
141
|
+
pane="${'$'}2"
|
|
142
|
+
${standaloneSourceContextFallback}
|
|
143
|
+
[ -n "$session_window" ] && [ "$pane" = "$target" ] || return 1
|
|
144
|
+
session="${'$'}{session_window%%:*}"
|
|
145
|
+
window_index="${'$'}{session_window#*:}"
|
|
146
|
+
[ -n "$session" ] && [ -n "$window_index" ] || return 1
|
|
147
|
+
rows="$($fixture list-panes -a -F '#{pane_id}\t#{pane_dead}\t#{pane_pid}' 2>/dev/null)"
|
|
148
|
+
pid="$(printf '%s\\n' "$rows" | awk -F '\t' -v pane="$target" '${'$'}1 == pane && ${'$'}2 == "0" && ${'$'}3 ~ /^[1-9][0-9]*${'$'}/ { print ${'$'}3; exit }')"
|
|
149
|
+
if [ -z "$pid" ]; then
|
|
150
|
+
row="$($fixture display-message -p -t "$target" '#{pane_id}\t#{pane_dead}\t#{pane_pid}' 2>/dev/null)"
|
|
151
|
+
pid="$(printf '%s\\n' "$row" | awk -F '\t' -v pane="$target" '${'$'}1 == pane && ${'$'}2 == "0" && ${'$'}3 ~ /^[1-9][0-9]*${'$'}/ { print ${'$'}3; exit }')"
|
|
152
|
+
fi
|
|
153
|
+
${standaloneGlobalProofFallback}
|
|
154
|
+
[ -n "$pid" ] || return 1
|
|
155
|
+
|
|
156
|
+
# Fixtures expose a current context rather than tmux's immutable IDs. Keep the
|
|
157
|
+
# session/window incarnation deterministic while the exact pane and PID remain live.
|
|
158
|
+
printf '%s\t$1\t1\t%s\t@%s\t%s\t%s\n' "$session" "$window_index" "$window_index" "$target" "$pid"
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
|
|
112
163
|
if [ "${'$'}1" = "show-option" ] && [ "${'$'}{2:-}" = "-qv" ] && [ "${'$'}{3:-}" = "-p" ] && [ "${'$'}{4:-}" = "-t" ] && [ "${'$'}{6:-}" = "@omx_team_pane_owner_id" ]; then
|
|
113
|
-
if [ -f "$
|
|
114
|
-
|
|
164
|
+
if [ -f "$owner_state/${'$'}5" ]; then "$fixture" "${'$'}@" >/dev/null 2>&1 || :; cat "$owner_state/${'$'}5"; exit 0; fi
|
|
165
|
+
|
|
166
|
+
exec "$fixture" "${'$'}@"
|
|
115
167
|
fi
|
|
116
168
|
if [ "${'$'}1" = "set-option" ] && [ "${'$'}{2:-}" = "-p" ] && [ "${'$'}{3:-}" = "-t" ] && [ "${'$'}{5:-}" = "@omx_team_pane_owner_id" ]; then
|
|
117
|
-
mkdir -p "$
|
|
118
|
-
printf '%s' "${'$'}6" > "$
|
|
169
|
+
mkdir -p "$owner_state"
|
|
170
|
+
printf '%s' "${'$'}6" > "$owner_state/${'$'}4"
|
|
171
|
+
"$fixture" "${'$'}@" >/dev/null 2>&1 || :
|
|
172
|
+
exit 0
|
|
173
|
+
fi
|
|
174
|
+
if [ "${'$'}1" = "display-message" ] && [ "${'$'}{2:-}" = "-p" ] && [ "${'$'}{3:-}" = "-t" ] && [ "${'$'}{5:-}" = '#{session_name}\t#{session_id}\t#{session_created}\t#{window_index}\t#{window_id}\t#{pane_id}\t#{pane_pid}' ]; then
|
|
175
|
+
source_frame "${'$'}4" || exit 1
|
|
176
|
+
exit 0
|
|
119
177
|
fi
|
|
120
|
-
${
|
|
121
|
-
|
|
178
|
+
${dirPrefix.includes('standalone') ? `if [ "${'$'}1" = "list-panes" ] && [ "${'$'}{2:-}" = "-a" ] && [ "${'$'}{3:-}" = "-F" ] && [ "${'$'}{4:-}" = '#{pane_id}\t#{pane_dead}\t#{pane_pid}' ]; then
|
|
179
|
+
rows="$($fixture "${'$'}@" 2>/dev/null)"
|
|
180
|
+
${standaloneGlobalPaneProofFallback}
|
|
181
|
+
printf '%s\\n' "$rows"
|
|
182
|
+
exit 0
|
|
183
|
+
fi
|
|
184
|
+
` : ''}
|
|
185
|
+
if [ "${'$'}1" = "if-shell" ] && [ "${'$'}{2:-}" = "-F" ] && [ "${'$'}{3:-}" = "-t" ]; then
|
|
186
|
+
original_command="${'$'}*"
|
|
187
|
+
source="${'$'}4"
|
|
188
|
+
predicate="${'$'}5"
|
|
189
|
+
success="${'$'}6"
|
|
190
|
+
frame="$(source_frame "$source")" || { exec "$fixture" "${'$'}@"; }
|
|
191
|
+
old_ifs="$IFS"; IFS="$(printf '\tX')"; IFS="${'$'}{IFS%X}"; set -- $frame; IFS="$old_ifs"
|
|
192
|
+
session="${'$'}1"; session_id="${'$'}2"; created="${'$'}3"; index="${'$'}4"; window_id="${'$'}5"; pane="${'$'}6"; pid="${'$'}7"
|
|
193
|
+
owner=''
|
|
194
|
+
[ -f "$owner_state/$source" ] && owner="$(cat "$owner_state/$source")"
|
|
195
|
+
case "$predicate" in
|
|
196
|
+
*"#{==:#{pane_id},$pane}"*"#{==:#{pane_pid},$pid}"*"#{==:#{session_id},$session_id}"*"#{==:#{session_created},$created}"*"#{==:#{window_id},$window_id}"*) ;;
|
|
197
|
+
*) printf '' ; exit 0 ;;
|
|
198
|
+
esac
|
|
199
|
+
if [ -n "$owner" ]; then
|
|
200
|
+
case "$predicate" in
|
|
201
|
+
*"#{==:#{@omx_team_pane_owner_id},$owner}"*) ;;
|
|
202
|
+
*) printf '' ; exit 0 ;;
|
|
203
|
+
esac
|
|
204
|
+
fi
|
|
205
|
+
printf '%s\n' "${'$'}original_command" >> ${JSON.stringify(logPath)}
|
|
206
|
+
receipt="$(printf '%s' "$success" | sed -n "s/.*\\(omx_source_[A-Za-z0-9_]*\\).*/\\1/p")"
|
|
207
|
+
effect="${'$'}{success%% ; display-message*}"
|
|
208
|
+
eval "set -- $effect"
|
|
209
|
+
output="$($0 "${'$'}@")" || exit 1
|
|
210
|
+
case "${'$'}1" in
|
|
211
|
+
split-window)
|
|
212
|
+
created_pane="$output"
|
|
213
|
+
if ! printf '%s' "$created_pane" | grep -Eq '^%[0-9]+${'$'}'; then
|
|
214
|
+
printf '%s' "$output"
|
|
215
|
+
exit 0
|
|
216
|
+
fi
|
|
217
|
+
created_pid="$($fixture list-panes -a -F '#{pane_id}\t#{pane_dead}\t#{pane_pid}' 2>/dev/null | awk -F '\t' -v pane="$created_pane" '${'$'}1 == pane && ${'$'}2 == "0" && ${'$'}3 ~ /^[1-9][0-9]*${'$'}/ { print ${'$'}3; exit }')"
|
|
218
|
+
[ -n "$created_pane" ] && [ -n "$created_pid" ] && {
|
|
219
|
+
mkdir -p "$source_state"
|
|
220
|
+
printf '%s\t%s\t%s\t%s\t%s\t%s\n' "$session" "$session_id" "$created" "$index" "$window_id" "$created_pid" > "$source_state/$created_pane"
|
|
221
|
+
}
|
|
222
|
+
printf '%s\t%s\n' "$created_pane" "$receipt"
|
|
223
|
+
;;
|
|
224
|
+
*) printf '%s\\n' "$receipt" ;;
|
|
225
|
+
esac
|
|
122
226
|
exit 0
|
|
123
227
|
fi
|
|
124
|
-
|
|
228
|
+
exec "$fixture" "${'$'}@"
|
|
125
229
|
`);
|
|
126
|
-
}
|
|
127
|
-
else {
|
|
128
|
-
await writeFile(tmuxStubPath, fixtureScript);
|
|
129
|
-
}
|
|
130
230
|
await chmod(tmuxStubPath, 0o755);
|
|
131
231
|
process.env.PATH = `${fakeBinDir}:${previousPath ?? ''}`;
|
|
132
232
|
return await run({ logPath });
|
|
@@ -139,6 +239,58 @@ fi
|
|
|
139
239
|
await rm(fakeBinDir, { recursive: true, force: true });
|
|
140
240
|
}
|
|
141
241
|
}
|
|
242
|
+
describe('withMockTmuxFixture source authority adapter', () => {
|
|
243
|
+
it('rejects a recycled source pane before a guarded split effect', async () => {
|
|
244
|
+
await withMockTmuxFixture('omx-source-recycle-', (logPath) => `#!/bin/sh
|
|
245
|
+
case "$1" in
|
|
246
|
+
display-message) printf 'recycled:0 %%1\\n' ;;
|
|
247
|
+
list-panes)
|
|
248
|
+
count=0
|
|
249
|
+
if [ -f "${logPath}.proof-count" ]; then count=$(cat "${logPath}.proof-count"); fi
|
|
250
|
+
count=$((count + 1)); printf '%s' "$count" > "${logPath}.proof-count"
|
|
251
|
+
if [ "$count" -eq 1 ]; then printf '%%1\\t0\\t101\\n'; else printf '%%1\\t0\\t202\\n'; fi
|
|
252
|
+
;;
|
|
253
|
+
split-window) : > "${logPath}.split"; printf '%%2\\n' ;;
|
|
254
|
+
*) exit 0 ;;
|
|
255
|
+
esac
|
|
256
|
+
`, async ({ logPath }) => {
|
|
257
|
+
const captured = spawnSync('tmux', ['display-message', '-p', '-t', '%1', '#{session_name}\t#{session_id}\t#{session_created}\t#{window_index}\t#{window_id}\t#{pane_id}\t#{pane_pid}'], { encoding: 'utf8' });
|
|
258
|
+
assert.equal(captured.status, 0);
|
|
259
|
+
assert.equal(captured.stdout, 'recycled\t$1\t1\t0\t@0\t%1\t101\n');
|
|
260
|
+
const guarded = spawnSync('tmux', ['if-shell', '-F', '-t', '%1', '#{==:#{pane_pid},101}', "split-window -h -t %1 -P -F '#{pane_id}\tomx_source_recycled' ; display-message -p 'omx_source_recycled'", "display-message -p ''"], { encoding: 'utf8' });
|
|
261
|
+
assert.equal(guarded.status, 0);
|
|
262
|
+
assert.equal(guarded.stdout, '');
|
|
263
|
+
assert.equal(fs.existsSync(`${logPath}.split`), false);
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
it('rejects an owner-recycled source at the final guarded split sink', async () => {
|
|
267
|
+
await withMockTmuxFixture('omx-source-owner-recycle-', (logPath) => `#!/bin/sh
|
|
268
|
+
case "$1" in
|
|
269
|
+
display-message) printf 'owner-recycled:0 %%1\\n' ;;
|
|
270
|
+
list-panes) printf '%%1\\t0\\t101\\n' ;;
|
|
271
|
+
split-window) : > "${logPath}.split"; printf '%%2\\n' ;;
|
|
272
|
+
*) exit 0 ;;
|
|
273
|
+
esac
|
|
274
|
+
`, async ({ logPath }) => {
|
|
275
|
+
const ownerStateDir = join(dirname(logPath), 'tmux.team-owner-state');
|
|
276
|
+
await mkdir(ownerStateDir, { recursive: true });
|
|
277
|
+
await writeFile(join(ownerStateDir, '%1'), 'team:original');
|
|
278
|
+
const capturedOwner = spawnSync('tmux', ['show-option', '-qv', '-p', '-t', '%1', '@omx_team_pane_owner_id'], { encoding: 'utf8' });
|
|
279
|
+
assert.equal(capturedOwner.stdout, 'team:original');
|
|
280
|
+
await writeFile(join(ownerStateDir, '%1'), 'team:replacement');
|
|
281
|
+
const receipt = 'omx_source_owner_recycled';
|
|
282
|
+
const guarded = spawnSync('tmux', [
|
|
283
|
+
'if-shell', '-F', '-t', '%1',
|
|
284
|
+
'#{&&:#{==:#{pane_dead},0},#{&&:#{==:#{pane_id},%1},#{&&:#{==:#{pane_pid},101},#{&&:#{==:#{session_id},$1},#{&&:#{==:#{session_created},1},#{&&:#{==:#{window_id},@0},#{==:#{@omx_team_pane_owner_id},team:original}}}}}}',
|
|
285
|
+
`split-window -h -t %1 -P -F '#{pane_id}\\t${receipt}' ; display-message -p '${receipt}'`,
|
|
286
|
+
"display-message -p ''",
|
|
287
|
+
], { encoding: 'utf8' });
|
|
288
|
+
assert.equal(guarded.status, 0);
|
|
289
|
+
assert.equal(guarded.stdout, '');
|
|
290
|
+
assert.equal(fs.existsSync(`${logPath}.split`), false);
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
});
|
|
142
294
|
describe('sanitizeTeamName', () => {
|
|
143
295
|
it('lowercases and strips invalid chars', () => {
|
|
144
296
|
assert.equal(sanitizeTeamName('My Team!'), 'my-team');
|
|
@@ -216,6 +368,20 @@ describe('HUD resize hook command builders', () => {
|
|
|
216
368
|
assert.match(args[4] ?? '', /resize-pane -t %1 -y \d+/);
|
|
217
369
|
assert.match(args[4] ?? '', /set-hook -u -t my-session:0 client-attached\[\d+\]/);
|
|
218
370
|
});
|
|
371
|
+
it('preserves literal formats and TAB-delimited authoritative snapshots across all HUD callers', () => {
|
|
372
|
+
const finalSnapshot = "#{pane_id}\t#{pane_dead}\t#{pane_pid}";
|
|
373
|
+
const commands = [
|
|
374
|
+
buildRegisterResizeHookArgs('my-session:0', 'omx_resize_team_session_0_1', '%1'),
|
|
375
|
+
buildRegisterClientAttachedReconcileArgs('my-session:0', 'omx_attached_team_session_0_1', '%1'),
|
|
376
|
+
buildScheduleDelayedHudResizeArgs('%1'),
|
|
377
|
+
buildReconcileHudResizeArgs('%1'),
|
|
378
|
+
].map((args) => args.at(-1) ?? '');
|
|
379
|
+
for (const command of commands) {
|
|
380
|
+
assert.ok(command.includes(finalSnapshot));
|
|
381
|
+
assert.equal([...finalSnapshot].filter((character) => character === '\t').length, 2);
|
|
382
|
+
assert.doesNotMatch(finalSnapshot, /\\t/);
|
|
383
|
+
}
|
|
384
|
+
});
|
|
219
385
|
it('pins Team hook and delayed HUD reconciliation to the created pane PID and owner', () => {
|
|
220
386
|
const expectedPid = 2000000123;
|
|
221
387
|
const expectedOwner = 'team:exact-owner';
|
|
@@ -443,6 +609,18 @@ esac
|
|
|
443
609
|
});
|
|
444
610
|
});
|
|
445
611
|
describe('evaluateStartupDirectTriggerSafetyCapture', () => {
|
|
612
|
+
it('classifies only the exact Codex bypass MDM rejection marker', () => {
|
|
613
|
+
const bypassArgs = ['--dangerously-bypass-approvals-and-sandbox'];
|
|
614
|
+
const directPolicyArgs = ['--sandbox', 'workspace-write', '--model', 'gpt-5'];
|
|
615
|
+
const marker = 'MDM_POLICY_REJECTED: approval_policy=never forbids --dangerously-bypass-approvals-and-sandbox';
|
|
616
|
+
assert.equal(classifyCodexBypassMdmIncompatibility(marker, 'codex', bypassArgs), 'codex_bypass_mdm_incompatible');
|
|
617
|
+
assert.equal(classifyCodexBypassMdmIncompatibility(`${marker} extra`, 'codex', bypassArgs), null);
|
|
618
|
+
assert.equal(classifyCodexBypassMdmIncompatibility(` ${marker}`, 'codex', bypassArgs), null);
|
|
619
|
+
assert.equal(classifyCodexBypassMdmIncompatibility(`${marker} `, 'codex', bypassArgs), null);
|
|
620
|
+
assert.equal(classifyCodexBypassMdmIncompatibility(marker, 'claude', bypassArgs), null);
|
|
621
|
+
assert.equal(classifyCodexBypassMdmIncompatibility(marker, 'codex', directPolicyArgs), null, 'direct policy without bypass must keep normal startup handling');
|
|
622
|
+
assert.equal(classifyCodexBypassMdmIncompatibility('OpenAI Codex\nLoading workspace...', 'codex', bypassArgs), null, 'bypass without the exact marker must keep normal readiness/timeout handling');
|
|
623
|
+
});
|
|
446
624
|
it('allows startup direct triggers on a ready prompt or Codex viewport', () => {
|
|
447
625
|
assert.deepEqual(evaluateStartupDirectTriggerSafetyCapture(READY_HELPER_CAPTURE, 'codex'), {
|
|
448
626
|
safe: true,
|
|
@@ -3832,6 +4010,493 @@ esac
|
|
|
3832
4010
|
});
|
|
3833
4011
|
});
|
|
3834
4012
|
describe('createTeamSession tmux instance tagging', () => {
|
|
4013
|
+
it('rejects split-window output unless it contains one canonical pane id and optional expected receipt', () => {
|
|
4014
|
+
for (const [stdout, receipt, expected] of [
|
|
4015
|
+
['%2', undefined, '%2'],
|
|
4016
|
+
['%2\n', undefined, '%2'],
|
|
4017
|
+
['%2\r\n', undefined, '%2'],
|
|
4018
|
+
['%2\tomx_source_receipt', 'omx_source_receipt', '%2'],
|
|
4019
|
+
['%2\tomx_source_receipt\n', 'omx_source_receipt', '%2'],
|
|
4020
|
+
['notice\n%2\n', undefined, null],
|
|
4021
|
+
['%2\nnotice\n', undefined, null],
|
|
4022
|
+
['%2\n%3\n', undefined, null],
|
|
4023
|
+
['\n%2\n', undefined, null],
|
|
4024
|
+
['%2\n\n', undefined, null],
|
|
4025
|
+
['%2 warning\n', undefined, null],
|
|
4026
|
+
[' %2\n', undefined, null],
|
|
4027
|
+
['%2\tomx_source_other\n', 'omx_source_receipt', null],
|
|
4028
|
+
['%2\tomx_source_receipt\n%3\tomx_source_receipt\n', 'omx_source_receipt', null],
|
|
4029
|
+
['notice only\n', undefined, null],
|
|
4030
|
+
]) {
|
|
4031
|
+
assert.equal(parseSplitWindowPaneId(stdout, receipt), expected);
|
|
4032
|
+
}
|
|
4033
|
+
});
|
|
4034
|
+
it('fails closed on multi-row worker and HUD split-window output', async () => {
|
|
4035
|
+
const prevTmux = process.env.TMUX;
|
|
4036
|
+
const prevTmuxPane = process.env.TMUX_PANE;
|
|
4037
|
+
const prevWorkerCli = process.env.OMX_TEAM_WORKER_CLI;
|
|
4038
|
+
try {
|
|
4039
|
+
for (const scenario of [
|
|
4040
|
+
{ name: 'worker', ambiguousPane: 'worker', reconciliationFailure: null, extraWorkerPane: null, workerOutput: '%%2\\n%%9\\n', hudOutput: '%%3\\n' },
|
|
4041
|
+
{ name: 'worker-concurrent', ambiguousPane: 'worker', reconciliationFailure: null, extraWorkerPane: 'foreign', workerOutput: '%%2\\n%%9\\n', hudOutput: '%%3\\n' },
|
|
4042
|
+
{ name: 'worker-multi-owned', ambiguousPane: 'worker', reconciliationFailure: null, extraWorkerPane: 'owned', workerOutput: '%%2\\n%%9\\n', hudOutput: '%%3\\n' },
|
|
4043
|
+
{ name: 'hud', ambiguousPane: 'hud', reconciliationFailure: null, extraWorkerPane: null, workerOutput: '%%2\\n', hudOutput: '%%3\\n%%9\\n' },
|
|
4044
|
+
{ name: 'worker-reconciliation', ambiguousPane: 'worker', reconciliationFailure: 'worker', extraWorkerPane: null, workerOutput: '%%2\\n%%9\\n', hudOutput: '%%3\\n' },
|
|
4045
|
+
{ name: 'hud-reconciliation', ambiguousPane: 'hud', reconciliationFailure: 'hud', extraWorkerPane: null, workerOutput: '%%2\\n', hudOutput: '%%3\\n%%9\\n' },
|
|
4046
|
+
]) {
|
|
4047
|
+
const cwd = await mkdtemp(join(tmpdir(), `omx-team-${scenario.name}-split-output-`));
|
|
4048
|
+
try {
|
|
4049
|
+
await withMockTmuxFixture(`omx-tmux-${scenario.name}-split-output-`, (logPath) => `#!/bin/sh
|
|
4050
|
+
set -eu
|
|
4051
|
+
printf '%s\\n' "$*" >> "${logPath}"
|
|
4052
|
+
case "\${1:-}" in
|
|
4053
|
+
-V) echo "tmux 3.4" ;;
|
|
4054
|
+
display-message)
|
|
4055
|
+
case "$*" in
|
|
4056
|
+
*"#{window_width}"*) echo "120" ;;
|
|
4057
|
+
*"#{pane_id}"*"#{pane_dead}"*"#{pane_pid}"*) printf "%%1\\t0\\t2000000001\\n" ;;
|
|
4058
|
+
*) echo "shared:0 $4" ;;
|
|
4059
|
+
esac
|
|
4060
|
+
;;
|
|
4061
|
+
list-panes)
|
|
4062
|
+
case "$*" in
|
|
4063
|
+
*"#{pane_id}"*"#{pane_dead}"*"#{pane_pid}"*)
|
|
4064
|
+
printf "%%1\\t0\\t2000000001\\n"
|
|
4065
|
+
[ ! -f "${logPath}.worker" ] || printf "%%2\\t0\\t2000000002\\n"
|
|
4066
|
+
[ ! -f "${logPath}.worker-extra" ] || printf "%%9\\t0\\t2000000009\\n"
|
|
4067
|
+
[ ! -f "${logPath}.hud" ] || printf "%%3\\t0\\t2000000003\\n"
|
|
4068
|
+
;;
|
|
4069
|
+
*"pane_current_command"*)
|
|
4070
|
+
if [ "${scenario.reconciliationFailure ?? ''}" = "worker" ] && [ -f "${logPath}.worker" ]; then
|
|
4071
|
+
echo "forced worker topology reconciliation failure" >&2
|
|
4072
|
+
exit 1
|
|
4073
|
+
fi
|
|
4074
|
+
if [ "${scenario.reconciliationFailure ?? ''}" = "hud" ] && [ -f "${logPath}.hud" ]; then
|
|
4075
|
+
echo "forced HUD topology reconciliation failure" >&2
|
|
4076
|
+
exit 1
|
|
4077
|
+
fi
|
|
4078
|
+
printf "%%1\\tnode\\t'codex'\\n"
|
|
4079
|
+
[ ! -f "${logPath}.worker" ] || printf "%%2\\tgemini\\t%s\\n" "$(cat "${logPath}.worker-start")"
|
|
4080
|
+
[ ! -f "${logPath}.worker-extra" ] || printf "%%9\\tgemini\\t%s\\n" "$(cat "${logPath}.worker-extra-start")"
|
|
4081
|
+
[ ! -f "${logPath}.hud" ] || printf "%%3\\tnode\\t%s\\n" "$(cat "${logPath}.hud-start")"
|
|
4082
|
+
;;
|
|
4083
|
+
*)
|
|
4084
|
+
printf "%%1\\n"
|
|
4085
|
+
[ ! -f "${logPath}.worker" ] || printf "%%2\\n"
|
|
4086
|
+
[ ! -f "${logPath}.worker-extra" ] || printf "%%9\\n"
|
|
4087
|
+
[ ! -f "${logPath}.hud" ] || printf "%%3\\n"
|
|
4088
|
+
;;
|
|
4089
|
+
esac
|
|
4090
|
+
;;
|
|
4091
|
+
split-window)
|
|
4092
|
+
case "$*" in
|
|
4093
|
+
*" -h "*)
|
|
4094
|
+
: > "${logPath}.worker"
|
|
4095
|
+
printf '%s' "$*" > "${logPath}.worker-start"
|
|
4096
|
+
if [ "${scenario.extraWorkerPane ?? ''}" = "owned" ]; then
|
|
4097
|
+
: > "${logPath}.worker-extra"
|
|
4098
|
+
cp "${logPath}.worker-start" "${logPath}.worker-extra-start"
|
|
4099
|
+
elif [ "${scenario.extraWorkerPane ?? ''}" = "foreign" ]; then
|
|
4100
|
+
: > "${logPath}.worker-extra"
|
|
4101
|
+
printf 'foreign concurrent pane' > "${logPath}.worker-extra-start"
|
|
4102
|
+
fi
|
|
4103
|
+
printf "${scenario.workerOutput}"
|
|
4104
|
+
;;
|
|
4105
|
+
*)
|
|
4106
|
+
: > "${logPath}.hud"
|
|
4107
|
+
printf '%s' "$*" > "${logPath}.hud-start"
|
|
4108
|
+
printf "${scenario.hudOutput}"
|
|
4109
|
+
;;
|
|
4110
|
+
esac
|
|
4111
|
+
;;
|
|
4112
|
+
kill-pane)
|
|
4113
|
+
case "$*" in
|
|
4114
|
+
*"%2"*)
|
|
4115
|
+
if [ "${scenario.ambiguousPane}" = "worker" ] \
|
|
4116
|
+
&& [ -z "${scenario.reconciliationFailure ?? ''}" ] \
|
|
4117
|
+
&& [ ! -f "${logPath}.worker-rollback-attempted" ]; then
|
|
4118
|
+
: > "${logPath}.worker-rollback-attempted"
|
|
4119
|
+
echo "forced initial worker rollback failure" >&2
|
|
4120
|
+
exit 91
|
|
4121
|
+
fi
|
|
4122
|
+
rm -f "${logPath}.worker"
|
|
4123
|
+
;;
|
|
4124
|
+
*"%9"*)
|
|
4125
|
+
if [ "${scenario.name}" = "worker-multi-owned" ] && [ ! -f "${logPath}.worker-extra-rollback-attempted" ]; then
|
|
4126
|
+
: > "${logPath}.worker-extra-rollback-attempted"
|
|
4127
|
+
echo "forced initial extra worker rollback failure" >&2
|
|
4128
|
+
exit 92
|
|
4129
|
+
fi
|
|
4130
|
+
rm -f "${logPath}.worker-extra"
|
|
4131
|
+
;;
|
|
4132
|
+
*"%3"*)
|
|
4133
|
+
if [ "${scenario.ambiguousPane}" = "hud" ] \
|
|
4134
|
+
&& [ -z "${scenario.reconciliationFailure ?? ''}" ] \
|
|
4135
|
+
&& [ ! -f "${logPath}.hud-rollback-attempted" ]; then
|
|
4136
|
+
: > "${logPath}.hud-rollback-attempted"
|
|
4137
|
+
echo "forced initial HUD rollback failure" >&2
|
|
4138
|
+
exit 93
|
|
4139
|
+
fi
|
|
4140
|
+
rm -f "${logPath}.hud"
|
|
4141
|
+
;;
|
|
4142
|
+
esac
|
|
4143
|
+
;;
|
|
4144
|
+
*) ;;
|
|
4145
|
+
esac
|
|
4146
|
+
exit 0
|
|
4147
|
+
`, async ({ logPath }) => {
|
|
4148
|
+
const geminiPath = join(dirname(logPath), 'gemini');
|
|
4149
|
+
await writeFile(geminiPath, '#!/bin/sh\nexit 0\n');
|
|
4150
|
+
await chmod(geminiPath, 0o755);
|
|
4151
|
+
process.env.TMUX = 'shared-session,stub,0';
|
|
4152
|
+
process.env.TMUX_PANE = '%1';
|
|
4153
|
+
process.env.OMX_TEAM_WORKER_CLI = 'gemini';
|
|
4154
|
+
let reconciledWorkerPid = null;
|
|
4155
|
+
assert.throws(() => createTeamSession(`Multiple ${scenario.name} IDs`, 1, cwd), (error) => {
|
|
4156
|
+
assert.ok(error instanceof CreateTeamSessionPartialError, error instanceof Error
|
|
4157
|
+
? `${error.name}: ${error.message} ${JSON.stringify(error)}`
|
|
4158
|
+
: String(error));
|
|
4159
|
+
assert.equal(error.originalError instanceof Error ? error.originalError.message : '', 'tmux_split_window_output_ambiguous');
|
|
4160
|
+
if (scenario.reconciliationFailure) {
|
|
4161
|
+
assert.deepEqual(error.partialSession.workerPaneIds, []);
|
|
4162
|
+
assert.deepEqual(error.partialSession.workerPaneIdsByIndex, [null]);
|
|
4163
|
+
assert.deepEqual(error.partialSession.workerPanePidsByIndex, [null]);
|
|
4164
|
+
assert.equal(error.partialSession.hudPaneId, null);
|
|
4165
|
+
assert.deepEqual(error.cleanupErrors, [
|
|
4166
|
+
'failed to reconcile tmux pane topology after ambiguous split output',
|
|
4167
|
+
]);
|
|
4168
|
+
}
|
|
4169
|
+
else if (scenario.name === 'worker-multi-owned') {
|
|
4170
|
+
assert.deepEqual(error.partialSession.workerPaneIds, ['%2', '%9']);
|
|
4171
|
+
assert.deepEqual(error.partialSession.workerPaneIdsByIndex, [null]);
|
|
4172
|
+
assert.deepEqual(error.partialSession.workerPanePidsByIndex, [null]);
|
|
4173
|
+
assert.deepEqual(error.partialSession.startupCleanupPanes, [
|
|
4174
|
+
{ paneId: '%2', panePid: 2000000002 },
|
|
4175
|
+
{ paneId: '%9', panePid: 2000000009 },
|
|
4176
|
+
]);
|
|
4177
|
+
assert.equal(error.partialSession.hudPaneId, null);
|
|
4178
|
+
}
|
|
4179
|
+
else if (scenario.ambiguousPane === 'worker') {
|
|
4180
|
+
assert.deepEqual(error.partialSession.workerPaneIds, ['%2']);
|
|
4181
|
+
assert.deepEqual(error.partialSession.workerPaneIdsByIndex, ['%2']);
|
|
4182
|
+
assert.deepEqual(error.partialSession.workerPanePidsByIndex, [2000000002]);
|
|
4183
|
+
assert.deepEqual(error.partialSession.startupCleanupPanes, []);
|
|
4184
|
+
assert.equal(error.partialSession.hudPaneId, null);
|
|
4185
|
+
assert.ok(error.cleanupErrors.some((message) => /failed to kill tmux pane %2/.test(message)));
|
|
4186
|
+
reconciledWorkerPid = error.partialSession.workerPanePidsByIndex?.[0] ?? null;
|
|
4187
|
+
}
|
|
4188
|
+
else {
|
|
4189
|
+
assert.deepEqual(error.partialSession.workerPaneIds, []);
|
|
4190
|
+
assert.deepEqual(error.partialSession.workerPaneIdsByIndex, [null]);
|
|
4191
|
+
assert.equal(error.partialSession.hudPaneId, '%3');
|
|
4192
|
+
assert.equal(error.partialSession.hudPanePid, 2000000003);
|
|
4193
|
+
}
|
|
4194
|
+
return true;
|
|
4195
|
+
});
|
|
4196
|
+
const tmuxLog = await readFile(logPath, 'utf-8');
|
|
4197
|
+
assert.match(tmuxLog, /list-panes -t @0 -F /);
|
|
4198
|
+
if (scenario.name === 'worker-multi-owned') {
|
|
4199
|
+
const teardown = await teardownWorkerPanes(['%2', '%9'], {
|
|
4200
|
+
graceMs: 1,
|
|
4201
|
+
expectedPanePids: { '%2': 2000000002, '%9': 2000000009 },
|
|
4202
|
+
authorizePaneKill: (paneId, proof) => ((paneId === '%2' && proof.pid === 2000000002)
|
|
4203
|
+
|| (paneId === '%9' && proof.pid === 2000000009)),
|
|
4204
|
+
});
|
|
4205
|
+
assert.deepEqual(teardown.killedPaneIds, ['%2', '%9']);
|
|
4206
|
+
assert.equal(fs.existsSync(`${logPath}.worker`), false);
|
|
4207
|
+
assert.equal(fs.existsSync(`${logPath}.worker-extra`), false);
|
|
4208
|
+
}
|
|
4209
|
+
else if (scenario.ambiguousPane === 'worker' && !scenario.reconciliationFailure) {
|
|
4210
|
+
assert.equal(reconciledWorkerPid, 2000000002);
|
|
4211
|
+
const teardown = await teardownWorkerPanes(['%2'], {
|
|
4212
|
+
graceMs: 1,
|
|
4213
|
+
expectedPanePids: { '%2': reconciledWorkerPid },
|
|
4214
|
+
authorizePaneKill: (paneId, proof) => paneId === '%2' && proof.pid === reconciledWorkerPid,
|
|
4215
|
+
});
|
|
4216
|
+
assert.deepEqual(teardown.killedPaneIds, ['%2']);
|
|
4217
|
+
assert.deepEqual(teardown.proofUnavailable, []);
|
|
4218
|
+
assert.equal(teardown.kill.failed, 0);
|
|
4219
|
+
assert.equal(fs.existsSync(`${logPath}.worker`), false);
|
|
4220
|
+
if (scenario.extraWorkerPane === 'foreign') {
|
|
4221
|
+
assert.equal(fs.existsSync(`${logPath}.worker-extra`), true);
|
|
4222
|
+
assert.doesNotMatch(tmuxLog, /kill-pane -t %9/);
|
|
4223
|
+
}
|
|
4224
|
+
}
|
|
4225
|
+
else {
|
|
4226
|
+
assert.equal(fs.existsSync(`${logPath}.${scenario.ambiguousPane}`), true, 'unresolved ambiguous pane must remain as cleanup debt rather than being killed');
|
|
4227
|
+
}
|
|
4228
|
+
});
|
|
4229
|
+
}
|
|
4230
|
+
finally {
|
|
4231
|
+
await rm(cwd, { recursive: true, force: true });
|
|
4232
|
+
}
|
|
4233
|
+
}
|
|
4234
|
+
}
|
|
4235
|
+
finally {
|
|
4236
|
+
if (typeof prevTmux === 'string')
|
|
4237
|
+
process.env.TMUX = prevTmux;
|
|
4238
|
+
else
|
|
4239
|
+
delete process.env.TMUX;
|
|
4240
|
+
if (typeof prevTmuxPane === 'string')
|
|
4241
|
+
process.env.TMUX_PANE = prevTmuxPane;
|
|
4242
|
+
else
|
|
4243
|
+
delete process.env.TMUX_PANE;
|
|
4244
|
+
if (typeof prevWorkerCli === 'string')
|
|
4245
|
+
process.env.OMX_TEAM_WORKER_CLI = prevWorkerCli;
|
|
4246
|
+
else
|
|
4247
|
+
delete process.env.OMX_TEAM_WORKER_CLI;
|
|
4248
|
+
}
|
|
4249
|
+
});
|
|
4250
|
+
it('rejects pane-ID reuse of the pinned ambiguous worker PID during rollback and later teardown', async () => {
|
|
4251
|
+
const prevTmux = process.env.TMUX;
|
|
4252
|
+
const prevTmuxPane = process.env.TMUX_PANE;
|
|
4253
|
+
const prevWorkerCli = process.env.OMX_TEAM_WORKER_CLI;
|
|
4254
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-team-worker-split-recycled-pid-'));
|
|
4255
|
+
try {
|
|
4256
|
+
await withMockTmuxFixture('omx-tmux-worker-split-recycled-pid-', (logPath) => `#!/bin/sh
|
|
4257
|
+
set -eu
|
|
4258
|
+
case "\${1:-}" in
|
|
4259
|
+
-V) echo "tmux 3.4" ;;
|
|
4260
|
+
display-message)
|
|
4261
|
+
case "$*" in
|
|
4262
|
+
*"#{window_width}"*) echo "120" ;;
|
|
4263
|
+
*"#{pane_id}"*"#{pane_dead}"*"#{pane_pid}"*) printf "%%1\\t0\\t2000000001\\n" ;;
|
|
4264
|
+
*) echo "shared:0 $4" ;;
|
|
4265
|
+
esac
|
|
4266
|
+
;;
|
|
4267
|
+
list-panes)
|
|
4268
|
+
case "$*" in
|
|
4269
|
+
*'-a -F #{pane_id}'*)
|
|
4270
|
+
printf "%%1\\t0\\t2000000001\\n"
|
|
4271
|
+
if [ -f "${logPath}.worker" ]; then
|
|
4272
|
+
# Preserve the original PID through exact proof and owner tagging,
|
|
4273
|
+
# then model pane-ID reuse before immediate rollback/retry proof.
|
|
4274
|
+
if [ -f "${logPath}.worker-owner-tagged" ]; then
|
|
4275
|
+
printf "%%2\\t0\\t2000000999\\n"
|
|
4276
|
+
else
|
|
4277
|
+
printf "%%2\\t0\\t2000000002\\n"
|
|
4278
|
+
fi
|
|
4279
|
+
fi
|
|
4280
|
+
;;
|
|
4281
|
+
*"pane_current_command"*)
|
|
4282
|
+
printf "%%1\\tnode\\t'codex'\\n"
|
|
4283
|
+
[ ! -f "${logPath}.worker" ] || printf "%%2\\tgemini\\t%s\\n" "$(cat "${logPath}.worker-start")"
|
|
4284
|
+
;;
|
|
4285
|
+
*)
|
|
4286
|
+
printf "%%1\\n"
|
|
4287
|
+
[ ! -f "${logPath}.worker" ] || printf "%%2\\n"
|
|
4288
|
+
;;
|
|
4289
|
+
esac
|
|
4290
|
+
;;
|
|
4291
|
+
split-window)
|
|
4292
|
+
case "$*" in
|
|
4293
|
+
*" -h "*)
|
|
4294
|
+
: > "${logPath}.worker"
|
|
4295
|
+
last=''
|
|
4296
|
+
for arg in "$@"; do last="$arg"; done
|
|
4297
|
+
printf '%s' "$last" > "${logPath}.worker-start"
|
|
4298
|
+
printf "%%2\\n%%9\\n"
|
|
4299
|
+
;;
|
|
4300
|
+
*) printf "%%3\\n" ;;
|
|
4301
|
+
esac
|
|
4302
|
+
;;
|
|
4303
|
+
set-option)
|
|
4304
|
+
case "$*" in
|
|
4305
|
+
*"-t %2"*"@omx_team_pane_owner_id"*) : > "${logPath}.worker-owner-tagged" ;;
|
|
4306
|
+
esac
|
|
4307
|
+
;;
|
|
4308
|
+
kill-pane) printf '%s\\n' "$*" >> "${logPath}.kills" ;;
|
|
4309
|
+
*) ;;
|
|
4310
|
+
esac
|
|
4311
|
+
exit 0
|
|
4312
|
+
`, async ({ logPath }) => {
|
|
4313
|
+
const geminiPath = join(dirname(logPath), 'gemini');
|
|
4314
|
+
await writeFile(geminiPath, '#!/bin/sh\nexit 0\n');
|
|
4315
|
+
await chmod(geminiPath, 0o755);
|
|
4316
|
+
process.env.TMUX = 'shared-session,stub,0';
|
|
4317
|
+
process.env.TMUX_PANE = '%1';
|
|
4318
|
+
process.env.OMX_TEAM_WORKER_CLI = 'gemini';
|
|
4319
|
+
let pinnedPid = null;
|
|
4320
|
+
assert.throws(() => createTeamSession('Recycled Worker Pane', 1, cwd), (error) => {
|
|
4321
|
+
assert.ok(error instanceof CreateTeamSessionPartialError, String(error));
|
|
4322
|
+
assert.deepEqual(error.partialSession.workerPaneIds, ['%2']);
|
|
4323
|
+
assert.deepEqual(error.partialSession.workerPanePidsByIndex, [2000000002], JSON.stringify({
|
|
4324
|
+
cleanupErrors: error.cleanupErrors,
|
|
4325
|
+
proofUnavailable: error.proofUnavailable,
|
|
4326
|
+
originalError: error.originalError instanceof Error
|
|
4327
|
+
? `${error.originalError.name}:${error.originalError.message}`
|
|
4328
|
+
: String(error.originalError),
|
|
4329
|
+
tmuxLog: fs.existsSync(logPath) ? fs.readFileSync(logPath, 'utf-8') : '',
|
|
4330
|
+
partialSession: error.partialSession,
|
|
4331
|
+
}));
|
|
4332
|
+
assert.ok(error.cleanupErrors.some((message) => /tmux pane identity changed: %2/.test(message)), JSON.stringify(error.cleanupErrors));
|
|
4333
|
+
pinnedPid = error.partialSession.workerPanePidsByIndex?.[0] ?? null;
|
|
4334
|
+
return true;
|
|
4335
|
+
});
|
|
4336
|
+
assert.equal(pinnedPid, 2000000002);
|
|
4337
|
+
// The pinned PID never authorized a kill: neither the immediate
|
|
4338
|
+
// rollback nor any later path may have issued kill-pane for %2.
|
|
4339
|
+
assert.equal(fs.existsSync(`${logPath}.kills`), false, 'recycled %2 must never be killed');
|
|
4340
|
+
assert.equal(fs.existsSync(`${logPath}.worker`), true, 'recycled pane remains as unresolved debt');
|
|
4341
|
+
const teardown = await teardownWorkerPanes(['%2'], {
|
|
4342
|
+
graceMs: 1,
|
|
4343
|
+
expectedPanePids: { '%2': pinnedPid },
|
|
4344
|
+
authorizePaneKill: (paneId, proof) => paneId === '%2' && proof.pid === pinnedPid,
|
|
4345
|
+
});
|
|
4346
|
+
assert.deepEqual(teardown.killedPaneIds, []);
|
|
4347
|
+
assert.equal(teardown.kill.attempted, 0);
|
|
4348
|
+
assert.ok(teardown.proofUnavailable.some((proof) => proof.paneId === '%2' && proof.reason === 'pane_pid_changed'), JSON.stringify(teardown.proofUnavailable));
|
|
4349
|
+
assert.equal(fs.existsSync(`${logPath}.kills`), false, 'later teardown must not kill recycled %2');
|
|
4350
|
+
assert.equal(fs.existsSync(`${logPath}.worker`), true);
|
|
4351
|
+
});
|
|
4352
|
+
}
|
|
4353
|
+
finally {
|
|
4354
|
+
await rm(cwd, { recursive: true, force: true });
|
|
4355
|
+
if (typeof prevTmux === 'string')
|
|
4356
|
+
process.env.TMUX = prevTmux;
|
|
4357
|
+
else
|
|
4358
|
+
delete process.env.TMUX;
|
|
4359
|
+
if (typeof prevTmuxPane === 'string')
|
|
4360
|
+
process.env.TMUX_PANE = prevTmuxPane;
|
|
4361
|
+
else
|
|
4362
|
+
delete process.env.TMUX_PANE;
|
|
4363
|
+
if (typeof prevWorkerCli === 'string')
|
|
4364
|
+
process.env.OMX_TEAM_WORKER_CLI = prevWorkerCli;
|
|
4365
|
+
else
|
|
4366
|
+
delete process.env.OMX_TEAM_WORKER_CLI;
|
|
4367
|
+
}
|
|
4368
|
+
});
|
|
4369
|
+
it('covers unavailable, gone, and fully-cleaned outcomes for a uniquely reconciled ambiguous worker pane', async () => {
|
|
4370
|
+
const prevTmux = process.env.TMUX;
|
|
4371
|
+
const prevTmuxPane = process.env.TMUX_PANE;
|
|
4372
|
+
const prevWorkerCli = process.env.OMX_TEAM_WORKER_CLI;
|
|
4373
|
+
try {
|
|
4374
|
+
for (const scenario of [
|
|
4375
|
+
{ name: 'unavailable', proof: 'unavailable' },
|
|
4376
|
+
{ name: 'gone', proof: 'gone' },
|
|
4377
|
+
{ name: 'clean-rollback', proof: 'live' },
|
|
4378
|
+
]) {
|
|
4379
|
+
const cwd = await mkdtemp(join(tmpdir(), `omx-team-worker-split-${scenario.name}-`));
|
|
4380
|
+
try {
|
|
4381
|
+
await withMockTmuxFixture(`omx-tmux-worker-split-${scenario.name}-`, (logPath) => `#!/bin/sh
|
|
4382
|
+
set -eu
|
|
4383
|
+
case "\${1:-}" in
|
|
4384
|
+
-V) echo "tmux 3.4" ;;
|
|
4385
|
+
display-message)
|
|
4386
|
+
case "$*" in
|
|
4387
|
+
*"#{window_width}"*) echo "120" ;;
|
|
4388
|
+
*"#{pane_id}"*"#{pane_dead}"*"#{pane_pid}"*) printf "%%1\\t0\\t2000000001\\n" ;;
|
|
4389
|
+
*) echo "shared:0 $4" ;;
|
|
4390
|
+
esac
|
|
4391
|
+
;;
|
|
4392
|
+
list-panes)
|
|
4393
|
+
case "$*" in
|
|
4394
|
+
*'-a -F #{pane_id}'*)
|
|
4395
|
+
if [ "${scenario.proof}" = "unavailable" ] && [ -f "${logPath}.worker" ]; then
|
|
4396
|
+
echo "forced exact proof query failure" >&2
|
|
4397
|
+
exit 1
|
|
4398
|
+
fi
|
|
4399
|
+
printf "%%1\\t0\\t2000000001\\n"
|
|
4400
|
+
if [ "${scenario.proof}" != "gone" ]; then
|
|
4401
|
+
[ ! -f "${logPath}.worker" ] || printf "%%2\\t0\\t2000000002\\n"
|
|
4402
|
+
fi
|
|
4403
|
+
;;
|
|
4404
|
+
*"pane_current_command"*)
|
|
4405
|
+
printf "%%1\\tnode\\t'codex'\\n"
|
|
4406
|
+
[ ! -f "${logPath}.worker" ] || printf "%%2\\tgemini\\t%s\\n" "$(cat "${logPath}.worker-start")"
|
|
4407
|
+
;;
|
|
4408
|
+
*)
|
|
4409
|
+
printf "%%1\\n"
|
|
4410
|
+
[ ! -f "${logPath}.worker" ] || printf "%%2\\n"
|
|
4411
|
+
;;
|
|
4412
|
+
esac
|
|
4413
|
+
;;
|
|
4414
|
+
split-window)
|
|
4415
|
+
case "$*" in
|
|
4416
|
+
*" -h "*)
|
|
4417
|
+
: > "${logPath}.worker"
|
|
4418
|
+
last=''
|
|
4419
|
+
for arg in "$@"; do last="$arg"; done
|
|
4420
|
+
printf '%s' "$last" > "${logPath}.worker-start"
|
|
4421
|
+
printf "%%2\\n%%9\\n"
|
|
4422
|
+
;;
|
|
4423
|
+
*) printf "%%3\\n" ;;
|
|
4424
|
+
esac
|
|
4425
|
+
;;
|
|
4426
|
+
kill-pane)
|
|
4427
|
+
printf '%s\\n' "$*" >> "${logPath}.kills"
|
|
4428
|
+
case "$*" in
|
|
4429
|
+
*"%2"*) rm -f "${logPath}.worker" ;;
|
|
4430
|
+
esac
|
|
4431
|
+
;;
|
|
4432
|
+
*) ;;
|
|
4433
|
+
esac
|
|
4434
|
+
exit 0
|
|
4435
|
+
`, async ({ logPath }) => {
|
|
4436
|
+
const geminiPath = join(dirname(logPath), 'gemini');
|
|
4437
|
+
await writeFile(geminiPath, '#!/bin/sh\nexit 0\n');
|
|
4438
|
+
await chmod(geminiPath, 0o755);
|
|
4439
|
+
process.env.TMUX = 'shared-session,stub,0';
|
|
4440
|
+
process.env.TMUX_PANE = '%1';
|
|
4441
|
+
process.env.OMX_TEAM_WORKER_CLI = 'gemini';
|
|
4442
|
+
if (scenario.proof === 'unavailable') {
|
|
4443
|
+
assert.throws(() => createTeamSession(`Worker Proof ${scenario.name}`, 1, cwd), (error) => {
|
|
4444
|
+
assert.ok(error instanceof CreateTeamSessionPartialError, String(error));
|
|
4445
|
+
assert.ok(error.originalError instanceof Error && error.originalError.name === 'ExactPaneProofUnavailableError', String(error.originalError));
|
|
4446
|
+
assert.deepEqual(error.partialSession.workerPaneIds, ['%2']);
|
|
4447
|
+
assert.deepEqual(error.partialSession.workerPaneIdsByIndex, ['%2']);
|
|
4448
|
+
assert.deepEqual(error.partialSession.workerPanePidsByIndex, [null]);
|
|
4449
|
+
assert.ok(error.proofUnavailable.some((proof) => proof.paneId === '%2' && proof.reason === 'query_failed'), JSON.stringify(error.proofUnavailable));
|
|
4450
|
+
return true;
|
|
4451
|
+
});
|
|
4452
|
+
// Null-PID debt authorizes no kill and the pane remains.
|
|
4453
|
+
assert.equal(fs.existsSync(`${logPath}.kills`), false);
|
|
4454
|
+
assert.equal(fs.existsSync(`${logPath}.worker`), true);
|
|
4455
|
+
}
|
|
4456
|
+
else if (scenario.proof === 'gone') {
|
|
4457
|
+
assert.throws(() => createTeamSession(`Worker Proof ${scenario.name}`, 1, cwd), (error) => {
|
|
4458
|
+
assert.ok(!(error instanceof CreateTeamSessionPartialError), String(error));
|
|
4459
|
+
assert.ok(error instanceof Error && error.message === 'tmux_split_window_output_ambiguous', String(error));
|
|
4460
|
+
return true;
|
|
4461
|
+
});
|
|
4462
|
+
// A proven-gone pane is dropped from rollback state: no phantom
|
|
4463
|
+
// kill and no retained debt.
|
|
4464
|
+
assert.equal(fs.existsSync(`${logPath}.kills`), false);
|
|
4465
|
+
assert.equal(fs.existsSync(`${logPath}.worker`), true);
|
|
4466
|
+
}
|
|
4467
|
+
else {
|
|
4468
|
+
// Live proof plus a fully successful immediate rollback removes
|
|
4469
|
+
// all recoverable artifacts, so the raw ambiguous error surfaces.
|
|
4470
|
+
assert.throws(() => createTeamSession(`Worker Proof ${scenario.name}`, 1, cwd), (error) => {
|
|
4471
|
+
assert.ok(!(error instanceof CreateTeamSessionPartialError), String(error));
|
|
4472
|
+
assert.ok(error instanceof Error && error.message === 'tmux_split_window_output_ambiguous', String(error));
|
|
4473
|
+
return true;
|
|
4474
|
+
});
|
|
4475
|
+
assert.match(await readFile(`${logPath}.kills`, 'utf-8'), /kill-pane -t %2/);
|
|
4476
|
+
assert.equal(fs.existsSync(`${logPath}.worker`), false);
|
|
4477
|
+
}
|
|
4478
|
+
});
|
|
4479
|
+
}
|
|
4480
|
+
finally {
|
|
4481
|
+
await rm(cwd, { recursive: true, force: true });
|
|
4482
|
+
}
|
|
4483
|
+
}
|
|
4484
|
+
}
|
|
4485
|
+
finally {
|
|
4486
|
+
if (typeof prevTmux === 'string')
|
|
4487
|
+
process.env.TMUX = prevTmux;
|
|
4488
|
+
else
|
|
4489
|
+
delete process.env.TMUX;
|
|
4490
|
+
if (typeof prevTmuxPane === 'string')
|
|
4491
|
+
process.env.TMUX_PANE = prevTmuxPane;
|
|
4492
|
+
else
|
|
4493
|
+
delete process.env.TMUX_PANE;
|
|
4494
|
+
if (typeof prevWorkerCli === 'string')
|
|
4495
|
+
process.env.OMX_TEAM_WORKER_CLI = prevWorkerCli;
|
|
4496
|
+
else
|
|
4497
|
+
delete process.env.OMX_TEAM_WORKER_CLI;
|
|
4498
|
+
}
|
|
4499
|
+
});
|
|
3835
4500
|
it('rejects incompatible non-Codex and mixed Codex plans before any direct tmux mutation', async () => {
|
|
3836
4501
|
const cwd = await mkdtemp(join(tmpdir(), 'omx-team-direct-policy-preflight-'));
|
|
3837
4502
|
const previousTmux = process.env.TMUX;
|
|
@@ -4018,16 +4683,17 @@ esac
|
|
|
4018
4683
|
createTeamSession('Diff Gutter Redraw', 1, cwd);
|
|
4019
4684
|
const tmuxLog = await readFile(logPath, 'utf-8');
|
|
4020
4685
|
const commands = tmuxLog.trim().split('\n').filter(Boolean);
|
|
4021
|
-
assert.match(tmuxLog, /select-layout -t
|
|
4022
|
-
assert.match(tmuxLog, /set-window-option -t
|
|
4686
|
+
assert.match(tmuxLog, /select-layout -t @0 main-vertical/);
|
|
4687
|
+
assert.match(tmuxLog, /set-window-option -t @0 main-pane-width 60/);
|
|
4023
4688
|
assert.match(tmuxLog, /split-window -v -f -l 3 -t %1 -d -P -F #\{pane_id\}/);
|
|
4024
|
-
const
|
|
4025
|
-
.map((command, index) => command
|
|
4026
|
-
.filter((
|
|
4027
|
-
|
|
4028
|
-
|
|
4689
|
+
const redrawTransactions = commands
|
|
4690
|
+
.map((command, index) => ({ command, index }))
|
|
4691
|
+
.filter(({ command }) => command.includes('send-keys -t %1 C-l')
|
|
4692
|
+
&& command.includes("display-message -p 'omx_source_"));
|
|
4693
|
+
assert.equal(redrawTransactions.length, 1);
|
|
4694
|
+
const redrawIndex = redrawTransactions[0].index;
|
|
4029
4695
|
assert.ok(redrawIndex > 0);
|
|
4030
|
-
assert.match(
|
|
4696
|
+
assert.match(redrawTransactions[0].command, /send-keys -t %1 C-l.*display-message -p 'omx_source_/, 'leader Codex pane redraw must carry the exact guarded transaction receipt');
|
|
4031
4697
|
});
|
|
4032
4698
|
}
|
|
4033
4699
|
finally {
|
|
@@ -4217,16 +4883,30 @@ esac
|
|
|
4217
4883
|
const commands = tmuxLog.trim().split('\n').filter(Boolean);
|
|
4218
4884
|
const globalExactPanePidProof = /^list-panes -a -F #\{pane_id\}\t#\{pane_dead\}\t#\{pane_pid\}$/;
|
|
4219
4885
|
const targetScopedExactPaneSetProof = /^list-panes -t shared:0 -F #\{pane_id\}\t#\{pane_current_command\}\t#\{pane_start_command\}$/;
|
|
4220
|
-
const exactPaneEffects = /^(
|
|
4886
|
+
const exactPaneEffects = /^(split-window .* -t %|resize-pane -t %|select-pane -t %|send-keys -t %|if-shell -F -t %)/;
|
|
4221
4887
|
for (const [index, command] of commands.entries()) {
|
|
4222
4888
|
if (!exactPaneEffects.test(command))
|
|
4223
4889
|
continue;
|
|
4224
4890
|
const immediatelyPrevious = commands[index - 1] ?? '';
|
|
4225
4891
|
const previousProof = commands[index - 2] ?? '';
|
|
4892
|
+
const guardedAuthorityTransaction = command.startsWith('if-shell -F -t %')
|
|
4893
|
+
&& command.includes('#{==:#{pane_dead},0}')
|
|
4894
|
+
&& /#\{==:#\{pane_id\},%[0-9]+\}/.test(command)
|
|
4895
|
+
&& /#\{==:#\{pane_pid\},[1-9][0-9]*\}/.test(command)
|
|
4896
|
+
&& /#\{==:#\{session_id\},\$[0-9]+\}/.test(command)
|
|
4897
|
+
&& /#\{==:#\{session_created\},[0-9]+\}/.test(command)
|
|
4898
|
+
&& /#\{==:#\{window_id\},@[0-9]+\}/.test(command)
|
|
4899
|
+
&& (/set-option(?: [^;]*)? @omx_(?:team_pane_owner_id|instance_id|pane_instance_id)\b/.test(command)
|
|
4900
|
+
|| new RegExp(`#\\{==:#\\{@omx_team_pane_owner_id\\},${escapeRegExp(session.teamPaneOwnerId)}\\}`).test(command));
|
|
4226
4901
|
const hasAdjacentAuthority = globalExactPanePidProof.test(immediatelyPrevious)
|
|
4227
4902
|
|| (targetScopedExactPaneSetProof.test(immediatelyPrevious)
|
|
4228
|
-
&& globalExactPanePidProof.test(previousProof))
|
|
4229
|
-
|
|
4903
|
+
&& globalExactPanePidProof.test(previousProof))
|
|
4904
|
+
|| guardedAuthorityTransaction
|
|
4905
|
+
|| (immediatelyPrevious.startsWith('if-shell -F -t %')
|
|
4906
|
+
&& immediatelyPrevious.includes("display-message -p 'omx_source_"))
|
|
4907
|
+
|| command.includes("display-message -p 'omx_source_")
|
|
4908
|
+
|| (command.startsWith('split-window ') && command.includes('omx_source_'));
|
|
4909
|
+
assert.equal(hasAdjacentAuthority, true, `exact-pane effect must be atomically guarded by, or immediately preceded by, authoritative exact-pane proof: ${command}`);
|
|
4230
4910
|
}
|
|
4231
4911
|
});
|
|
4232
4912
|
}
|
|
@@ -4494,7 +5174,17 @@ esac
|
|
|
4494
5174
|
process.env.TMUX_PANE = '%1';
|
|
4495
5175
|
process.env.OMX_SESSION_ID = 'leader-session-a';
|
|
4496
5176
|
process.env.OMX_TEAM_WORKER_CLI = 'gemini';
|
|
4497
|
-
assert.throws(() => createTeamSession('Owned HUD Startup', 1, cwd),
|
|
5177
|
+
assert.throws(() => createTeamSession('Owned HUD Startup', 1, cwd), (error) => {
|
|
5178
|
+
assert.ok(error instanceof Error);
|
|
5179
|
+
assert.match(error.message, /tmux window topology changed before layout mutation/);
|
|
5180
|
+
assert.match(error.message, /target=shared:0/);
|
|
5181
|
+
assert.match(error.message, /expected=\[%1,%2\]/);
|
|
5182
|
+
assert.match(error.message, /actual=\[%1,%2,%7,%8\]/);
|
|
5183
|
+
assert.match(error.message, /unexpected=\[%7,%8\]/);
|
|
5184
|
+
assert.match(error.message, /missing=\[\]/);
|
|
5185
|
+
assert.match(error.message, /isolated tmux window with no host-owned foreign panes/);
|
|
5186
|
+
return true;
|
|
5187
|
+
});
|
|
4498
5188
|
const tmuxLog = await readFile(logPath, 'utf-8');
|
|
4499
5189
|
assert.doesNotMatch(tmuxLog, /set-option|kill-pane|split-window|resize-pane|select-layout|set-window-option|select-pane|send-keys/);
|
|
4500
5190
|
});
|
|
@@ -4543,12 +5233,7 @@ case "$1" in
|
|
|
4543
5233
|
list-panes)
|
|
4544
5234
|
case "$*" in
|
|
4545
5235
|
*"-a -F #{pane_id}"*)
|
|
4546
|
-
|
|
4547
|
-
if [ -f "${logPath}.proof-count" ]; then proof_count=$(cat "${logPath}.proof-count"); fi
|
|
4548
|
-
proof_count=$((proof_count + 1))
|
|
4549
|
-
printf '%s' "$proof_count" > "${logPath}.proof-count"
|
|
4550
|
-
if [ "$proof_count" -gt 11 ]; then printf 'not-a-pane-snapshot\n'; else printf "%%1\t0\t2000000001\n%%2\t0\t2000000002\n"; fi
|
|
4551
|
-
|
|
5236
|
+
printf "%%1\t0\t2000000001\n%%2\t0\t2000000002\n"
|
|
4552
5237
|
;;
|
|
4553
5238
|
*"pane_current_command"*)
|
|
4554
5239
|
if [ -f "${logPath}.worker" ]; then printf "%%1\\tnode\\t'codex'\\n%%2\\tgemini\\t'gemini'\\n"; else printf "%%1\\tnode\\t'codex'\\n"; fi
|
|
@@ -4560,7 +5245,7 @@ case "$1" in
|
|
|
4560
5245
|
exit 0
|
|
4561
5246
|
;;
|
|
4562
5247
|
show-option)
|
|
4563
|
-
|
|
5248
|
+
echo "team:partial-rollback-proof"
|
|
4564
5249
|
exit 0
|
|
4565
5250
|
;;
|
|
4566
5251
|
split-window)
|
|
@@ -4572,6 +5257,7 @@ case "$1" in
|
|
|
4572
5257
|
if [ "$is_horizontal" = "1" ]; then
|
|
4573
5258
|
echo "%2"
|
|
4574
5259
|
else
|
|
5260
|
+
printf '%s' 'team:foreign' > "$(dirname "${logPath}")/tmux.team-owner-state/%2"
|
|
4575
5261
|
echo "second worker rejected" >&2
|
|
4576
5262
|
exit 1
|
|
4577
5263
|
fi
|
|
@@ -5990,6 +6676,78 @@ esac
|
|
|
5990
6676
|
await rm(cwd, { recursive: true, force: true });
|
|
5991
6677
|
}
|
|
5992
6678
|
});
|
|
6679
|
+
it('pins and replays restored HUD debt when successful split output is ambiguous', async () => {
|
|
6680
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-restored-hud-ambiguous-output-'));
|
|
6681
|
+
try {
|
|
6682
|
+
await withMockTmuxFixture('omx-restored-hud-ambiguous-output-', (logPath) => {
|
|
6683
|
+
const killedPath = `${logPath}.killed`;
|
|
6684
|
+
return `#!/bin/sh
|
|
6685
|
+
set -eu
|
|
6686
|
+
printf '%s\n' "$*" >> "${logPath}"
|
|
6687
|
+
case "$1" in
|
|
6688
|
+
list-panes)
|
|
6689
|
+
if [ "$2" = "-a" ]; then
|
|
6690
|
+
printf '%%11\t0\t2000000011\n'
|
|
6691
|
+
if [ -f "${logPath}.created" ] && [ ! -f "${killedPath}" ]; then printf '%%44\t0\t2000000044\n'; fi
|
|
6692
|
+
printf '%%99\t0\t2000000099\n'
|
|
6693
|
+
else
|
|
6694
|
+
printf '%%11\tzsh\tzsh\n'
|
|
6695
|
+
if [ -f "${logPath}.created" ] && [ ! -f "${killedPath}" ]; then printf "%%44\tnode\t%s\n" "$(cat "${logPath}.start")"; fi
|
|
6696
|
+
printf '%%99\tbash\tforeign concurrent pane\n'
|
|
6697
|
+
fi
|
|
6698
|
+
;;
|
|
6699
|
+
split-window)
|
|
6700
|
+
: > "${logPath}.created"
|
|
6701
|
+
last=''
|
|
6702
|
+
for arg in "$@"; do last="$arg"; done
|
|
6703
|
+
printf '%s' "$last" > "${logPath}.start"
|
|
6704
|
+
printf '%%44\n%%99\n'
|
|
6705
|
+
;;
|
|
6706
|
+
show-option)
|
|
6707
|
+
if [ "$5" = "%11" ] && [ "$6" = "@omx_team_pane_owner_id" ]; then
|
|
6708
|
+
printf 'team:ambiguous-output\n'
|
|
6709
|
+
else
|
|
6710
|
+
exit 1
|
|
6711
|
+
fi
|
|
6712
|
+
;;
|
|
6713
|
+
kill-pane)
|
|
6714
|
+
case "$*" in
|
|
6715
|
+
*"%44"*) : > "${killedPath}" ;;
|
|
6716
|
+
*"%99"*) : > "${logPath}.killed-foreign"; exit 99 ;;
|
|
6717
|
+
esac
|
|
6718
|
+
;;
|
|
6719
|
+
*) exit 0 ;;
|
|
6720
|
+
esac
|
|
6721
|
+
`;
|
|
6722
|
+
}, async ({ logPath }) => {
|
|
6723
|
+
assert.throws(() => restoreStandaloneHudPane('%11', cwd, {
|
|
6724
|
+
expectedLeaderPanePid: 2000000011,
|
|
6725
|
+
expectedLeaderPaneOwnerId: 'team:ambiguous-output',
|
|
6726
|
+
}), /restored_hud_split_output_ambiguous/);
|
|
6727
|
+
const debtPath = join(cwd, '.omx', 'state', '.restored-hud-cleanup-debt.json');
|
|
6728
|
+
const debt = JSON.parse(await readFile(debtPath, 'utf-8'));
|
|
6729
|
+
assert.deepEqual(debt, {
|
|
6730
|
+
schema_version: 1,
|
|
6731
|
+
operation: 'restored_hud_cleanup',
|
|
6732
|
+
pane_id: '%44',
|
|
6733
|
+
pane_pid: 2000000044,
|
|
6734
|
+
leader_pane_id: '%11',
|
|
6735
|
+
leader_pane_pid: 2000000011,
|
|
6736
|
+
leader_pane_owner_id: 'team:ambiguous-output',
|
|
6737
|
+
hud_owner_leader_pane_id: '%11',
|
|
6738
|
+
});
|
|
6739
|
+
reconcileRestoredHudCleanupDebtSync(cwd);
|
|
6740
|
+
await assert.rejects(() => readFile(debtPath, 'utf-8'));
|
|
6741
|
+
const commands = await readFile(logPath, 'utf-8');
|
|
6742
|
+
assert.match(commands, /kill-pane -t %44/);
|
|
6743
|
+
assert.doesNotMatch(commands, /kill-pane -t %99/);
|
|
6744
|
+
assert.equal(fs.existsSync(`${logPath}.killed-foreign`), false);
|
|
6745
|
+
});
|
|
6746
|
+
}
|
|
6747
|
+
finally {
|
|
6748
|
+
await rm(cwd, { recursive: true, force: true });
|
|
6749
|
+
}
|
|
6750
|
+
});
|
|
5993
6751
|
it('rejects restored HUD debt roots outside the canonical direct Team root before splitting', async () => {
|
|
5994
6752
|
const cwd = await mkdtemp(join(tmpdir(), 'omx-restored-hud-debt-root-'));
|
|
5995
6753
|
const canonicalTeamsRoot = join(cwd, '.omx', 'state', 'team');
|
|
@@ -6786,8 +7544,6 @@ describe('dismissTrustPromptIfPresent', () => {
|
|
|
6786
7544
|
});
|
|
6787
7545
|
describe('isWorkerAlive', () => {
|
|
6788
7546
|
it('does not require pane_current_command to match "codex"', () => {
|
|
6789
|
-
// This was a real failure mode: tmux reports pane_current_command=node for the Codex TUI,
|
|
6790
|
-
// which caused workers to be treated as dead and the leader to clean up state too early.
|
|
6791
7547
|
withEmptyPath(() => {
|
|
6792
7548
|
assert.equal(isWorkerAlive('omx-team-x', 1), false);
|
|
6793
7549
|
});
|
|
@@ -6801,97 +7557,125 @@ exit 1
|
|
|
6801
7557
|
assert.equal(isWorkerAlive('compat-session', 1, ' '), true);
|
|
6802
7558
|
});
|
|
6803
7559
|
});
|
|
6804
|
-
it('
|
|
6805
|
-
await withMockTmuxFixture('omx-worker-liveness-
|
|
7560
|
+
it('queries each exact pane even when a broad snapshot would return the leader first', async () => {
|
|
7561
|
+
await withMockTmuxFixture('omx-worker-liveness-exact-target-', (logPath) => `#!/bin/sh
|
|
6806
7562
|
set -eu
|
|
6807
7563
|
printf '%s\n' "$*" >> "${logPath}"
|
|
6808
7564
|
case "$1" in
|
|
6809
|
-
|
|
6810
|
-
|
|
6811
|
-
|
|
6812
|
-
|
|
7565
|
+
display-message)
|
|
7566
|
+
case "$4" in
|
|
7567
|
+
%21) printf '%%21\t0\t%s\tteam:liveness\n' "$PPID" ;;
|
|
7568
|
+
%22) printf '%%22\t0\t%s\tteam:liveness\n' "$PPID" ;;
|
|
7569
|
+
*) exit 1 ;;
|
|
7570
|
+
esac
|
|
6813
7571
|
;;
|
|
6814
|
-
|
|
7572
|
+
list-panes) printf '%%11\t0\t111\n%%21\t0\t%s\n%%22\t0\t%s\n' "$PPID" "$PPID" ;;
|
|
6815
7573
|
*) exit 1 ;;
|
|
6816
7574
|
esac
|
|
6817
7575
|
`, async ({ logPath }) => {
|
|
7576
|
+
assert.equal(isWorkerAlive('ignored-session', 1, '%21', process.pid, 'team:liveness'), true);
|
|
7577
|
+
assert.equal(isWorkerAlive('ignored-session', 2, '%22', process.pid, 'team:liveness'), true);
|
|
7578
|
+
const log = await readFile(logPath, 'utf-8');
|
|
7579
|
+
assert.match(log, /display-message -p -t %21/);
|
|
7580
|
+
assert.match(log, /display-message -p -t %22/);
|
|
7581
|
+
assert.doesNotMatch(log, /list-panes -a/);
|
|
7582
|
+
});
|
|
7583
|
+
});
|
|
7584
|
+
it('treats pane recycle and PID reuse identity mismatches as diagnostics, not death', async () => {
|
|
7585
|
+
await withMockTmuxFixture('omx-worker-liveness-reuse-', () => `#!/bin/sh
|
|
7586
|
+
set -eu
|
|
7587
|
+
case "$1" in
|
|
7588
|
+
display-message)
|
|
7589
|
+
case "$4" in
|
|
7590
|
+
%31) printf '%%31\t0\t999999\tteam:liveness\n' ;;
|
|
7591
|
+
%32) printf '%%32\t0\t%s\tteam:replacement\n' "$PPID" ;;
|
|
7592
|
+
*) exit 1 ;;
|
|
7593
|
+
esac
|
|
7594
|
+
;;
|
|
7595
|
+
*) exit 1 ;;
|
|
7596
|
+
esac
|
|
7597
|
+
`, async () => {
|
|
7598
|
+
assert.throws(() => isWorkerAlive('ignored-session', 1, '%31', process.pid, 'team:liveness'), /worker pane PID changed/);
|
|
7599
|
+
assert.throws(() => isWorkerAlive('ignored-session', 2, '%32', process.pid, 'team:liveness'), /worker pane incarnation changed/);
|
|
7600
|
+
});
|
|
7601
|
+
});
|
|
7602
|
+
it('classifies only missing, dead, or ESRCH exact panes as dead', async () => {
|
|
7603
|
+
await withMockTmuxFixture('omx-worker-liveness-gone-', () => `#!/bin/sh
|
|
7604
|
+
set -eu
|
|
7605
|
+
case "$1" in
|
|
7606
|
+
display-message)
|
|
7607
|
+
case "$4" in
|
|
7608
|
+
%41) echo "can't find pane: %41" >&2; exit 1 ;;
|
|
7609
|
+
%42) printf '%%42\t1\t42\tteam:liveness\n' ;;
|
|
7610
|
+
%43) printf '%%43\t0\t%s\tteam:liveness\n' "$PPID" ;;
|
|
7611
|
+
*) exit 1 ;;
|
|
7612
|
+
esac
|
|
7613
|
+
;;
|
|
7614
|
+
*) exit 1 ;;
|
|
7615
|
+
esac
|
|
7616
|
+
`, async () => {
|
|
6818
7617
|
const originalProcessKill = process.kill;
|
|
6819
7618
|
process.kill = ((pid, signal) => {
|
|
6820
7619
|
if (pid === process.pid && signal === 0) {
|
|
6821
|
-
const error = new Error('
|
|
6822
|
-
error.code = '
|
|
7620
|
+
const error = new Error('no such process');
|
|
7621
|
+
error.code = 'ESRCH';
|
|
6823
7622
|
throw error;
|
|
6824
7623
|
}
|
|
6825
7624
|
return originalProcessKill(pid, signal);
|
|
6826
7625
|
});
|
|
6827
7626
|
try {
|
|
6828
|
-
assert.equal(isWorkerAlive('
|
|
6829
|
-
assert.equal(isWorkerAlive('ignored-session',
|
|
6830
|
-
|
|
7627
|
+
assert.equal(isWorkerAlive('ignored-session', 1, '%41', 41, 'team:liveness'), false);
|
|
7628
|
+
assert.equal(isWorkerAlive('ignored-session', 2, '%42', 42, 'team:liveness'), false);
|
|
7629
|
+
assert.equal(isWorkerAlive('ignored-session', 3, '%43', process.pid, 'team:liveness'), false);
|
|
6831
7630
|
}
|
|
6832
7631
|
finally {
|
|
6833
7632
|
process.kill = originalProcessKill;
|
|
6834
7633
|
}
|
|
6835
|
-
const log = await readFile(logPath, 'utf-8');
|
|
6836
|
-
assert.doesNotMatch(log, /send-keys -t %77|kill-pane -t %77/);
|
|
6837
7634
|
});
|
|
6838
7635
|
});
|
|
6839
|
-
it('
|
|
6840
|
-
await withMockTmuxFixture('omx-worker-liveness-
|
|
7636
|
+
it('keeps EPERM and malformed exact identity as diagnostic/live-unknown outcomes', async () => {
|
|
7637
|
+
await withMockTmuxFixture('omx-worker-liveness-fail-closed-', () => `#!/bin/sh
|
|
7638
|
+
set -eu
|
|
6841
7639
|
case "$1" in
|
|
6842
|
-
|
|
6843
|
-
|
|
6844
|
-
|
|
6845
|
-
|
|
7640
|
+
display-message)
|
|
7641
|
+
case "$4" in
|
|
7642
|
+
%51) printf '%%51\t0\t%s\tteam:liveness\n' "$PPID" ;;
|
|
7643
|
+
%52) printf '%%leader\t0\t52\tteam:liveness\n' ;;
|
|
7644
|
+
%53) printf '%%53\t0\t53\t\n' ;;
|
|
7645
|
+
%54) printf '%%54\t1\t54\t\n' ;;
|
|
7646
|
+
%55) printf '%%55\t0\t55\tteam:liveness\n' ;;
|
|
7647
|
+
%56) printf '%%56\t1\t56\tteam:liveness\n' ;;
|
|
7648
|
+
*) exit 2 ;;
|
|
7649
|
+
esac
|
|
6846
7650
|
;;
|
|
6847
|
-
show-option) printf 'team:liveness\n'; exit 0 ;;
|
|
6848
7651
|
*) exit 1 ;;
|
|
6849
7652
|
esac
|
|
6850
7653
|
`, async () => {
|
|
6851
7654
|
const originalProcessKill = process.kill;
|
|
6852
7655
|
process.kill = ((pid, signal) => {
|
|
6853
7656
|
if (pid === process.pid && signal === 0) {
|
|
6854
|
-
const error = new Error('
|
|
6855
|
-
error.code = '
|
|
7657
|
+
const error = new Error('permission denied');
|
|
7658
|
+
error.code = 'EPERM';
|
|
6856
7659
|
throw error;
|
|
6857
7660
|
}
|
|
6858
7661
|
return originalProcessKill(pid, signal);
|
|
6859
7662
|
});
|
|
6860
7663
|
try {
|
|
6861
|
-
assert.equal(isWorkerAlive('
|
|
6862
|
-
assert.
|
|
7664
|
+
assert.equal(isWorkerAlive('ignored-session', 1, '%51', process.pid, 'team:liveness'), true);
|
|
7665
|
+
assert.throws(() => isWorkerAlive('ignored-session', 2, '%52', 52, 'team:liveness'), /worker pane identity changed.*pane_id_changed/);
|
|
7666
|
+
assert.throws(() => isWorkerAlive('ignored-session', 3, '%53', 53, 'team:liveness'), /worker pane incarnation changed.*missing/);
|
|
7667
|
+
assert.equal(isWorkerAlive('ignored-session', 4, '%54', 54, 'team:liveness'), false);
|
|
7668
|
+
assert.equal(isWorkerAlive('ignored-session', 6, '%56', undefined, 'team:liveness'), false);
|
|
7669
|
+
assert.throws(() => isWorkerAlive('ignored-session', 5, '%55', 55, 'team:liveness', '%55'), /worker pane is HUD target/);
|
|
7670
|
+
assert.throws(() => isWorkerAlive('ignored-session', 5, '%55', undefined, 'team:liveness', '%55'), /worker pane is HUD target/);
|
|
7671
|
+
assert.throws(() => isWorkerAlive('ignored-session', 5, '%55', 55, undefined, '%55'), /worker pane is HUD target/);
|
|
7672
|
+
assert.throws(() => getWorkerPanePid('ignored-session', 5, '%55', 55, 'team:liveness', '%55'), /worker pane is HUD target/);
|
|
6863
7673
|
}
|
|
6864
7674
|
finally {
|
|
6865
7675
|
process.kill = originalProcessKill;
|
|
6866
7676
|
}
|
|
6867
7677
|
});
|
|
6868
7678
|
});
|
|
6869
|
-
it('uses the exact global row for explicit pane liveness and fails closed for dead rows', async () => {
|
|
6870
|
-
await withMockTmuxFixture('omx-pane-id-liveness-', (logPath) => `#!/bin/sh
|
|
6871
|
-
set -eu
|
|
6872
|
-
printf '%s\n' "$*" >> "${logPath}"
|
|
6873
|
-
case "\${1:-}" in
|
|
6874
|
-
list-panes)
|
|
6875
|
-
if [ "$2" = "-a" ]; then
|
|
6876
|
-
printf '%%77\t0\t%s\n%%88\t1\t1\n' "$PPID"
|
|
6877
|
-
exit 0
|
|
6878
|
-
fi
|
|
6879
|
-
exit 1
|
|
6880
|
-
;;
|
|
6881
|
-
show-option)
|
|
6882
|
-
printf 'team:liveness\n'
|
|
6883
|
-
;;
|
|
6884
|
-
*)
|
|
6885
|
-
exit 1
|
|
6886
|
-
;;
|
|
6887
|
-
esac
|
|
6888
|
-
`, async () => {
|
|
6889
|
-
assert.equal(isWorkerAlive('ignored-session', 1, '%77', process.pid, 'team:liveness'), true);
|
|
6890
|
-
assert.equal(isWorkerPaneOpen('ignored-session', 1, '%77', process.pid, 'team:liveness'), true);
|
|
6891
|
-
assert.equal(isWorkerAlive('ignored-session', 2, '%88', 1, 'team:liveness'), false);
|
|
6892
|
-
assert.equal(isWorkerPaneOpen('ignored-session', 2, '%88', 1, 'team:liveness'), false);
|
|
6893
|
-
});
|
|
6894
|
-
});
|
|
6895
7679
|
});
|
|
6896
7680
|
describe('exact global pane proof contract', () => {
|
|
6897
7681
|
it('matches only exact pane IDs and distinguishes live, dead, and absent rows', async () => {
|
|
@@ -6906,6 +7690,13 @@ case "$1" in
|
|
|
6906
7690
|
show-option)
|
|
6907
7691
|
printf 'team:rows\n'
|
|
6908
7692
|
;;
|
|
7693
|
+
display-message)
|
|
7694
|
+
case "$4" in
|
|
7695
|
+
%13) printf '%%13\t0\t%s\tteam:rows\n' "${pane13Pid}" ;;
|
|
7696
|
+
%130) printf '%%130\t0\t%s\tteam:rows\n' "${pane130Pid}" ;;
|
|
7697
|
+
*) exit 1 ;;
|
|
7698
|
+
esac
|
|
7699
|
+
;;
|
|
6909
7700
|
*)
|
|
6910
7701
|
exit 1
|
|
6911
7702
|
;;
|
|
@@ -6993,6 +7784,10 @@ case "$1" in
|
|
|
6993
7784
|
list-panes)
|
|
6994
7785
|
printf '%%13\t0\t%s\n' "$PPID"
|
|
6995
7786
|
;;
|
|
7787
|
+
display-message)
|
|
7788
|
+
echo "can't find pane: $4" >&2
|
|
7789
|
+
exit 1
|
|
7790
|
+
;;
|
|
6996
7791
|
*)
|
|
6997
7792
|
exit 0
|
|
6998
7793
|
;;
|