oh-my-codex 0.17.3 → 0.18.1
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 +13 -5
- package/Cargo.toml +2 -1
- package/README.md +44 -19
- package/crates/omx-api/Cargo.toml +19 -0
- package/crates/omx-api/src/lib.rs +2997 -0
- package/crates/omx-api/src/main.rs +10 -0
- package/crates/omx-api/tests/cli.rs +558 -0
- package/crates/omx-explore/src/main.rs +4 -0
- package/crates/omx-sparkshell/src/codex_bridge.rs +437 -123
- package/crates/omx-sparkshell/src/exec.rs +127 -1
- package/crates/omx-sparkshell/src/main.rs +829 -30
- package/crates/omx-sparkshell/src/prompt.rs +25 -3
- package/crates/omx-sparkshell/src/redaction.rs +241 -0
- package/crates/omx-sparkshell/tests/execution.rs +702 -237
- package/dist/cli/__tests__/api.test.d.ts +2 -0
- package/dist/cli/__tests__/api.test.d.ts.map +1 -0
- package/dist/cli/__tests__/api.test.js +175 -0
- package/dist/cli/__tests__/api.test.js.map +1 -0
- package/dist/cli/__tests__/ask.test.js +72 -5
- package/dist/cli/__tests__/ask.test.js.map +1 -1
- package/dist/cli/__tests__/autoresearch-goal.test.js +14 -1
- package/dist/cli/__tests__/autoresearch-goal.test.js.map +1 -1
- package/dist/cli/__tests__/codex-plugin-layout.test.js +15 -7
- package/dist/cli/__tests__/codex-plugin-layout.test.js.map +1 -1
- package/dist/cli/__tests__/doctor-warning-copy.test.js +76 -3
- package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
- package/dist/cli/__tests__/explore.test.js +23 -0
- package/dist/cli/__tests__/explore.test.js.map +1 -1
- package/dist/cli/__tests__/index.test.js +171 -5
- package/dist/cli/__tests__/index.test.js.map +1 -1
- package/dist/cli/__tests__/install-docs-contract.test.d.ts +2 -0
- package/dist/cli/__tests__/install-docs-contract.test.d.ts.map +1 -0
- package/dist/cli/__tests__/install-docs-contract.test.js +55 -0
- package/dist/cli/__tests__/install-docs-contract.test.js.map +1 -0
- package/dist/cli/__tests__/launch-fallback.test.js +191 -0
- package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
- package/dist/cli/__tests__/package-bin-contract.test.js +4 -3
- package/dist/cli/__tests__/package-bin-contract.test.js.map +1 -1
- package/dist/cli/__tests__/question.test.js +27 -41
- package/dist/cli/__tests__/question.test.js.map +1 -1
- package/dist/cli/__tests__/setup-install-mode.test.js +232 -35
- package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
- package/dist/cli/__tests__/sparkshell-cli.test.js +25 -1
- package/dist/cli/__tests__/sparkshell-cli.test.js.map +1 -1
- package/dist/cli/__tests__/sparkshell-packaging.test.js +1 -0
- package/dist/cli/__tests__/sparkshell-packaging.test.js.map +1 -1
- package/dist/cli/__tests__/ultragoal.test.js +227 -4
- package/dist/cli/__tests__/ultragoal.test.js.map +1 -1
- package/dist/cli/__tests__/update.test.js +72 -1
- package/dist/cli/__tests__/update.test.js.map +1 -1
- package/dist/cli/__tests__/version-sync-contract.test.js +4 -0
- package/dist/cli/__tests__/version-sync-contract.test.js.map +1 -1
- package/dist/cli/__tests__/windows-popup-loop-contract.test.js +1 -1
- package/dist/cli/__tests__/windows-popup-loop-contract.test.js.map +1 -1
- package/dist/cli/api.d.ts +26 -0
- package/dist/cli/api.d.ts.map +1 -0
- package/dist/cli/api.js +153 -0
- package/dist/cli/api.js.map +1 -0
- package/dist/cli/codex-feature-probe.d.ts +5 -0
- package/dist/cli/codex-feature-probe.d.ts.map +1 -1
- package/dist/cli/codex-feature-probe.js +13 -7
- package/dist/cli/codex-feature-probe.js.map +1 -1
- package/dist/cli/doctor.d.ts +7 -0
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +119 -10
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/explore.d.ts +2 -0
- package/dist/cli/explore.d.ts.map +1 -1
- package/dist/cli/explore.js +43 -1
- package/dist/cli/explore.js.map +1 -1
- package/dist/cli/index.d.ts +12 -4
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +460 -87
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/native-assets.d.ts +2 -1
- package/dist/cli/native-assets.d.ts.map +1 -1
- package/dist/cli/native-assets.js +1 -0
- package/dist/cli/native-assets.js.map +1 -1
- package/dist/cli/plugin-marketplace.d.ts +2 -0
- package/dist/cli/plugin-marketplace.d.ts.map +1 -1
- package/dist/cli/plugin-marketplace.js +15 -1
- package/dist/cli/plugin-marketplace.js.map +1 -1
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +71 -11
- package/dist/cli/setup.js.map +1 -1
- package/dist/cli/sparkshell.d.ts +7 -1
- package/dist/cli/sparkshell.d.ts.map +1 -1
- package/dist/cli/sparkshell.js +31 -4
- 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 +184 -10
- package/dist/cli/ultragoal.js.map +1 -1
- package/dist/cli/update.d.ts +2 -0
- package/dist/cli/update.d.ts.map +1 -1
- package/dist/cli/update.js +14 -3
- package/dist/cli/update.js.map +1 -1
- package/dist/compat/__tests__/doctor-contract.test.js +3 -0
- package/dist/compat/__tests__/doctor-contract.test.js.map +1 -1
- package/dist/config/__tests__/codex-feature-flags.test.js +11 -1
- package/dist/config/__tests__/codex-feature-flags.test.js.map +1 -1
- package/dist/config/__tests__/codex-hooks.test.js +19 -8
- package/dist/config/__tests__/codex-hooks.test.js.map +1 -1
- package/dist/config/__tests__/commit-lore-guard.test.d.ts +2 -0
- package/dist/config/__tests__/commit-lore-guard.test.d.ts.map +1 -0
- package/dist/config/__tests__/commit-lore-guard.test.js +20 -0
- package/dist/config/__tests__/commit-lore-guard.test.js.map +1 -0
- package/dist/config/codex-feature-flags.d.ts +4 -0
- package/dist/config/codex-feature-flags.d.ts.map +1 -1
- package/dist/config/codex-feature-flags.js +4 -0
- package/dist/config/codex-feature-flags.js.map +1 -1
- package/dist/config/codex-hooks.js +6 -6
- package/dist/config/codex-hooks.js.map +1 -1
- package/dist/config/commit-lore-guard.d.ts +1 -0
- package/dist/config/commit-lore-guard.d.ts.map +1 -1
- package/dist/config/commit-lore-guard.js +29 -3
- package/dist/config/commit-lore-guard.js.map +1 -1
- package/dist/config/generator.d.ts +3 -1
- package/dist/config/generator.d.ts.map +1 -1
- package/dist/config/generator.js +114 -10
- package/dist/config/generator.js.map +1 -1
- package/dist/goal-workflows/codex-goal-snapshot.d.ts +1 -0
- package/dist/goal-workflows/codex-goal-snapshot.d.ts.map +1 -1
- package/dist/goal-workflows/codex-goal-snapshot.js +5 -1
- package/dist/goal-workflows/codex-goal-snapshot.js.map +1 -1
- package/dist/hooks/__tests__/autopilot-skill-contract.test.js +10 -6
- package/dist/hooks/__tests__/autopilot-skill-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/best-practice-research-skill.test.d.ts +2 -0
- package/dist/hooks/__tests__/best-practice-research-skill.test.d.ts.map +1 -0
- package/dist/hooks/__tests__/best-practice-research-skill.test.js +27 -0
- package/dist/hooks/__tests__/best-practice-research-skill.test.js.map +1 -0
- package/dist/hooks/__tests__/consensus-execution-handoff.test.d.ts +1 -1
- package/dist/hooks/__tests__/consensus-execution-handoff.test.js +13 -11
- package/dist/hooks/__tests__/consensus-execution-handoff.test.js.map +1 -1
- package/dist/hooks/__tests__/deep-interview-contract.test.js +4 -3
- package/dist/hooks/__tests__/deep-interview-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/keyword-detector.test.js +15 -3
- package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js +6 -0
- 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 +33 -0
- package/dist/hooks/__tests__/notify-hook-team-tmux-guard.test.js.map +1 -1
- package/dist/hooks/__tests__/prompt-guidance-wave-two.test.js +4 -0
- package/dist/hooks/__tests__/prompt-guidance-wave-two.test.js.map +1 -1
- package/dist/hooks/extensibility/__tests__/dispatcher.test.js +26 -3
- package/dist/hooks/extensibility/__tests__/dispatcher.test.js.map +1 -1
- package/dist/hooks/extensibility/dispatcher.d.ts.map +1 -1
- package/dist/hooks/extensibility/dispatcher.js +29 -14
- package/dist/hooks/extensibility/dispatcher.js.map +1 -1
- package/dist/hooks/keyword-detector.d.ts.map +1 -1
- package/dist/hooks/keyword-detector.js +8 -3
- package/dist/hooks/keyword-detector.js.map +1 -1
- package/dist/hooks/keyword-registry.d.ts.map +1 -1
- package/dist/hooks/keyword-registry.js +1 -0
- package/dist/hooks/keyword-registry.js.map +1 -1
- package/dist/hooks/prompt-guidance-contract.d.ts.map +1 -1
- package/dist/hooks/prompt-guidance-contract.js +3 -2
- package/dist/hooks/prompt-guidance-contract.js.map +1 -1
- package/dist/hud/__tests__/hud-tmux-injection.test.js +14 -8
- package/dist/hud/__tests__/hud-tmux-injection.test.js.map +1 -1
- package/dist/hud/__tests__/reconcile.test.js +4 -4
- package/dist/hud/__tests__/reconcile.test.js.map +1 -1
- package/dist/hud/__tests__/resource-leak-watch.test.d.ts +2 -0
- package/dist/hud/__tests__/resource-leak-watch.test.d.ts.map +1 -0
- package/dist/hud/__tests__/resource-leak-watch.test.js +28 -0
- package/dist/hud/__tests__/resource-leak-watch.test.js.map +1 -0
- package/dist/hud/__tests__/tmux.test.js +23 -18
- package/dist/hud/__tests__/tmux.test.js.map +1 -1
- package/dist/hud/index.d.ts +1 -1
- package/dist/hud/index.d.ts.map +1 -1
- package/dist/hud/index.js +10 -4
- package/dist/hud/index.js.map +1 -1
- package/dist/hud/tmux.d.ts.map +1 -1
- package/dist/hud/tmux.js +9 -8
- package/dist/hud/tmux.js.map +1 -1
- package/dist/mcp/__tests__/bootstrap.test.js +75 -1
- package/dist/mcp/__tests__/bootstrap.test.js.map +1 -1
- package/dist/mcp/bootstrap.d.ts +3 -1
- package/dist/mcp/bootstrap.d.ts.map +1 -1
- package/dist/mcp/bootstrap.js +71 -2
- package/dist/mcp/bootstrap.js.map +1 -1
- package/dist/notifications/__tests__/http-client-resource.test.d.ts +2 -0
- package/dist/notifications/__tests__/http-client-resource.test.d.ts.map +1 -0
- package/dist/notifications/__tests__/http-client-resource.test.js +41 -0
- package/dist/notifications/__tests__/http-client-resource.test.js.map +1 -0
- package/dist/notifications/__tests__/verbosity.test.js +20 -0
- package/dist/notifications/__tests__/verbosity.test.js.map +1 -1
- package/dist/notifications/config.d.ts.map +1 -1
- package/dist/notifications/config.js +6 -3
- package/dist/notifications/config.js.map +1 -1
- package/dist/notifications/http-client.d.ts.map +1 -1
- package/dist/notifications/http-client.js +78 -27
- package/dist/notifications/http-client.js.map +1 -1
- package/dist/notifications/types.d.ts +2 -0
- package/dist/notifications/types.d.ts.map +1 -1
- package/dist/openclaw/__tests__/dispatcher.test.js +49 -1
- package/dist/openclaw/__tests__/dispatcher.test.js.map +1 -1
- package/dist/openclaw/dispatcher.d.ts +7 -4
- package/dist/openclaw/dispatcher.d.ts.map +1 -1
- package/dist/openclaw/dispatcher.js +32 -69
- package/dist/openclaw/dispatcher.js.map +1 -1
- package/dist/pipeline/__tests__/orchestrator.test.js +65 -3
- package/dist/pipeline/__tests__/orchestrator.test.js.map +1 -1
- package/dist/pipeline/__tests__/stages.test.js +50 -5
- package/dist/pipeline/__tests__/stages.test.js.map +1 -1
- package/dist/pipeline/index.d.ts +8 -2
- package/dist/pipeline/index.d.ts.map +1 -1
- package/dist/pipeline/index.js +5 -2
- package/dist/pipeline/index.js.map +1 -1
- package/dist/pipeline/orchestrator.d.ts +5 -4
- package/dist/pipeline/orchestrator.d.ts.map +1 -1
- package/dist/pipeline/orchestrator.js +56 -15
- package/dist/pipeline/orchestrator.js.map +1 -1
- package/dist/pipeline/stages/code-review.d.ts +2 -2
- package/dist/pipeline/stages/code-review.d.ts.map +1 -1
- package/dist/pipeline/stages/code-review.js +5 -3
- package/dist/pipeline/stages/code-review.js.map +1 -1
- package/dist/pipeline/stages/deep-interview.d.ts +15 -0
- package/dist/pipeline/stages/deep-interview.d.ts.map +1 -0
- package/dist/pipeline/stages/deep-interview.js +32 -0
- package/dist/pipeline/stages/deep-interview.js.map +1 -0
- package/dist/pipeline/stages/ralph-verify.d.ts +5 -5
- package/dist/pipeline/stages/ralph-verify.d.ts.map +1 -1
- package/dist/pipeline/stages/ralph-verify.js +2 -2
- package/dist/pipeline/stages/ralph-verify.js.map +1 -1
- package/dist/pipeline/stages/ultragoal.d.ts +19 -0
- package/dist/pipeline/stages/ultragoal.d.ts.map +1 -0
- package/dist/pipeline/stages/ultragoal.js +38 -0
- package/dist/pipeline/stages/ultragoal.js.map +1 -0
- package/dist/pipeline/stages/ultraqa.d.ts +30 -0
- package/dist/pipeline/stages/ultraqa.d.ts.map +1 -0
- package/dist/pipeline/stages/ultraqa.js +46 -0
- package/dist/pipeline/stages/ultraqa.js.map +1 -0
- package/dist/pipeline/types.d.ts +8 -6
- package/dist/pipeline/types.d.ts.map +1 -1
- package/dist/pipeline/types.js +2 -2
- package/dist/scripts/__tests__/codex-native-hook.test.js +1488 -117
- package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
- package/dist/scripts/__tests__/notify-dispatcher.test.js +183 -1
- package/dist/scripts/__tests__/notify-dispatcher.test.js.map +1 -1
- package/dist/scripts/__tests__/smoke-packed-install.test.js +27 -2
- package/dist/scripts/__tests__/smoke-packed-install.test.js.map +1 -1
- package/dist/scripts/__tests__/verify-native-agents.test.js +16 -1
- package/dist/scripts/__tests__/verify-native-agents.test.js.map +1 -1
- package/dist/scripts/build-api.d.ts +2 -0
- package/dist/scripts/build-api.d.ts.map +1 -0
- package/dist/scripts/build-api.js +44 -0
- package/dist/scripts/build-api.js.map +1 -0
- package/dist/scripts/cleanup-explore-harness.js +1 -0
- package/dist/scripts/cleanup-explore-harness.js.map +1 -1
- package/dist/scripts/codex-native-hook.d.ts.map +1 -1
- package/dist/scripts/codex-native-hook.js +364 -16
- package/dist/scripts/codex-native-hook.js.map +1 -1
- package/dist/scripts/codex-native-pre-post.d.ts.map +1 -1
- package/dist/scripts/codex-native-pre-post.js +98 -25
- package/dist/scripts/codex-native-pre-post.js.map +1 -1
- package/dist/scripts/notify-dispatcher.js +88 -0
- package/dist/scripts/notify-dispatcher.js.map +1 -1
- package/dist/scripts/notify-hook/process-runner.d.ts.map +1 -1
- package/dist/scripts/notify-hook/process-runner.js +39 -17
- package/dist/scripts/notify-hook/process-runner.js.map +1 -1
- package/dist/scripts/notify-hook/team-dispatch.d.ts.map +1 -1
- package/dist/scripts/notify-hook/team-dispatch.js +36 -14
- 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 +26 -11
- package/dist/scripts/notify-hook/team-leader-nudge.js.map +1 -1
- package/dist/scripts/notify-hook/team-tmux-guard.d.ts +2 -1
- package/dist/scripts/notify-hook/team-tmux-guard.d.ts.map +1 -1
- package/dist/scripts/notify-hook/team-tmux-guard.js +45 -1
- 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 +27 -14
- package/dist/scripts/notify-hook/team-worker-stop.js.map +1 -1
- package/dist/scripts/run-provider-advisor.js +9 -3
- package/dist/scripts/run-provider-advisor.js.map +1 -1
- package/dist/scripts/smoke-packed-install.d.ts +4 -1
- package/dist/scripts/smoke-packed-install.d.ts.map +1 -1
- package/dist/scripts/smoke-packed-install.js +101 -1
- package/dist/scripts/smoke-packed-install.js.map +1 -1
- package/dist/scripts/sync-plugin-mirror.js +2 -2
- package/dist/scripts/sync-plugin-mirror.js.map +1 -1
- package/dist/scripts/verify-native-agents.js +2 -2
- package/dist/scripts/verify-native-agents.js.map +1 -1
- package/dist/sidecar/__tests__/resource-leak-watch.test.d.ts +2 -0
- package/dist/sidecar/__tests__/resource-leak-watch.test.d.ts.map +1 -0
- package/dist/sidecar/__tests__/resource-leak-watch.test.js +38 -0
- package/dist/sidecar/__tests__/resource-leak-watch.test.js.map +1 -0
- package/dist/sidecar/index.d.ts +1 -1
- package/dist/sidecar/index.d.ts.map +1 -1
- package/dist/sidecar/index.js +29 -12
- package/dist/sidecar/index.js.map +1 -1
- package/dist/state/__tests__/operations-ralph-phase.test.js +88 -1
- package/dist/state/__tests__/operations-ralph-phase.test.js.map +1 -1
- package/dist/state/operations.d.ts.map +1 -1
- package/dist/state/operations.js +11 -0
- package/dist/state/operations.js.map +1 -1
- package/dist/team/__tests__/runtime.test.js +2 -2
- package/dist/team/__tests__/runtime.test.js.map +1 -1
- package/dist/team/__tests__/tmux-session.test.js +207 -22
- package/dist/team/__tests__/tmux-session.test.js.map +1 -1
- package/dist/team/tmux-session.d.ts +1 -0
- package/dist/team/tmux-session.d.ts.map +1 -1
- package/dist/team/tmux-session.js +73 -28
- package/dist/team/tmux-session.js.map +1 -1
- package/dist/ultragoal/__tests__/artifacts.test.js +714 -10
- package/dist/ultragoal/__tests__/artifacts.test.js.map +1 -1
- package/dist/ultragoal/__tests__/docs-contract.test.js +57 -1
- package/dist/ultragoal/__tests__/docs-contract.test.js.map +1 -1
- package/dist/ultragoal/__tests__/steering-fixtures.d.ts +68 -0
- package/dist/ultragoal/__tests__/steering-fixtures.d.ts.map +1 -0
- package/dist/ultragoal/__tests__/steering-fixtures.js +259 -0
- package/dist/ultragoal/__tests__/steering-fixtures.js.map +1 -0
- package/dist/ultragoal/__tests__/steering-fixtures.test.d.ts +2 -0
- package/dist/ultragoal/__tests__/steering-fixtures.test.d.ts.map +1 -0
- package/dist/ultragoal/__tests__/steering-fixtures.test.js +65 -0
- package/dist/ultragoal/__tests__/steering-fixtures.test.js.map +1 -0
- package/dist/ultragoal/artifacts.d.ts +97 -2
- package/dist/ultragoal/artifacts.d.ts.map +1 -1
- package/dist/ultragoal/artifacts.js +811 -256
- package/dist/ultragoal/artifacts.js.map +1 -1
- package/dist/utils/__tests__/sleep-resource.test.d.ts +2 -0
- package/dist/utils/__tests__/sleep-resource.test.d.ts.map +1 -0
- package/dist/utils/__tests__/sleep-resource.test.js +39 -0
- package/dist/utils/__tests__/sleep-resource.test.js.map +1 -0
- package/dist/utils/sleep.d.ts.map +1 -1
- package/dist/utils/sleep.js +17 -6
- package/dist/utils/sleep.js.map +1 -1
- package/dist/verification/__tests__/ci-rust-gates.test.js +85 -10
- package/dist/verification/__tests__/ci-rust-gates.test.js.map +1 -1
- package/dist/verification/__tests__/explore-harness-release-workflow.test.js +1 -0
- package/dist/verification/__tests__/explore-harness-release-workflow.test.js.map +1 -1
- package/package.json +5 -3
- package/plugins/oh-my-codex/.codex-plugin/plugin.json +4 -3
- package/plugins/oh-my-codex/hooks/codex-native-hook.mjs +56 -0
- package/plugins/oh-my-codex/hooks/hooks.json +77 -0
- package/plugins/oh-my-codex/skills/autopilot/SKILL.md +77 -47
- package/plugins/oh-my-codex/skills/best-practice-research/SKILL.md +83 -0
- package/plugins/oh-my-codex/skills/cancel/SKILL.md +2 -2
- package/plugins/oh-my-codex/skills/deep-interview/SKILL.md +9 -8
- package/plugins/oh-my-codex/skills/omx-setup/SKILL.md +1 -1
- package/plugins/oh-my-codex/skills/pipeline/SKILL.md +22 -11
- package/plugins/oh-my-codex/skills/plan/SKILL.md +8 -8
- package/plugins/oh-my-codex/skills/ralph/SKILL.md +7 -0
- package/plugins/oh-my-codex/skills/ralplan/SKILL.md +5 -5
- package/plugins/oh-my-codex/skills/team/SKILL.md +1 -1
- package/plugins/oh-my-codex/skills/ultragoal/SKILL.md +38 -4
- package/plugins/oh-my-codex/skills/ultrawork/SKILL.md +1 -1
- package/prompts/planner.md +1 -1
- package/prompts/researcher.md +15 -10
- package/skills/autopilot/SKILL.md +77 -47
- package/skills/best-practice-research/SKILL.md +83 -0
- package/skills/cancel/SKILL.md +2 -2
- package/skills/deep-interview/SKILL.md +9 -8
- package/skills/omx-setup/SKILL.md +1 -1
- package/skills/pipeline/SKILL.md +22 -11
- package/skills/plan/SKILL.md +8 -8
- package/skills/ralph/SKILL.md +7 -0
- package/skills/ralplan/SKILL.md +5 -5
- package/skills/team/SKILL.md +1 -1
- package/skills/ultragoal/SKILL.md +38 -4
- package/skills/ultrawork/SKILL.md +1 -1
- package/src/scripts/__tests__/codex-native-hook.test.ts +1758 -166
- package/src/scripts/__tests__/notify-dispatcher.test.ts +223 -1
- package/src/scripts/__tests__/smoke-packed-install.test.ts +39 -2
- package/src/scripts/__tests__/verify-native-agents.test.ts +21 -1
- package/src/scripts/build-api.ts +48 -0
- package/src/scripts/cleanup-explore-harness.ts +1 -0
- package/src/scripts/codex-native-hook.ts +416 -18
- package/src/scripts/codex-native-pre-post.ts +119 -25
- package/src/scripts/notify-dispatcher.ts +97 -0
- package/src/scripts/notify-hook/process-runner.ts +40 -16
- package/src/scripts/notify-hook/team-dispatch.ts +36 -13
- package/src/scripts/notify-hook/team-leader-nudge.ts +25 -11
- package/src/scripts/notify-hook/team-tmux-guard.ts +49 -0
- package/src/scripts/notify-hook/team-worker-stop.ts +24 -13
- package/src/scripts/run-provider-advisor.ts +11 -3
- package/src/scripts/smoke-packed-install.ts +107 -0
- package/src/scripts/sync-plugin-mirror.ts +3 -3
- package/src/scripts/verify-native-agents.ts +2 -2
- package/templates/catalog-manifest.json +7 -0
package/dist/cli/index.js
CHANGED
|
@@ -7,6 +7,7 @@ import { basename, dirname, join, posix, win32 } from "path";
|
|
|
7
7
|
import { existsSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from "fs";
|
|
8
8
|
import { copyFile, cp, lstat, mkdir, readFile, readdir, rm, symlink, writeFile } from "fs/promises";
|
|
9
9
|
import { constants as osConstants, homedir } from "os";
|
|
10
|
+
import { createHash } from "crypto";
|
|
10
11
|
import { setup, SETUP_MCP_MODES, SETUP_SCOPES, } from "./setup.js";
|
|
11
12
|
import { uninstall } from "./uninstall.js";
|
|
12
13
|
import { version } from "./version.js";
|
|
@@ -24,6 +25,7 @@ import { stateCommand } from "./state.js";
|
|
|
24
25
|
import { cleanupCommand, cleanupOmxMcpProcesses, findLaunchSafeCleanupCandidates, } from "./cleanup.js";
|
|
25
26
|
import { exploreCommand } from "./explore.js";
|
|
26
27
|
import { sparkshellCommand } from "./sparkshell.js";
|
|
28
|
+
import { apiCommand } from "./api.js";
|
|
27
29
|
import { agentsInitCommand } from "./agents-init.js";
|
|
28
30
|
import { agentsCommand } from "./agents.js";
|
|
29
31
|
import { sessionCommand } from "./session-search.js";
|
|
@@ -51,7 +53,7 @@ import { cleanCodexModelAvailabilityNuxIfNeeded, extractSharedMcpRegistryServers
|
|
|
51
53
|
import { OMX_FIRST_PARTY_MCP_SERVER_NAMES } from "../config/omx-first-party-mcp.js";
|
|
52
54
|
import { HUD_TMUX_HEIGHT_LINES } from "../hud/constants.js";
|
|
53
55
|
import { OMX_TMUX_HUD_OWNER_ENV } from "../hud/reconcile.js";
|
|
54
|
-
import { createHudWatchPane as createSharedHudWatchPane, killTmuxPane as killSharedTmuxPane, listCurrentWindowHudPaneIds, parsePaneIdFromTmuxOutput, } from "../hud/tmux.js";
|
|
56
|
+
import { createHudWatchPane as createSharedHudWatchPane, killTmuxPane as killSharedTmuxPane, listCurrentWindowHudPaneIds, parsePaneIdFromTmuxOutput, registerHudResizeHook, } from "../hud/tmux.js";
|
|
55
57
|
export { parseTmuxPaneSnapshot, isHudWatchPane, findHudWatchPaneIds } from "../hud/tmux.js";
|
|
56
58
|
rememberOmxLaunchContext();
|
|
57
59
|
import { classifySpawnError, resolveTmuxBinaryForPlatform, spawnPlatformCommandSync, } from "../utils/platform-command.js";
|
|
@@ -98,6 +100,7 @@ Usage:
|
|
|
98
100
|
omx adapt Scaffold OMX-owned adapter foundations for persistent external targets
|
|
99
101
|
omx resume Resume a previous interactive Codex session
|
|
100
102
|
omx explore Default read-only exploration entrypoint (may adaptively use sparkshell backend)
|
|
103
|
+
omx api Run native omx-api localhost gateway commands (serve|status|stop|generate)
|
|
101
104
|
omx session Search prior local session transcripts and history artifacts
|
|
102
105
|
omx agents-init [path]
|
|
103
106
|
Bootstrap lightweight AGENTS.md files for a repo/subtree
|
|
@@ -250,6 +253,7 @@ const NESTED_HELP_COMMANDS = new Set([
|
|
|
250
253
|
"performance-goal",
|
|
251
254
|
"resume",
|
|
252
255
|
"session",
|
|
256
|
+
"api",
|
|
253
257
|
"sparkshell",
|
|
254
258
|
"team",
|
|
255
259
|
"tmux-hook",
|
|
@@ -475,6 +479,27 @@ async function linkOrCopyCodexHomeEntry(source, destination) {
|
|
|
475
479
|
function isCodexSqliteArtifact(entryName) {
|
|
476
480
|
return /^(?:state|logs)_\d+\.sqlite(?:-(?:shm|wal))?$/.test(entryName);
|
|
477
481
|
}
|
|
482
|
+
const PROJECT_LAUNCH_PERSISTED_RUNTIME_ENTRY_NAMES = new Set([
|
|
483
|
+
// Codex CLI writes browser/OTP login state here when CODEX_HOME points at
|
|
484
|
+
// the per-session mirror. Persist only the opaque file itself; never parse or
|
|
485
|
+
// log the contents.
|
|
486
|
+
"auth.json",
|
|
487
|
+
]);
|
|
488
|
+
function shouldPersistProjectLaunchRuntimeEntry(entryName) {
|
|
489
|
+
return PROJECT_LAUNCH_PERSISTED_RUNTIME_ENTRY_NAMES.has(entryName);
|
|
490
|
+
}
|
|
491
|
+
export async function persistProjectLaunchRuntimeAuthState(runtimeCodexHome, projectCodexHome) {
|
|
492
|
+
if (!runtimeCodexHome || !projectCodexHome)
|
|
493
|
+
return;
|
|
494
|
+
if (!existsSync(runtimeCodexHome))
|
|
495
|
+
return;
|
|
496
|
+
await mkdir(projectCodexHome, { recursive: true });
|
|
497
|
+
for (const entry of await readdir(runtimeCodexHome, { withFileTypes: true })) {
|
|
498
|
+
if (!shouldPersistProjectLaunchRuntimeEntry(entry.name) || !entry.isFile())
|
|
499
|
+
continue;
|
|
500
|
+
await copyFile(join(runtimeCodexHome, entry.name), join(projectCodexHome, entry.name));
|
|
501
|
+
}
|
|
502
|
+
}
|
|
478
503
|
/**
|
|
479
504
|
* Project-scope setup keeps durable Codex config under <repo>/.codex, but the
|
|
480
505
|
* Codex TUI also stores model-availability NUX counters in CODEX_HOME/config.toml.
|
|
@@ -530,9 +555,10 @@ export async function prepareCodexHomeForLaunch(cwd, sessionId, env = process.en
|
|
|
530
555
|
projectLocalCodexHomeForCleanup,
|
|
531
556
|
};
|
|
532
557
|
}
|
|
533
|
-
async function cleanupRuntimeCodexHome(runtimeCodexHomeForCleanup) {
|
|
558
|
+
async function cleanupRuntimeCodexHome(runtimeCodexHomeForCleanup, projectCodexHomeForPersistence) {
|
|
534
559
|
if (!runtimeCodexHomeForCleanup)
|
|
535
560
|
return;
|
|
561
|
+
await persistProjectLaunchRuntimeAuthState(runtimeCodexHomeForCleanup, projectCodexHomeForPersistence);
|
|
536
562
|
await rm(runtimeCodexHomeForCleanup, { recursive: true, force: true });
|
|
537
563
|
}
|
|
538
564
|
function execTmuxFileSync(args, options) {
|
|
@@ -797,22 +823,192 @@ export function shouldAutoIsolateMadmaxLaunch(command, launchArgs, env = process
|
|
|
797
823
|
function sanitizeRunIdSegment(value) {
|
|
798
824
|
return value.replace(/[^a-zA-Z0-9._-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
799
825
|
}
|
|
826
|
+
const MADMAX_DETACHED_ACTIVE_DIR = "active-detached";
|
|
827
|
+
const MADMAX_DETACHED_LOCK_STALE_MS = 30_000;
|
|
828
|
+
const MADMAX_DETACHED_LOCK_RETRY_MS = 50;
|
|
829
|
+
const MADMAX_DETACHED_LOCK_MAX_ATTEMPTS = 100;
|
|
830
|
+
const OMX_MADMAX_DETACHED_CONTEXT_ENV = "OMX_MADMAX_DETACHED_CONTEXT";
|
|
831
|
+
function resolveMadmaxRunsRoot(env = process.env) {
|
|
832
|
+
return env.OMX_RUNS_DIR || join(homedir(), ".omx-runs");
|
|
833
|
+
}
|
|
834
|
+
function canonicalizeLaunchCwd(cwd) {
|
|
835
|
+
try {
|
|
836
|
+
return execFileSync("git", ["rev-parse", "--show-toplevel"], {
|
|
837
|
+
cwd,
|
|
838
|
+
encoding: "utf-8",
|
|
839
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
840
|
+
}).trim() || cwd;
|
|
841
|
+
}
|
|
842
|
+
catch {
|
|
843
|
+
return cwd;
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
function normalizeMadmaxDetachedLaunchArgv(argv) {
|
|
847
|
+
const passthrough = [];
|
|
848
|
+
const semanticFlags = new Set();
|
|
849
|
+
let reasoningFlag = null;
|
|
850
|
+
let afterEndOfOptions = false;
|
|
851
|
+
for (const arg of argv) {
|
|
852
|
+
if (afterEndOfOptions) {
|
|
853
|
+
passthrough.push(arg);
|
|
854
|
+
continue;
|
|
855
|
+
}
|
|
856
|
+
if (arg === "--") {
|
|
857
|
+
afterEndOfOptions = true;
|
|
858
|
+
passthrough.push(arg);
|
|
859
|
+
continue;
|
|
860
|
+
}
|
|
861
|
+
if (arg === "--tmux" || arg === "--direct") {
|
|
862
|
+
continue;
|
|
863
|
+
}
|
|
864
|
+
if (arg === MADMAX_FLAG ||
|
|
865
|
+
arg === MADMAX_SPARK_FLAG) {
|
|
866
|
+
semanticFlags.add(arg);
|
|
867
|
+
continue;
|
|
868
|
+
}
|
|
869
|
+
if (arg === HIGH_REASONING_FLAG || arg === XHIGH_REASONING_FLAG) {
|
|
870
|
+
reasoningFlag = arg;
|
|
871
|
+
continue;
|
|
872
|
+
}
|
|
873
|
+
passthrough.push(arg);
|
|
874
|
+
}
|
|
875
|
+
return [
|
|
876
|
+
...Array.from(semanticFlags).sort(),
|
|
877
|
+
...(reasoningFlag ? [reasoningFlag] : []),
|
|
878
|
+
...passthrough,
|
|
879
|
+
];
|
|
880
|
+
}
|
|
881
|
+
export function buildMadmaxDetachedLaunchContextKey(sourceCwd, argv) {
|
|
882
|
+
const payload = JSON.stringify({
|
|
883
|
+
source_cwd: canonicalizeLaunchCwd(sourceCwd),
|
|
884
|
+
argv: normalizeMadmaxDetachedLaunchArgv(argv),
|
|
885
|
+
});
|
|
886
|
+
return createHash("sha256").update(payload).digest("hex").slice(0, 32);
|
|
887
|
+
}
|
|
888
|
+
function madmaxDetachedActiveRecordPath(runsRoot, contextKey) {
|
|
889
|
+
return join(runsRoot, MADMAX_DETACHED_ACTIVE_DIR, `${contextKey}.json`);
|
|
890
|
+
}
|
|
891
|
+
function readMadmaxDetachedActiveRecord(recordPath) {
|
|
892
|
+
if (!existsSync(recordPath))
|
|
893
|
+
return null;
|
|
894
|
+
try {
|
|
895
|
+
const parsed = JSON.parse(readFileSync(recordPath, "utf-8"));
|
|
896
|
+
if (parsed.version !== 1 ||
|
|
897
|
+
typeof parsed.context_key !== "string" ||
|
|
898
|
+
typeof parsed.source_cwd !== "string" ||
|
|
899
|
+
typeof parsed.run_dir !== "string" ||
|
|
900
|
+
typeof parsed.tmux_session_name !== "string" ||
|
|
901
|
+
!Array.isArray(parsed.argv) ||
|
|
902
|
+
!parsed.argv.every((arg) => typeof arg === "string")) {
|
|
903
|
+
return null;
|
|
904
|
+
}
|
|
905
|
+
return {
|
|
906
|
+
version: 1,
|
|
907
|
+
context_key: parsed.context_key,
|
|
908
|
+
created_at: typeof parsed.created_at === "string" ? parsed.created_at : "",
|
|
909
|
+
source_cwd: parsed.source_cwd,
|
|
910
|
+
argv: [...parsed.argv],
|
|
911
|
+
run_dir: parsed.run_dir,
|
|
912
|
+
tmux_session_name: parsed.tmux_session_name,
|
|
913
|
+
};
|
|
914
|
+
}
|
|
915
|
+
catch {
|
|
916
|
+
return null;
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
function detachedTmuxSessionExists(sessionName) {
|
|
920
|
+
try {
|
|
921
|
+
execTmuxFileSync(["has-session", "-t", sessionName], { stdio: "ignore" });
|
|
922
|
+
return true;
|
|
923
|
+
}
|
|
924
|
+
catch {
|
|
925
|
+
return false;
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
function withMadmaxDetachedContextLock(runsRoot, contextKey, run) {
|
|
929
|
+
const lockPath = join(runsRoot, MADMAX_DETACHED_ACTIVE_DIR, `${contextKey}.lock`);
|
|
930
|
+
mkdirSync(dirname(lockPath), { recursive: true });
|
|
931
|
+
for (let attempt = 0; attempt < MADMAX_DETACHED_LOCK_MAX_ATTEMPTS; attempt++) {
|
|
932
|
+
try {
|
|
933
|
+
mkdirSync(lockPath);
|
|
934
|
+
try {
|
|
935
|
+
writeFileSync(join(lockPath, "pid"), String(process.pid));
|
|
936
|
+
return run();
|
|
937
|
+
}
|
|
938
|
+
finally {
|
|
939
|
+
rmSync(lockPath, { recursive: true, force: true });
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
catch (err) {
|
|
943
|
+
const code = err && typeof err === "object" && "code" in err
|
|
944
|
+
? String(err.code)
|
|
945
|
+
: "";
|
|
946
|
+
if (code !== "EEXIST")
|
|
947
|
+
throw err;
|
|
948
|
+
const lockStat = statSync(lockPath, { throwIfNoEntry: false });
|
|
949
|
+
if (lockStat && Date.now() - lockStat.mtimeMs > MADMAX_DETACHED_LOCK_STALE_MS) {
|
|
950
|
+
let holderAlive = false;
|
|
951
|
+
try {
|
|
952
|
+
const holderPid = Number.parseInt(readFileSync(join(lockPath, "pid"), "utf-8").trim(), 10);
|
|
953
|
+
if (Number.isSafeInteger(holderPid) && holderPid > 0) {
|
|
954
|
+
process.kill(holderPid, 0);
|
|
955
|
+
holderAlive = true;
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
catch {
|
|
959
|
+
holderAlive = false;
|
|
960
|
+
}
|
|
961
|
+
if (!holderAlive) {
|
|
962
|
+
rmSync(lockPath, { recursive: true, force: true });
|
|
963
|
+
continue;
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
blockMs(MADMAX_DETACHED_LOCK_RETRY_MS);
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
throw new MadmaxDetachedGuardError(`timed out waiting for madmax detached launch context lock: ${lockPath}`);
|
|
970
|
+
}
|
|
971
|
+
function isMadmaxDetachedGuardEnabled(env) {
|
|
972
|
+
return env.OMXBOX_ACTIVE === "1" && typeof env[OMX_MADMAX_DETACHED_CONTEXT_ENV] === "string";
|
|
973
|
+
}
|
|
974
|
+
function cleanupCurrentMadmaxReuseRunRoot(env, runsRoot) {
|
|
975
|
+
const runRoot = env.OMX_ROOT;
|
|
976
|
+
if (!runRoot || !env.OMXBOX_ACTIVE)
|
|
977
|
+
return;
|
|
978
|
+
const normalizedRunsRoot = runsRoot.endsWith("/") ? runsRoot : `${runsRoot}/`;
|
|
979
|
+
if (runRoot !== runsRoot && !runRoot.startsWith(normalizedRunsRoot))
|
|
980
|
+
return;
|
|
981
|
+
rmSync(runRoot, { recursive: true, force: true });
|
|
982
|
+
}
|
|
983
|
+
function writeMadmaxDetachedActiveRecord(recordPath, record) {
|
|
984
|
+
mkdirSync(dirname(recordPath), { recursive: true });
|
|
985
|
+
writeFileSync(recordPath, `${JSON.stringify(record, null, 2)}\n`, { mode: 0o600 });
|
|
986
|
+
}
|
|
987
|
+
class MadmaxDetachedReuseError extends Error {
|
|
988
|
+
failClosed = true;
|
|
989
|
+
}
|
|
990
|
+
class MadmaxDetachedGuardError extends Error {
|
|
991
|
+
failClosed = true;
|
|
992
|
+
}
|
|
800
993
|
export function createMadmaxIsolatedRoot(sourceCwd, argv, env = process.env) {
|
|
801
|
-
const runsRoot = env
|
|
994
|
+
const runsRoot = resolveMadmaxRunsRoot(env);
|
|
802
995
|
mkdirSync(runsRoot, { recursive: true });
|
|
803
996
|
const stamp = new Date().toISOString().replace(/[-:TZ.]/g, "").slice(0, 14);
|
|
804
997
|
const suffix = Math.random().toString(16).slice(2, 6);
|
|
805
998
|
const runDir = join(runsRoot, sanitizeRunIdSegment(`run-${stamp}-${suffix}`));
|
|
806
999
|
mkdirSync(runDir, { recursive: false });
|
|
1000
|
+
const detachedLaunchContext = buildMadmaxDetachedLaunchContextKey(sourceCwd, argv);
|
|
807
1001
|
const metadata = {
|
|
808
1002
|
launcher: "omx --madmax",
|
|
809
1003
|
created_at: new Date().toISOString(),
|
|
810
1004
|
cwd: runDir,
|
|
811
1005
|
source_cwd: sourceCwd,
|
|
812
1006
|
argv,
|
|
1007
|
+
detached_launch_context: detachedLaunchContext,
|
|
813
1008
|
};
|
|
814
1009
|
writeFileSync(join(runDir, ".omxbox-run.json"), `${JSON.stringify(metadata, null, 2)}\n`);
|
|
815
1010
|
writeFileSync(join(runsRoot, "registry.jsonl"), `${JSON.stringify(metadata)}\n`, { flag: "a" });
|
|
1011
|
+
env[OMX_MADMAX_DETACHED_CONTEXT_ENV] = detachedLaunchContext;
|
|
816
1012
|
return runDir;
|
|
817
1013
|
}
|
|
818
1014
|
function activateMadmaxIsolationIfNeeded(command, launchArgs, cwd, env = process.env) {
|
|
@@ -843,6 +1039,7 @@ export async function main(args) {
|
|
|
843
1039
|
"autoresearch",
|
|
844
1040
|
"autoresearch-goal",
|
|
845
1041
|
"explore",
|
|
1042
|
+
"api",
|
|
846
1043
|
"sparkshell",
|
|
847
1044
|
"team",
|
|
848
1045
|
"ralph",
|
|
@@ -947,6 +1144,9 @@ export async function main(args) {
|
|
|
947
1144
|
case "explore":
|
|
948
1145
|
await exploreCommand(args.slice(1));
|
|
949
1146
|
break;
|
|
1147
|
+
case "api":
|
|
1148
|
+
await apiCommand(args.slice(1));
|
|
1149
|
+
break;
|
|
950
1150
|
case "exec":
|
|
951
1151
|
if (launchArgs[0] === "inject") {
|
|
952
1152
|
await execInjectCommand(launchArgs);
|
|
@@ -1021,6 +1221,11 @@ export async function main(args) {
|
|
|
1021
1221
|
case "reasoning":
|
|
1022
1222
|
await reasoningCommand(args.slice(1));
|
|
1023
1223
|
break;
|
|
1224
|
+
case "codex-native-hook": {
|
|
1225
|
+
const { runCodexNativeHookCli } = await import("../scripts/codex-native-hook.js");
|
|
1226
|
+
await runCodexNativeHookCli();
|
|
1227
|
+
break;
|
|
1228
|
+
}
|
|
1024
1229
|
case "help":
|
|
1025
1230
|
case "--help":
|
|
1026
1231
|
case "-h":
|
|
@@ -1220,7 +1425,7 @@ export async function launchWithHud(args) {
|
|
|
1220
1425
|
// ── Phase 3: postLaunch ─────────────────────────────────────────────
|
|
1221
1426
|
if (!postLaunchHandledExternally) {
|
|
1222
1427
|
await postLaunch(cwd, sessionId, codexHomeOverride, enableNotifyFallbackAuthority, projectLocalCodexHomeForCleanup);
|
|
1223
|
-
await cleanupRuntimeCodexHome(preparedCodexHome.runtimeCodexHomeForCleanup).catch(logCliOperationFailure);
|
|
1428
|
+
await cleanupRuntimeCodexHome(preparedCodexHome.runtimeCodexHomeForCleanup, projectLocalCodexHomeForCleanup).catch(logCliOperationFailure);
|
|
1224
1429
|
}
|
|
1225
1430
|
}
|
|
1226
1431
|
}
|
|
@@ -1312,7 +1517,7 @@ export async function execWithOverlay(args) {
|
|
|
1312
1517
|
}
|
|
1313
1518
|
finally {
|
|
1314
1519
|
await postLaunch(cwd, sessionId, codexHomeOverride, true, projectLocalCodexHomeForCleanup);
|
|
1315
|
-
await cleanupRuntimeCodexHome(preparedCodexHome.runtimeCodexHomeForCleanup).catch(logCliOperationFailure);
|
|
1520
|
+
await cleanupRuntimeCodexHome(preparedCodexHome.runtimeCodexHomeForCleanup, projectLocalCodexHomeForCleanup).catch(logCliOperationFailure);
|
|
1316
1521
|
}
|
|
1317
1522
|
}
|
|
1318
1523
|
export function normalizeCodexLaunchArgs(args) {
|
|
@@ -1833,10 +2038,16 @@ function withTmuxExtendedKeysLeaseLock(cwd, socketPath, run) {
|
|
|
1833
2038
|
}
|
|
1834
2039
|
throw new Error(`timed out waiting for tmux extended-keys lease lock: ${lockPath}`);
|
|
1835
2040
|
}
|
|
1836
|
-
function buildDetachedSessionLeaderCommand(cwd, sessionName, codexCmd, sessionId, codexHomeOverride, projectLocalCodexHomeForCleanup, runtimeCodexHomeForCleanup) {
|
|
2041
|
+
function buildDetachedSessionLeaderCommand(cwd, sessionName, codexCmd, sessionId, codexHomeOverride, projectLocalCodexHomeForCleanup, runtimeCodexHomeForCleanup, parentEnvFilePath) {
|
|
1837
2042
|
const detachedPostLaunchHelper = sessionId
|
|
1838
2043
|
? `${buildDetachedSessionPostLaunchHelperCommand(cwd, sessionId, codexHomeOverride, projectLocalCodexHomeForCleanup, runtimeCodexHomeForCleanup)} >/dev/null 2>&1 || true;`
|
|
1839
2044
|
: "";
|
|
2045
|
+
const parentEnvSource = parentEnvFilePath && parentEnvFilePath.trim()
|
|
2046
|
+
? `if [ -r ${quoteShellArg(parentEnvFilePath)} ]; then . ${quoteShellArg(parentEnvFilePath)}; rm -f ${quoteShellArg(parentEnvFilePath)}; fi;`
|
|
2047
|
+
: "";
|
|
2048
|
+
const parentEnvCleanup = parentEnvFilePath && parentEnvFilePath.trim()
|
|
2049
|
+
? `rm -f ${quoteShellArg(parentEnvFilePath)} 2>/dev/null || true;`
|
|
2050
|
+
: "";
|
|
1840
2051
|
const wrapped = [
|
|
1841
2052
|
buildTmuxExtendedKeysAcquireShellSnippet(cwd),
|
|
1842
2053
|
'exec 3<&0;',
|
|
@@ -1850,6 +2061,7 @@ function buildDetachedSessionLeaderCommand(cwd, sessionName, codexCmd, sessionId
|
|
|
1850
2061
|
"fi;",
|
|
1851
2062
|
'exec 3<&- 2>/dev/null || true;',
|
|
1852
2063
|
buildTmuxExtendedKeysReleaseShellSnippet(cwd),
|
|
2064
|
+
parentEnvCleanup,
|
|
1853
2065
|
detachedPostLaunchHelper,
|
|
1854
2066
|
'if [ "$status" -eq 0 ]; then',
|
|
1855
2067
|
`tmux kill-session -t "${escapeShellDoubleQuotedValue(sessionName)}" >/dev/null 2>&1 || true;`,
|
|
@@ -1857,6 +2069,7 @@ function buildDetachedSessionLeaderCommand(cwd, sessionName, codexCmd, sessionId
|
|
|
1857
2069
|
"exit $status;",
|
|
1858
2070
|
"};",
|
|
1859
2071
|
"trap omx_detached_session_cleanup 0 INT TERM HUP;",
|
|
2072
|
+
parentEnvSource,
|
|
1860
2073
|
"omx_codex_started_at=$(date +%s 2>/dev/null || printf 0);",
|
|
1861
2074
|
`${codexCmd} <&3 &`,
|
|
1862
2075
|
"omx_codex_pid=$!;",
|
|
@@ -1997,6 +2210,34 @@ function buildTmuxExtendedKeysAcquireShellSnippet(cwd) {
|
|
|
1997
2210
|
function buildTmuxExtendedKeysReleaseShellSnippet(cwd) {
|
|
1998
2211
|
return `if [ -n "\${OMX_TMUX_EXTENDED_KEYS_LEASE:-}" ]; then ${buildTmuxExtendedKeysHelperCommand(cwd, "release")} "\${OMX_TMUX_EXTENDED_KEYS_LEASE}" >/dev/null 2>&1 || true; fi;`;
|
|
1999
2212
|
}
|
|
2213
|
+
const SHELL_ENV_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
2214
|
+
export function serializeDetachedSessionParentEnv(env) {
|
|
2215
|
+
const lines = [];
|
|
2216
|
+
for (const key of Object.keys(env).sort()) {
|
|
2217
|
+
if (!SHELL_ENV_NAME_PATTERN.test(key))
|
|
2218
|
+
continue;
|
|
2219
|
+
const value = env[key];
|
|
2220
|
+
if (typeof value !== "string")
|
|
2221
|
+
continue;
|
|
2222
|
+
if (value.includes("\0"))
|
|
2223
|
+
continue;
|
|
2224
|
+
lines.push(`export ${key}=${quoteShellArg(value)}`);
|
|
2225
|
+
}
|
|
2226
|
+
return `${lines.join("\n")}\n`;
|
|
2227
|
+
}
|
|
2228
|
+
export function detachedSessionParentEnvFilePath(cwd, sessionId) {
|
|
2229
|
+
const safeSessionId = sessionId.replace(/[^A-Za-z0-9_.-]/g, "_");
|
|
2230
|
+
return join(omxRoot(cwd), "runtime", "tmux-env", `${safeSessionId}.env`);
|
|
2231
|
+
}
|
|
2232
|
+
export function writeDetachedSessionParentEnvFile(cwd, sessionId, env) {
|
|
2233
|
+
const filePath = detachedSessionParentEnvFilePath(cwd, sessionId);
|
|
2234
|
+
mkdirSync(dirname(filePath), { recursive: true, mode: 0o700 });
|
|
2235
|
+
writeFileSync(filePath, serializeDetachedSessionParentEnv(env), {
|
|
2236
|
+
encoding: "utf-8",
|
|
2237
|
+
mode: 0o600,
|
|
2238
|
+
});
|
|
2239
|
+
return filePath;
|
|
2240
|
+
}
|
|
2000
2241
|
export function withTmuxExtendedKeys(cwd, run, execFileSyncImpl = (file, tmuxArgs) => execFileSync(file, tmuxArgs, {
|
|
2001
2242
|
encoding: "utf-8",
|
|
2002
2243
|
...(process.platform === "win32" ? { windowsHide: true } : {}),
|
|
@@ -2010,10 +2251,10 @@ export function withTmuxExtendedKeys(cwd, run, execFileSyncImpl = (file, tmuxArg
|
|
|
2010
2251
|
releaseTmuxExtendedKeysLease(cwd, leaseHandle, execFileSyncImpl);
|
|
2011
2252
|
}
|
|
2012
2253
|
}
|
|
2013
|
-
export function buildDetachedSessionBootstrapSteps(sessionName, cwd, codexCmd, hudCmd, workerLaunchArgs, codexHomeOverride, notifyTempContractRaw, nativeWindows = false, sessionId, projectLocalCodexHomeForCleanup, runtimeCodexHomeForCleanup, omxRootOverride, env = process.env, sqliteHomeOverride) {
|
|
2254
|
+
export function buildDetachedSessionBootstrapSteps(sessionName, cwd, codexCmd, hudCmd, workerLaunchArgs, codexHomeOverride, notifyTempContractRaw, nativeWindows = false, sessionId, projectLocalCodexHomeForCleanup, runtimeCodexHomeForCleanup, omxRootOverride, env = process.env, sqliteHomeOverride, parentEnvFilePath) {
|
|
2014
2255
|
const detachedLeaderCmd = nativeWindows
|
|
2015
2256
|
? "powershell.exe"
|
|
2016
|
-
: buildDetachedSessionLeaderCommand(cwd, sessionName, codexCmd, sessionId, codexHomeOverride, projectLocalCodexHomeForCleanup, runtimeCodexHomeForCleanup);
|
|
2257
|
+
: buildDetachedSessionLeaderCommand(cwd, sessionName, codexCmd, sessionId, codexHomeOverride, projectLocalCodexHomeForCleanup, runtimeCodexHomeForCleanup, parentEnvFilePath);
|
|
2017
2258
|
const newSessionArgs = [
|
|
2018
2259
|
"new-session",
|
|
2019
2260
|
"-d",
|
|
@@ -2249,6 +2490,26 @@ async function readPostLaunchModeStateFile(path, dependencies = {}) {
|
|
|
2249
2490
|
function cleanPostLaunchString(value) {
|
|
2250
2491
|
return typeof value === "string" ? value.trim() : "";
|
|
2251
2492
|
}
|
|
2493
|
+
function isAutopilotReviewPendingPostLaunchState(state) {
|
|
2494
|
+
if (!state || state.active !== true)
|
|
2495
|
+
return false;
|
|
2496
|
+
const mode = cleanPostLaunchString(state.mode).toLowerCase();
|
|
2497
|
+
if (mode && mode !== "autopilot")
|
|
2498
|
+
return false;
|
|
2499
|
+
const phase = cleanPostLaunchString(state.current_phase ?? state.currentPhase)
|
|
2500
|
+
.toLowerCase()
|
|
2501
|
+
.replace(/_/g, "-");
|
|
2502
|
+
if (phase === "code-review" || phase === "review" || phase === "reviewing" || phase === "review-pending") {
|
|
2503
|
+
return true;
|
|
2504
|
+
}
|
|
2505
|
+
const nestedState = state.state && typeof state.state === "object"
|
|
2506
|
+
? state.state
|
|
2507
|
+
: {};
|
|
2508
|
+
return state.review_pending === true
|
|
2509
|
+
|| state.reviewPending === true
|
|
2510
|
+
|| nestedState.review_pending === true
|
|
2511
|
+
|| nestedState.reviewPending === true;
|
|
2512
|
+
}
|
|
2252
2513
|
function postLaunchUniqueStrings(values) {
|
|
2253
2514
|
return [...new Set(values.map((value) => value.trim()).filter(Boolean))];
|
|
2254
2515
|
}
|
|
@@ -2336,8 +2597,16 @@ export async function cleanupPostLaunchModeStateFiles(cwd, sessionId, dependenci
|
|
|
2336
2597
|
const rootSkillActiveStateBeforeCleanup = sessionId
|
|
2337
2598
|
? await readSkillActiveState(getSkillActiveStatePathsForStateDir(rootStateDir).rootPath)
|
|
2338
2599
|
: null;
|
|
2600
|
+
let preserveSkillActiveForReviewPendingAutopilot = false;
|
|
2339
2601
|
for (const stateDir of scopedDirs) {
|
|
2340
2602
|
const files = await readdir(stateDir).catch(() => []);
|
|
2603
|
+
const autopilotPath = join(stateDir, "autopilot-state.json");
|
|
2604
|
+
const autopilotPrecheck = files.includes("autopilot-state.json")
|
|
2605
|
+
? await readPostLaunchModeStateFile(autopilotPath, dependencies)
|
|
2606
|
+
: null;
|
|
2607
|
+
const preserveReviewPendingAutopilot = autopilotPrecheck?.kind === "ok"
|
|
2608
|
+
&& isAutopilotReviewPendingPostLaunchState(autopilotPrecheck.state);
|
|
2609
|
+
preserveSkillActiveForReviewPendingAutopilot ||= preserveReviewPendingAutopilot;
|
|
2341
2610
|
for (const file of files) {
|
|
2342
2611
|
if (!file.endsWith("-state.json") || file === "session.json")
|
|
2343
2612
|
continue;
|
|
@@ -2395,6 +2664,10 @@ export async function cleanupPostLaunchModeStateFiles(cwd, sessionId, dependenci
|
|
|
2395
2664
|
}
|
|
2396
2665
|
continue;
|
|
2397
2666
|
}
|
|
2667
|
+
if (preserveReviewPendingAutopilot
|
|
2668
|
+
&& (mode === "autopilot" || mode === SKILL_ACTIVE_STATE_MODE)) {
|
|
2669
|
+
continue;
|
|
2670
|
+
}
|
|
2398
2671
|
try {
|
|
2399
2672
|
const completedAt = now().toISOString();
|
|
2400
2673
|
if (mode === SKILL_ACTIVE_STATE_MODE) {
|
|
@@ -2433,7 +2706,9 @@ export async function cleanupPostLaunchModeStateFiles(cwd, sessionId, dependenci
|
|
|
2433
2706
|
}
|
|
2434
2707
|
if (sessionId) {
|
|
2435
2708
|
try {
|
|
2436
|
-
|
|
2709
|
+
if (!preserveSkillActiveForReviewPendingAutopilot) {
|
|
2710
|
+
await scrubPostLaunchRootSkillActiveForSession(rootStateDir, sessionId, now().toISOString(), writeFile, rootSkillActiveStateBeforeCleanup);
|
|
2711
|
+
}
|
|
2437
2712
|
}
|
|
2438
2713
|
catch (err) {
|
|
2439
2714
|
writeWarn(`[omx] postLaunch: failed to reconcile root skill-active state: ${err instanceof Error ? err.message : err}`);
|
|
@@ -2610,6 +2885,9 @@ function runCodex(cwd, args, sessionId, workerDefaultModel, codexHomeOverride, s
|
|
|
2610
2885
|
let hudPaneId = null;
|
|
2611
2886
|
try {
|
|
2612
2887
|
hudPaneId = createHudWatchPane(cwd, hudCmd);
|
|
2888
|
+
if (hudPaneId && currentPaneId) {
|
|
2889
|
+
registerHudResizeHook(hudPaneId, currentPaneId, HUD_TMUX_HEIGHT_LINES);
|
|
2890
|
+
}
|
|
2613
2891
|
}
|
|
2614
2892
|
catch (err) {
|
|
2615
2893
|
logCliOperationFailure(err);
|
|
@@ -2670,96 +2948,166 @@ function runCodex(cwd, args, sessionId, workerDefaultModel, codexHomeOverride, s
|
|
|
2670
2948
|
? buildWindowsPromptCommand("codex", launchArgs)
|
|
2671
2949
|
: null;
|
|
2672
2950
|
const sessionName = buildDetachedTmuxSessionName(cwd, sessionId);
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2951
|
+
const launchDetachedSession = () => {
|
|
2952
|
+
const contextKey = process.env[OMX_MADMAX_DETACHED_CONTEXT_ENV]?.trim();
|
|
2953
|
+
const runsRoot = resolveMadmaxRunsRoot(process.env);
|
|
2954
|
+
const activeRecordPath = contextKey
|
|
2955
|
+
? madmaxDetachedActiveRecordPath(runsRoot, contextKey)
|
|
2956
|
+
: null;
|
|
2957
|
+
const activeRecord = activeRecordPath
|
|
2958
|
+
? readMadmaxDetachedActiveRecord(activeRecordPath)
|
|
2959
|
+
: null;
|
|
2960
|
+
if (activeRecord &&
|
|
2961
|
+
activeRecord.context_key === contextKey &&
|
|
2962
|
+
detachedTmuxSessionExists(activeRecord.tmux_session_name)) {
|
|
2963
|
+
cleanupCurrentMadmaxReuseRunRoot(process.env, runsRoot);
|
|
2964
|
+
process.stderr.write(`[omx] madmax detached launch already active for this context; attaching ${activeRecord.tmux_session_name} instead of starting a duplicate.\n`);
|
|
2965
|
+
try {
|
|
2966
|
+
execTmuxFileSync(["attach-session", "-t", activeRecord.tmux_session_name], {
|
|
2967
|
+
stdio: "inherit",
|
|
2968
|
+
});
|
|
2691
2969
|
}
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2970
|
+
catch (err) {
|
|
2971
|
+
logCliOperationFailure(err);
|
|
2972
|
+
throw new MadmaxDetachedReuseError(`refusing duplicate madmax detached launch: existing session ${activeRecord.tmux_session_name} is active but attach failed`);
|
|
2973
|
+
}
|
|
2974
|
+
return { postLaunchHandledExternally: true };
|
|
2975
|
+
}
|
|
2976
|
+
if (activeRecordPath && activeRecord) {
|
|
2977
|
+
rmSync(activeRecordPath, { force: true });
|
|
2978
|
+
}
|
|
2979
|
+
void writeSessionStart(cwd, sessionId, { tmuxSessionName: sessionName }).catch((err) => {
|
|
2980
|
+
logCliOperationFailure(err);
|
|
2981
|
+
// Non-fatal: managed tmux recovery can still use compatibility fallback.
|
|
2982
|
+
});
|
|
2983
|
+
let createdDetachedSession = false;
|
|
2984
|
+
let registeredHookTarget = null;
|
|
2985
|
+
let registeredHookName = null;
|
|
2986
|
+
let registeredClientAttachedHookName = null;
|
|
2987
|
+
let detachedParentEnvFilePath;
|
|
2988
|
+
try {
|
|
2989
|
+
// This path is the user-shell interactive launch: OMX creates a tmux
|
|
2990
|
+
// session and immediately attaches the user's terminal to it. If a tmux
|
|
2991
|
+
// server already exists, `new-session -e` only forwards explicit values,
|
|
2992
|
+
// so provider-specific parent-shell keys would disappear. Source a
|
|
2993
|
+
// private env file inside the leader shell instead of putting every
|
|
2994
|
+
// parent env value on the tmux command line or in logs.
|
|
2995
|
+
if (!nativeWindows) {
|
|
2996
|
+
detachedParentEnvFilePath = writeDetachedSessionParentEnvFile(cwd, sessionId, codexEnvWithNotify);
|
|
2997
|
+
}
|
|
2998
|
+
const bootstrapSteps = buildDetachedSessionBootstrapSteps(sessionName, cwd, codexCmd, hudCmd, workerLaunchArgs, codexHomeOverride, notifyTempContractRaw, nativeWindows, sessionId, projectLocalCodexHomeForCleanup, runtimeCodexHomeForCleanup, omxRootOverride, process.env, sqliteHomeOverride, detachedParentEnvFilePath);
|
|
2999
|
+
for (const step of bootstrapSteps) {
|
|
3000
|
+
const output = execTmuxFileSync(step.args, {
|
|
3001
|
+
stdio: "pipe",
|
|
3002
|
+
encoding: "utf-8",
|
|
3003
|
+
});
|
|
3004
|
+
if (step.name === "new-session") {
|
|
3005
|
+
createdDetachedSession = true;
|
|
3006
|
+
if (activeRecordPath && contextKey) {
|
|
3007
|
+
writeMadmaxDetachedActiveRecord(activeRecordPath, {
|
|
3008
|
+
version: 1,
|
|
3009
|
+
context_key: contextKey,
|
|
3010
|
+
created_at: new Date().toISOString(),
|
|
3011
|
+
source_cwd: process.env.OMX_SOURCE_CWD || cwd,
|
|
3012
|
+
argv: args,
|
|
3013
|
+
run_dir: process.env.OMX_ROOT || cwd,
|
|
3014
|
+
tmux_session_name: sessionName,
|
|
3015
|
+
});
|
|
3016
|
+
}
|
|
3017
|
+
parsePaneIdFromTmuxOutput(output || "");
|
|
2709
3018
|
}
|
|
2710
|
-
|
|
2711
|
-
|
|
3019
|
+
if (step.name === "split-and-capture-hud-pane") {
|
|
3020
|
+
const hudPaneId = parsePaneIdFromTmuxOutput(output || "");
|
|
3021
|
+
const hookWindowIndex = hudPaneId
|
|
3022
|
+
? detectDetachedSessionWindowIndex(sessionName)
|
|
3023
|
+
: null;
|
|
3024
|
+
const hookTarget = hudPaneId && hookWindowIndex
|
|
3025
|
+
? buildResizeHookTarget(sessionName, hookWindowIndex)
|
|
3026
|
+
: null;
|
|
3027
|
+
const hookName = hudPaneId && hookWindowIndex
|
|
3028
|
+
? buildResizeHookName("launch", sessionName, hookWindowIndex, hudPaneId)
|
|
3029
|
+
: null;
|
|
3030
|
+
const clientAttachedHookName = hudPaneId && hookWindowIndex
|
|
3031
|
+
? buildClientAttachedReconcileHookName("launch", sessionName, hookWindowIndex, hudPaneId)
|
|
3032
|
+
: null;
|
|
3033
|
+
const finalizeSteps = buildDetachedSessionFinalizeSteps(sessionName, hudPaneId, hookWindowIndex, process.env.OMX_MOUSE !== "0", nativeWindows);
|
|
3034
|
+
if (nativeWindows && detachedWindowsCodexCmd) {
|
|
3035
|
+
scheduleDetachedWindowsCodexLaunch(sessionName, detachedWindowsCodexCmd);
|
|
3036
|
+
}
|
|
3037
|
+
for (const finalizeStep of finalizeSteps) {
|
|
3038
|
+
if (finalizeStep.name === "sanitize-copy-mode-style") {
|
|
3039
|
+
try {
|
|
3040
|
+
mitigateCopyModeUnderlineArtifacts(sessionName);
|
|
3041
|
+
}
|
|
3042
|
+
catch (err) {
|
|
3043
|
+
logCliOperationFailure(err);
|
|
3044
|
+
}
|
|
3045
|
+
continue;
|
|
3046
|
+
}
|
|
3047
|
+
const stdio = finalizeStep.name === "attach-session" ? "inherit" : "ignore";
|
|
2712
3048
|
try {
|
|
2713
|
-
|
|
3049
|
+
const startedAtMs = Date.now();
|
|
3050
|
+
execTmuxFileSync(finalizeStep.args, { stdio });
|
|
3051
|
+
if (finalizeStep.name === "attach-session") {
|
|
3052
|
+
assertDetachedAttachDidNotNoop(sessionName, Date.now() - startedAtMs, process.env);
|
|
3053
|
+
}
|
|
2714
3054
|
}
|
|
2715
3055
|
catch (err) {
|
|
2716
3056
|
logCliOperationFailure(err);
|
|
3057
|
+
if (finalizeStep.name === "attach-session")
|
|
3058
|
+
throw new Error("failed to attach detached tmux session");
|
|
3059
|
+
continue;
|
|
2717
3060
|
}
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
if (finalizeStep.name === "
|
|
2725
|
-
|
|
3061
|
+
if (finalizeStep.name === "register-resize-hook" &&
|
|
3062
|
+
hookTarget &&
|
|
3063
|
+
hookName) {
|
|
3064
|
+
registeredHookTarget = hookTarget;
|
|
3065
|
+
registeredHookName = hookName;
|
|
3066
|
+
}
|
|
3067
|
+
if (finalizeStep.name === "register-client-attached-reconcile" &&
|
|
3068
|
+
clientAttachedHookName) {
|
|
3069
|
+
registeredClientAttachedHookName = clientAttachedHookName;
|
|
2726
3070
|
}
|
|
2727
3071
|
}
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
3072
|
+
}
|
|
3073
|
+
}
|
|
3074
|
+
return { postLaunchHandledExternally: !nativeWindows };
|
|
3075
|
+
}
|
|
3076
|
+
catch (err) {
|
|
3077
|
+
if (detachedParentEnvFilePath) {
|
|
3078
|
+
rmSync(detachedParentEnvFilePath, { force: true });
|
|
3079
|
+
}
|
|
3080
|
+
if (activeRecordPath) {
|
|
3081
|
+
rmSync(activeRecordPath, { force: true });
|
|
3082
|
+
}
|
|
3083
|
+
if (createdDetachedSession) {
|
|
3084
|
+
const rollbackSteps = buildDetachedSessionRollbackSteps(sessionName, registeredHookTarget, registeredHookName, registeredClientAttachedHookName);
|
|
3085
|
+
for (const rollbackStep of rollbackSteps) {
|
|
3086
|
+
try {
|
|
3087
|
+
execTmuxFileSync(rollbackStep.args, { stdio: "ignore" });
|
|
2739
3088
|
}
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
3089
|
+
catch (rollbackErr) {
|
|
3090
|
+
logCliOperationFailure(rollbackErr);
|
|
3091
|
+
// best-effort rollback only
|
|
2743
3092
|
}
|
|
2744
3093
|
}
|
|
2745
3094
|
}
|
|
3095
|
+
throw err;
|
|
3096
|
+
}
|
|
3097
|
+
};
|
|
3098
|
+
const contextKey = process.env[OMX_MADMAX_DETACHED_CONTEXT_ENV]?.trim();
|
|
3099
|
+
const runsRoot = resolveMadmaxRunsRoot(process.env);
|
|
3100
|
+
try {
|
|
3101
|
+
if (isMadmaxDetachedGuardEnabled(process.env) && contextKey) {
|
|
3102
|
+
return withMadmaxDetachedContextLock(runsRoot, contextKey, launchDetachedSession);
|
|
2746
3103
|
}
|
|
2747
|
-
return
|
|
3104
|
+
return launchDetachedSession();
|
|
2748
3105
|
}
|
|
2749
3106
|
catch (err) {
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
const rollbackSteps = buildDetachedSessionRollbackSteps(sessionName, registeredHookTarget, registeredHookName, registeredClientAttachedHookName);
|
|
2753
|
-
for (const rollbackStep of rollbackSteps) {
|
|
2754
|
-
try {
|
|
2755
|
-
execTmuxFileSync(rollbackStep.args, { stdio: "ignore" });
|
|
2756
|
-
}
|
|
2757
|
-
catch (err) {
|
|
2758
|
-
logCliOperationFailure(err);
|
|
2759
|
-
// best-effort rollback only
|
|
2760
|
-
}
|
|
2761
|
-
}
|
|
3107
|
+
if (err instanceof MadmaxDetachedReuseError || err instanceof MadmaxDetachedGuardError) {
|
|
3108
|
+
throw err;
|
|
2762
3109
|
}
|
|
3110
|
+
logCliOperationFailure(err);
|
|
2763
3111
|
// tmux not available or failed, just run codex directly
|
|
2764
3112
|
runCodexBlocking(cwd, launchArgs, codexEnvWithNotify);
|
|
2765
3113
|
return { postLaunchHandledExternally: false };
|
|
@@ -3016,7 +3364,7 @@ async function postLaunch(cwd, sessionId, codexHomeOverride, enableNotifyFallbac
|
|
|
3016
3364
|
}
|
|
3017
3365
|
export async function runDetachedSessionPostLaunch(cwd, sessionId, codexHomeOverride, projectLocalCodexHomeForCleanup, runtimeCodexHomeForCleanup) {
|
|
3018
3366
|
await postLaunch(cwd, sessionId, codexHomeOverride, false, projectLocalCodexHomeForCleanup);
|
|
3019
|
-
await cleanupRuntimeCodexHome(runtimeCodexHomeForCleanup).catch(logCliOperationFailure);
|
|
3367
|
+
await cleanupRuntimeCodexHome(runtimeCodexHomeForCleanup, projectLocalCodexHomeForCleanup).catch(logCliOperationFailure);
|
|
3020
3368
|
}
|
|
3021
3369
|
async function emitNativeHookEvent(cwd, event, opts = {}) {
|
|
3022
3370
|
const payload = buildHookEvent(event, {
|
|
@@ -3117,6 +3465,22 @@ function parseWatcherPidFile(content) {
|
|
|
3117
3465
|
return Number.isFinite(pid) && pid > 0 ? pid : null;
|
|
3118
3466
|
}
|
|
3119
3467
|
}
|
|
3468
|
+
const DEFAULT_NOTIFY_FALLBACK_REAP_GRACE_MS = 5000;
|
|
3469
|
+
function resolveNotifyFallbackReapGraceMs(env = process.env) {
|
|
3470
|
+
const parsed = Number.parseInt(env.OMX_NOTIFY_FALLBACK_REAP_GRACE_MS || "", 10);
|
|
3471
|
+
if (Number.isFinite(parsed) && parsed >= 0)
|
|
3472
|
+
return parsed;
|
|
3473
|
+
return DEFAULT_NOTIFY_FALLBACK_REAP_GRACE_MS;
|
|
3474
|
+
}
|
|
3475
|
+
function isWatcherRecordWithinReapGrace(record, nowMs = Date.now(), graceMs = resolveNotifyFallbackReapGraceMs()) {
|
|
3476
|
+
if (graceMs <= 0 || !record.startedAt)
|
|
3477
|
+
return false;
|
|
3478
|
+
const startedMs = Date.parse(record.startedAt);
|
|
3479
|
+
if (!Number.isFinite(startedMs))
|
|
3480
|
+
return false;
|
|
3481
|
+
const ageMs = nowMs - startedMs;
|
|
3482
|
+
return ageMs >= 0 && ageMs < graceMs;
|
|
3483
|
+
}
|
|
3120
3484
|
function parseWatcherPidRecord(content) {
|
|
3121
3485
|
const trimmed = content.trim();
|
|
3122
3486
|
if (!trimmed)
|
|
@@ -3159,7 +3523,7 @@ function isLikelyOmxWatcherProcess(pid, execFileSyncFn = execFileSync, platform
|
|
|
3159
3523
|
export async function reapStaleNotifyFallbackWatcher(pidPath, deps = {}) {
|
|
3160
3524
|
const exists = deps.exists ?? existsSync;
|
|
3161
3525
|
if (!exists(pidPath))
|
|
3162
|
-
return;
|
|
3526
|
+
return "missing";
|
|
3163
3527
|
const { readFile } = await import("fs/promises");
|
|
3164
3528
|
const readFileImpl = deps.readFile ?? readFile;
|
|
3165
3529
|
const tryKillPidImpl = deps.tryKillPid ?? tryKillPid;
|
|
@@ -3168,9 +3532,15 @@ export async function reapStaleNotifyFallbackWatcher(pidPath, deps = {}) {
|
|
|
3168
3532
|
const isWatcherProcessImpl = deps.isWatcherProcess ?? isLikelyOmxWatcherProcess;
|
|
3169
3533
|
try {
|
|
3170
3534
|
const record = parseWatcherPidRecord(await readFileImpl(pidPath, "utf-8"));
|
|
3171
|
-
if (record
|
|
3172
|
-
|
|
3535
|
+
if (!record)
|
|
3536
|
+
return "invalid";
|
|
3537
|
+
if (!isWatcherProcessImpl(record.pid))
|
|
3538
|
+
return "identity_mismatch";
|
|
3539
|
+
if (isWatcherRecordWithinReapGrace(record, deps.nowMs?.() ?? Date.now(), deps.reapGraceMs ?? resolveNotifyFallbackReapGraceMs())) {
|
|
3540
|
+
return "recent_active";
|
|
3173
3541
|
}
|
|
3542
|
+
tryKillPidImpl(record.pid, "SIGTERM");
|
|
3543
|
+
return "reaped";
|
|
3174
3544
|
}
|
|
3175
3545
|
catch (error) {
|
|
3176
3546
|
if (!hasErrnoCodeImpl(error, "ESRCH")) {
|
|
@@ -3179,6 +3549,7 @@ export async function reapStaleNotifyFallbackWatcher(pidPath, deps = {}) {
|
|
|
3179
3549
|
error: error instanceof Error ? error.message : String(error),
|
|
3180
3550
|
});
|
|
3181
3551
|
}
|
|
3552
|
+
return "failed";
|
|
3182
3553
|
}
|
|
3183
3554
|
}
|
|
3184
3555
|
function tryKillPid(pid, signal = "SIGTERM") {
|
|
@@ -3196,7 +3567,9 @@ function tryKillPid(pid, signal = "SIGTERM") {
|
|
|
3196
3567
|
async function startNotifyFallbackWatcher(cwd, options = {}) {
|
|
3197
3568
|
const { mkdir, writeFile } = await import("fs/promises");
|
|
3198
3569
|
const pidPath = notifyFallbackPidPath(cwd);
|
|
3199
|
-
await reapStaleNotifyFallbackWatcher(pidPath);
|
|
3570
|
+
const reapResult = await reapStaleNotifyFallbackWatcher(pidPath);
|
|
3571
|
+
if (reapResult === "recent_active")
|
|
3572
|
+
return;
|
|
3200
3573
|
if (!shouldEnableNotifyFallbackWatcher(process.env, process.platform))
|
|
3201
3574
|
return;
|
|
3202
3575
|
const pkgRoot = getPackageRoot();
|