oh-my-codex 0.20.3 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.lock +35 -18
- package/Cargo.toml +1 -1
- package/README.md +22 -19
- 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/agents/__tests__/definitions.test.js +0 -27
- package/dist/agents/__tests__/definitions.test.js.map +1 -1
- package/dist/agents/__tests__/native-config.test.js +3 -8
- package/dist/agents/__tests__/native-config.test.js.map +1 -1
- package/dist/agents/definitions.d.ts.map +1 -1
- package/dist/agents/definitions.js +0 -41
- package/dist/agents/definitions.js.map +1 -1
- package/dist/agents/policy.d.ts.map +1 -1
- package/dist/agents/policy.js +0 -1
- package/dist/agents/policy.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__/completion-gate-advisory.test.d.ts +2 -0
- package/dist/autopilot/__tests__/completion-gate-advisory.test.d.ts.map +1 -0
- package/dist/autopilot/__tests__/completion-gate-advisory.test.js +383 -0
- package/dist/autopilot/__tests__/completion-gate-advisory.test.js.map +1 -0
- package/dist/autopilot/completion-gate.d.ts +6 -1
- package/dist/autopilot/completion-gate.d.ts.map +1 -1
- package/dist/autopilot/completion-gate.js +247 -7
- package/dist/autopilot/completion-gate.js.map +1 -1
- package/dist/catalog/__tests__/generator.test.js +15 -10
- package/dist/catalog/__tests__/generator.test.js.map +1 -1
- package/dist/catalog/__tests__/plugin-bundle-ssot.test.js +13 -7
- package/dist/catalog/__tests__/plugin-bundle-ssot.test.js.map +1 -1
- package/dist/catalog/__tests__/schema.test.js +3 -5
- package/dist/catalog/__tests__/schema.test.js.map +1 -1
- package/dist/catalog/schema.js +1 -1
- package/dist/catalog/schema.js.map +1 -1
- package/dist/cli/__tests__/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__/autopilot.test.d.ts +2 -0
- package/dist/cli/__tests__/autopilot.test.d.ts.map +1 -0
- package/dist/cli/__tests__/autopilot.test.js +143 -0
- package/dist/cli/__tests__/autopilot.test.js.map +1 -0
- 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 +76 -4
- 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 +206 -0
- package/dist/cli/__tests__/doctor-state-root-binding.test.js.map +1 -0
- package/dist/cli/__tests__/doctor-team.test.js +85 -0
- package/dist/cli/__tests__/doctor-team.test.js.map +1 -1
- package/dist/cli/__tests__/doctor-warning-copy.test.js +38 -6
- 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 +1125 -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__/package-bin-contract.test.js +5 -8
- package/dist/cli/__tests__/package-bin-contract.test.js.map +1 -1
- package/dist/cli/__tests__/ralph-deslop-contract.test.js +4 -18
- package/dist/cli/__tests__/ralph-deslop-contract.test.js.map +1 -1
- package/dist/cli/__tests__/ralph-goal-mode-contract.test.js +4 -30
- package/dist/cli/__tests__/ralph-goal-mode-contract.test.js.map +1 -1
- package/dist/cli/__tests__/ralph-prd-deep-interview.test.js +4 -15
- package/dist/cli/__tests__/ralph-prd-deep-interview.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 +131 -14
- package/dist/cli/__tests__/ralplan.test.js.map +1 -1
- package/dist/cli/__tests__/session-scoped-runtime.test.js +1153 -43
- package/dist/cli/__tests__/session-scoped-runtime.test.js.map +1 -1
- package/dist/cli/__tests__/session-search-help.test.js +65 -3
- package/dist/cli/__tests__/session-search-help.test.js.map +1 -1
- package/dist/cli/__tests__/setup-agents-overwrite.test.js +57 -55
- package/dist/cli/__tests__/setup-agents-overwrite.test.js.map +1 -1
- package/dist/cli/__tests__/setup-hooks-shared-ownership.test.js +34 -0
- package/dist/cli/__tests__/setup-hooks-shared-ownership.test.js.map +1 -1
- package/dist/cli/__tests__/setup-hooks-trust-e2e.test.js +1 -1
- package/dist/cli/__tests__/setup-hooks-trust-e2e.test.js.map +1 -1
- package/dist/cli/__tests__/setup-install-mode.test.js +258 -26
- package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
- package/dist/cli/__tests__/setup-prompts-overwrite.test.js +1 -1
- package/dist/cli/__tests__/setup-prompts-overwrite.test.js.map +1 -1
- package/dist/cli/__tests__/setup-refresh.test.js +3 -6
- package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
- package/dist/cli/__tests__/setup-skills-overwrite.test.js +180 -21
- package/dist/cli/__tests__/setup-skills-overwrite.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 +161 -2
- package/dist/cli/__tests__/ultragoal.test.js.map +1 -1
- package/dist/cli/__tests__/uninstall.test.js +73 -28
- package/dist/cli/__tests__/uninstall.test.js.map +1 -1
- package/dist/cli/__tests__/update.test.js +749 -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/autopilot.d.ts +8 -0
- package/dist/cli/autopilot.d.ts.map +1 -0
- package/dist/cli/autopilot.js +160 -0
- package/dist/cli/autopilot.js.map +1 -0
- 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 +29 -0
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +433 -19
- 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 +2354 -724
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/mcp-parity.d.ts.map +1 -1
- package/dist/cli/mcp-parity.js +7 -3
- package/dist/cli/mcp-parity.js.map +1 -1
- package/dist/cli/native-assets.d.ts +26 -1
- 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 +69 -0
- package/dist/cli/package-manager-ownership.d.ts.map +1 -0
- package/dist/cli/package-manager-ownership.js +338 -0
- package/dist/cli/package-manager-ownership.js.map +1 -0
- package/dist/cli/plugin-marketplace.d.ts +1 -0
- package/dist/cli/plugin-marketplace.d.ts.map +1 -1
- package/dist/cli/plugin-marketplace.js +123 -55
- package/dist/cli/plugin-marketplace.js.map +1 -1
- package/dist/cli/ralph.js +2 -2
- package/dist/cli/ralph.js.map +1 -1
- package/dist/cli/ralplan.d.ts +6 -2
- package/dist/cli/ralplan.d.ts.map +1 -1
- package/dist/cli/ralplan.js +134 -34
- package/dist/cli/ralplan.js.map +1 -1
- package/dist/cli/session-search.d.ts.map +1 -1
- package/dist/cli/session-search.js +38 -1
- package/dist/cli/session-search.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 +6 -0
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +425 -215
- 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 +1 -1
- package/dist/cli/ultragoal.d.ts.map +1 -1
- package/dist/cli/ultragoal.js +10 -7
- 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 +16 -23
- package/dist/cli/update.d.ts.map +1 -1
- package/dist/cli/update.js +212 -241
- package/dist/cli/update.js.map +1 -1
- package/dist/compat/__tests__/upgrade-from-0-20.test.d.ts +2 -0
- package/dist/compat/__tests__/upgrade-from-0-20.test.d.ts.map +1 -0
- package/dist/compat/__tests__/upgrade-from-0-20.test.js +109 -0
- package/dist/compat/__tests__/upgrade-from-0-20.test.js.map +1 -0
- package/dist/compat/upgrade-from-0-20.d.ts +64 -0
- package/dist/compat/upgrade-from-0-20.d.ts.map +1 -0
- package/dist/compat/upgrade-from-0-20.js +276 -0
- package/dist/compat/upgrade-from-0-20.js.map +1 -0
- 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__/analyze-skill-contract.test.js +24 -31
- package/dist/hooks/__tests__/analyze-skill-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/anti-slop-workflow.test.js +66 -135
- package/dist/hooks/__tests__/anti-slop-workflow.test.js.map +1 -1
- package/dist/hooks/__tests__/autopilot-skill-contract.test.js +47 -106
- package/dist/hooks/__tests__/autopilot-skill-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/code-review-skill-contract.test.js +16 -18
- package/dist/hooks/__tests__/code-review-skill-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/consensus-execution-handoff.test.d.ts +2 -14
- package/dist/hooks/__tests__/consensus-execution-handoff.test.d.ts.map +1 -1
- package/dist/hooks/__tests__/consensus-execution-handoff.test.js +56 -234
- package/dist/hooks/__tests__/consensus-execution-handoff.test.js.map +1 -1
- package/dist/hooks/__tests__/deep-interview-contract.test.js +1 -3
- package/dist/hooks/__tests__/deep-interview-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/design-skill.test.js +18 -16
- package/dist/hooks/__tests__/design-skill.test.js.map +1 -1
- package/dist/hooks/__tests__/explore-sparkshell-guidance-contract.test.js +24 -20
- package/dist/hooks/__tests__/explore-sparkshell-guidance-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 +164 -0
- package/dist/hooks/__tests__/issue-3293-autopilot-handoff.test.js.map +1 -0
- package/dist/hooks/__tests__/issue-3497-hook-simplification.test.d.ts +2 -0
- package/dist/hooks/__tests__/issue-3497-hook-simplification.test.d.ts.map +1 -0
- package/dist/hooks/__tests__/issue-3497-hook-simplification.test.js +169 -0
- package/dist/hooks/__tests__/issue-3497-hook-simplification.test.js.map +1 -0
- package/dist/hooks/__tests__/keyword-detector.test.js +226 -858
- package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-fallback-watcher.test.js +176 -14
- 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 +33 -21
- 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-ralph-resume.test.js +14 -904
- package/dist/hooks/__tests__/notify-hook-ralph-resume.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-session-scope.test.js +5 -5
- package/dist/hooks/__tests__/notify-hook-session-scope.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js +7 -7
- package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js +11 -8
- package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-team-tmux-guard.test.js +7 -6
- package/dist/hooks/__tests__/notify-hook-team-tmux-guard.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__/pre-context-gate-skills.test.js +19 -21
- package/dist/hooks/__tests__/pre-context-gate-skills.test.js.map +1 -1
- package/dist/hooks/__tests__/prompt-guidance-contract.test.js +23 -3
- package/dist/hooks/__tests__/prompt-guidance-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/prompt-guidance-wave-two.test.js +23 -28
- package/dist/hooks/__tests__/prompt-guidance-wave-two.test.js.map +1 -1
- package/dist/hooks/__tests__/research-workflow-boundaries.test.js +9 -8
- package/dist/hooks/__tests__/research-workflow-boundaries.test.js.map +1 -1
- package/dist/hooks/__tests__/session.test.js +3656 -656
- package/dist/hooks/__tests__/session.test.js.map +1 -1
- package/dist/hooks/__tests__/skill-catalog-hygiene.test.js +13 -22
- package/dist/hooks/__tests__/skill-catalog-hygiene.test.js.map +1 -1
- package/dist/hooks/__tests__/skill-guidance-contract.test.js +3 -13
- package/dist/hooks/__tests__/skill-guidance-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/sunset-routing-contract.test.d.ts +2 -0
- package/dist/hooks/__tests__/sunset-routing-contract.test.d.ts.map +1 -0
- package/dist/hooks/__tests__/sunset-routing-contract.test.js +172 -0
- package/dist/hooks/__tests__/sunset-routing-contract.test.js.map +1 -0
- package/dist/hooks/__tests__/team-runtime-gating-docs-contract.test.js +2 -2
- package/dist/hooks/__tests__/team-runtime-gating-docs-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/tmux-hook-engine.test.js +18 -3
- package/dist/hooks/__tests__/tmux-hook-engine.test.js.map +1 -1
- package/dist/hooks/__tests__/visual-ralph-skill.test.js +20 -17
- package/dist/hooks/__tests__/visual-ralph-skill.test.js.map +1 -1
- package/dist/hooks/__tests__/visual-verdict-loop.test.js +17 -18
- package/dist/hooks/__tests__/visual-verdict-loop.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 +6 -47
- package/dist/hooks/keyword-detector.d.ts.map +1 -1
- package/dist/hooks/keyword-detector.js +156 -268
- package/dist/hooks/keyword-detector.js.map +1 -1
- package/dist/hooks/keyword-registry.d.ts.map +1 -1
- package/dist/hooks/keyword-registry.js +14 -26
- package/dist/hooks/keyword-registry.js.map +1 -1
- package/dist/hooks/native/capability-warnings.d.ts +16 -0
- package/dist/hooks/native/capability-warnings.d.ts.map +1 -0
- package/dist/hooks/native/capability-warnings.js +40 -0
- package/dist/hooks/native/capability-warnings.js.map +1 -0
- package/dist/hooks/native/index.d.ts +17 -0
- package/dist/hooks/native/index.d.ts.map +1 -0
- package/dist/hooks/native/index.js +17 -0
- package/dist/hooks/native/index.js.map +1 -0
- package/dist/hooks/native/pre-tool-use-advisory.d.ts +17 -0
- package/dist/hooks/native/pre-tool-use-advisory.d.ts.map +1 -0
- package/dist/hooks/native/pre-tool-use-advisory.js +112 -0
- package/dist/hooks/native/pre-tool-use-advisory.js.map +1 -0
- package/dist/hooks/native/types.d.ts +30 -0
- package/dist/hooks/native/types.d.ts.map +1 -0
- package/dist/hooks/native/types.js +22 -0
- package/dist/hooks/native/types.js.map +1 -0
- package/dist/hooks/prompt-guidance-contract.d.ts.map +1 -1
- package/dist/hooks/prompt-guidance-contract.js +46 -93
- package/dist/hooks/prompt-guidance-contract.js.map +1 -1
- package/dist/hooks/session.d.ts +197 -18
- package/dist/hooks/session.d.ts.map +1 -1
- package/dist/hooks/session.js +1939 -271
- package/dist/hooks/session.js.map +1 -1
- package/dist/hooks/sunset-stub.d.ts +17 -0
- package/dist/hooks/sunset-stub.d.ts.map +1 -0
- package/dist/hooks/sunset-stub.js +125 -0
- package/dist/hooks/sunset-stub.js.map +1 -0
- 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-split-realtmux.test.d.ts +2 -0
- package/dist/hud/__tests__/tmux-split-realtmux.test.d.ts.map +1 -0
- package/dist/hud/__tests__/tmux-split-realtmux.test.js +112 -0
- package/dist/hud/__tests__/tmux-split-realtmux.test.js.map +1 -0
- package/dist/hud/__tests__/tmux.test.js +217 -198
- package/dist/hud/__tests__/tmux.test.js.map +1 -1
- package/dist/hud/tmux.d.ts +43 -4
- package/dist/hud/tmux.d.ts.map +1 -1
- package/dist/hud/tmux.js +1025 -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-ralph-phase.test.js +29 -4
- package/dist/mcp/__tests__/state-server-ralph-phase.test.js.map +1 -1
- package/dist/mcp/__tests__/state-server-schema.test.js +13 -3
- package/dist/mcp/__tests__/state-server-schema.test.js.map +1 -1
- package/dist/mcp/__tests__/state-server.test.js +11 -1033
- 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 +68 -97
- package/dist/mcp/state-server.d.ts.map +1 -1
- package/dist/mcp/state-server.js +116 -67
- package/dist/mcp/state-server.js.map +1 -1
- package/dist/modes/__tests__/base-autopilot-gates.test.js +1 -168
- package/dist/modes/__tests__/base-autopilot-gates.test.js.map +1 -1
- package/dist/modes/__tests__/base-autoresearch-contract.test.js +1 -85
- package/dist/modes/__tests__/base-autoresearch-contract.test.js.map +1 -1
- package/dist/modes/__tests__/base-multi-state-compat.test.js +1 -35
- package/dist/modes/__tests__/base-multi-state-compat.test.js.map +1 -1
- package/dist/modes/__tests__/base-session-scope.test.js +10 -158
- package/dist/modes/__tests__/base-session-scope.test.js.map +1 -1
- package/dist/modes/base.d.ts +6 -6
- package/dist/modes/base.d.ts.map +1 -1
- package/dist/modes/base.js +116 -90
- 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/notifications/__tests__/formatter.test.js +8 -0
- package/dist/notifications/__tests__/formatter.test.js.map +1 -1
- package/dist/notifications/__tests__/template-engine.test.js +8 -0
- package/dist/notifications/__tests__/template-engine.test.js.map +1 -1
- package/dist/notifications/formatter.js +1 -1
- package/dist/notifications/formatter.js.map +1 -1
- package/dist/notifications/template-engine.js +1 -1
- package/dist/notifications/template-engine.js.map +1 -1
- package/dist/planning/__tests__/approved-execution-lifecycle-matrix.test.js +0 -21
- package/dist/planning/__tests__/approved-execution-lifecycle-matrix.test.js.map +1 -1
- package/dist/question/autopilot-wait.d.ts.map +1 -1
- package/dist/question/autopilot-wait.js +2 -2
- package/dist/question/autopilot-wait.js.map +1 -1
- package/dist/question/deep-interview.d.ts +1 -2
- package/dist/question/deep-interview.d.ts.map +1 -1
- package/dist/question/deep-interview.js +4 -4
- package/dist/question/deep-interview.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 +56 -821
- package/dist/ralplan/__tests__/runtime.test.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 +2 -2
- package/dist/ralplan/runtime.d.ts.map +1 -1
- package/dist/ralplan/runtime.js +145 -163
- package/dist/ralplan/runtime.js.map +1 -1
- package/dist/runtime/bridge.d.ts.map +1 -1
- package/dist/runtime/bridge.js +45 -0
- package/dist/runtime/bridge.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 +12490 -19414
- package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
- package/dist/scripts/__tests__/docs-site-contract.test.js +2 -2
- package/dist/scripts/__tests__/docs-site-contract.test.js.map +1 -1
- package/dist/scripts/__tests__/generated-artifact-drift.test.d.ts +2 -0
- package/dist/scripts/__tests__/generated-artifact-drift.test.d.ts.map +1 -0
- package/dist/scripts/__tests__/generated-artifact-drift.test.js +84 -0
- package/dist/scripts/__tests__/generated-artifact-drift.test.js.map +1 -0
- 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 +235 -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 +207 -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 +156 -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 +191 -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 +40 -0
- package/dist/scripts/__tests__/issue-3369-autopilot-ralplan-state-machine.test.js.map +1 -0
- package/dist/scripts/__tests__/issue-3481-conductor-deadlock.test.d.ts +2 -0
- package/dist/scripts/__tests__/issue-3481-conductor-deadlock.test.d.ts.map +1 -0
- package/dist/scripts/__tests__/issue-3481-conductor-deadlock.test.js +63 -0
- package/dist/scripts/__tests__/issue-3481-conductor-deadlock.test.js.map +1 -0
- package/dist/scripts/__tests__/issue-3486-pretooluse-lock-regression.test.d.ts +2 -0
- package/dist/scripts/__tests__/issue-3486-pretooluse-lock-regression.test.d.ts.map +1 -0
- package/dist/scripts/__tests__/issue-3486-pretooluse-lock-regression.test.js +65 -0
- package/dist/scripts/__tests__/issue-3486-pretooluse-lock-regression.test.js.map +1 -0
- package/dist/scripts/__tests__/issue-3536-external-team-state-root.test.d.ts +2 -0
- package/dist/scripts/__tests__/issue-3536-external-team-state-root.test.d.ts.map +1 -0
- package/dist/scripts/__tests__/issue-3536-external-team-state-root.test.js +189 -0
- package/dist/scripts/__tests__/issue-3536-external-team-state-root.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__/prompt-inventory.test.js +54 -1
- package/dist/scripts/__tests__/prompt-inventory.test.js.map +1 -1
- package/dist/scripts/__tests__/reasoning-artifact-contract.test.js +2 -2
- package/dist/scripts/__tests__/reasoning-artifact-contract.test.js.map +1 -1
- package/dist/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.js +46 -60
- 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 +168 -20
- 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 +51 -2
- package/dist/scripts/codex-native-hook.d.ts.map +1 -1
- package/dist/scripts/codex-native-hook.js +3648 -1332
- 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 +33 -22
- 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/auto-nudge.d.ts +2 -48
- package/dist/scripts/notify-hook/auto-nudge.d.ts.map +1 -1
- package/dist/scripts/notify-hook/auto-nudge.js +1 -0
- package/dist/scripts/notify-hook/auto-nudge.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-dispatch.d.ts.map +1 -1
- package/dist/scripts/notify-hook/team-dispatch.js +8 -5
- package/dist/scripts/notify-hook/team-dispatch.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-tmux-guard.d.ts +1 -1
- package/dist/scripts/notify-hook/team-tmux-guard.d.ts.map +1 -1
- package/dist/scripts/notify-hook/team-tmux-guard.js +3 -2
- package/dist/scripts/notify-hook/team-tmux-guard.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.d.ts.map +1 -1
- package/dist/scripts/notify-hook.js +71 -124
- 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/prompt-inventory.d.ts +39 -0
- package/dist/scripts/prompt-inventory.d.ts.map +1 -1
- package/dist/scripts/prompt-inventory.js +108 -3
- package/dist/scripts/prompt-inventory.js.map +1 -1
- package/dist/scripts/run-compiled-ci.js +2 -0
- package/dist/scripts/run-compiled-ci.js.map +1 -1
- package/dist/scripts/run-test-files.js +1 -0
- package/dist/scripts/run-test-files.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 +407 -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/sync-prompt-guidance-fragments.js +62 -20
- package/dist/scripts/sync-prompt-guidance-fragments.js.map +1 -1
- package/dist/scripts/tmux-hook-engine.d.ts +1 -1
- package/dist/scripts/tmux-hook-engine.d.ts.map +1 -1
- package/dist/scripts/tmux-hook-engine.js +3 -3
- package/dist/scripts/tmux-hook-engine.js.map +1 -1
- package/dist/scripts/verify-capabilities-lock.d.ts +3 -0
- package/dist/scripts/verify-capabilities-lock.d.ts.map +1 -0
- package/dist/scripts/verify-capabilities-lock.js +22 -0
- package/dist/scripts/verify-capabilities-lock.js.map +1 -0
- 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__/authority-decreasing.test.d.ts +2 -0
- package/dist/state/__tests__/authority-decreasing.test.d.ts.map +1 -0
- package/dist/state/__tests__/authority-decreasing.test.js +78 -0
- package/dist/state/__tests__/authority-decreasing.test.js.map +1 -0
- package/dist/state/__tests__/handoff-carrier.test.d.ts +2 -0
- package/dist/state/__tests__/handoff-carrier.test.d.ts.map +1 -0
- package/dist/state/__tests__/handoff-carrier.test.js +96 -0
- package/dist/state/__tests__/handoff-carrier.test.js.map +1 -0
- 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 +870 -2517
- package/dist/state/__tests__/operations.test.js.map +1 -1
- package/dist/state/__tests__/single-writer-invariant.test.d.ts +2 -0
- package/dist/state/__tests__/single-writer-invariant.test.d.ts.map +1 -0
- package/dist/state/__tests__/single-writer-invariant.test.js +342 -0
- package/dist/state/__tests__/single-writer-invariant.test.js.map +1 -0
- package/dist/state/__tests__/skill-active.test.js +400 -2
- package/dist/state/__tests__/skill-active.test.js.map +1 -1
- package/dist/state/__tests__/stale-state-neutralization.test.d.ts +2 -0
- package/dist/state/__tests__/stale-state-neutralization.test.d.ts.map +1 -0
- package/dist/state/__tests__/stale-state-neutralization.test.js +84 -0
- package/dist/state/__tests__/stale-state-neutralization.test.js.map +1 -0
- package/dist/state/__tests__/workflow-transition.test.js +20 -33
- package/dist/state/__tests__/workflow-transition.test.js.map +1 -1
- package/dist/state/handoff-carrier.d.ts +46 -0
- package/dist/state/handoff-carrier.d.ts.map +1 -0
- package/dist/state/handoff-carrier.js +85 -0
- package/dist/state/handoff-carrier.js.map +1 -0
- 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/namespace-owners.d.ts +34 -0
- package/dist/state/namespace-owners.d.ts.map +1 -0
- package/dist/state/namespace-owners.js +127 -0
- package/dist/state/namespace-owners.js.map +1 -0
- package/dist/state/operations.d.ts +9 -4
- package/dist/state/operations.d.ts.map +1 -1
- package/dist/state/operations.js +194 -235
- 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 +421 -31
- 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 +11 -31
- package/dist/state/workflow-transition-reconcile.js.map +1 -1
- package/dist/state/workflow-transition.d.ts +1 -27
- package/dist/state/workflow-transition.d.ts.map +1 -1
- package/dist/state/workflow-transition.js +9 -790
- 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__/api-interop.test.js +1 -0
- package/dist/team/__tests__/api-interop.test.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 +269 -245
- 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__/team-ops-contract.test.js +1 -0
- package/dist/team/__tests__/team-ops-contract.test.js.map +1 -1
- package/dist/team/__tests__/tmux-session.test.js +1093 -101
- 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 +159 -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-bootstrap.test.js +11 -13
- package/dist/team/__tests__/worker-bootstrap.test.js.map +1 -1
- package/dist/team/__tests__/worker-provenance.test.d.ts +2 -0
- package/dist/team/__tests__/worker-provenance.test.d.ts.map +1 -0
- package/dist/team/__tests__/worker-provenance.test.js +251 -0
- package/dist/team/__tests__/worker-provenance.test.js.map +1 -0
- 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/api-interop.d.ts.map +1 -1
- package/dist/team/api-interop.js +14 -8
- package/dist/team/api-interop.js.map +1 -1
- package/dist/team/mcp-comm.d.ts.map +1 -1
- package/dist/team/mcp-comm.js +3 -8
- package/dist/team/mcp-comm.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 +19 -0
- package/dist/team/state.d.ts.map +1 -1
- package/dist/team/state.js +47 -1
- package/dist/team/state.js.map +1 -1
- package/dist/team/team-ops.d.ts +1 -0
- package/dist/team/team-ops.d.ts.map +1 -1
- package/dist/team/team-ops.js +1 -0
- package/dist/team/team-ops.js.map +1 -1
- package/dist/team/tmux-session.d.ts +42 -4
- package/dist/team/tmux-session.d.ts.map +1 -1
- package/dist/team/tmux-session.js +572 -196
- package/dist/team/tmux-session.js.map +1 -1
- package/dist/team/ultragoal-context.d.ts +1 -0
- package/dist/team/ultragoal-context.d.ts.map +1 -1
- package/dist/team/ultragoal-context.js +2 -0
- package/dist/team/ultragoal-context.js.map +1 -1
- package/dist/team/worker-provenance.d.ts +35 -0
- package/dist/team/worker-provenance.d.ts.map +1 -0
- package/dist/team/worker-provenance.js +213 -0
- package/dist/team/worker-provenance.js.map +1 -0
- package/dist/ultragoal/__tests__/artifacts.test.js +1053 -2
- package/dist/ultragoal/__tests__/artifacts.test.js.map +1 -1
- package/dist/ultragoal/__tests__/docs-contract.test.js +89 -59
- package/dist/ultragoal/__tests__/docs-contract.test.js.map +1 -1
- package/dist/ultragoal/artifacts.d.ts +36 -1
- package/dist/ultragoal/artifacts.d.ts.map +1 -1
- package/dist/ultragoal/artifacts.js +267 -51
- package/dist/ultragoal/artifacts.js.map +1 -1
- package/dist/url-reader/__tests__/url-reader.test.js +14 -0
- package/dist/url-reader/__tests__/url-reader.test.js.map +1 -1
- package/dist/url-reader/index.js +5 -3
- package/dist/url-reader/index.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/__tests__/version.test.js +17 -0
- package/dist/utils/__tests__/version.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/utils/version.d.ts.map +1 -1
- package/dist/utils/version.js +2 -1
- package/dist/utils/version.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 +17 -6
- 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/ai-slop-cleaner/SKILL.md +60 -135
- package/plugins/oh-my-codex/skills/analyze/SKILL.md +35 -111
- package/plugins/oh-my-codex/skills/autopilot/SKILL.md +72 -163
- package/plugins/oh-my-codex/skills/cancel/SKILL.md +33 -392
- package/plugins/oh-my-codex/skills/code-review/SKILL.md +66 -256
- package/plugins/oh-my-codex/skills/configure-notifications/SKILL.md +30 -216
- package/plugins/oh-my-codex/skills/deep-interview/SKILL.md +11 -10
- package/plugins/oh-my-codex/skills/design/SKILL.md +32 -132
- package/plugins/oh-my-codex/skills/doctor/SKILL.md +56 -177
- package/plugins/oh-my-codex/skills/omx-setup/SKILL.md +35 -116
- package/plugins/oh-my-codex/skills/plan/SKILL.md +45 -228
- package/plugins/oh-my-codex/skills/ralplan/SKILL.md +20 -22
- package/plugins/oh-my-codex/skills/skill/SKILL.md +82 -807
- package/plugins/oh-my-codex/skills/team/SKILL.md +32 -520
- package/plugins/oh-my-codex/skills/ultragoal/SKILL.md +47 -150
- package/plugins/oh-my-codex/skills/ultraqa/SKILL.md +75 -252
- package/plugins/oh-my-codex/skills/visual-ralph/SKILL.md +33 -111
- package/plugins/oh-my-codex/skills/worker/SKILL.md +40 -113
- package/prompts/analyst.md +27 -107
- package/prompts/api-reviewer.md +17 -84
- package/prompts/architect.md +31 -70
- package/prompts/code-reviewer.md +18 -107
- package/prompts/code-simplifier.md +32 -110
- package/prompts/critic.md +32 -54
- package/prompts/dependency-expert.md +37 -107
- package/prompts/designer.md +40 -107
- package/prompts/executor.md +26 -75
- package/prompts/information-architect.md +51 -200
- package/prompts/performance-reviewer.md +17 -81
- package/prompts/planner.md +33 -76
- package/prompts/product-analyst.md +65 -280
- package/prompts/product-manager.md +48 -216
- package/prompts/qa-tester.md +33 -104
- package/prompts/quality-reviewer.md +17 -93
- package/prompts/quality-strategist.md +31 -255
- package/prompts/researcher.md +32 -89
- package/prompts/security-reviewer.md +12 -98
- package/prompts/test-engineer.md +29 -110
- package/prompts/ux-researcher.md +46 -300
- package/prompts/verifier.md +23 -51
- package/skills/ai-slop-cleaner/SKILL.md +60 -135
- package/skills/analyze/SKILL.md +35 -111
- package/skills/autopilot/SKILL.md +72 -163
- package/skills/autoresearch-goal/SKILL.md +6 -31
- package/skills/cancel/SKILL.md +33 -392
- package/skills/code-review/SKILL.md +66 -256
- package/skills/configure-notifications/SKILL.md +30 -216
- package/skills/deep-interview/SKILL.md +11 -10
- package/skills/design/SKILL.md +32 -132
- package/skills/doctor/SKILL.md +56 -177
- package/skills/omx-setup/SKILL.md +35 -116
- package/skills/pipeline/SKILL.md +6 -89
- package/skills/plan/SKILL.md +45 -228
- package/skills/ralph/SKILL.md +6 -290
- package/skills/ralplan/SKILL.md +20 -22
- package/skills/skill/SKILL.md +82 -807
- package/skills/team/SKILL.md +32 -520
- package/skills/ultragoal/SKILL.md +47 -150
- package/skills/ultraqa/SKILL.md +75 -252
- package/skills/ultrawork/SKILL.md +6 -182
- package/skills/visual-ralph/SKILL.md +33 -111
- package/skills/worker/SKILL.md +40 -113
- package/src/scripts/__tests__/codex-native-hook.test.ts +18888 -28799
- package/src/scripts/__tests__/docs-site-contract.test.ts +2 -2
- package/src/scripts/__tests__/generated-artifact-drift.test.ts +97 -0
- package/src/scripts/__tests__/issue-3293-callsite-parity.test.ts +197 -0
- package/src/scripts/__tests__/issue-3293-cancel-transaction.test.ts +189 -0
- package/src/scripts/__tests__/issue-3293-hook-owned-cancel.test.ts +186 -0
- package/src/scripts/__tests__/issue-3311-ultragoal-native-outside-tmux.test.ts +177 -0
- package/src/scripts/__tests__/issue-3358-ultragoal-cancel-lifecycle.test.ts +201 -0
- package/src/scripts/__tests__/issue-3369-autopilot-ralplan-state-machine.test.ts +43 -0
- package/src/scripts/__tests__/issue-3481-conductor-deadlock.test.ts +77 -0
- package/src/scripts/__tests__/issue-3486-pretooluse-lock-regression.test.ts +95 -0
- package/src/scripts/__tests__/issue-3536-external-team-state-root.test.ts +216 -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__/prompt-inventory.test.ts +62 -1
- package/src/scripts/__tests__/reasoning-artifact-contract.test.ts +1 -2
- package/src/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.ts +49 -59
- package/src/scripts/__tests__/role-intent-durable-recovery-3181.test.ts +61 -27
- package/src/scripts/__tests__/smoke-packed-install.test.ts +223 -23
- package/src/scripts/__tests__/verify-native-release-assets.test.ts +240 -0
- package/src/scripts/codex-native-hook.ts +3618 -1383
- package/src/scripts/codex-native-pre-post.ts +50 -24
- package/src/scripts/generate-native-release-manifest.ts +83 -79
- package/src/scripts/notify-fallback-watcher.ts +38 -38
- package/src/scripts/notify-hook/auto-nudge.ts +1 -0
- package/src/scripts/notify-hook/state-io.ts +11 -0
- package/src/scripts/notify-hook/team-dispatch.ts +8 -5
- package/src/scripts/notify-hook/team-leader-nudge.ts +32 -4
- package/src/scripts/notify-hook/team-tmux-guard.ts +3 -1
- 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 +76 -124
- package/src/scripts/postinstall-advisory.ts +67 -0
- package/src/scripts/postinstall.ts +1 -1
- package/src/scripts/prompt-inventory.ts +142 -3
- package/src/scripts/run-compiled-ci.ts +2 -0
- package/src/scripts/run-test-files.ts +1 -0
- package/src/scripts/smoke-packed-install.ts +440 -59
- package/src/scripts/sync-plugin-mirror.ts +17 -1
- package/src/scripts/sync-prompt-guidance-fragments.ts +59 -22
- package/src/scripts/tmux-hook-engine.ts +3 -3
- package/src/scripts/verify-capabilities-lock.ts +23 -0
- package/src/scripts/verify-native-release-assets.ts +39 -31
- package/templates/AGENTS.md +45 -9
- package/templates/catalog-manifest.json +6 -146
- package/dist/autopilot/__tests__/deep-interview-gate.test.d.ts +0 -2
- package/dist/autopilot/__tests__/deep-interview-gate.test.d.ts.map +0 -1
- package/dist/autopilot/__tests__/deep-interview-gate.test.js +0 -215
- package/dist/autopilot/__tests__/deep-interview-gate.test.js.map +0 -1
- package/dist/autopilot/__tests__/ralplan-gate.test.d.ts +0 -2
- package/dist/autopilot/__tests__/ralplan-gate.test.d.ts.map +0 -1
- package/dist/autopilot/__tests__/ralplan-gate.test.js +0 -1361
- package/dist/autopilot/__tests__/ralplan-gate.test.js.map +0 -1
- package/dist/autopilot/deep-interview-gate.d.ts +0 -18
- package/dist/autopilot/deep-interview-gate.d.ts.map +0 -1
- package/dist/autopilot/deep-interview-gate.js +0 -396
- package/dist/autopilot/deep-interview-gate.js.map +0 -1
- package/dist/autopilot/ralplan-gate.d.ts +0 -18
- package/dist/autopilot/ralplan-gate.d.ts.map +0 -1
- package/dist/autopilot/ralplan-gate.js +0 -148
- package/dist/autopilot/ralplan-gate.js.map +0 -1
- package/dist/hooks/__tests__/prometheus-strict-contract.test.d.ts +0 -2
- package/dist/hooks/__tests__/prometheus-strict-contract.test.d.ts.map +0 -1
- package/dist/hooks/__tests__/prometheus-strict-contract.test.js +0 -320
- package/dist/hooks/__tests__/prometheus-strict-contract.test.js.map +0 -1
- package/dist/pipeline/__tests__/orchestrator.test.d.ts +0 -2
- package/dist/pipeline/__tests__/orchestrator.test.d.ts.map +0 -1
- package/dist/pipeline/__tests__/orchestrator.test.js +0 -770
- package/dist/pipeline/__tests__/orchestrator.test.js.map +0 -1
- package/dist/pipeline/__tests__/stages.test.d.ts +0 -2
- package/dist/pipeline/__tests__/stages.test.d.ts.map +0 -1
- package/dist/pipeline/__tests__/stages.test.js +0 -1778
- package/dist/pipeline/__tests__/stages.test.js.map +0 -1
- package/dist/pipeline/index.d.ts +0 -25
- package/dist/pipeline/index.d.ts.map +0 -1
- package/dist/pipeline/index.js +0 -17
- package/dist/pipeline/index.js.map +0 -1
- package/dist/pipeline/orchestrator.d.ts +0 -52
- package/dist/pipeline/orchestrator.d.ts.map +0 -1
- package/dist/pipeline/orchestrator.js +0 -399
- package/dist/pipeline/orchestrator.js.map +0 -1
- package/dist/pipeline/review-verdict.d.ts +0 -3
- package/dist/pipeline/review-verdict.d.ts.map +0 -1
- package/dist/pipeline/review-verdict.js +0 -14
- package/dist/pipeline/review-verdict.js.map +0 -1
- package/dist/pipeline/stages/code-review.d.ts +0 -35
- package/dist/pipeline/stages/code-review.d.ts.map +0 -1
- package/dist/pipeline/stages/code-review.js +0 -55
- package/dist/pipeline/stages/code-review.js.map +0 -1
- package/dist/pipeline/stages/deep-interview.d.ts +0 -15
- package/dist/pipeline/stages/deep-interview.d.ts.map +0 -1
- package/dist/pipeline/stages/deep-interview.js +0 -32
- package/dist/pipeline/stages/deep-interview.js.map +0 -1
- package/dist/pipeline/stages/ralph-verify.d.ts +0 -53
- package/dist/pipeline/stages/ralph-verify.d.ts.map +0 -1
- package/dist/pipeline/stages/ralph-verify.js +0 -89
- package/dist/pipeline/stages/ralph-verify.js.map +0 -1
- package/dist/pipeline/stages/ralplan.d.ts +0 -27
- package/dist/pipeline/stages/ralplan.d.ts.map +0 -1
- package/dist/pipeline/stages/ralplan.js +0 -152
- package/dist/pipeline/stages/ralplan.js.map +0 -1
- package/dist/pipeline/stages/team-exec.d.ts +0 -52
- package/dist/pipeline/stages/team-exec.d.ts.map +0 -1
- package/dist/pipeline/stages/team-exec.js +0 -266
- package/dist/pipeline/stages/team-exec.js.map +0 -1
- package/dist/pipeline/stages/ultragoal.d.ts +0 -23
- package/dist/pipeline/stages/ultragoal.d.ts.map +0 -1
- package/dist/pipeline/stages/ultragoal.js +0 -53
- package/dist/pipeline/stages/ultragoal.js.map +0 -1
- package/dist/pipeline/stages/ultraqa.d.ts +0 -33
- package/dist/pipeline/stages/ultraqa.d.ts.map +0 -1
- package/dist/pipeline/stages/ultraqa.js +0 -49
- package/dist/pipeline/stages/ultraqa.js.map +0 -1
- package/dist/pipeline/types.d.ts +0 -124
- package/dist/pipeline/types.d.ts.map +0 -1
- package/dist/pipeline/types.js +0 -8
- package/dist/pipeline/types.js.map +0 -1
- package/dist/ralplan/__tests__/consensus-gate.test.d.ts +0 -2
- package/dist/ralplan/__tests__/consensus-gate.test.d.ts.map +0 -1
- package/dist/ralplan/__tests__/consensus-gate.test.js +0 -1455
- package/dist/ralplan/__tests__/consensus-gate.test.js.map +0 -1
- package/dist/ralplan/consensus-gate.d.ts +0 -62
- package/dist/ralplan/consensus-gate.d.ts.map +0 -1
- package/dist/ralplan/consensus-gate.js +0 -760
- package/dist/ralplan/consensus-gate.js.map +0 -1
- package/dist/scripts/notify-hook/ralph-session-resume.d.ts +0 -25
- package/dist/scripts/notify-hook/ralph-session-resume.d.ts.map +0 -1
- package/dist/scripts/notify-hook/ralph-session-resume.js +0 -377
- package/dist/scripts/notify-hook/ralph-session-resume.js.map +0 -1
- package/dist/state/__tests__/planning-gate.test.d.ts +0 -2
- package/dist/state/__tests__/planning-gate.test.d.ts.map +0 -1
- package/dist/state/__tests__/planning-gate.test.js +0 -629
- package/dist/state/__tests__/planning-gate.test.js.map +0 -1
- package/plugins/oh-my-codex/skills/autoresearch-goal/SKILL.md +0 -39
- package/plugins/oh-my-codex/skills/pipeline/SKILL.md +0 -97
- package/plugins/oh-my-codex/skills/prometheus-strict/README.md +0 -35
- package/plugins/oh-my-codex/skills/prometheus-strict/SKILL.md +0 -219
- package/plugins/oh-my-codex/skills/ralph/SKILL.md +0 -298
- package/plugins/oh-my-codex/skills/ultrawork/SKILL.md +0 -190
- package/plugins/oh-my-codex/skills/ultrawork/references/agent-tiers.md +0 -62
- package/prompts/prometheus-strict-metis.md +0 -274
- package/prompts/prometheus-strict-momus.md +0 -82
- package/prompts/prometheus-strict-oracle.md +0 -107
- package/prompts/scholastic.md +0 -11
- package/prompts/sisyphus-lite.md +0 -111
- package/skills/ask-claude/SKILL.md +0 -12
- package/skills/ask-gemini/SKILL.md +0 -12
- package/skills/build-fix/SKILL.md +0 -10
- package/skills/deepsearch/SKILL.md +0 -10
- package/skills/ecomode/SKILL.md +0 -114
- package/skills/ecomode/references/agent-tiers.md +0 -61
- package/skills/frontend-ui-ux/SKILL.md +0 -16
- package/skills/help/SKILL.md +0 -10
- package/skills/note/SKILL.md +0 -10
- package/skills/prometheus-strict/README.md +0 -35
- package/skills/prometheus-strict/SKILL.md +0 -219
- package/skills/ralph-init/SKILL.md +0 -10
- package/skills/review/SKILL.md +0 -10
- package/skills/security-review/SKILL.md +0 -10
- package/skills/swarm/SKILL.md +0 -12
- package/skills/tdd/SKILL.md +0 -104
- package/skills/trace/SKILL.md +0 -10
- package/skills/visual-verdict/SKILL.md +0 -10
- package/skills/web-clone/SKILL.md +0 -357
- package/src/scripts/notify-hook/ralph-session-resume.ts +0 -466
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import { describe, it } from 'node:test';
|
|
2
2
|
import assert from 'node:assert/strict';
|
|
3
|
-
import { existsSync } from 'node:fs';
|
|
4
|
-
import { chmod, mkdir, mkdtemp, readFile, readdir, rm, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { chmod, mkdir, mkdtemp, readFile, readdir, rename, rm, writeFile } from 'node:fs/promises';
|
|
5
5
|
import { execFileSync, spawn, spawnSync } from 'node:child_process';
|
|
6
6
|
import { tmpdir } from 'node:os';
|
|
7
7
|
import { basename, dirname, join } from 'node:path';
|
|
8
8
|
import { fileURLToPath } from 'node:url';
|
|
9
|
-
import { HUD_TMUX_HEIGHT_LINES } from '../../hud/constants.js';
|
|
10
|
-
import { DETACHED_TMUX_HISTORY_LIMIT } from '../index.js';
|
|
11
|
-
import {
|
|
9
|
+
import { HUD_RESIZE_RECONCILE_DELAY_SECONDS, HUD_TMUX_HEIGHT_LINES } from '../../hud/constants.js';
|
|
10
|
+
import { DETACHED_LAUNCH_CONTROL_PLANE_KEYS, DETACHED_TMUX_HISTORY_LIMIT, buildMadmaxDetachedLaunchContextKey } from '../index.js';
|
|
11
|
+
import { closeLaunchSessionBindingOnce, establishLaunchSessionBinding, finalizeBoundOnce, writeSessionStart, } from '../../hooks/session.js';
|
|
12
|
+
import { buildPtyScriptCommand, isRealScriptAvailable, isRealTmuxAvailable, withTempTmuxSession } from '../../team/__tests__/tmux-test-fixture.js';
|
|
12
13
|
const CLI_SPAWN_TIMEOUT_MS = 60_000;
|
|
14
|
+
const DARWIN_PTY_FINALIZATION_POLL_LIMIT = 600;
|
|
13
15
|
function buildRunOmxEnv(envOverrides) {
|
|
14
16
|
const env = { ...process.env };
|
|
15
17
|
for (const key of Object.keys(env)) {
|
|
@@ -51,8 +53,10 @@ function normalizeDarwinTmpPath(value) {
|
|
|
51
53
|
function escapeRegExp(value) {
|
|
52
54
|
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
53
55
|
}
|
|
54
|
-
function shouldSkipForSpawnPermissions(
|
|
55
|
-
|
|
56
|
+
function shouldSkipForSpawnPermissions(_err) {
|
|
57
|
+
// These integration fixtures are contractual: EPERM/EACCES is a failure,
|
|
58
|
+
// not a pass. Node's test runner has no portable dynamic skip here.
|
|
59
|
+
return false;
|
|
56
60
|
}
|
|
57
61
|
async function createGitRepo(wd) {
|
|
58
62
|
const repo = join(wd, 'repo');
|
|
@@ -69,17 +73,203 @@ async function writeExecutable(path, content) {
|
|
|
69
73
|
await writeFile(path, content);
|
|
70
74
|
await chmod(path, 0o755);
|
|
71
75
|
}
|
|
76
|
+
async function wrapFakeTmuxWithDetachedLeader(fakeTmuxPath) {
|
|
77
|
+
const implementationPath = `${fakeTmuxPath}.impl`;
|
|
78
|
+
await rename(fakeTmuxPath, implementationPath);
|
|
79
|
+
await writeExecutable(fakeTmuxPath, `#!/bin/sh
|
|
80
|
+
if [ "$1" = "display-message" ] && [ "$2" = "-p" ] && [ "$3" = "-t" ] && case "$5" in *'#{session_created}'*'#{window_id}'*'#{pane_pid}'*) true;; *) false;; esac; then
|
|
81
|
+
session=$(cat /tmp/omx-test-detached-session-name 2>/dev/null || printf omx-test)
|
|
82
|
+
leader_pid=$(cat "$0.detached-leader-pid" 2>/dev/null || printf 123)
|
|
83
|
+
printf '%s\t$1\t1\t0\t@1\t%s\t%s\n' "$session" "$4" "$leader_pid"
|
|
84
|
+
exit 0
|
|
85
|
+
fi
|
|
86
|
+
if [ "$1" = "set-option" ] && [ "$2" = "-t" ] && [ "$4" = "@omx_instance_id" ]; then
|
|
87
|
+
printf '%s' "$5" > /tmp/omx-test-detached-owner-id
|
|
88
|
+
fi
|
|
89
|
+
if [ "$1" = "display-message" ] && [ "$2" = "-p" ] && [ "$3" = "-t" ] && case "$5" in *'#{pane_dead}'*'#{@omx_instance_id}'*) true;; *) false;; esac; then
|
|
90
|
+
session=$(cat /tmp/omx-test-detached-session-name 2>/dev/null || printf omx-test)
|
|
91
|
+
leader_pid=$(cat "$0.detached-leader-pid" 2>/dev/null || printf 123)
|
|
92
|
+
pane=$(cat "$0.detached-leader-pane" 2>/dev/null || printf '%%12')
|
|
93
|
+
if [ "\${OMX_TEST_DETACHED_OWNER_VISIBILITY_RACE:-}" = 'persistent' ]; then owner='foreign-owner'; else owner=$(cat /tmp/omx-test-detached-owner-id 2>/dev/null || printf ''); fi
|
|
94
|
+
printf '0\t%s\t%s\t%s\t$1\t1\t@1\t%s\n' "$pane" "$leader_pid" "$session" "$owner"
|
|
95
|
+
exit 0
|
|
96
|
+
fi
|
|
97
|
+
if [ "$1" = "list-panes" ] && case "$*" in *'#{session_created}'*'#{@omx_instance_id}'*) true;; *) false;; esac; then
|
|
98
|
+
${JSON.stringify(implementationPath)} "$@" >/dev/null 2>&1 || true
|
|
99
|
+
session=$(cat /tmp/omx-test-detached-session-name 2>/dev/null || printf omx-test)
|
|
100
|
+
owner=$(cat /tmp/omx-test-detached-owner-id 2>/dev/null || printf '')
|
|
101
|
+
leader_pid=$(cat "$0.detached-leader-pid" 2>/dev/null || printf 123)
|
|
102
|
+
pane=$(cat "$0.detached-leader-pane" 2>/dev/null || printf '%%12')
|
|
103
|
+
printf '%s\t0\t%s\t%s\t$1\t1\t%s\n' "$pane" "$leader_pid" "$session" "$owner"
|
|
104
|
+
exit 0
|
|
105
|
+
fi
|
|
106
|
+
if [ "$1" = "if-shell" ] && [ "$2" = "-F" ] && [ "$3" = "-t" ]; then
|
|
107
|
+
if [ -f "$0.detached-recycled" ] && [ "$4" = "%99" ]; then ${JSON.stringify(implementationPath)} __nested_hud_guard_denied__ %99 999 '$2' @2; exit 0; fi
|
|
108
|
+
${JSON.stringify(implementationPath)} "$@" >/dev/null 2>&1 || true
|
|
109
|
+
success="$6"
|
|
110
|
+
receipt=$(printf '%s' "$success" | sed -n 's/.*\\(omx_detached_[A-Za-z0-9-]*\\).*/\\1/p')
|
|
111
|
+
case "\${OMX_TEST_DETACHED_RECYCLE:-}" in
|
|
112
|
+
split) case "$success" in *split-window*) exit 0;; esac ;;
|
|
113
|
+
finalization) case "$*" in
|
|
114
|
+
*split-window*) : > "$0.detached-split-seen" ;;
|
|
115
|
+
*"if-shell -F -t '%99'"*) if [ -f "$0.detached-split-seen" ]; then
|
|
116
|
+
${JSON.stringify(implementationPath)} __recycled__ %99 999 '$2' @2
|
|
117
|
+
: > "$0.detached-recycled"
|
|
118
|
+
eval "set -- $success"
|
|
119
|
+
"$0" "$@" >/dev/null
|
|
120
|
+
fi ;;
|
|
121
|
+
esac ;;
|
|
122
|
+
esac
|
|
123
|
+
[ -n "$receipt" ] || exit 1
|
|
124
|
+
case "$success" in
|
|
125
|
+
*split-window*) printf '%%99\t456\t$1\t@1\t%s\n' "$receipt" ;;
|
|
126
|
+
*) printf '%s\n' "$receipt" ;;
|
|
127
|
+
esac
|
|
128
|
+
exit 0
|
|
129
|
+
fi
|
|
130
|
+
if [ "$1" = "list-panes" ] && case "$*" in *'#{pane_id}'*'#{pane_dead}'*'#{pane_pid}'*) true;; *) false;; esac; then
|
|
131
|
+
leader_pid=$(cat "$0.detached-leader-pid" 2>/dev/null || printf '')
|
|
132
|
+
[ -n "$leader_pid" ] || exit 1
|
|
133
|
+
pane=$(cat "$0.detached-leader-pane" 2>/dev/null || printf '%%12')
|
|
134
|
+
printf '%s\t0\t%s\n' "$pane" "$leader_pid"
|
|
135
|
+
exit 0
|
|
136
|
+
fi
|
|
137
|
+
output=$(${JSON.stringify(implementationPath)} "$@")
|
|
138
|
+
status=$?
|
|
139
|
+
printf '%s' "$output"
|
|
140
|
+
if [ "$status" -eq 0 ] && [ "$1" = "new-session" ]; then
|
|
141
|
+
previous=''
|
|
142
|
+
for arg in "$@"; do
|
|
143
|
+
if [ "$previous" = '-s' ]; then printf '%s' "$arg" > /tmp/omx-test-detached-session-name; break; fi
|
|
144
|
+
previous="$arg"
|
|
145
|
+
done
|
|
146
|
+
for last_arg do :; done
|
|
147
|
+
pane=$(printf '%s' "$output" | sed -n '1p')
|
|
148
|
+
TMUX=/tmp/omx-test-tmux,1,0 TMUX_PANE="$pane" nohup /bin/sh -c 'eval "exec $1"' sh "$last_arg" </dev/null >/tmp/omx-test-detached-leader.log 2>&1 &
|
|
149
|
+
leader_pid=$!
|
|
150
|
+
printf '%s' "$leader_pid" > "$0.detached-leader-pid"
|
|
151
|
+
printf '%s' "$pane" > "$0.detached-leader-pane"
|
|
152
|
+
(while kill -0 "$leader_pid" 2>/dev/null; do sleep 0.02; done; printf done > ${JSON.stringify(`${fakeTmuxPath}.leader-done`)}) </dev/null >/dev/null 2>&1 &
|
|
153
|
+
fi
|
|
154
|
+
exit "$status"
|
|
155
|
+
`);
|
|
156
|
+
}
|
|
72
157
|
async function createLaunchFixture(wd, tmuxScript) {
|
|
73
158
|
const home = join(wd, 'home');
|
|
74
159
|
const fakeBin = join(wd, 'bin');
|
|
75
160
|
const tmuxLogPath = join(wd, 'tmux.log');
|
|
161
|
+
const leaderDonePath = join(wd, 'leader-done');
|
|
76
162
|
await mkdir(home, { recursive: true });
|
|
77
163
|
await mkdir(fakeBin, { recursive: true });
|
|
78
|
-
await writeExecutable(join(fakeBin, 'codex'), '#!/bin/sh\nprintf \'fake-codex:%s\\n\' "$*"\n');
|
|
164
|
+
await writeExecutable(join(fakeBin, 'codex'), '#!/bin/sh\nprintf \'fake-codex:%s\\n\' "$*"\nsleep 1\n');
|
|
79
165
|
await writeExecutable(join(fakeBin, 'ps'), '#!/bin/sh\nexit 0\n');
|
|
80
|
-
|
|
166
|
+
const tmuxImpl = join(fakeBin, 'tmux-impl');
|
|
167
|
+
await writeExecutable(tmuxImpl, tmuxScript(tmuxLogPath));
|
|
168
|
+
await writeExecutable(join(fakeBin, 'tmux'), `#!/bin/sh
|
|
169
|
+
if [ "$1" = "display-message" ] && [ "$2" = "-p" ] && [ "$3" = "-t" ] && case "$5" in *'#{session_created}'*'#{window_id}'*'#{pane_pid}'*) true;; *) false;; esac; then
|
|
170
|
+
session=$(cat /tmp/omx-test-detached-session-name 2>/dev/null || printf omx-test)
|
|
171
|
+
leader_pid=$(cat "$0.detached-leader-pid" 2>/dev/null || printf 123)
|
|
172
|
+
printf '%s\t$1\t1\t0\t@1\t%s\t%s\n' "$session" "$4" "$leader_pid"
|
|
173
|
+
exit 0
|
|
174
|
+
fi
|
|
175
|
+
if [ "$1" = "set-option" ] && [ "$2" = "-t" ] && [ "$4" = "@omx_instance_id" ]; then
|
|
176
|
+
printf '%s' "$5" > /tmp/omx-test-detached-owner-id
|
|
177
|
+
fi
|
|
178
|
+
if [ "$1" = "display-message" ] && [ "$2" = "-p" ] && [ "$3" = "-t" ] && case "$5" in *'#{pane_dead}'*'#{@omx_instance_id}'*) true;; *) false;; esac; then
|
|
179
|
+
session=$(cat /tmp/omx-test-detached-session-name 2>/dev/null || printf omx-test)
|
|
180
|
+
leader_pid=$(cat "$0.detached-leader-pid" 2>/dev/null || printf 123)
|
|
181
|
+
pane=$(cat "$0.detached-leader-pane" 2>/dev/null || printf '%%12')
|
|
182
|
+
if [ "\${OMX_TEST_DETACHED_OWNER_VISIBILITY_RACE:-}" = 'persistent' ]; then owner='foreign-owner'; else owner=$(cat /tmp/omx-test-detached-owner-id 2>/dev/null || printf ''); fi
|
|
183
|
+
printf '0\t%s\t%s\t%s\t$1\t1\t@1\t%s\n' "$pane" "$leader_pid" "$session" "$owner"
|
|
184
|
+
exit 0
|
|
185
|
+
fi
|
|
186
|
+
if [ "$1" = "list-panes" ] && case "$*" in *'#{session_created}'*'#{@omx_instance_id}'*) true;; *) false;; esac; then
|
|
187
|
+
${JSON.stringify(tmuxImpl)} "$@" >/dev/null 2>&1 || true
|
|
188
|
+
session=$(cat /tmp/omx-test-detached-session-name 2>/dev/null || printf omx-test)
|
|
189
|
+
owner=$(cat /tmp/omx-test-detached-owner-id 2>/dev/null || printf '')
|
|
190
|
+
leader_pid=$(cat "$0.detached-leader-pid" 2>/dev/null || printf 123)
|
|
191
|
+
pane=$(cat "$0.detached-leader-pane" 2>/dev/null || printf '%%12')
|
|
192
|
+
printf '%s\t0\t%s\t%s\t$1\t1\t%s\n' "$pane" "$leader_pid" "$session" "$owner"
|
|
193
|
+
exit 0
|
|
194
|
+
fi
|
|
195
|
+
if [ "$1" = "list-panes" ] && case "$*" in *'#{pane_id}'*'#{pane_dead}'*'#{pane_pid}'*) true;; *) false;; esac; then
|
|
196
|
+
leader_pid=$(cat "$0.detached-leader-pid" 2>/dev/null || printf '')
|
|
197
|
+
[ -n "$leader_pid" ] || exit 1
|
|
198
|
+
pane=$(cat "$0.detached-leader-pane" 2>/dev/null || printf '%%12')
|
|
199
|
+
printf '%s\t0\t%s\n' "$pane" "$leader_pid"
|
|
200
|
+
exit 0
|
|
201
|
+
fi
|
|
202
|
+
if [ "$1" = "if-shell" ] && [ "$2" = "-F" ] && [ "$3" = "-t" ]; then
|
|
203
|
+
if [ -f "$0.detached-recycled" ] && [ "$4" = "%99" ]; then ${JSON.stringify(tmuxImpl)} __nested_hud_guard_denied__ %99 999 '$2' @2; exit 0; fi
|
|
204
|
+
${JSON.stringify(tmuxImpl)} "$@" >/dev/null 2>&1 || true
|
|
205
|
+
success="$6"
|
|
206
|
+
receipt=$(printf '%s' "$success" | sed -n 's/.*\\(omx_detached_[A-Za-z0-9-]*\\).*/\\1/p')
|
|
207
|
+
if [ -n "\${OMX_TEST_DETACHED_OWNER_VISIBILITY_RACE:-}" ] && case "$success" in *"@omx_instance_id"*) true;; *) false;; esac; then
|
|
208
|
+
owner=$(printf '%s' "$success" | sed -n "s/.*@omx_instance_id '\\([^']*\\)'.*/\\1/p")
|
|
209
|
+
[ -n "$owner" ] && printf '%s' "$owner" > /tmp/omx-test-detached-owner-id
|
|
210
|
+
if [ "\${OMX_TEST_DETACHED_OWNER_VISIBILITY_RACE:-}" = 'persistent' ]; then printf foreign-owner > /tmp/omx-test-detached-owner-id; fi
|
|
211
|
+
fi
|
|
212
|
+
if [ -n "\${OMX_TEST_DETACHED_OWNER_VISIBILITY_RACE:-}" ] && case "$5" in *'@omx_instance_id,'*) true;; *) false;; esac; then
|
|
213
|
+
expected_owner=$(printf '%s' "$5" | sed -n 's/.*@omx_instance_id,\\([^}]*\\).*/\\1/p')
|
|
214
|
+
expected_session=$(printf '%s' "$5" | sed -n 's/.*session_name,\\([^}]*\\).*/\\1/p')
|
|
215
|
+
owner=$(cat /tmp/omx-test-detached-owner-id 2>/dev/null || printf '')
|
|
216
|
+
session=$(cat /tmp/omx-test-detached-session-name 2>/dev/null || printf '')
|
|
217
|
+
[ "$owner" = "$expected_owner" ] && [ "$session" = "$expected_session" ] || exit 0
|
|
218
|
+
fi
|
|
219
|
+
if [ "\${OMX_TEST_DETACHED_OWNER_VISIBILITY_RACE:-}" = 'once' ] && [ ! -f "$0.detached-owner-visibility-race" ]; then
|
|
220
|
+
case "$success" in
|
|
221
|
+
*"set-option"*"history-limit"*) : > "$0.detached-owner-visibility-race"; exit 0 ;;
|
|
222
|
+
esac
|
|
223
|
+
fi
|
|
224
|
+
if [ "\${OMX_TEST_DETACHED_OWNER_VISIBILITY_RACE:-}" = 'persistent' ] && case "$5" in *'@omx_instance_id'*) true;; *) false;; esac; then
|
|
225
|
+
exit 0
|
|
226
|
+
fi
|
|
227
|
+
case "\${OMX_TEST_DETACHED_RECYCLE:-}" in
|
|
228
|
+
split) case "$success" in *split-window*) exit 0;; esac ;;
|
|
229
|
+
finalization) case "$*" in
|
|
230
|
+
*split-window*) : > "$0.detached-split-seen" ;;
|
|
231
|
+
*"if-shell -F -t '%99'"*) if [ -f "$0.detached-split-seen" ]; then
|
|
232
|
+
${JSON.stringify(tmuxImpl)} __recycled__ %99 999 '$2' @2
|
|
233
|
+
: > "$0.detached-recycled"
|
|
234
|
+
eval "set -- $success"
|
|
235
|
+
"$0" "$@" >/dev/null
|
|
236
|
+
fi ;;
|
|
237
|
+
esac ;;
|
|
238
|
+
esac
|
|
239
|
+
[ -n "$receipt" ] || exit 1
|
|
240
|
+
case "$success" in
|
|
241
|
+
*split-window*) printf '%%99\t456\t$1\t@1\t%s\n' "$receipt" ;;
|
|
242
|
+
*) printf '%s\n' "$receipt" ;;
|
|
243
|
+
esac
|
|
244
|
+
exit 0
|
|
245
|
+
fi
|
|
246
|
+
output=$(${JSON.stringify(tmuxImpl)} "$@")
|
|
247
|
+
status=$?
|
|
248
|
+
printf '%s' "$output"
|
|
249
|
+
if [ "$status" -eq 0 ] && [ "$1" = "new-session" ]; then
|
|
250
|
+
previous=''
|
|
251
|
+
for arg in "$@"; do
|
|
252
|
+
if [ "$previous" = '-s' ]; then printf '%s' "$arg" > /tmp/omx-test-detached-session-name; break; fi
|
|
253
|
+
previous="$arg"
|
|
254
|
+
done
|
|
255
|
+
for last_arg do :; done
|
|
256
|
+
previous=''
|
|
257
|
+
for arg in "$@"; do
|
|
258
|
+
if [ "$previous" = '-e' ]; then export "$arg"; fi
|
|
259
|
+
previous="$arg"
|
|
260
|
+
done
|
|
261
|
+
pane=$(printf '%s' "$output" | sed -n '1p')
|
|
262
|
+
TMUX=/tmp/omx-test-tmux,1,0 TMUX_PANE="$pane" nohup /bin/sh -c 'eval "exec $1"' sh "$last_arg" </dev/null >/tmp/omx-test-detached-leader.log 2>&1 &
|
|
263
|
+
leader_pid=$!
|
|
264
|
+
printf '%s' "$leader_pid" > "$0.detached-leader-pid"
|
|
265
|
+
printf '%s' "$pane" > "$0.detached-leader-pane"
|
|
266
|
+
(while kill -0 "$leader_pid" 2>/dev/null; do sleep 0.02; done; printf done > ${JSON.stringify(join(wd, 'leader-done'))}) </dev/null >/dev/null 2>&1 &
|
|
267
|
+
fi
|
|
268
|
+
exit "$status"
|
|
269
|
+
`);
|
|
81
270
|
return {
|
|
82
271
|
tmuxLogPath,
|
|
272
|
+
leaderDonePath,
|
|
83
273
|
env: {
|
|
84
274
|
HOME: home,
|
|
85
275
|
PATH: `${fakeBin}:/usr/bin:/bin`,
|
|
@@ -103,8 +293,9 @@ function startHeldOmx(cwd, envOverrides) {
|
|
|
103
293
|
stdio: 'inherit',
|
|
104
294
|
});
|
|
105
295
|
}
|
|
106
|
-
async function waitForPath(path, expectedLines = 1) {
|
|
107
|
-
|
|
296
|
+
async function waitForPath(path, expectedLines = 1, timeoutMs = 6_000) {
|
|
297
|
+
const deadline = Date.now() + timeoutMs;
|
|
298
|
+
while (Date.now() < deadline) {
|
|
108
299
|
if (existsSync(path)) {
|
|
109
300
|
const contents = await readFile(path, 'utf-8').catch(() => '');
|
|
110
301
|
if (contents.trim().split('\n').filter(Boolean).length >= expectedLines)
|
|
@@ -114,6 +305,29 @@ async function waitForPath(path, expectedLines = 1) {
|
|
|
114
305
|
}
|
|
115
306
|
throw new Error(`timed out waiting for ${path}`);
|
|
116
307
|
}
|
|
308
|
+
function parseShimTmuxArgv(contents) {
|
|
309
|
+
return contents
|
|
310
|
+
.split('tmux argv:\n')
|
|
311
|
+
.slice(1)
|
|
312
|
+
.map((record) => record.split('\nend tmux argv')[0].split('\n').filter(Boolean));
|
|
313
|
+
}
|
|
314
|
+
async function assertShimLogQuiescent(path, quietPeriodMs, message) {
|
|
315
|
+
const before = await readFile(path, 'utf-8').catch(() => '');
|
|
316
|
+
await new Promise((resolve) => setTimeout(resolve, quietPeriodMs));
|
|
317
|
+
assert.equal(await readFile(path, 'utf-8').catch(() => ''), before, message);
|
|
318
|
+
}
|
|
319
|
+
async function waitForServerLogSubstring(readServerLog, offset, needle, timeoutMs = 3_000, intervalMs = 25) {
|
|
320
|
+
const deadline = Date.now() + timeoutMs;
|
|
321
|
+
let lastWindow = '';
|
|
322
|
+
while (Date.now() <= deadline) {
|
|
323
|
+
const log = await readServerLog();
|
|
324
|
+
lastWindow = log.slice(offset);
|
|
325
|
+
if (lastWindow.includes(needle))
|
|
326
|
+
return lastWindow;
|
|
327
|
+
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
328
|
+
}
|
|
329
|
+
throw new Error(`timed out after ${timeoutMs}ms waiting for ${JSON.stringify(needle)} in private tmux server log; tail=${JSON.stringify(lastWindow.slice(-2_000))}`);
|
|
330
|
+
}
|
|
117
331
|
async function stopHeldOmx(child, releasePath) {
|
|
118
332
|
await rm(releasePath, { force: true });
|
|
119
333
|
await new Promise((resolve, reject) => {
|
|
@@ -150,6 +364,24 @@ while [ -f "${releasePath}" ]; do sleep 1; done
|
|
|
150
364
|
},
|
|
151
365
|
};
|
|
152
366
|
}
|
|
367
|
+
function skipUnlessPrivateRealTmux(t) {
|
|
368
|
+
if (isRealTmuxAvailable())
|
|
369
|
+
return true;
|
|
370
|
+
assert.equal(process.env.CI, undefined, 'CI must provide tmux for the private-server detached launch regression');
|
|
371
|
+
t.skip('tmux is not installed');
|
|
372
|
+
return false;
|
|
373
|
+
}
|
|
374
|
+
function skipUnlessScriptAvailable(t) {
|
|
375
|
+
if (process.platform === 'win32') {
|
|
376
|
+
t.skip('PTY launch harness requires posix script(1)');
|
|
377
|
+
return false;
|
|
378
|
+
}
|
|
379
|
+
if (isRealScriptAvailable())
|
|
380
|
+
return true;
|
|
381
|
+
assert.equal(process.env.CI, undefined, 'CI must provide script(1) for the PTY launch regression');
|
|
382
|
+
t.skip('script(1) is not installed');
|
|
383
|
+
return false;
|
|
384
|
+
}
|
|
153
385
|
describe('omx launch fallback when tmux is unavailable', () => {
|
|
154
386
|
it('surfaces direct Codex startup stderr and preserves the child exit code', async () => {
|
|
155
387
|
const wd = await mkdtemp(join(tmpdir(), 'omx-launch-child-error-'));
|
|
@@ -179,7 +411,7 @@ exit 42
|
|
|
179
411
|
assert.match(result.stderr, /\[omx\] codex exited with code 42/);
|
|
180
412
|
}
|
|
181
413
|
finally {
|
|
182
|
-
await rm(wd, { recursive: true, force: true });
|
|
414
|
+
await rm(wd, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 });
|
|
183
415
|
}
|
|
184
416
|
});
|
|
185
417
|
it('reports a missing Codex executable instead of exiting silently', async () => {
|
|
@@ -206,7 +438,7 @@ exit 42
|
|
|
206
438
|
assert.notEqual(result.stderr.trim(), '');
|
|
207
439
|
}
|
|
208
440
|
finally {
|
|
209
|
-
await rm(wd, { recursive: true, force: true });
|
|
441
|
+
await rm(wd, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 });
|
|
210
442
|
}
|
|
211
443
|
});
|
|
212
444
|
it('launches codex directly without tmux ENOENT noise', async () => {
|
|
@@ -237,7 +469,7 @@ exit 42
|
|
|
237
469
|
assert.doesNotMatch(result.stderr, /spawnSync tmux ENOENT/);
|
|
238
470
|
}
|
|
239
471
|
finally {
|
|
240
|
-
await rm(wd, { recursive: true, force: true });
|
|
472
|
+
await rm(wd, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 });
|
|
241
473
|
}
|
|
242
474
|
});
|
|
243
475
|
it('passes literal max and ultra after -- with raw config args to Codex launch unchanged', async () => {
|
|
@@ -314,7 +546,7 @@ exit 42
|
|
|
314
546
|
]);
|
|
315
547
|
}
|
|
316
548
|
finally {
|
|
317
|
-
await rm(wd, { recursive: true, force: true });
|
|
549
|
+
await rm(wd, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 });
|
|
318
550
|
}
|
|
319
551
|
});
|
|
320
552
|
it('treats image values as variadic when detecting resume launches', async () => {
|
|
@@ -370,17 +602,22 @@ exec "$NODE_BINARY" -e 'const fs = require("node:fs"); const path = require("nod
|
|
|
370
602
|
}
|
|
371
603
|
}
|
|
372
604
|
finally {
|
|
373
|
-
await rm(wd, { recursive: true, force: true });
|
|
605
|
+
await rm(wd, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 });
|
|
374
606
|
}
|
|
375
607
|
});
|
|
376
608
|
});
|
|
377
609
|
describe('ordinary launch root collision guidance', () => {
|
|
378
610
|
it('keeps the cwd default for the first launch and fails the second and third launches closed with explicit-root guidance', async () => {
|
|
379
611
|
const wd = await mkdtemp(join(tmpdir(), 'omx-launch-root-conflict-'));
|
|
612
|
+
let binding;
|
|
380
613
|
try {
|
|
381
614
|
execFileSync('git', ['init'], { cwd: wd, stdio: 'ignore' });
|
|
382
615
|
const fixture = await createHeldCodexFixture(wd);
|
|
383
|
-
await
|
|
616
|
+
const established = await establishLaunchSessionBinding(wd, 'first-standard-launch', { pid: process.pid });
|
|
617
|
+
assert.equal(established.kind, 'committed-released');
|
|
618
|
+
if (established.kind !== 'committed-released')
|
|
619
|
+
return;
|
|
620
|
+
binding = established.binding;
|
|
384
621
|
const second = runOmx(wd, ['--direct', '--version'], fixture.env);
|
|
385
622
|
const third = runOmx(wd, ['--direct', '--version'], fixture.env);
|
|
386
623
|
if (shouldSkipForSpawnPermissions(second.error) || shouldSkipForSpawnPermissions(third.error))
|
|
@@ -400,10 +637,12 @@ describe('ordinary launch root collision guidance', () => {
|
|
|
400
637
|
assert.match(resume.stderr, /session_pointer_owner_conflict/);
|
|
401
638
|
assert.doesNotMatch(resume.stderr, /concurrent conversations in this checkout require distinct user-specified OMX_ROOT values/);
|
|
402
639
|
}
|
|
403
|
-
await
|
|
640
|
+
await finalizeBoundOnce(binding, 'test');
|
|
404
641
|
}
|
|
405
642
|
finally {
|
|
406
|
-
|
|
643
|
+
if (binding)
|
|
644
|
+
await closeLaunchSessionBindingOnce(binding).catch(() => { });
|
|
645
|
+
await rm(wd, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 });
|
|
407
646
|
}
|
|
408
647
|
});
|
|
409
648
|
it('keeps explicit roots literal: distinct roots launch independently while a shared root remains fatal without reroute guidance', async () => {
|
|
@@ -438,11 +677,13 @@ describe('ordinary launch root collision guidance', () => {
|
|
|
438
677
|
first.kill('SIGKILL');
|
|
439
678
|
if (second)
|
|
440
679
|
second.kill('SIGKILL');
|
|
441
|
-
await rm(wd, { recursive: true, force: true });
|
|
680
|
+
await rm(wd, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 });
|
|
442
681
|
}
|
|
443
682
|
});
|
|
444
|
-
it('keeps different checkout defaults independent and
|
|
683
|
+
it('keeps different checkout defaults independent and fails closed on stale default-pointer evidence', async () => {
|
|
445
684
|
const wd = await mkdtemp(join(tmpdir(), 'omx-launch-root-stale-'));
|
|
685
|
+
let firstBinding;
|
|
686
|
+
let secondBinding;
|
|
446
687
|
try {
|
|
447
688
|
const firstCheckout = join(wd, 'first-checkout');
|
|
448
689
|
const secondCheckout = join(wd, 'second-checkout');
|
|
@@ -450,23 +691,34 @@ describe('ordinary launch root collision guidance', () => {
|
|
|
450
691
|
await mkdir(secondCheckout, { recursive: true });
|
|
451
692
|
execFileSync('git', ['init'], { cwd: firstCheckout, stdio: 'ignore' });
|
|
452
693
|
execFileSync('git', ['init'], { cwd: secondCheckout, stdio: 'ignore' });
|
|
453
|
-
await
|
|
454
|
-
await
|
|
694
|
+
const firstEstablished = await establishLaunchSessionBinding(firstCheckout, 'first-checkout-owner', { pid: process.pid });
|
|
695
|
+
const secondEstablished = await establishLaunchSessionBinding(secondCheckout, 'second-checkout-owner', { pid: process.pid });
|
|
696
|
+
assert.equal(firstEstablished.kind, 'committed-released');
|
|
697
|
+
assert.equal(secondEstablished.kind, 'committed-released');
|
|
698
|
+
if (firstEstablished.kind !== 'committed-released' || secondEstablished.kind !== 'committed-released')
|
|
699
|
+
return;
|
|
700
|
+
firstBinding = firstEstablished.binding;
|
|
701
|
+
secondBinding = secondEstablished.binding;
|
|
455
702
|
assert.match(await readFile(join(firstCheckout, '.omx', 'state', 'session.json'), 'utf-8'), /first-checkout-owner/);
|
|
456
703
|
assert.match(await readFile(join(secondCheckout, '.omx', 'state', 'session.json'), 'utf-8'), /second-checkout-owner/);
|
|
457
|
-
await
|
|
458
|
-
await
|
|
704
|
+
await finalizeBoundOnce(firstBinding, 'test');
|
|
705
|
+
await finalizeBoundOnce(secondBinding, 'test');
|
|
459
706
|
await writeSessionStart(firstCheckout, 'stale-owner', { pid: 2_147_483_647 });
|
|
460
707
|
const fixture = await createHeldCodexFixture(wd);
|
|
461
708
|
await rm(fixture.releasePath, { force: true });
|
|
462
709
|
const relaunch = runOmx(firstCheckout, ['--direct', '--version'], fixture.env);
|
|
463
710
|
if (shouldSkipForSpawnPermissions(relaunch.error))
|
|
464
711
|
return;
|
|
465
|
-
assert.
|
|
466
|
-
assert.
|
|
712
|
+
assert.notEqual(relaunch.status, 0, relaunch.error || relaunch.stderr || relaunch.stdout);
|
|
713
|
+
assert.match(relaunch.stderr, /session_pointer_unusable|stale-dead/);
|
|
714
|
+
assert.match(await readFile(join(firstCheckout, '.omx', 'state', 'session.json'), 'utf-8'), /stale-owner/);
|
|
467
715
|
}
|
|
468
716
|
finally {
|
|
469
|
-
|
|
717
|
+
if (firstBinding)
|
|
718
|
+
await closeLaunchSessionBindingOnce(firstBinding).catch(() => { });
|
|
719
|
+
if (secondBinding)
|
|
720
|
+
await closeLaunchSessionBindingOnce(secondBinding).catch(() => { });
|
|
721
|
+
await rm(wd, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 });
|
|
470
722
|
}
|
|
471
723
|
});
|
|
472
724
|
});
|
|
@@ -504,7 +756,7 @@ printf 'fake-codex:%s\n' "$*"
|
|
|
504
756
|
assert.equal(existsSync(join(worktreePath, '.omx')), false);
|
|
505
757
|
}
|
|
506
758
|
finally {
|
|
507
|
-
await rm(wd, { recursive: true, force: true });
|
|
759
|
+
await rm(wd, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 });
|
|
508
760
|
}
|
|
509
761
|
});
|
|
510
762
|
it('preserves explicit OMX_ROOT for launch worktree state', async () => {
|
|
@@ -540,7 +792,7 @@ printf 'fake-codex:%s\n' "$*"
|
|
|
540
792
|
assert.equal(existsSync(join(repo, '.omx')), false);
|
|
541
793
|
}
|
|
542
794
|
finally {
|
|
543
|
-
await rm(wd, { recursive: true, force: true });
|
|
795
|
+
await rm(wd, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 });
|
|
544
796
|
}
|
|
545
797
|
});
|
|
546
798
|
it('keeps direct madmax worktree launches bound to the boxed run root', async () => {
|
|
@@ -590,7 +842,7 @@ printf 'fake-codex:%s\n' "$*"
|
|
|
590
842
|
assert.match(normalizedStdout, /fake-codex-context:[0-9a-f]{32}/);
|
|
591
843
|
}
|
|
592
844
|
finally {
|
|
593
|
-
await rm(wd, { recursive: true, force: true });
|
|
845
|
+
await rm(wd, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 });
|
|
594
846
|
}
|
|
595
847
|
});
|
|
596
848
|
});
|
|
@@ -649,20 +901,406 @@ exit 0
|
|
|
649
901
|
const tmuxLog = await readFile(tmuxLogPath, 'utf-8');
|
|
650
902
|
assert.equal(result.status, 0, result.error || result.stderr || result.stdout);
|
|
651
903
|
assert.match(tmuxLog, /tmux:new-session /);
|
|
652
|
-
assert.match(tmuxLog, /tmux:split-window
|
|
904
|
+
assert.match(tmuxLog, /tmux:if-shell -F -t %12 .*split-window/);
|
|
653
905
|
assert.doesNotMatch(tmuxLog, /tmux:attach-session/);
|
|
654
906
|
assert.doesNotMatch(result.stderr, /failed to attach detached tmux session/);
|
|
655
907
|
}
|
|
656
908
|
finally {
|
|
657
|
-
await rm(wd, { recursive: true, force: true });
|
|
909
|
+
await rm(wd, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 });
|
|
658
910
|
}
|
|
659
911
|
});
|
|
660
912
|
});
|
|
661
913
|
describe('omx launcher when tmux is available', () => {
|
|
662
|
-
it('
|
|
914
|
+
it('restores an enlarged detached HUD on the first larger client attach', async (t) => {
|
|
915
|
+
if (!skipUnlessPrivateRealTmux(t))
|
|
916
|
+
return;
|
|
917
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-launch-private-real-tmux-'));
|
|
918
|
+
try {
|
|
919
|
+
await withTempTmuxSession(async (fixture) => {
|
|
920
|
+
const home = join(wd, 'home');
|
|
921
|
+
const bin = join(wd, 'bin');
|
|
922
|
+
const shimLogPath = join(wd, 'private-tmux-shim.log');
|
|
923
|
+
await mkdir(home, { recursive: true });
|
|
924
|
+
await mkdir(bin, { recursive: true });
|
|
925
|
+
await writeExecutable(join(bin, 'codex'), '#!/bin/sh\nsleep 300\n');
|
|
926
|
+
await fixture.createPathShim(bin, shimLogPath);
|
|
927
|
+
const result = runOmx(wd, ['--madmax', '--tmux'], {
|
|
928
|
+
HOME: home,
|
|
929
|
+
PATH: `${bin}:${process.env.PATH ?? ''}`,
|
|
930
|
+
OMX_AUTO_UPDATE: '0',
|
|
931
|
+
OMX_NOTIFY_FALLBACK: '0',
|
|
932
|
+
OMX_HOOK_DERIVED_SIGNALS: '0',
|
|
933
|
+
OMX_HERMES_MCP_BRIDGE: '1',
|
|
934
|
+
OMX_LAUNCH_POLICY: 'direct',
|
|
935
|
+
TMUX: '',
|
|
936
|
+
TMUX_PANE: '',
|
|
937
|
+
});
|
|
938
|
+
assert.equal(result.status, 0, result.error || result.stderr || result.stdout);
|
|
939
|
+
const launchedSession = fixture.run(['list-sessions', '-F', '#{session_name}'])
|
|
940
|
+
.split('\n')
|
|
941
|
+
.find((name) => name.startsWith('omx-') && name !== fixture.sessionName);
|
|
942
|
+
assert.ok(launchedSession, 'compiled launcher must create its detached session on the private server');
|
|
943
|
+
// tmux 3.2a does not expose session_width/session_height; the detached window is the session geometry.
|
|
944
|
+
assert.equal(fixture.run(['display-message', '-p', '-t', launchedSession, '#{window_width}x#{window_height}']), '80x24', 'the detached session must remain at its initial geometry before any client attaches');
|
|
945
|
+
const panes = fixture.run(['list-panes', '-t', launchedSession, '-F', '#{pane_id}\t#{pane_height}\t#{pane_start_command}'])
|
|
946
|
+
.split('\n')
|
|
947
|
+
.map((line) => line.split('\t'));
|
|
948
|
+
const hud = panes.find(([, , command]) => command.includes('OMX_DETACHED_HUD_OPERATION='));
|
|
949
|
+
assert.ok(hud, 'HUD split must carry its operation marker');
|
|
950
|
+
assert.equal(Number(hud[1]), HUD_TMUX_HEIGHT_LINES, 'authorized direct reconciliation must resize the HUD');
|
|
951
|
+
assert.equal(fixture.run(['show-options', '-v', '-t', launchedSession, 'history-limit']), String(DETACHED_TMUX_HISTORY_LIMIT), 'direct leader mutation must receive its receipt and set session history');
|
|
952
|
+
const hooks = fixture.run(['show-hooks', '-t', launchedSession]);
|
|
953
|
+
assert.match(hooks, /client-resized\[[0-9]+\].*run-shell -b/);
|
|
954
|
+
assert.match(hooks, /tmux if-shell -F -t/);
|
|
955
|
+
assert.match(hooks, /OMX_DETACHED_HUD_OPERATION=/);
|
|
956
|
+
const storedPaneFormat = hooks.match(/##\{pane_id\}\\t##\{pane_dead\}\\t##\{pane_pid\}/)?.[0] ?? '';
|
|
957
|
+
assert.equal(storedPaneFormat, '##{pane_id}\\t##{pane_dead}\\t##{pane_pid}', 'stored hook display must retain one outer format escape and encode TAB bytes');
|
|
958
|
+
const executablePaneFormat = storedPaneFormat.replaceAll('##{', '#{').replaceAll('\\t', '\t');
|
|
959
|
+
assert.equal(executablePaneFormat, '#{pane_id}\t#{pane_dead}\t#{pane_pid}', 'one outer tmux format pass must decode the stored format into the executable nested argv');
|
|
960
|
+
const clientAttachedHookSlots = [...hooks.matchAll(/client-attached\[[0-9]+\]/g)].map(([slot]) => slot);
|
|
961
|
+
for (const hookSlot of clientAttachedHookSlots) {
|
|
962
|
+
fixture.run(['set-hook', '-u', '-t', launchedSession, hookSlot]);
|
|
963
|
+
}
|
|
964
|
+
const installedHooks = fixture.run(['show-hooks', '-t', launchedSession]);
|
|
965
|
+
const storedResizeHookSlots = [...installedHooks.matchAll(/client-resized\[[0-9]+\]/g)].map(([slot]) => slot);
|
|
966
|
+
assert.equal(storedResizeHookSlots.length, 1, 'exactly one installed client-resized hook must remain for the natural resize trigger');
|
|
967
|
+
assert.match(installedHooks, /client-resized\[[0-9]+\].*run-shell -b/);
|
|
968
|
+
assert.doesNotMatch(installedHooks, /client-attached\[[0-9]+\]/, 'the resize trigger must not be conflated with client-attached hook execution');
|
|
969
|
+
await new Promise((resolve) => setTimeout(resolve, (HUD_RESIZE_RECONCILE_DELAY_SECONDS * 1_000) + 250));
|
|
970
|
+
const launchPaneSnapshots = parseShimTmuxArgv(await readFile(shimLogPath, 'utf-8'))
|
|
971
|
+
.filter((argv) => argv[0] === 'list-panes' && argv[1] === '-a');
|
|
972
|
+
assert.ok(launchPaneSnapshots.length >= 2, 'the direct and delayed launch reconciliations must both complete before the stored-hook baseline is cleared');
|
|
973
|
+
await assertShimLogQuiescent(shimLogPath, 250, 'launch and delayed reconciliation must settle before clearing the stored-hook baseline');
|
|
974
|
+
await writeFile(shimLogPath, '');
|
|
975
|
+
assert.equal(await readFile(shimLogPath, 'utf-8'), '', 'the shim baseline must start after launch and reconciliation activity settles');
|
|
976
|
+
fixture.run(['resize-pane', '-t', hud[0], '-y', '7']);
|
|
977
|
+
assert.equal(Number(fixture.run(['display-message', '-p', '-t', hud[0], '#{pane_height}'])), 7, 'the detached HUD must be enlarged before the restore trigger');
|
|
978
|
+
assert.equal(fixture.run(['display-message', '-p', '-t', launchedSession, '#{window_width}x#{window_height}']), '80x24', 'enlarging the detached HUD must not attach a client');
|
|
979
|
+
fixture.triggerClientResize(launchedSession, { cols: 121, rows: 41 });
|
|
980
|
+
await new Promise((resolve) => setTimeout(resolve, (HUD_RESIZE_RECONCILE_DELAY_SECONDS * 1_000) + 250));
|
|
981
|
+
await assertShimLogQuiescent(shimLogPath, 250, 'the stored client-resized hook must finish both its immediate and delayed passes before assertions');
|
|
982
|
+
assert.equal(Number(fixture.run(['display-message', '-p', '-t', hud[0], '#{pane_height}'])), HUD_TMUX_HEIGHT_LINES, 'the first larger client attach must restore the enlarged detached HUD');
|
|
983
|
+
const paneSnapshots = parseShimTmuxArgv(await readFile(shimLogPath, 'utf-8'))
|
|
984
|
+
.filter((argv) => argv[0] === 'list-panes' && argv[1] === '-a');
|
|
985
|
+
assert.equal(paneSnapshots.length, 2, 'only the explicitly triggered stored client-resized hook may produce the post-baseline immediate and delayed pane snapshots');
|
|
986
|
+
for (const argv of paneSnapshots) {
|
|
987
|
+
assert.deepEqual(argv, ['list-panes', '-a', '-F', executablePaneFormat], 'post-baseline stored client-resized hook execution must preserve the exact executable TAB-delimited nested argv');
|
|
988
|
+
}
|
|
989
|
+
});
|
|
990
|
+
}
|
|
991
|
+
finally {
|
|
992
|
+
await rm(wd, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 });
|
|
993
|
+
}
|
|
994
|
+
});
|
|
995
|
+
it('records the guarded resize command without an unknown-command parser error', async (t) => {
|
|
996
|
+
if (!skipUnlessPrivateRealTmux(t))
|
|
997
|
+
return;
|
|
998
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-launch-private-real-tmux-'));
|
|
999
|
+
try {
|
|
1000
|
+
await withTempTmuxSession({ serverLog: true }, async (fixture) => {
|
|
1001
|
+
const home = join(wd, 'home');
|
|
1002
|
+
const bin = join(wd, 'bin');
|
|
1003
|
+
const shimLogPath = join(wd, 'private-tmux-shim.log');
|
|
1004
|
+
await mkdir(home, { recursive: true });
|
|
1005
|
+
await mkdir(bin, { recursive: true });
|
|
1006
|
+
await writeExecutable(join(bin, 'codex'), '#!/bin/sh\nsleep 300\n');
|
|
1007
|
+
await fixture.createPathShim(bin, shimLogPath);
|
|
1008
|
+
const result = runOmx(wd, ['--madmax', '--tmux'], {
|
|
1009
|
+
HOME: home,
|
|
1010
|
+
PATH: `${bin}:${process.env.PATH ?? ''}`,
|
|
1011
|
+
OMX_AUTO_UPDATE: '0',
|
|
1012
|
+
OMX_NOTIFY_FALLBACK: '0',
|
|
1013
|
+
OMX_HOOK_DERIVED_SIGNALS: '0',
|
|
1014
|
+
OMX_HERMES_MCP_BRIDGE: '1',
|
|
1015
|
+
OMX_LAUNCH_POLICY: 'direct',
|
|
1016
|
+
TMUX: '',
|
|
1017
|
+
TMUX_PANE: '',
|
|
1018
|
+
});
|
|
1019
|
+
assert.equal(result.status, 0, result.error || result.stderr || result.stdout);
|
|
1020
|
+
const launchedSession = fixture.run(['list-sessions', '-F', '#{session_name}'])
|
|
1021
|
+
.split('\n')
|
|
1022
|
+
.find((name) => name.startsWith('omx-') && name !== fixture.sessionName);
|
|
1023
|
+
assert.ok(launchedSession, 'compiled launcher must create its detached session on the private server');
|
|
1024
|
+
const panes = fixture.run(['list-panes', '-t', launchedSession, '-F', '#{pane_id}\t#{pane_height}\t#{pane_start_command}'])
|
|
1025
|
+
.split('\n')
|
|
1026
|
+
.map((line) => line.split('\t'));
|
|
1027
|
+
const hud = panes.find(([, , command]) => command.includes('OMX_DETACHED_HUD_OPERATION='));
|
|
1028
|
+
assert.ok(hud, 'HUD split must carry its operation marker');
|
|
1029
|
+
const hooks = fixture.run(['show-hooks', '-t', launchedSession]);
|
|
1030
|
+
const storedPaneFormat = hooks.match(/##\{pane_id\}\\t##\{pane_dead\}\\t##\{pane_pid\}/)?.[0] ?? '';
|
|
1031
|
+
const executablePaneFormat = storedPaneFormat.replaceAll('##{', '#{').replaceAll('\\t', '\t');
|
|
1032
|
+
assert.equal(executablePaneFormat, '#{pane_id}\t#{pane_dead}\t#{pane_pid}', 'one outer tmux format pass must decode the stored format into the executable nested argv');
|
|
1033
|
+
const clientAttachedHookSlots = [...hooks.matchAll(/client-attached\[[0-9]+\]/g)].map(([slot]) => slot);
|
|
1034
|
+
for (const hookSlot of clientAttachedHookSlots) {
|
|
1035
|
+
fixture.run(['set-hook', '-u', '-t', launchedSession, hookSlot]);
|
|
1036
|
+
}
|
|
1037
|
+
const installedHooks = fixture.run(['show-hooks', '-t', launchedSession]);
|
|
1038
|
+
const storedResizeHookSlots = [...installedHooks.matchAll(/client-resized\[[0-9]+\]/g)].map(([slot]) => slot);
|
|
1039
|
+
assert.equal(storedResizeHookSlots.length, 1, 'exactly one installed client-resized hook must remain for the natural resize trigger');
|
|
1040
|
+
await new Promise((resolve) => setTimeout(resolve, (HUD_RESIZE_RECONCILE_DELAY_SECONDS * 1_000) + 250));
|
|
1041
|
+
const launchPaneSnapshots = parseShimTmuxArgv(await readFile(shimLogPath, 'utf-8'))
|
|
1042
|
+
.filter((argv) => argv[0] === 'list-panes' && argv[1] === '-a');
|
|
1043
|
+
assert.ok(launchPaneSnapshots.length >= 2, 'the direct and delayed launch reconciliations must both complete before the stored-hook baseline is cleared');
|
|
1044
|
+
await assertShimLogQuiescent(shimLogPath, 250, 'launch and delayed reconciliation must settle before clearing the stored-hook baseline');
|
|
1045
|
+
await writeFile(shimLogPath, '');
|
|
1046
|
+
const probeOffset = (await fixture.readServerLog()).length;
|
|
1047
|
+
const probe = fixture.runResult([
|
|
1048
|
+
'if-shell', '-F', '-t', fixture.leaderPaneId, '1',
|
|
1049
|
+
'tmux display-message -p omx-oracle-probe', '',
|
|
1050
|
+
]);
|
|
1051
|
+
assert.equal(probe.error, '', 'the positive-control tmux client must start successfully');
|
|
1052
|
+
assert.notEqual(probe.status, 0, 'tmux must reject the deliberately malformed selected command list');
|
|
1053
|
+
assert.ok(probe.stderr === '' || /unknown command: tmux/.test(probe.stderr), `accepted probe stderr is empty or the same parse diagnostic, got ${JSON.stringify(probe.stderr)}`);
|
|
1054
|
+
assert.equal(fixture.sessionExists(), true, 'the private tmux server must remain alive after the malformed client command');
|
|
1055
|
+
assert.equal(fixture.run(['display-message', '-p', '-t', fixture.leaderPaneId, '#{pane_id}']), fixture.leaderPaneId, 'a successful post-probe command must prove the server remains responsive');
|
|
1056
|
+
await waitForServerLogSubstring(fixture.readServerLog, probeOffset, 'unknown command: tmux', 3_000, 25);
|
|
1057
|
+
const triggerOffset = (await fixture.readServerLog()).length;
|
|
1058
|
+
fixture.run(['resize-pane', '-t', hud[0], '-y', '7']);
|
|
1059
|
+
assert.equal(Number(fixture.run(['display-message', '-p', '-t', hud[0], '#{pane_height}'])), 7, 'the detached HUD must be enlarged before the parser oracle trigger');
|
|
1060
|
+
fixture.triggerClientResize(launchedSession, { cols: 121, rows: 41 });
|
|
1061
|
+
await new Promise((resolve) => setTimeout(resolve, (HUD_RESIZE_RECONCILE_DELAY_SECONDS * 1_000) + 250));
|
|
1062
|
+
await assertShimLogQuiescent(shimLogPath, 250, 'the stored client-resized hook must finish both its immediate and delayed passes before assertions');
|
|
1063
|
+
const paneSnapshots = parseShimTmuxArgv(await readFile(shimLogPath, 'utf-8'))
|
|
1064
|
+
.filter((argv) => argv[0] === 'list-panes' && argv[1] === '-a');
|
|
1065
|
+
assert.equal(paneSnapshots.length, 2, 'only the explicitly triggered stored client-resized hook may produce the post-baseline immediate and delayed pane snapshots');
|
|
1066
|
+
for (const argv of paneSnapshots) {
|
|
1067
|
+
assert.deepEqual(argv, ['list-panes', '-a', '-F', executablePaneFormat], 'post-baseline stored client-resized hook execution must preserve the exact executable TAB-delimited nested argv');
|
|
1068
|
+
}
|
|
1069
|
+
const triggerWindow = (await fixture.readServerLog()).slice(triggerOffset);
|
|
1070
|
+
assert.ok(triggerWindow.includes(`resize-pane -t ${hud[0]} -y ${HUD_TMUX_HEIGHT_LINES}`), 'the server-log window must cover the stored hook resize command');
|
|
1071
|
+
assert.doesNotMatch(triggerWindow, /unknown command: tmux/, 'the triggered guarded sink must parse as a bare tmux command-list entry (#3292)');
|
|
1072
|
+
});
|
|
1073
|
+
}
|
|
1074
|
+
finally {
|
|
1075
|
+
await rm(wd, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 });
|
|
1076
|
+
}
|
|
1077
|
+
});
|
|
1078
|
+
it('denies detached HUD mutation after the pane owner changes', async (t) => {
|
|
1079
|
+
if (!skipUnlessPrivateRealTmux(t))
|
|
1080
|
+
return;
|
|
1081
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-launch-private-real-tmux-'));
|
|
1082
|
+
try {
|
|
1083
|
+
await withTempTmuxSession(async (fixture) => {
|
|
1084
|
+
const home = join(wd, 'home');
|
|
1085
|
+
const bin = join(wd, 'bin');
|
|
1086
|
+
const shimLogPath = join(wd, 'private-tmux-shim.log');
|
|
1087
|
+
const foreignSession = 'foreign-control';
|
|
1088
|
+
let launchedSession;
|
|
1089
|
+
let originalOwner;
|
|
1090
|
+
await mkdir(home, { recursive: true });
|
|
1091
|
+
await mkdir(bin, { recursive: true });
|
|
1092
|
+
await writeExecutable(join(bin, 'codex'), '#!/bin/sh\nsleep 300\n');
|
|
1093
|
+
await fixture.createPathShim(bin, shimLogPath);
|
|
1094
|
+
fixture.run(['new-session', '-d', '-s', foreignSession, 'sleep 300']);
|
|
1095
|
+
const foreignBefore = fixture.run(['list-panes', '-t', foreignSession, '-F', '#{pane_id}\t#{pane_pid}\t#{pane_height}']);
|
|
1096
|
+
try {
|
|
1097
|
+
const result = runOmx(wd, ['--madmax', '--tmux'], {
|
|
1098
|
+
HOME: home,
|
|
1099
|
+
PATH: `${bin}:${process.env.PATH ?? ''}`,
|
|
1100
|
+
OMX_AUTO_UPDATE: '0',
|
|
1101
|
+
OMX_NOTIFY_FALLBACK: '0',
|
|
1102
|
+
OMX_HOOK_DERIVED_SIGNALS: '0',
|
|
1103
|
+
OMX_HERMES_MCP_BRIDGE: '1',
|
|
1104
|
+
OMX_LAUNCH_POLICY: 'direct',
|
|
1105
|
+
TMUX: '',
|
|
1106
|
+
TMUX_PANE: '',
|
|
1107
|
+
});
|
|
1108
|
+
assert.equal(result.status, 0, result.error || result.stderr || result.stdout);
|
|
1109
|
+
launchedSession = fixture.run(['list-sessions', '-F', '#{session_name}'])
|
|
1110
|
+
.split('\n')
|
|
1111
|
+
.find((name) => name.startsWith('omx-') && name !== fixture.sessionName);
|
|
1112
|
+
assert.ok(launchedSession, 'compiled launcher must create its detached session on the private server');
|
|
1113
|
+
const panes = fixture.run(['list-panes', '-t', launchedSession, '-F', '#{pane_id}\t#{pane_height}\t#{pane_start_command}'])
|
|
1114
|
+
.split('\n')
|
|
1115
|
+
.map((line) => line.split('\t'));
|
|
1116
|
+
const hud = panes.find(([, , command]) => command.includes('OMX_DETACHED_HUD_OPERATION='));
|
|
1117
|
+
assert.ok(hud, 'HUD split must carry its operation marker');
|
|
1118
|
+
const hooks = fixture.run(['show-hooks', '-t', launchedSession]);
|
|
1119
|
+
const storedPaneFormat = hooks.match(/##\{pane_id\}\\t##\{pane_dead\}\\t##\{pane_pid\}/)?.[0] ?? '';
|
|
1120
|
+
const executablePaneFormat = storedPaneFormat.replaceAll('##{', '#{').replaceAll('\\t', '\t');
|
|
1121
|
+
assert.equal(executablePaneFormat, '#{pane_id}\t#{pane_dead}\t#{pane_pid}', 'one outer tmux format pass must decode the stored format into the executable nested argv');
|
|
1122
|
+
const clientAttachedHookSlots = [...hooks.matchAll(/client-attached\[[0-9]+\]/g)].map(([slot]) => slot);
|
|
1123
|
+
for (const hookSlot of clientAttachedHookSlots) {
|
|
1124
|
+
fixture.run(['set-hook', '-u', '-t', launchedSession, hookSlot]);
|
|
1125
|
+
}
|
|
1126
|
+
const installedHooks = fixture.run(['show-hooks', '-t', launchedSession]);
|
|
1127
|
+
const storedResizeHookSlots = [...installedHooks.matchAll(/client-resized\[[0-9]+\]/g)].map(([slot]) => slot);
|
|
1128
|
+
assert.equal(storedResizeHookSlots.length, 1, 'exactly one installed client-resized hook must remain for the natural resize trigger');
|
|
1129
|
+
await new Promise((resolve) => setTimeout(resolve, (HUD_RESIZE_RECONCILE_DELAY_SECONDS * 1_000) + 250));
|
|
1130
|
+
const launchPaneSnapshots = parseShimTmuxArgv(await readFile(shimLogPath, 'utf-8'))
|
|
1131
|
+
.filter((argv) => argv[0] === 'list-panes' && argv[1] === '-a');
|
|
1132
|
+
assert.ok(launchPaneSnapshots.length >= 2, 'the direct and delayed launch reconciliations must both complete before the stored-hook baseline is cleared');
|
|
1133
|
+
await assertShimLogQuiescent(shimLogPath, 250, 'launch and delayed reconciliation must settle before clearing the stored-hook baseline');
|
|
1134
|
+
await writeFile(shimLogPath, '');
|
|
1135
|
+
originalOwner = fixture.run(['show-options', '-v', '-t', launchedSession, '@omx_instance_id']);
|
|
1136
|
+
assert.ok(originalOwner, 'the detached session must retain its original pane owner before the denial trigger');
|
|
1137
|
+
fixture.run(['resize-pane', '-t', hud[0], '-y', '7']);
|
|
1138
|
+
assert.equal(Number(fixture.run(['display-message', '-p', '-t', hud[0], '#{pane_height}'])), 7, 'the detached HUD must be enlarged before the authority denial trigger');
|
|
1139
|
+
fixture.run(['set-option', '-t', launchedSession, '@omx_instance_id', 'foreign-owner']);
|
|
1140
|
+
await writeFile(shimLogPath, '');
|
|
1141
|
+
fixture.triggerClientResize(launchedSession, { cols: 122, rows: 42 });
|
|
1142
|
+
await new Promise((resolve) => setTimeout(resolve, (HUD_RESIZE_RECONCILE_DELAY_SECONDS * 1_000) + 250));
|
|
1143
|
+
await assertShimLogQuiescent(shimLogPath, 250, 'the stored client-resized hook must finish both its immediate and delayed passes before assertions');
|
|
1144
|
+
const paneSnapshots = parseShimTmuxArgv(await readFile(shimLogPath, 'utf-8'))
|
|
1145
|
+
.filter((argv) => argv[0] === 'list-panes' && argv[1] === '-a');
|
|
1146
|
+
assert.equal(paneSnapshots.length, 2, 'only the explicitly triggered stored client-resized hook may produce the post-baseline immediate and delayed pane snapshots');
|
|
1147
|
+
for (const argv of paneSnapshots) {
|
|
1148
|
+
assert.deepEqual(argv, ['list-panes', '-a', '-F', executablePaneFormat], 'post-baseline stored client-resized hook execution must preserve the exact executable TAB-delimited nested argv');
|
|
1149
|
+
}
|
|
1150
|
+
assert.equal(Number(fixture.run(['display-message', '-p', '-t', hud[0], '#{pane_height}'])), 7, 'the foreign pane owner must deny the deferred HUD mutation');
|
|
1151
|
+
assert.equal(fixture.run(['list-panes', '-t', foreignSession, '-F', '#{pane_id}\t#{pane_pid}\t#{pane_height}']), foreignBefore, 'the denial trigger must not affect a foreign session');
|
|
1152
|
+
}
|
|
1153
|
+
finally {
|
|
1154
|
+
if (launchedSession && originalOwner && fixture.sessionExists(launchedSession)) {
|
|
1155
|
+
fixture.run(['set-option', '-t', launchedSession, '@omx_instance_id', originalOwner]);
|
|
1156
|
+
assert.equal(fixture.run(['show-options', '-v', '-t', launchedSession, '@omx_instance_id']), originalOwner, 'the original detached pane owner must be restored before fixture teardown');
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
});
|
|
1160
|
+
}
|
|
1161
|
+
finally {
|
|
1162
|
+
await rm(wd, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 });
|
|
1163
|
+
}
|
|
1164
|
+
});
|
|
1165
|
+
it('cleans a poisoned private tmux server before replaying a held child and Stop', async (t) => {
|
|
1166
|
+
if (!skipUnlessPrivateRealTmux(t))
|
|
1167
|
+
return;
|
|
1168
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-launch-private-poisoned-server-'));
|
|
1169
|
+
try {
|
|
1170
|
+
await withTempTmuxSession(async (fixture) => {
|
|
1171
|
+
const home = join(wd, 'home');
|
|
1172
|
+
const bin = join(wd, 'bin');
|
|
1173
|
+
const releasePath = join(wd, 'child-release');
|
|
1174
|
+
const capturePath = join(wd, 'child-b-env.log');
|
|
1175
|
+
const poisonRuns = join(wd, 'poison-runs');
|
|
1176
|
+
const shimLogPath = join(wd, 'private-tmux-shim.log');
|
|
1177
|
+
const poisonEnv = Object.fromEntries(DETACHED_LAUNCH_CONTROL_PLANE_KEYS.map((key) => [
|
|
1178
|
+
key,
|
|
1179
|
+
key === 'OMX_RUNS_DIR' ? poisonRuns : `poison-${key.toLowerCase()}`,
|
|
1180
|
+
]));
|
|
1181
|
+
await mkdir(home, { recursive: true });
|
|
1182
|
+
await mkdir(bin, { recursive: true });
|
|
1183
|
+
await writeFile(releasePath, 'hold\n');
|
|
1184
|
+
const captureLines = DETACHED_LAUNCH_CONTROL_PLANE_KEYS
|
|
1185
|
+
.map((key) => `printf '%s=%s\\n' '${key}' "$${key}"`)
|
|
1186
|
+
.join('\n');
|
|
1187
|
+
await writeExecutable(join(bin, 'codex'), `#!/bin/sh
|
|
1188
|
+
{
|
|
1189
|
+
${captureLines}
|
|
1190
|
+
} > ${JSON.stringify(capturePath)}
|
|
1191
|
+
while [ -f ${JSON.stringify(releasePath)} ]; do sleep 1; done
|
|
1192
|
+
`);
|
|
1193
|
+
await fixture.createPathShim(bin, shimLogPath);
|
|
1194
|
+
for (const key of DETACHED_LAUNCH_CONTROL_PLANE_KEYS) {
|
|
1195
|
+
fixture.run(['set-environment', '-g', key, poisonEnv[key]]);
|
|
1196
|
+
}
|
|
1197
|
+
const result = runOmx(wd, ['--madmax', '--tmux'], {
|
|
1198
|
+
...poisonEnv,
|
|
1199
|
+
HOME: home,
|
|
1200
|
+
PATH: `${bin}:${process.env.PATH ?? ''}`,
|
|
1201
|
+
OMX_AUTO_UPDATE: '0',
|
|
1202
|
+
OMX_NOTIFY_FALLBACK: '0',
|
|
1203
|
+
OMX_HOOK_DERIVED_SIGNALS: '0',
|
|
1204
|
+
OMX_HERMES_MCP_BRIDGE: '1',
|
|
1205
|
+
OMX_LAUNCH_POLICY: 'direct',
|
|
1206
|
+
TMUX: '',
|
|
1207
|
+
TMUX_PANE: '',
|
|
1208
|
+
});
|
|
1209
|
+
assert.equal(result.status, 0, result.error || result.stderr || result.stdout);
|
|
1210
|
+
assert.doesNotMatch(`${result.stderr}\n${result.stdout}`, /scope|pointer|state root.*error/i);
|
|
1211
|
+
await waitForPath(capturePath, 1, 15_000);
|
|
1212
|
+
const captured = Object.fromEntries((await readFile(capturePath, 'utf-8')).trim().split('\n').map((line) => {
|
|
1213
|
+
const separator = line.indexOf('=');
|
|
1214
|
+
return [line.slice(0, separator), line.slice(separator + 1)];
|
|
1215
|
+
}));
|
|
1216
|
+
assert.deepEqual(Object.keys(captured), [...DETACHED_LAUNCH_CONTROL_PLANE_KEYS]);
|
|
1217
|
+
assert.match(captured.OMX_SESSION_ID ?? '', /^omx-/);
|
|
1218
|
+
assert.equal(captured.CODEX_SESSION_ID, '');
|
|
1219
|
+
assert.equal(captured.SESSION_ID, '');
|
|
1220
|
+
assert.equal(captured.OMX_TMUX_HUD_OWNER, '1');
|
|
1221
|
+
assert.equal(captured.OMX_TMUX_HUD_LEADER_PANE, '');
|
|
1222
|
+
assert.match(captured.OMX_ROOT ?? '', new RegExp(`^${escapeRegExp(poisonRuns)}/run-`));
|
|
1223
|
+
assert.equal(captured.OMX_STATE_ROOT, '');
|
|
1224
|
+
assert.equal(captured.OMX_TEAM_STATE_ROOT, '');
|
|
1225
|
+
assert.equal(captured.OMXBOX_ACTIVE, '1');
|
|
1226
|
+
assert.equal(captured.OMX_SOURCE_CWD, wd);
|
|
1227
|
+
assert.match(captured.OMX_MADMAX_DETACHED_CONTEXT ?? '', /^[0-9a-f]{32}$/);
|
|
1228
|
+
assert.equal(captured.OMX_RUNS_DIR, poisonRuns);
|
|
1229
|
+
assert.equal(captured.OMX_TEAM_LEADER_CWD, '');
|
|
1230
|
+
assert.equal(captured.OMX_TEAM_WORKER, '');
|
|
1231
|
+
assert.equal(captured.OMX_TEAM_INTERNAL_WORKER, '');
|
|
1232
|
+
const launchedSession = fixture.run(['list-sessions', '-F', '#{session_name}'])
|
|
1233
|
+
.split('\n')
|
|
1234
|
+
.find((name) => name.startsWith('omx-') && name !== fixture.sessionName);
|
|
1235
|
+
assert.ok(launchedSession, 'compiled launcher must create a detached session on the private server');
|
|
1236
|
+
assert.equal(fixture.run(['show-options', '-v', '-t', launchedSession, 'history-limit']), String(DETACHED_TMUX_HISTORY_LIMIT));
|
|
1237
|
+
const sessionJsonPath = join(captured.OMX_ROOT, '.omx', 'state', 'session.json');
|
|
1238
|
+
await waitForPath(sessionJsonPath);
|
|
1239
|
+
const sessionJsonBeforeStop = await readFile(sessionJsonPath, 'utf-8');
|
|
1240
|
+
const sessionStateBeforeStop = JSON.parse(sessionJsonBeforeStop);
|
|
1241
|
+
assert.equal(sessionStateBeforeStop.session_id, captured.OMX_SESSION_ID);
|
|
1242
|
+
assert.equal(sessionStateBeforeStop.tmux_session_name, launchedSession);
|
|
1243
|
+
assert.match(sessionStateBeforeStop.tmux_pane_id ?? '', /^%[0-9]+$/);
|
|
1244
|
+
const stop = spawnSync(process.execPath, [join(dirname(fileURLToPath(import.meta.url)), '..', '..', 'scripts', 'codex-native-hook.js')], {
|
|
1245
|
+
cwd: wd,
|
|
1246
|
+
input: JSON.stringify({
|
|
1247
|
+
hook_event_name: 'Stop',
|
|
1248
|
+
source: 'codex-app',
|
|
1249
|
+
cwd: wd,
|
|
1250
|
+
session_id: captured.OMX_SESSION_ID,
|
|
1251
|
+
thread_id: 'poisoned-server-child-b',
|
|
1252
|
+
turn_id: 'stop-poisoned-server-child-b',
|
|
1253
|
+
}),
|
|
1254
|
+
encoding: 'utf-8',
|
|
1255
|
+
env: buildRunOmxEnv({
|
|
1256
|
+
...captured,
|
|
1257
|
+
HOME: home,
|
|
1258
|
+
PATH: `${bin}:${process.env.PATH ?? ''}`,
|
|
1259
|
+
OMX_AUTO_UPDATE: '0',
|
|
1260
|
+
OMX_NOTIFY_FALLBACK: '0',
|
|
1261
|
+
OMX_HOOK_DERIVED_SIGNALS: '0',
|
|
1262
|
+
TMUX: '',
|
|
1263
|
+
TMUX_PANE: '',
|
|
1264
|
+
}),
|
|
1265
|
+
});
|
|
1266
|
+
assert.equal(stop.status, 0, stop.stderr || stop.stdout);
|
|
1267
|
+
assert.doesNotMatch(`${stop.stderr}\n${stop.stdout}`, /scope|pointer|state root.*error/i);
|
|
1268
|
+
const stopOutput = JSON.parse(stop.stdout.trim());
|
|
1269
|
+
assert.deepEqual(stopOutput, {}, 'a clean held-child Stop must be a JSON no-op');
|
|
1270
|
+
assert.notEqual(stopOutput.decision, 'block');
|
|
1271
|
+
const sessionJsonAfterStop = await readFile(sessionJsonPath, 'utf-8');
|
|
1272
|
+
const sessionStateAfterStop = JSON.parse(sessionJsonAfterStop);
|
|
1273
|
+
assert.equal(sessionStateAfterStop.session_id, captured.OMX_SESSION_ID);
|
|
1274
|
+
assert.equal(sessionStateAfterStop.tmux_session_name, launchedSession);
|
|
1275
|
+
assert.equal(sessionJsonAfterStop, sessionJsonBeforeStop, 'a no-op Stop must preserve the live B session pointer');
|
|
1276
|
+
const activeRecordPath = join(captured.OMX_RUNS_DIR, 'active-detached', `${captured.OMX_MADMAX_DETACHED_CONTEXT}.json`);
|
|
1277
|
+
assert.equal(existsSync(activeRecordPath), true, 'leader must retain its active record while child B is live');
|
|
1278
|
+
await rm(releasePath, { force: true });
|
|
1279
|
+
for (let attempt = 0; attempt < 300 && fixture.sessionExists(launchedSession); attempt += 1) {
|
|
1280
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
1281
|
+
}
|
|
1282
|
+
assert.equal(fixture.sessionExists(launchedSession), false, 'released child must clean up its detached tmux session');
|
|
1283
|
+
await waitForPath(join(captured.OMX_ROOT, '.omx', 'logs', 'session-history.jsonl'));
|
|
1284
|
+
assert.equal(existsSync(activeRecordPath), false, 'released child must remove its active record');
|
|
1285
|
+
assert.match(await readFile(join(captured.OMX_ROOT, '.omx', 'logs', 'session-history.jsonl'), 'utf-8'), new RegExp(escapeRegExp(captured.OMX_SESSION_ID)));
|
|
1286
|
+
});
|
|
1287
|
+
}
|
|
1288
|
+
finally {
|
|
1289
|
+
await rm(wd, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 });
|
|
1290
|
+
}
|
|
1291
|
+
});
|
|
1292
|
+
it('does not let the outer launcher fabricate leader-owned active records', async () => {
|
|
663
1293
|
const wd = await mkdtemp(join(tmpdir(), 'omx-launch-madmax-reuse-'));
|
|
664
1294
|
try {
|
|
665
1295
|
const runs = join(wd, 'runs');
|
|
1296
|
+
const madmaxRoot = join(wd, 'madmax-root');
|
|
1297
|
+
const madmaxContext = buildMadmaxDetachedLaunchContextKey(wd, ['--madmax', '--tmux'], madmaxRoot);
|
|
1298
|
+
await mkdir(madmaxRoot, { recursive: true });
|
|
1299
|
+
await writeFile(join(madmaxRoot, '.omxbox-run.json'), JSON.stringify({
|
|
1300
|
+
cwd: madmaxRoot,
|
|
1301
|
+
source_cwd: wd,
|
|
1302
|
+
detached_launch_context: madmaxContext,
|
|
1303
|
+
}));
|
|
666
1304
|
const activeMarker = join(wd, 'active-session');
|
|
667
1305
|
const instanceMarker = join(wd, 'active-instance');
|
|
668
1306
|
const { env, tmuxLogPath } = await createLaunchFixture(wd, (logPath) => `#!/bin/sh
|
|
@@ -689,6 +1327,12 @@ case "$1" in
|
|
|
689
1327
|
printf 'hud-pane\n'
|
|
690
1328
|
exit 0
|
|
691
1329
|
;;
|
|
1330
|
+
list-panes)
|
|
1331
|
+
session=$(cat "${activeMarker}")
|
|
1332
|
+
instance=$(cat "${instanceMarker}")
|
|
1333
|
+
printf '%%12\t0\t4242\t%s\t$1\t100\t%s\n' "$session" "$instance"
|
|
1334
|
+
exit 0
|
|
1335
|
+
;;
|
|
692
1336
|
display-message)
|
|
693
1337
|
if [ "$2" = '-p' ] && [ "$3" = '#{socket_path}' ]; then
|
|
694
1338
|
printf '/tmp/tmux-test.sock\n'
|
|
@@ -724,8 +1368,10 @@ exit 0
|
|
|
724
1368
|
const baseEnv = {
|
|
725
1369
|
...env,
|
|
726
1370
|
OMX_RUNS_DIR: runs,
|
|
1371
|
+
OMX_ROOT: madmaxRoot,
|
|
727
1372
|
OMXBOX_ACTIVE: '1',
|
|
728
|
-
|
|
1373
|
+
OMX_SOURCE_CWD: wd,
|
|
1374
|
+
OMX_MADMAX_DETACHED_CONTEXT: madmaxContext,
|
|
729
1375
|
OMX_LAUNCH_POLICY: 'direct',
|
|
730
1376
|
TMUX: '',
|
|
731
1377
|
TMUX_PANE: '',
|
|
@@ -738,27 +1384,34 @@ exit 0
|
|
|
738
1384
|
if (shouldSkipForSpawnPermissions(second.error))
|
|
739
1385
|
return;
|
|
740
1386
|
assert.equal(second.status, 0, second.error || second.stderr || second.stdout);
|
|
741
|
-
assert.match(second.stderr, /madmax detached launch already active for this context
|
|
1387
|
+
assert.match(second.stderr, /madmax detached launch already active for this context/);
|
|
742
1388
|
const tmuxLog = await readFile(tmuxLogPath, 'utf-8');
|
|
1389
|
+
assert.match(tmuxLog, new RegExp(`-e OMX_MADMAX_DETACHED_CONTEXT=${escapeRegExp(madmaxContext)}`));
|
|
1390
|
+
assert.match(tmuxLog, new RegExp(`-e OMX_RUNS_DIR=${escapeRegExp(runs)}`));
|
|
743
1391
|
assert.equal((tmuxLog.match(/tmux:new-session/g) || []).length, 1);
|
|
744
|
-
assert.equal((tmuxLog.match(/tmux:has-session/g) || []).length,
|
|
1392
|
+
assert.equal((tmuxLog.match(/tmux:has-session/g) || []).length, 0);
|
|
1393
|
+
assert.ok((tmuxLog.match(/tmux:list-panes/g) || []).length >= 2);
|
|
745
1394
|
assert.equal((tmuxLog.match(/tmux:attach-session/g) || []).length, 2);
|
|
746
|
-
const
|
|
747
|
-
assert.match(
|
|
748
|
-
assert.match(
|
|
749
|
-
assert.match(
|
|
1395
|
+
const activeRecord = await readFile(join(runs, 'active-detached', `${madmaxContext}.json`), 'utf-8');
|
|
1396
|
+
assert.match(activeRecord, /"tmux_session_name"/);
|
|
1397
|
+
assert.match(activeRecord, /"session_id"/);
|
|
1398
|
+
assert.match(activeRecord, /"tmux_pane_id": "%12"/);
|
|
1399
|
+
assert.match(activeRecord, /"leader_pid"/);
|
|
1400
|
+
assert.match(activeRecord, /"base_state_root"/);
|
|
1401
|
+
assert.match(activeRecord, /"lifecycle_phase": "ready"/);
|
|
750
1402
|
}
|
|
751
1403
|
finally {
|
|
752
|
-
await rm(wd, { recursive: true, force: true });
|
|
1404
|
+
await rm(wd, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 });
|
|
753
1405
|
}
|
|
754
1406
|
});
|
|
755
|
-
it('records boxed runtime identity
|
|
1407
|
+
it('records boxed runtime identity in the leader-owned active record', async () => {
|
|
756
1408
|
const wd = await mkdtemp(join(tmpdir(), 'omx-launch-madmax-worktree-detached-'));
|
|
757
1409
|
try {
|
|
758
1410
|
const repo = await createGitRepo(wd);
|
|
759
1411
|
const runs = join(wd, 'runs');
|
|
760
1412
|
const instanceMarker = join(wd, 'active-instance');
|
|
761
|
-
const
|
|
1413
|
+
const sessionMarker = join(wd, 'active-session-name');
|
|
1414
|
+
const { env, tmuxLogPath, leaderDonePath } = await createLaunchFixture(wd, (logPath) => `#!/bin/sh
|
|
762
1415
|
printf 'tmux:%s\n' "$*" >> "${logPath}"
|
|
763
1416
|
case "$1" in
|
|
764
1417
|
-V)
|
|
@@ -769,13 +1422,25 @@ case "$1" in
|
|
|
769
1422
|
exit 1
|
|
770
1423
|
;;
|
|
771
1424
|
new-session)
|
|
1425
|
+
prev=''
|
|
1426
|
+
for arg in "$@"; do
|
|
1427
|
+
if [ "$prev" = '-s' ]; then printf '%s' "$arg" > "${sessionMarker}"; fi
|
|
1428
|
+
prev="$arg"
|
|
1429
|
+
done
|
|
772
1430
|
printf '%%77\n'
|
|
773
1431
|
exit 0
|
|
774
1432
|
;;
|
|
1433
|
+
|
|
775
1434
|
split-window)
|
|
776
1435
|
printf '%%78\n'
|
|
777
1436
|
exit 0
|
|
778
1437
|
;;
|
|
1438
|
+
list-panes)
|
|
1439
|
+
instance=$(cat "${instanceMarker}")
|
|
1440
|
+
printf '%%77\t0\t4242\t%s\t$1\t100\t%s\n' "$(cat "${sessionMarker}")" "$instance"
|
|
1441
|
+
exit 0
|
|
1442
|
+
;;
|
|
1443
|
+
|
|
779
1444
|
display-message)
|
|
780
1445
|
if [ "$2" = '-p' ] && [ "$3" = '#{socket_path}' ]; then
|
|
781
1446
|
printf '/tmp/tmux-test.sock\n'
|
|
@@ -821,19 +1486,17 @@ exit 0
|
|
|
821
1486
|
assert.equal(activeFiles.length, 1);
|
|
822
1487
|
const activeRecord = JSON.parse(await readFile(join(runs, 'active-detached', activeFiles[0]), 'utf-8'));
|
|
823
1488
|
const worktreePath = join(dirname(repo), `${basename(repo)}.omx-worktrees`, 'launch-detached');
|
|
824
|
-
assert.
|
|
825
|
-
assert.equal(
|
|
826
|
-
assert.equal(
|
|
827
|
-
assert.equal(activeRecord.session_id.startsWith('omx-'), true);
|
|
828
|
-
assert.equal(activeRecord.tmux_pane_id, '%77');
|
|
1489
|
+
assert.equal(normalizeDarwinTmpPath(activeRecord.source_cwd), normalizeDarwinTmpPath(worktreePath));
|
|
1490
|
+
assert.equal(typeof activeRecord.leader_pid, 'number');
|
|
1491
|
+
assert.equal(activeRecord.lifecycle_phase, 'ready');
|
|
829
1492
|
const tmuxLog = normalizeDarwinTmpPath(await readFile(tmuxLogPath, 'utf-8'));
|
|
830
|
-
assert.match(tmuxLog,
|
|
1493
|
+
assert.match(tmuxLog, /__detached-session-leader/);
|
|
831
1494
|
assert.match(tmuxLog, /-e OMXBOX_ACTIVE=1/);
|
|
832
1495
|
assert.match(tmuxLog, new RegExp(`-e OMX_SOURCE_CWD=${escapeRegExp(normalizeDarwinTmpPath(repo))}`));
|
|
833
|
-
|
|
1496
|
+
await waitForPath(leaderDonePath);
|
|
834
1497
|
}
|
|
835
1498
|
finally {
|
|
836
|
-
await rm(wd, { recursive: true, force: true });
|
|
1499
|
+
await rm(wd, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 });
|
|
837
1500
|
}
|
|
838
1501
|
});
|
|
839
1502
|
it('does not mutate a stale active-detached tmux session without OMX ownership proof', async () => {
|
|
@@ -853,7 +1516,7 @@ exit 0
|
|
|
853
1516
|
session_id: 'expected-omx-session-id',
|
|
854
1517
|
tmux_pane_id: '%99',
|
|
855
1518
|
})}\n`);
|
|
856
|
-
const { env, tmuxLogPath } = await createLaunchFixture(wd, (logPath) => `#!/bin/sh
|
|
1519
|
+
const { env, tmuxLogPath, leaderDonePath } = await createLaunchFixture(wd, (logPath) => `#!/bin/sh
|
|
857
1520
|
printf 'tmux:%s\n' "$*" >> "${logPath}"
|
|
858
1521
|
case "$1" in
|
|
859
1522
|
-V)
|
|
@@ -907,29 +1570,35 @@ exit 0
|
|
|
907
1570
|
if (shouldSkipForSpawnPermissions(result.error))
|
|
908
1571
|
return;
|
|
909
1572
|
assert.equal(result.status, 0, result.error || result.stderr || result.stdout);
|
|
1573
|
+
await waitForPath(leaderDonePath);
|
|
910
1574
|
const tmuxLog = await readFile(tmuxLogPath, 'utf-8');
|
|
911
1575
|
assert.doesNotMatch(tmuxLog, /tmux:set-option .* -t user-owned-session .*history-limit/);
|
|
912
1576
|
assert.doesNotMatch(tmuxLog, /tmux:clear-history .*user-owned-session|tmux:clear-history .*%99/);
|
|
913
1577
|
assert.match(tmuxLog, /tmux:new-session /);
|
|
914
1578
|
}
|
|
915
1579
|
finally {
|
|
916
|
-
await rm(wd, { recursive: true, force: true });
|
|
1580
|
+
await rm(wd, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 });
|
|
917
1581
|
}
|
|
918
1582
|
});
|
|
919
1583
|
it('does not reuse the same active-detached lock for independent --madmax --high launches', async () => {
|
|
920
1584
|
const wd = await mkdtemp(join(tmpdir(), 'omx-launch-madmax-independent-high-'));
|
|
921
1585
|
try {
|
|
922
1586
|
const runs = join(wd, 'runs');
|
|
1587
|
+
const sessionMarker = join(wd, 'independent-session');
|
|
1588
|
+
const instanceMarker = join(wd, 'independent-instance');
|
|
923
1589
|
const { env, tmuxLogPath } = await createLaunchFixture(wd, (logPath) => `#!/bin/sh
|
|
924
1590
|
printf 'tmux:%s\n' "$*" >> "${logPath}"
|
|
925
1591
|
case "$1" in
|
|
926
1592
|
-V) printf 'tmux 3.4\n'; exit 0 ;;
|
|
927
1593
|
has-session) exit 1 ;;
|
|
928
|
-
new-session) printf '%%12\n'; exit 0 ;;
|
|
1594
|
+
new-session) prev=''; for arg in "$@"; do if [ "$prev" = '-s' ]; then printf '%s' "$arg" > "${sessionMarker}"; fi; prev="$arg"; done; printf '%%12\n'; exit 0 ;;
|
|
1595
|
+
|
|
929
1596
|
split-window) printf 'hud-pane\n'; exit 0 ;;
|
|
930
1597
|
display-message) if [ "$2" = '-p' ] && [ "$3" = '#{socket_path}' ]; then printf '/tmp/tmux-test.sock\n'; else printf '0\n'; fi; exit 0 ;;
|
|
1598
|
+
list-panes) printf '%%12\t0\t4242\t%s\t$1\t100\t%s\n' "$(cat "${sessionMarker}")" "$(cat "${instanceMarker}")"; exit 0 ;;
|
|
931
1599
|
show-options) printf 'off\n'; exit 0 ;;
|
|
932
|
-
set-option
|
|
1600
|
+
set-option) if [ "$4" = '@omx_instance_id' ]; then printf '%s' "$5" > "${instanceMarker}"; fi; exit 0 ;;
|
|
1601
|
+
set-hook|attach-session|kill-session|run-shell|resize-pane) exit 0 ;;
|
|
933
1602
|
esac
|
|
934
1603
|
exit 0
|
|
935
1604
|
`);
|
|
@@ -954,13 +1623,14 @@ exit 0
|
|
|
954
1623
|
.map((line) => JSON.parse(line));
|
|
955
1624
|
assert.equal(registryEntries.length, 2);
|
|
956
1625
|
assert.notEqual(registryEntries[0].detached_launch_context, registryEntries[1].detached_launch_context, 'independent launches must get distinct active-detached lock identities');
|
|
957
|
-
|
|
958
|
-
assert.equal(
|
|
1626
|
+
const activeDetachedEntries = await readdir(join(runs, 'active-detached'));
|
|
1627
|
+
assert.equal(activeDetachedEntries.filter((entry) => entry.endsWith('.json')).length, 2);
|
|
1628
|
+
assert.equal(activeDetachedEntries.filter((entry) => entry.endsWith('.lock')).length, 0);
|
|
959
1629
|
const tmuxLog = await readFile(tmuxLogPath, 'utf-8');
|
|
960
1630
|
assert.equal((tmuxLog.match(/tmux:new-session/g) || []).length, 2);
|
|
961
1631
|
}
|
|
962
1632
|
finally {
|
|
963
|
-
await rm(wd, { recursive: true, force: true });
|
|
1633
|
+
await rm(wd, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 });
|
|
964
1634
|
}
|
|
965
1635
|
});
|
|
966
1636
|
it('allows distinct madmax detached launch contexts to create separate sessions', async () => {
|
|
@@ -997,7 +1667,113 @@ exit 0
|
|
|
997
1667
|
assert.equal((tmuxLog.match(/tmux:new-session/g) || []).length, 2);
|
|
998
1668
|
}
|
|
999
1669
|
finally {
|
|
1000
|
-
await rm(wd, { recursive: true, force: true });
|
|
1670
|
+
await rm(wd, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 });
|
|
1671
|
+
}
|
|
1672
|
+
});
|
|
1673
|
+
it('denies detached HUD finalization when the receipted pane id is recycled', async () => {
|
|
1674
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-launch-detached-hud-recycle-'));
|
|
1675
|
+
try {
|
|
1676
|
+
const { env, tmuxLogPath } = await createLaunchFixture(wd, (logPath) => `#!/bin/sh
|
|
1677
|
+
printf 'tmux:%s\n' "$*" >> "${logPath}"
|
|
1678
|
+
case "$1" in
|
|
1679
|
+
-V) printf 'tmux 3.4\n'; exit 0 ;;
|
|
1680
|
+
has-session) exit 1 ;;
|
|
1681
|
+
new-session) printf '%%12\n'; exit 0 ;;
|
|
1682
|
+
split-window) printf '%%99\n'; exit 0 ;;
|
|
1683
|
+
display-message) printf '0\n'; exit 0 ;;
|
|
1684
|
+
show-options) printf 'off\n'; exit 0 ;;
|
|
1685
|
+
set-option|set-hook|attach-session|kill-session|run-shell|resize-pane) exit 0 ;;
|
|
1686
|
+
esac
|
|
1687
|
+
exit 0
|
|
1688
|
+
`);
|
|
1689
|
+
const result = runOmx(wd, ['--madmax', '--tmux'], {
|
|
1690
|
+
...env,
|
|
1691
|
+
OMX_LAUNCH_POLICY: 'direct',
|
|
1692
|
+
OMX_TEST_DETACHED_RECYCLE: 'finalization',
|
|
1693
|
+
TMUX: '',
|
|
1694
|
+
TMUX_PANE: '',
|
|
1695
|
+
});
|
|
1696
|
+
if (shouldSkipForSpawnPermissions(result.error))
|
|
1697
|
+
return;
|
|
1698
|
+
assert.equal(result.status, 0, result.error || result.stderr || result.stdout);
|
|
1699
|
+
const tmuxLog = await readFile(tmuxLogPath, 'utf-8');
|
|
1700
|
+
assert.match(tmuxLog, /tmux:if-shell -F -t %12 .*OMX_DETACHED_HUD_OPERATION=/);
|
|
1701
|
+
assert.match(tmuxLog, /if-shell -F -t '%99' .*456.*\$1.*@1.*OMX_DETACHED_HUD_OPERATION=/);
|
|
1702
|
+
assert.match(tmuxLog, /^tmux:__nested_hud_guard_denied__ %99 999 \$2 @2$/m);
|
|
1703
|
+
assert.doesNotMatch(tmuxLog, /run-shell -b ['"]run-shell -b /);
|
|
1704
|
+
assert.doesNotMatch(tmuxLog, /^tmux:resize-pane .*%99/m);
|
|
1705
|
+
}
|
|
1706
|
+
finally {
|
|
1707
|
+
await rm(wd, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 });
|
|
1708
|
+
}
|
|
1709
|
+
});
|
|
1710
|
+
it('retries the idempotent first owner-guarded mutation after a successful session tag', async () => {
|
|
1711
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-launch-detached-owner-visibility-'));
|
|
1712
|
+
try {
|
|
1713
|
+
const { env, tmuxLogPath } = await createLaunchFixture(wd, (logPath) => `#!/bin/sh
|
|
1714
|
+
printf 'tmux:%s\n' "$*" >> "${logPath}"
|
|
1715
|
+
case "$1" in
|
|
1716
|
+
-V) printf 'tmux 3.4\n'; exit 0 ;;
|
|
1717
|
+
has-session) exit 1 ;;
|
|
1718
|
+
new-session) printf '%%12\n'; exit 0 ;;
|
|
1719
|
+
split-window) printf '%%99\n'; exit 0 ;;
|
|
1720
|
+
display-message) printf '0\n'; exit 0 ;;
|
|
1721
|
+
show-options) printf 'off\n'; exit 0 ;;
|
|
1722
|
+
set-option|set-hook|attach-session|kill-session|run-shell|resize-pane) exit 0 ;;
|
|
1723
|
+
esac
|
|
1724
|
+
exit 0
|
|
1725
|
+
`);
|
|
1726
|
+
const result = runOmx(wd, ['--madmax', '--tmux'], {
|
|
1727
|
+
...env,
|
|
1728
|
+
OMX_LAUNCH_POLICY: 'direct',
|
|
1729
|
+
OMX_TEST_DETACHED_OWNER_VISIBILITY_RACE: 'once',
|
|
1730
|
+
TMUX: '',
|
|
1731
|
+
TMUX_PANE: '',
|
|
1732
|
+
});
|
|
1733
|
+
if (shouldSkipForSpawnPermissions(result.error))
|
|
1734
|
+
return;
|
|
1735
|
+
assert.equal(result.status, 0, result.error || result.stderr || result.stdout);
|
|
1736
|
+
const tmuxLog = await readFile(tmuxLogPath, 'utf-8');
|
|
1737
|
+
assert.equal((tmuxLog.match(/tmux:if-shell -F -t %12 .*set-option.*history-limit/g) || []).length, 3, 'the first session history mutation must retry once; the pane history mutation must remain separately guarded');
|
|
1738
|
+
}
|
|
1739
|
+
finally {
|
|
1740
|
+
await rm(wd, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 });
|
|
1741
|
+
}
|
|
1742
|
+
});
|
|
1743
|
+
it('keeps rollback owner-guarded when post-tag owner visibility never becomes available', async () => {
|
|
1744
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-launch-detached-owner-denial-'));
|
|
1745
|
+
try {
|
|
1746
|
+
const { env, tmuxLogPath } = await createLaunchFixture(wd, (logPath) => `#!/bin/sh
|
|
1747
|
+
printf 'tmux:%s\n' "$*" >> "${logPath}"
|
|
1748
|
+
case "$1" in
|
|
1749
|
+
-V) printf 'tmux 3.4\n'; exit 0 ;;
|
|
1750
|
+
has-session) exit 1 ;;
|
|
1751
|
+
new-session) printf '%%12\n'; exit 0 ;;
|
|
1752
|
+
split-window) printf '%%99\n'; exit 0 ;;
|
|
1753
|
+
display-message) printf '0\n'; exit 0 ;;
|
|
1754
|
+
show-options) printf 'off\n'; exit 0 ;;
|
|
1755
|
+
set-option|set-hook|attach-session|kill-session|run-shell|resize-pane) exit 0 ;;
|
|
1756
|
+
esac
|
|
1757
|
+
exit 0
|
|
1758
|
+
`);
|
|
1759
|
+
const result = runOmx(wd, ['--madmax', '--tmux'], {
|
|
1760
|
+
...env,
|
|
1761
|
+
OMX_LAUNCH_POLICY: 'direct',
|
|
1762
|
+
OMX_TEST_DETACHED_OWNER_VISIBILITY_RACE: 'persistent',
|
|
1763
|
+
TMUX: '',
|
|
1764
|
+
TMUX_PANE: '',
|
|
1765
|
+
});
|
|
1766
|
+
if (shouldSkipForSpawnPermissions(result.error))
|
|
1767
|
+
return;
|
|
1768
|
+
assert.notEqual(result.status, 0, result.error || result.stderr || result.stdout);
|
|
1769
|
+
assert.match(result.stderr, /detached leader authority blocked tmux mutation set-option history-limit/);
|
|
1770
|
+
assert.match(result.stderr, /owner expected .* observed "foreign-owner"/);
|
|
1771
|
+
const tmuxLog = await readFile(tmuxLogPath, 'utf-8');
|
|
1772
|
+
assert.match(tmuxLog, /tmux:if-shell -F -t %12 .*kill-session/);
|
|
1773
|
+
assert.doesNotMatch(tmuxLog, /^tmux:kill-session/m, 'rollback must not issue an unguarded kill-session mutation');
|
|
1774
|
+
}
|
|
1775
|
+
finally {
|
|
1776
|
+
await rm(wd, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 });
|
|
1001
1777
|
}
|
|
1002
1778
|
});
|
|
1003
1779
|
it('launches --madmax through explicitly requested detached tmux so HUD bootstrap can run', async () => {
|
|
@@ -1049,6 +1825,7 @@ esac
|
|
|
1049
1825
|
exit 0
|
|
1050
1826
|
`);
|
|
1051
1827
|
await chmod(fakeTmuxPath, 0o755);
|
|
1828
|
+
await wrapFakeTmuxWithDetachedLeader(fakeTmuxPath);
|
|
1052
1829
|
const result = runOmx(wd, ['--madmax', '--tmux'], {
|
|
1053
1830
|
HOME: home,
|
|
1054
1831
|
PATH: `${fakeBin}:/usr/bin:/bin`,
|
|
@@ -1065,14 +1842,15 @@ exit 0
|
|
|
1065
1842
|
assert.doesNotMatch(tmuxLog, /tmux:show-options -gv history-limit/);
|
|
1066
1843
|
assert.doesNotMatch(tmuxLog, /tmux:set-option -g[q ]+history-limit/);
|
|
1067
1844
|
assert.match(tmuxLog, /tmux:new-session .* -s /);
|
|
1068
|
-
assert.match(tmuxLog, new RegExp(`tmux:
|
|
1069
|
-
assert.match(tmuxLog, new RegExp(`tmux:
|
|
1070
|
-
assert.match(tmuxLog, /tmux:
|
|
1071
|
-
assert.match(tmuxLog, new RegExp(`tmux:
|
|
1845
|
+
assert.match(tmuxLog, new RegExp(`tmux:if-shell -F -t %12 .*set-option.*history-limit.*${DETACHED_TMUX_HISTORY_LIMIT}`));
|
|
1846
|
+
assert.match(tmuxLog, new RegExp(`tmux:if-shell -F -t %12 .*set-option.*-pq.*%12.*history-limit.*${DETACHED_TMUX_HISTORY_LIMIT}`));
|
|
1847
|
+
assert.match(tmuxLog, /tmux:if-shell -F -t %12 .*set-hook.*client-detached\[[0-9]+\].*clear-history -t %12/);
|
|
1848
|
+
assert.match(tmuxLog, new RegExp(`tmux:if-shell -F -t %12 .*split-window.*-v.*-l.*${HUD_TMUX_HEIGHT_LINES}.*-t.*%12`));
|
|
1072
1849
|
assert.equal(result.status, 0, result.error || result.stderr || result.stdout);
|
|
1850
|
+
await waitForPath(`${fakeTmuxPath}.leader-done`);
|
|
1073
1851
|
}
|
|
1074
1852
|
finally {
|
|
1075
|
-
await rm(wd, { recursive: true, force: true });
|
|
1853
|
+
await rm(wd, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 });
|
|
1076
1854
|
}
|
|
1077
1855
|
});
|
|
1078
1856
|
it('preserves parent provider env without replaying terminal state over an OMX-created tmux pane', async () => {
|
|
@@ -1124,8 +1902,8 @@ case "$1" in
|
|
|
1124
1902
|
TERMINFO=/tmp/server-terminfo \
|
|
1125
1903
|
TERMINFO_DIRS=/tmp/server-terminfo-dirs \
|
|
1126
1904
|
TERMCAP=server-termcap \
|
|
1127
|
-
sh -c "$last"
|
|
1128
|
-
printf '
|
|
1905
|
+
nohup /bin/sh -c "$last" </dev/null >/tmp/omx-test-provider-leader.log 2>&1 &
|
|
1906
|
+
printf '%%12\n'
|
|
1129
1907
|
exit 0
|
|
1130
1908
|
;;
|
|
1131
1909
|
split-window)
|
|
@@ -1150,6 +1928,7 @@ case "$1" in
|
|
|
1150
1928
|
esac
|
|
1151
1929
|
exit 0
|
|
1152
1930
|
`);
|
|
1931
|
+
await wrapFakeTmuxWithDetachedLeader(join(fakeBin, 'tmux'));
|
|
1153
1932
|
const result = runOmx(wd, ['--tmux', '--madmax'], {
|
|
1154
1933
|
HOME: home,
|
|
1155
1934
|
PATH: `${fakeBin}:/usr/bin:/bin`,
|
|
@@ -1173,28 +1952,12 @@ exit 0
|
|
|
1173
1952
|
if (shouldSkipForSpawnPermissions(result.error))
|
|
1174
1953
|
return;
|
|
1175
1954
|
assert.equal(result.status, 0, result.error || result.stderr || result.stdout);
|
|
1176
|
-
assert.equal(await readFile(envLogPath, 'utf-8'), [
|
|
1177
|
-
'custom=fake-provider-key',
|
|
1178
|
-
'marker=1',
|
|
1179
|
-
'term=tmux-256color',
|
|
1180
|
-
'term_program=tmux',
|
|
1181
|
-
'term_program_version=3.4',
|
|
1182
|
-
'colorterm=truecolor',
|
|
1183
|
-
'tmux=/tmp/tmux-test.sock,123,0',
|
|
1184
|
-
'tmux_pane=%12',
|
|
1185
|
-
'columns=211',
|
|
1186
|
-
'lines=77',
|
|
1187
|
-
'terminfo=/tmp/outer-terminfo',
|
|
1188
|
-
'terminfo_dirs=/tmp/outer-terminfo-dirs',
|
|
1189
|
-
'termcap=outer-termcap',
|
|
1190
|
-
'',
|
|
1191
|
-
].join('\n'));
|
|
1192
1955
|
const tmuxLog = await readFile(tmuxLogPath, 'utf-8');
|
|
1193
|
-
assert.
|
|
1194
|
-
assert.doesNotMatch(tmuxLog, /CUSTOM_LLM_API_KEY=/);
|
|
1956
|
+
assert.match(tmuxLog, /__detached-session-leader/);
|
|
1957
|
+
assert.doesNotMatch(tmuxLog, /fake-provider-key|CUSTOM_LLM_API_KEY=/);
|
|
1195
1958
|
}
|
|
1196
1959
|
finally {
|
|
1197
|
-
await rm(wd, { recursive: true, force: true });
|
|
1960
|
+
await rm(wd, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 });
|
|
1198
1961
|
}
|
|
1199
1962
|
});
|
|
1200
1963
|
it('launches directly with --direct and skips detached tmux bootstrap', async () => {
|
|
@@ -1222,7 +1985,7 @@ exit 0
|
|
|
1222
1985
|
assert.doesNotMatch(tmuxLog, /new-session|split-window|attach-session/);
|
|
1223
1986
|
}
|
|
1224
1987
|
finally {
|
|
1225
|
-
await rm(wd, { recursive: true, force: true });
|
|
1988
|
+
await rm(wd, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 });
|
|
1226
1989
|
}
|
|
1227
1990
|
});
|
|
1228
1991
|
it('launches directly from OMX_LAUNCH_POLICY=direct and skips detached tmux bootstrap', async () => {
|
|
@@ -1246,7 +2009,7 @@ exit 0
|
|
|
1246
2009
|
assert.doesNotMatch(tmuxLog, /new-session|split-window|attach-session/);
|
|
1247
2010
|
}
|
|
1248
2011
|
finally {
|
|
1249
|
-
await rm(wd, { recursive: true, force: true });
|
|
2012
|
+
await rm(wd, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 });
|
|
1250
2013
|
}
|
|
1251
2014
|
});
|
|
1252
2015
|
it('launches directly inside tmux with --direct and skips HUD/mouse/extended-key tmux calls', async () => {
|
|
@@ -1269,35 +2032,68 @@ exit 0
|
|
|
1269
2032
|
assert.doesNotMatch(tmuxLog, /split-window|show-options|extended-keys|mouse on/);
|
|
1270
2033
|
}
|
|
1271
2034
|
finally {
|
|
1272
|
-
await rm(wd, { recursive: true, force: true });
|
|
2035
|
+
await rm(wd, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 });
|
|
1273
2036
|
}
|
|
1274
2037
|
});
|
|
1275
|
-
it('
|
|
2038
|
+
it('fails closed on an incomplete synthetic HUD split source inside tmux', async () => {
|
|
1276
2039
|
const wd = await mkdtemp(join(tmpdir(), 'omx-launch-inside-tmux-managed-'));
|
|
1277
2040
|
try {
|
|
1278
2041
|
const { env, tmuxLogPath } = await createLaunchFixture(wd, (tmuxLogPath) => `#!/bin/sh
|
|
2042
|
+
state="${tmuxLogPath}.hud-created"
|
|
2043
|
+
proof="${tmuxLogPath}.hud-proof"
|
|
2044
|
+
marker="${tmuxLogPath}.hud-marker"
|
|
1279
2045
|
printf 'tmux:%s\n' "$*" >> "${tmuxLogPath}"
|
|
1280
2046
|
case "$1" in
|
|
1281
2047
|
list-panes)
|
|
2048
|
+
case "$*" in
|
|
2049
|
+
*'#{pane_id}'*'#{pane_start_command}'*)
|
|
2050
|
+
if [ -f "$state" ]; then printf '%%1\tcodex\n%%2\tOMX_TMUX_SPLIT_OPERATION_MARKER='\''%s'\''; export OMX_TMUX_SPLIT_OPERATION_MARKER; exec env OMX_TMUX_HUD_OWNER=1 OMX_TMUX_HUD_LEADER_PANE='\''%%1'\'' node hud\n' "$(cat "$marker")"; else printf '%%1\tcodex\n'; fi
|
|
2051
|
+
;;
|
|
2052
|
+
*'#{pane_id}\t#{pane_dead}\t#{pane_pid}'*)
|
|
2053
|
+
printf '%%1\t0\t101\n%%2\t0\t202\n'
|
|
2054
|
+
;;
|
|
2055
|
+
*'#{pane_id}'*)
|
|
2056
|
+
if [ -f "$state" ]; then printf '%%1\n%%2\n'; else printf '%%1\n'; fi
|
|
2057
|
+
;;
|
|
2058
|
+
esac
|
|
1282
2059
|
exit 0
|
|
1283
2060
|
;;
|
|
1284
2061
|
split-window)
|
|
1285
|
-
printf '
|
|
2062
|
+
printf '%%2\n'
|
|
2063
|
+
exit 0
|
|
2064
|
+
;;
|
|
2065
|
+
if-shell)
|
|
2066
|
+
[ "$2" = '-F' ] && [ "$3" = '-t' ] && [ "$4" = '%1' ] || exit 1
|
|
2067
|
+
case "$5" in *'#{pane_pid},101'*'#{session_id},$7'*'#{window_id},@1'*) ;; *) exit 1 ;; esac
|
|
2068
|
+
success="$6"
|
|
2069
|
+
receipt=\${success##*display-message -p }
|
|
2070
|
+
receipt=\${receipt%% *}
|
|
2071
|
+
split_marker=$(printf '%s' "$success" | sed -n "s/.*OMX_TMUX_SPLIT_OPERATION_MARKER='\\([^']*\\)'.*/\\1/p")
|
|
2072
|
+
[ -n "$split_marker" ] || exit 1
|
|
2073
|
+
printf '%s' "$split_marker" > "$marker"
|
|
2074
|
+
: > "$state"
|
|
2075
|
+
printf '%s\n' "$receipt"
|
|
1286
2076
|
exit 0
|
|
1287
2077
|
;;
|
|
1288
2078
|
display-message)
|
|
1289
2079
|
case "$*" in
|
|
1290
2080
|
*'#{socket_path}'*) printf '/tmp/tmux-test.sock\n' ;;
|
|
2081
|
+
*'#{pane_id}'*'#{pane_dead}'*'#{pane_pid}'*'#{session_id}'*'#{window_id}'*) printf '%%1\t0\t101\t$7\t@1\n' ;;
|
|
2082
|
+
*'#{session_id}'*'#{window_id}'*) printf '$7\t@1\n' ;;
|
|
1291
2083
|
*'#S'*) printf 'managed-session\n' ;;
|
|
1292
2084
|
*) printf '0\n' ;;
|
|
1293
2085
|
esac
|
|
1294
2086
|
exit 0
|
|
1295
2087
|
;;
|
|
1296
2088
|
show-options)
|
|
1297
|
-
printf 'off\n'
|
|
2089
|
+
if [ "$2" = '-g' ] && [ "$3" = '-v' ]; then cat "$proof" 2>/dev/null || true; else printf 'off\n'; fi
|
|
2090
|
+
exit 0
|
|
2091
|
+
;;
|
|
2092
|
+
set-option)
|
|
2093
|
+
if [ "$2" = '-g' ]; then printf '%s\n' "$4" > "$proof"; fi
|
|
1298
2094
|
exit 0
|
|
1299
2095
|
;;
|
|
1300
|
-
|
|
2096
|
+
kill-pane)
|
|
1301
2097
|
exit 0
|
|
1302
2098
|
;;
|
|
1303
2099
|
esac
|
|
@@ -1313,15 +2109,15 @@ exit 0
|
|
|
1313
2109
|
const tmuxLog = await readFile(tmuxLogPath, 'utf-8');
|
|
1314
2110
|
assert.equal(result.status, 0, result.error || result.stderr || result.stdout);
|
|
1315
2111
|
assert.match(result.stdout, /fake-codex:.*--dangerously-bypass-approvals-and-sandbox/);
|
|
1316
|
-
assert.
|
|
2112
|
+
assert.doesNotMatch(tmuxLog, /tmux:if-shell -F -t %1 .*split-window/);
|
|
1317
2113
|
assert.match(tmuxLog, /tmux:set-option -t managed-session mouse on/);
|
|
1318
2114
|
assert.match(tmuxLog, /tmux:set-option -sq extended-keys always/);
|
|
1319
2115
|
}
|
|
1320
2116
|
finally {
|
|
1321
|
-
await rm(wd, { recursive: true, force: true });
|
|
2117
|
+
await rm(wd, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 });
|
|
1322
2118
|
}
|
|
1323
2119
|
});
|
|
1324
|
-
it('
|
|
2120
|
+
it('does not probe an existing tmux server before detached tmux startup', async () => {
|
|
1325
2121
|
const wd = await mkdtemp(join(tmpdir(), 'omx-launch-tmux-missing-socket-'));
|
|
1326
2122
|
try {
|
|
1327
2123
|
const home = join(wd, 'home');
|
|
@@ -1343,12 +2139,9 @@ case "$1" in
|
|
|
1343
2139
|
printf 'tmux 3.6a\n'
|
|
1344
2140
|
exit 0
|
|
1345
2141
|
;;
|
|
1346
|
-
|
|
1347
|
-
printf 'error connecting to /private/tmp/tmux-501/default (No such file or directory)\n' >&2
|
|
1348
|
-
exit 1
|
|
1349
|
-
;;
|
|
2142
|
+
|
|
1350
2143
|
new-session)
|
|
1351
|
-
printf '
|
|
2144
|
+
printf '%%12\n'
|
|
1352
2145
|
exit 0
|
|
1353
2146
|
;;
|
|
1354
2147
|
split-window)
|
|
@@ -1374,6 +2167,7 @@ esac
|
|
|
1374
2167
|
exit 0
|
|
1375
2168
|
`);
|
|
1376
2169
|
await chmod(fakeTmuxPath, 0o755);
|
|
2170
|
+
await wrapFakeTmuxWithDetachedLeader(fakeTmuxPath);
|
|
1377
2171
|
const result = runOmx(wd, ['--madmax', '--tmux'], {
|
|
1378
2172
|
HOME: home,
|
|
1379
2173
|
PATH: `${fakeBin}:/usr/bin:/bin`,
|
|
@@ -1387,15 +2181,15 @@ exit 0
|
|
|
1387
2181
|
return;
|
|
1388
2182
|
const tmuxLog = await readFile(tmuxLogPath, 'utf-8');
|
|
1389
2183
|
assert.equal(result.status, 0, result.error || result.stderr || result.stdout);
|
|
1390
|
-
assert.
|
|
2184
|
+
assert.doesNotMatch(tmuxLog, /tmux:list-sessions/);
|
|
1391
2185
|
assert.match(tmuxLog, /tmux:new-session .* -s /);
|
|
1392
2186
|
assert.doesNotMatch(result.stderr, /server\/socket is unusable/);
|
|
1393
2187
|
}
|
|
1394
2188
|
finally {
|
|
1395
|
-
await rm(wd, { recursive: true, force: true });
|
|
2189
|
+
await rm(wd, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 });
|
|
1396
2190
|
}
|
|
1397
2191
|
});
|
|
1398
|
-
it('
|
|
2192
|
+
it('does not fall back directly when a later detached tmux operation fails', async () => {
|
|
1399
2193
|
const wd = await mkdtemp(join(tmpdir(), 'omx-launch-tmux-stale-socket-'));
|
|
1400
2194
|
try {
|
|
1401
2195
|
const home = join(wd, 'home');
|
|
@@ -1404,6 +2198,7 @@ exit 0
|
|
|
1404
2198
|
const fakePsPath = join(fakeBin, 'ps');
|
|
1405
2199
|
const fakeTmuxPath = join(fakeBin, 'tmux');
|
|
1406
2200
|
const tmuxLogPath = join(wd, 'tmux.log');
|
|
2201
|
+
const runsPath = join(wd, 'runs');
|
|
1407
2202
|
await mkdir(home, { recursive: true });
|
|
1408
2203
|
await mkdir(fakeBin, { recursive: true });
|
|
1409
2204
|
await writeFile(fakeCodexPath, '#!/bin/sh\nprintf \'fake-codex:%s\\n\' "$*"\n');
|
|
@@ -1414,40 +2209,49 @@ exit 0
|
|
|
1414
2209
|
printf 'tmux:%s\n' "$*" >> "${tmuxLogPath}"
|
|
1415
2210
|
case "$1" in
|
|
1416
2211
|
-V)
|
|
2212
|
+
printf 'tmux:d0-runs:%s\n' "$(test -e "${runsPath}" && printf present || printf missing)" >> "${tmuxLogPath}"
|
|
2213
|
+
|
|
1417
2214
|
printf 'tmux 3.6a\n'
|
|
1418
2215
|
exit 0
|
|
1419
2216
|
;;
|
|
1420
|
-
|
|
1421
|
-
printf '
|
|
2217
|
+
new-session)
|
|
2218
|
+
printf 'unsafe-tmux-handle-secret\n' >&2
|
|
1422
2219
|
exit 1
|
|
1423
2220
|
;;
|
|
2221
|
+
|
|
1424
2222
|
esac
|
|
1425
2223
|
printf 'unexpected tmux command: %s\n' "$*" >&2
|
|
1426
2224
|
exit 1
|
|
1427
2225
|
`);
|
|
1428
2226
|
await chmod(fakeTmuxPath, 0o755);
|
|
2227
|
+
await wrapFakeTmuxWithDetachedLeader(fakeTmuxPath);
|
|
1429
2228
|
const result = runOmx(wd, ['--madmax', '--tmux'], {
|
|
1430
2229
|
HOME: home,
|
|
1431
2230
|
PATH: `${fakeBin}:/usr/bin:/bin`,
|
|
1432
2231
|
OMX_AUTO_UPDATE: '0',
|
|
1433
2232
|
OMX_NOTIFY_FALLBACK: '0',
|
|
1434
2233
|
OMX_HOOK_DERIVED_SIGNALS: '0',
|
|
2234
|
+
OMX_RUNS_DIR: runsPath,
|
|
1435
2235
|
TMUX: '',
|
|
1436
2236
|
TMUX_PANE: '',
|
|
1437
2237
|
});
|
|
1438
2238
|
if (shouldSkipForSpawnPermissions(result.error))
|
|
1439
2239
|
return;
|
|
1440
2240
|
const tmuxLog = await readFile(tmuxLogPath, 'utf-8');
|
|
1441
|
-
assert.equal(result.status,
|
|
1442
|
-
assert.match(result.
|
|
1443
|
-
assert.
|
|
1444
|
-
assert.doesNotMatch(
|
|
2241
|
+
assert.equal(result.status, 1, result.error || result.stderr || result.stdout);
|
|
2242
|
+
assert.match(result.stderr, /detached launch safety failure during inert-session/);
|
|
2243
|
+
assert.doesNotMatch(result.stderr, /unsafe-tmux-handle-secret/);
|
|
2244
|
+
assert.doesNotMatch(result.stdout, /fake-codex/);
|
|
2245
|
+
assert.match(tmuxLog, /tmux:-V/);
|
|
2246
|
+
assert.match(tmuxLog, /tmux:d0-runs:missing/);
|
|
2247
|
+
assert.match(tmuxLog, /tmux:new-session/);
|
|
2248
|
+
assert.doesNotMatch(tmuxLog, /tmux:list-sessions|attach-session/);
|
|
1445
2249
|
}
|
|
1446
2250
|
finally {
|
|
1447
|
-
await rm(wd, { recursive: true, force: true });
|
|
2251
|
+
await rm(wd, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 });
|
|
1448
2252
|
}
|
|
1449
2253
|
});
|
|
1450
|
-
it('
|
|
2254
|
+
it('preserves detached ownership when attaching the released tmux session fails', async () => {
|
|
1451
2255
|
const wd = await mkdtemp(join(tmpdir(), 'omx-launch-tmux-attach-fail-'));
|
|
1452
2256
|
try {
|
|
1453
2257
|
const home = join(wd, 'home');
|
|
@@ -1469,7 +2273,7 @@ case "$1" in
|
|
|
1469
2273
|
exit 0
|
|
1470
2274
|
;;
|
|
1471
2275
|
new-session)
|
|
1472
|
-
printf '
|
|
2276
|
+
printf '%%12\n'
|
|
1473
2277
|
exit 0
|
|
1474
2278
|
;;
|
|
1475
2279
|
split-window)
|
|
@@ -1499,6 +2303,7 @@ esac
|
|
|
1499
2303
|
exit 0
|
|
1500
2304
|
`);
|
|
1501
2305
|
await chmod(fakeTmuxPath, 0o755);
|
|
2306
|
+
await wrapFakeTmuxWithDetachedLeader(fakeTmuxPath);
|
|
1502
2307
|
const result = runOmx(wd, ['--madmax', '--tmux'], {
|
|
1503
2308
|
HOME: home,
|
|
1504
2309
|
PATH: `${fakeBin}:/usr/bin:/bin`,
|
|
@@ -1510,17 +2315,18 @@ exit 0
|
|
|
1510
2315
|
});
|
|
1511
2316
|
if (shouldSkipForSpawnPermissions(result.error))
|
|
1512
2317
|
return;
|
|
2318
|
+
await waitForPath(`${fakeTmuxPath}.leader-done`);
|
|
1513
2319
|
const tmuxLog = await readFile(tmuxLogPath, 'utf-8');
|
|
1514
|
-
assert.equal(result.status,
|
|
1515
|
-
assert.match(result.
|
|
2320
|
+
assert.equal(result.status, 1, result.error || result.stderr || result.stdout);
|
|
2321
|
+
assert.match(result.stderr, /detached launch safety failure during post-release-attach/);
|
|
1516
2322
|
assert.match(tmuxLog, /tmux:attach-session -t /);
|
|
1517
|
-
assert.
|
|
2323
|
+
assert.doesNotMatch(tmuxLog, /tmux:kill-session -t /);
|
|
1518
2324
|
}
|
|
1519
2325
|
finally {
|
|
1520
|
-
await rm(wd, { recursive: true, force: true });
|
|
2326
|
+
await rm(wd, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 });
|
|
1521
2327
|
}
|
|
1522
2328
|
});
|
|
1523
|
-
it('
|
|
2329
|
+
it('does not probe or fall back after a released WSL attach returns immediately', async () => {
|
|
1524
2330
|
const wd = await mkdtemp(join(tmpdir(), 'omx-launch-tmux-attach-noop-'));
|
|
1525
2331
|
try {
|
|
1526
2332
|
const { env, tmuxLogPath } = await createLaunchFixture(wd, (tmuxLogPath) => `#!/bin/sh
|
|
@@ -1530,7 +2336,7 @@ case "$1" in
|
|
|
1530
2336
|
exit 0
|
|
1531
2337
|
;;
|
|
1532
2338
|
new-session)
|
|
1533
|
-
printf '
|
|
2339
|
+
printf '%%12\n'
|
|
1534
2340
|
exit 0
|
|
1535
2341
|
;;
|
|
1536
2342
|
split-window)
|
|
@@ -1567,15 +2373,12 @@ exit 0
|
|
|
1567
2373
|
return;
|
|
1568
2374
|
const tmuxLog = await readFile(tmuxLogPath, 'utf-8');
|
|
1569
2375
|
assert.equal(result.status, 0, result.error || result.stderr || result.stdout);
|
|
1570
|
-
assert.
|
|
1571
|
-
assert.match(result.stderr, /attach-session returned immediately without attaching a client/i);
|
|
1572
|
-
assert.match(result.stderr, /Falling back to direct Codex launch/i);
|
|
2376
|
+
assert.doesNotMatch(result.stderr, /Falling back to direct Codex launch|attach-session returned immediately/);
|
|
1573
2377
|
assert.match(tmuxLog, /tmux:attach-session -t /);
|
|
1574
|
-
assert.
|
|
1575
|
-
assert.match(tmuxLog, /tmux:kill-session -t /);
|
|
2378
|
+
assert.doesNotMatch(tmuxLog, /tmux:display-message -p -t .* #\{session_attached\}|tmux:kill-session -t/);
|
|
1576
2379
|
}
|
|
1577
2380
|
finally {
|
|
1578
|
-
await rm(wd, { recursive: true, force: true });
|
|
2381
|
+
await rm(wd, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 });
|
|
1579
2382
|
}
|
|
1580
2383
|
});
|
|
1581
2384
|
it('preserves the requested cwd through detached tmux launch when an unsupported SHELL value falls back away from rc-driven cwd drift', async () => {
|
|
@@ -1611,11 +2414,7 @@ case "$cmd" in
|
|
|
1611
2414
|
exit 0
|
|
1612
2415
|
;;
|
|
1613
2416
|
new-session)
|
|
1614
|
-
|
|
1615
|
-
if [ -n "\${last:-}" ]; then
|
|
1616
|
-
/bin/sh -c "$last"
|
|
1617
|
-
fi
|
|
1618
|
-
printf 'leader-pane\\n'
|
|
2417
|
+
printf '%%12\n'
|
|
1619
2418
|
exit 0
|
|
1620
2419
|
;;
|
|
1621
2420
|
split-window)
|
|
@@ -1641,6 +2440,7 @@ esac
|
|
|
1641
2440
|
exit 0
|
|
1642
2441
|
`);
|
|
1643
2442
|
await chmod(fakeTmuxPath, 0o755);
|
|
2443
|
+
await wrapFakeTmuxWithDetachedLeader(fakeTmuxPath);
|
|
1644
2444
|
const result = runOmx(wd, ['--madmax', '--tmux'], {
|
|
1645
2445
|
HOME: home,
|
|
1646
2446
|
SHELL: '/definitely/missing-shell',
|
|
@@ -1653,13 +2453,14 @@ exit 0
|
|
|
1653
2453
|
});
|
|
1654
2454
|
if (shouldSkipForSpawnPermissions(result.error))
|
|
1655
2455
|
return;
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
assert.match(
|
|
2456
|
+
await waitForPath(`${fakeTmuxPath}.leader-done`);
|
|
2457
|
+
const tmuxLog = await readFile(tmuxLogPath, 'utf-8');
|
|
2458
|
+
assert.match(tmuxLog, /\/bin\/sh/);
|
|
2459
|
+
assert.match(tmuxLog, /__detached-session-leader/);
|
|
1659
2460
|
assert.equal(result.status, 0, result.error || result.stderr || result.stdout);
|
|
1660
2461
|
}
|
|
1661
2462
|
finally {
|
|
1662
|
-
await rm(wd, { recursive: true, force: true });
|
|
2463
|
+
await rm(wd, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 });
|
|
1663
2464
|
}
|
|
1664
2465
|
});
|
|
1665
2466
|
it('falls back to /bin/sh for detached tmux launch when SHELL drifts to an unsupported path', async () => {
|
|
@@ -1693,11 +2494,7 @@ case "$cmd" in
|
|
|
1693
2494
|
exit 0
|
|
1694
2495
|
;;
|
|
1695
2496
|
new-session)
|
|
1696
|
-
|
|
1697
|
-
if [ -n "\${last:-}" ]; then
|
|
1698
|
-
/bin/sh -c "$last"
|
|
1699
|
-
fi
|
|
1700
|
-
printf 'leader-pane\\n'
|
|
2497
|
+
printf '%%12\n'
|
|
1701
2498
|
exit 0
|
|
1702
2499
|
;;
|
|
1703
2500
|
split-window)
|
|
@@ -1723,6 +2520,7 @@ esac
|
|
|
1723
2520
|
exit 0
|
|
1724
2521
|
`);
|
|
1725
2522
|
await chmod(fakeTmuxPath, 0o755);
|
|
2523
|
+
await wrapFakeTmuxWithDetachedLeader(fakeTmuxPath);
|
|
1726
2524
|
const result = runOmx(wd, ['--madmax', '--tmux'], {
|
|
1727
2525
|
HOME: home,
|
|
1728
2526
|
SHELL: '/bin/not-a-real-shell',
|
|
@@ -1736,15 +2534,199 @@ exit 0
|
|
|
1736
2534
|
if (shouldSkipForSpawnPermissions(result.error))
|
|
1737
2535
|
return;
|
|
1738
2536
|
const tmuxLog = await readFile(tmuxLogPath, 'utf-8');
|
|
1739
|
-
const codexLog = normalizeDarwinTmpPath(await readFile(codexLogPath, 'utf-8'));
|
|
1740
2537
|
assert.match(tmuxLog, /\/bin\/sh/);
|
|
1741
2538
|
assert.doesNotMatch(tmuxLog, /not-a-real-shell/);
|
|
1742
|
-
assert.match(
|
|
1743
|
-
assert.match(codexLog, new RegExp(`codex-pwd:${escapeRegExp(normalizeDarwinTmpPath(wd))}`));
|
|
2539
|
+
assert.match(tmuxLog, /__detached-session-leader/);
|
|
1744
2540
|
assert.equal(result.status, 0, result.error || result.stderr || result.stdout);
|
|
2541
|
+
await waitForPath(`${fakeTmuxPath}.leader-done`);
|
|
2542
|
+
}
|
|
2543
|
+
finally {
|
|
2544
|
+
await rm(wd, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 });
|
|
2545
|
+
}
|
|
2546
|
+
});
|
|
2547
|
+
it('keeps finalization authority in the real detached leader process through child exit', async () => {
|
|
2548
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-detached-leader-process-'));
|
|
2549
|
+
const home = join(wd, 'home');
|
|
2550
|
+
const fakeBin = join(wd, 'bin');
|
|
2551
|
+
const fakeChild = join(wd, 'fake-codex.sh');
|
|
2552
|
+
const childReady = join(wd, 'child-ready');
|
|
2553
|
+
const childRelease = join(wd, 'child-release');
|
|
2554
|
+
const childEnv = join(wd, 'child-env');
|
|
2555
|
+
const descendantPidPath = join(wd, 'descendant-pid');
|
|
2556
|
+
const readyPath = join(wd, '.omx', 'runtime', 'detached-release', 'ready');
|
|
2557
|
+
const omxBin = join(dirname(fileURLToPath(import.meta.url)), '..', '..', '..', 'dist', 'cli', 'omx.js');
|
|
2558
|
+
const sessionId = 'omx-detached-leader-process';
|
|
2559
|
+
try {
|
|
2560
|
+
await mkdir(home, { recursive: true });
|
|
2561
|
+
await mkdir(fakeBin, { recursive: true });
|
|
2562
|
+
await writeExecutable(join(fakeBin, 'tmux'), `#!/bin/sh
|
|
2563
|
+
if [ "$1" = "list-panes" ]; then
|
|
2564
|
+
printf '%s\t0\t%s\n' "$TMUX_PANE" "$PPID"
|
|
2565
|
+
fi
|
|
2566
|
+
exit 0
|
|
2567
|
+
`);
|
|
2568
|
+
await writeExecutable(fakeChild, `#!/bin/sh\nprintf '%s' "$OMX_NOTIFY_TEMP_CONTRACT" > ${JSON.stringify(childEnv)}\n(sh -c 'trap "" TERM; while :; do sleep 1; done') &\nprintf '%s' "$!" > ${JSON.stringify(descendantPidPath)}\nprintf ready > ${JSON.stringify(childReady)}\nwhile [ ! -f ${JSON.stringify(childRelease)} ]; do sleep 0.02; done\nexit 0\n`);
|
|
2569
|
+
const payload = Buffer.from(JSON.stringify({
|
|
2570
|
+
cwd: wd,
|
|
2571
|
+
sessionName: 'omx-detached-leader-process',
|
|
2572
|
+
sessionId,
|
|
2573
|
+
codexCmd: fakeChild,
|
|
2574
|
+
readyPath,
|
|
2575
|
+
preLaunchOptions: {
|
|
2576
|
+
notifyTempContract: {
|
|
2577
|
+
active: true,
|
|
2578
|
+
selectors: ['custom:test-provider'],
|
|
2579
|
+
canonicalSelectors: ['custom:test-provider'],
|
|
2580
|
+
warnings: [],
|
|
2581
|
+
source: 'cli',
|
|
2582
|
+
},
|
|
2583
|
+
enableNotifyFallbackAuthority: false,
|
|
2584
|
+
worktreeDirty: false,
|
|
2585
|
+
shouldAttach: true,
|
|
2586
|
+
},
|
|
2587
|
+
})).toString('base64url');
|
|
2588
|
+
const leader = spawn(process.execPath, [omxBin, '__detached-session-leader', payload], {
|
|
2589
|
+
cwd: wd,
|
|
2590
|
+
env: buildRunOmxEnv({
|
|
2591
|
+
HOME: home,
|
|
2592
|
+
TMUX: '/tmp/fake-tmux,1,0',
|
|
2593
|
+
TMUX_PANE: '%3202',
|
|
2594
|
+
PATH: `${fakeBin}:/usr/bin:/bin`,
|
|
2595
|
+
OMX_AUTO_UPDATE: '0',
|
|
2596
|
+
OMX_NOTIFY_FALLBACK: '0',
|
|
2597
|
+
OMX_HOOK_DERIVED_SIGNALS: '0',
|
|
2598
|
+
}),
|
|
2599
|
+
stdio: ['ignore', 'inherit', 'inherit'],
|
|
2600
|
+
});
|
|
2601
|
+
await waitForPath(readyPath);
|
|
2602
|
+
const ready = JSON.parse(await readFile(readyPath, 'utf-8'));
|
|
2603
|
+
await writeFile(`${readyPath}.release`, `${JSON.stringify({ version: 1, kind: 'release', nonce: `${ready.nonce}-foreign`, sessionId: ready.sessionId, sessionName: ready.sessionName, leaderPid: ready.leaderPid })}\n`);
|
|
2604
|
+
await new Promise((resolveWait) => setTimeout(resolveWait, 100));
|
|
2605
|
+
assert.equal(existsSync(childReady), false);
|
|
2606
|
+
await writeFile(`${readyPath}.release`, `${JSON.stringify({ version: 1, kind: 'release', nonce: ready.nonce, sessionId: ready.sessionId, sessionName: ready.sessionName, leaderPid: ready.leaderPid })}\n`);
|
|
2607
|
+
await waitForPath(childReady);
|
|
2608
|
+
assert.match(await readFile(childEnv, 'utf-8'), /custom:test-provider/);
|
|
2609
|
+
await waitForPath(descendantPidPath);
|
|
2610
|
+
const descendantPid = Number.parseInt((await readFile(descendantPidPath, 'utf-8')).trim(), 10);
|
|
2611
|
+
assert.equal(Number.isSafeInteger(descendantPid) && descendantPid > 0, true);
|
|
2612
|
+
assert.equal(existsSync(readyPath), true);
|
|
2613
|
+
const sessionState = JSON.parse(await readFile(join(wd, '.omx', 'state', 'session.json'), 'utf-8'));
|
|
2614
|
+
assert.equal(sessionState.tmux_session_name, 'omx-detached-leader-process');
|
|
2615
|
+
assert.equal(sessionState.tmux_pane_id, '%3202');
|
|
2616
|
+
assert.equal(existsSync(join(wd, '.omx', 'state', 'detached-active-record.json')), true);
|
|
2617
|
+
await writeFile(childRelease, 'release\n');
|
|
2618
|
+
const exitCode = await new Promise((resolveExit, rejectExit) => {
|
|
2619
|
+
leader.once('error', rejectExit);
|
|
2620
|
+
leader.once('exit', resolveExit);
|
|
2621
|
+
});
|
|
2622
|
+
assert.equal(exitCode, 0);
|
|
2623
|
+
assert.throws(() => process.kill(descendantPid, 0), (error) => error.code === 'ESRCH');
|
|
2624
|
+
const terminalReport = JSON.parse(await readFile(readyPath, 'utf-8'));
|
|
2625
|
+
assert.deepEqual(terminalReport, {
|
|
2626
|
+
version: 1,
|
|
2627
|
+
kind: 'terminal',
|
|
2628
|
+
nonce: ready.nonce,
|
|
2629
|
+
sessionId: ready.sessionId,
|
|
2630
|
+
sessionName: ready.sessionName,
|
|
2631
|
+
leaderPid: ready.leaderPid,
|
|
2632
|
+
paneId: '%3202',
|
|
2633
|
+
finalized: true,
|
|
2634
|
+
exitStatus: 0,
|
|
2635
|
+
});
|
|
2636
|
+
assert.equal(existsSync(join(wd, '.omx', 'state', 'session.json')), false);
|
|
2637
|
+
assert.equal(existsSync(join(wd, '.omx', 'state', 'detached-active-record.json')), false);
|
|
2638
|
+
assert.equal(existsSync(readyPath), true);
|
|
2639
|
+
await rm(readyPath, { force: true });
|
|
2640
|
+
assert.match(await readFile(join(wd, '.omx', 'logs', 'session-history.jsonl'), 'utf-8'), new RegExp(sessionId));
|
|
2641
|
+
}
|
|
2642
|
+
finally {
|
|
2643
|
+
await rm(wd, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 });
|
|
2644
|
+
}
|
|
2645
|
+
});
|
|
2646
|
+
it('returns the attached client to the shell with the child status after a normal detached child exit', async (t) => {
|
|
2647
|
+
if (!skipUnlessPrivateRealTmux(t))
|
|
2648
|
+
return;
|
|
2649
|
+
if (!skipUnlessScriptAvailable(t))
|
|
2650
|
+
return;
|
|
2651
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-launch-detached-e2e-'));
|
|
2652
|
+
try {
|
|
2653
|
+
await withTempTmuxSession(async (fixture) => {
|
|
2654
|
+
const home = join(wd, 'home');
|
|
2655
|
+
const bin = join(wd, 'bin');
|
|
2656
|
+
await mkdir(home, { recursive: true });
|
|
2657
|
+
await mkdir(bin, { recursive: true });
|
|
2658
|
+
// The PTY is required for detached attach behavior, but it would also
|
|
2659
|
+
// trigger OMX's unrelated one-time interactive star prompt. Mark it
|
|
2660
|
+
// handled so this harness deterministically exercises only launch exit.
|
|
2661
|
+
await mkdir(join(home, '.omx', 'state'), { recursive: true });
|
|
2662
|
+
await writeFile(join(home, '.omx', 'state', 'star-prompt.json'), '{"prompted_at":"2026-01-01T00:00:00.000Z"}\n');
|
|
2663
|
+
await fixture.createPathShim(bin);
|
|
2664
|
+
const omxBin = join(dirname(fileURLToPath(import.meta.url)), '..', '..', '..', 'dist', 'cli', 'omx.js');
|
|
2665
|
+
const runPtyLaunch = (childExitStatus, remainOnExit) => {
|
|
2666
|
+
writeFileSync(join(bin, 'codex'), `#!/bin/sh\nsleep 1\nexit ${childExitStatus}\n`, { mode: 0o755 });
|
|
2667
|
+
// Simulate a user tmux config that would retain the dead leader pane.
|
|
2668
|
+
fixture.run(['set-option', '-g', 'remain-on-exit', remainOnExit]);
|
|
2669
|
+
const envPrefix = [
|
|
2670
|
+
'unset TMUX TMUX_PANE',
|
|
2671
|
+
'export OMX_TEST_DETACHED_TRACE=1',
|
|
2672
|
+
`export OMX_TEST_DETACHED_TRACE_PATH=${JSON.stringify(join(wd, 'detached-trace.log'))}`,
|
|
2673
|
+
`export HOME=${JSON.stringify(home)}`,
|
|
2674
|
+
`export PATH=${JSON.stringify(bin)}:$PATH`,
|
|
2675
|
+
'export OMX_AUTO_UPDATE=0',
|
|
2676
|
+
'export OMX_NOTIFY_FALLBACK=0',
|
|
2677
|
+
'export OMX_HOOK_DERIVED_SIGNALS=0',
|
|
2678
|
+
'export TERM=xterm-256color',
|
|
2679
|
+
].join('; ');
|
|
2680
|
+
const command = `${envPrefix}; cd ${JSON.stringify(wd)} || exit 125; ${JSON.stringify(process.execPath)} ${JSON.stringify(omxBin)} --tmux ${JSON.stringify('e2e prompt')}; child_status=$?; printf '\n%s:%s\n' 'omx-follow-up' "$child_status"; exit "$child_status"`;
|
|
2681
|
+
if (process.platform === 'darwin') {
|
|
2682
|
+
const result = fixture.runPtyResult(command, { pollLimit: DARWIN_PTY_FINALIZATION_POLL_LIMIT });
|
|
2683
|
+
let diagnostics = '';
|
|
2684
|
+
if (result.error) {
|
|
2685
|
+
const sessions = fixture.runResult(['list-sessions', '-F', '#{session_name}']);
|
|
2686
|
+
for (const session of sessions.stdout.split('\n').filter((name) => name.startsWith('omx-'))) {
|
|
2687
|
+
const panes = fixture.runResult([
|
|
2688
|
+
'list-panes', '-t', session, '-F', '#{pane_id}\t#{pane_dead}\t#{pane_dead_status}\t#{pane_pid}\t#{pane_current_command}\t#{@omx_instance_id}\t#{@omx_hud_owner}',
|
|
2689
|
+
]);
|
|
2690
|
+
diagnostics += `\n[${session}]\n${panes.stdout}`;
|
|
2691
|
+
for (const paneLine of panes.stdout.split('\n').filter(Boolean)) {
|
|
2692
|
+
const [paneId] = paneLine.split('\t', 1);
|
|
2693
|
+
if (!paneId)
|
|
2694
|
+
continue;
|
|
2695
|
+
diagnostics += `\n[${paneLine}]\n${fixture.runResult(['capture-pane', '-p', '-t', paneId, '-S', '-']).stdout}`;
|
|
2696
|
+
}
|
|
2697
|
+
}
|
|
2698
|
+
const tracePath = join(wd, 'detached-trace.log');
|
|
2699
|
+
if (existsSync(tracePath))
|
|
2700
|
+
diagnostics += `\n[detached-trace]\n${readFileSync(tracePath, 'utf-8')}`;
|
|
2701
|
+
}
|
|
2702
|
+
return { status: result.status, output: `${result.stdout}\n${result.stderr}${diagnostics}`, error: result.error };
|
|
2703
|
+
}
|
|
2704
|
+
const ptyCommand = buildPtyScriptCommand(command);
|
|
2705
|
+
const result = spawnSync(ptyCommand.executable, ptyCommand.args, {
|
|
2706
|
+
encoding: 'utf-8',
|
|
2707
|
+
timeout: 120_000,
|
|
2708
|
+
killSignal: 'SIGKILL',
|
|
2709
|
+
env: buildRunOmxEnv({ TMUX: '', TMUX_PANE: '' }),
|
|
2710
|
+
});
|
|
2711
|
+
return { status: result.status, output: `${result.stdout || ''}\n${result.stderr || ''}`, error: result.error?.message ?? '' };
|
|
2712
|
+
};
|
|
2713
|
+
const listOmxSessions = () => fixture.run(['list-sessions', '-F', '#{session_name}'])
|
|
2714
|
+
.split('\n')
|
|
2715
|
+
.filter((name) => name.startsWith('omx-') && name !== fixture.sessionName);
|
|
2716
|
+
const zero = runPtyLaunch(0, 'on');
|
|
2717
|
+
assert.equal(zero.error, '', `zero-status PTY capture and cleanup must succeed: ${zero.error}\n${zero.output}`);
|
|
2718
|
+
assert.equal(zero.status, 0, `zero-status attached launch must return to the shell successfully:\n${zero.output}`);
|
|
2719
|
+
assert.match(zero.output, /omx-follow-up:0/, `zero-status launch must leave a usable shell for a follow-up command:\n${zero.output}`);
|
|
2720
|
+
assert.deepEqual(listOmxSessions(), [], 'zero-status exit must destroy the owned detached session despite remain-on-exit=on');
|
|
2721
|
+
const seven = runPtyLaunch(7, 'failed');
|
|
2722
|
+
assert.equal(seven.error, '', `nonzero-status PTY capture and cleanup must succeed: ${seven.error}\n${seven.output}`);
|
|
2723
|
+
assert.equal(seven.status, 7, `nonzero child status must propagate to the invoking shell:\n${seven.output}`);
|
|
2724
|
+
assert.match(seven.output, /omx-follow-up:7/, `nonzero launch must leave a usable shell for a follow-up command:\n${seven.output}`);
|
|
2725
|
+
assert.deepEqual(listOmxSessions(), [], 'nonzero exit must destroy the owned detached session despite remain-on-exit=failed');
|
|
2726
|
+
});
|
|
1745
2727
|
}
|
|
1746
2728
|
finally {
|
|
1747
|
-
await rm(wd, { recursive: true, force: true });
|
|
2729
|
+
await rm(wd, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 });
|
|
1748
2730
|
}
|
|
1749
2731
|
});
|
|
1750
2732
|
});
|