oh-my-codex 0.15.2 → 0.16.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 +10 -7
- package/Cargo.toml +1 -1
- package/README.md +3 -0
- package/crates/omx-explore/Cargo.toml +3 -0
- package/crates/omx-explore/src/main.rs +517 -16
- package/dist/agents/__tests__/native-config.test.js +33 -0
- package/dist/agents/__tests__/native-config.test.js.map +1 -1
- package/dist/autoresearch/goal.d.ts +90 -0
- package/dist/autoresearch/goal.d.ts.map +1 -0
- package/dist/autoresearch/goal.js +237 -0
- package/dist/autoresearch/goal.js.map +1 -0
- package/dist/autoresearch/skill-validation.d.ts +1 -0
- package/dist/autoresearch/skill-validation.d.ts.map +1 -1
- package/dist/autoresearch/skill-validation.js +10 -3
- package/dist/autoresearch/skill-validation.js.map +1 -1
- package/dist/catalog/__tests__/generator.test.js +9 -4
- package/dist/catalog/__tests__/generator.test.js.map +1 -1
- package/dist/catalog/__tests__/plugin-bundle-ssot.test.js +29 -2
- package/dist/catalog/__tests__/plugin-bundle-ssot.test.js.map +1 -1
- package/dist/catalog/__tests__/schema.test.js +14 -3
- 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__/autoresearch-goal.test.d.ts +2 -0
- package/dist/cli/__tests__/autoresearch-goal.test.d.ts.map +1 -0
- package/dist/cli/__tests__/autoresearch-goal.test.js +194 -0
- package/dist/cli/__tests__/autoresearch-goal.test.js.map +1 -0
- package/dist/cli/__tests__/cleanup.test.js +82 -1
- package/dist/cli/__tests__/cleanup.test.js.map +1 -1
- package/dist/cli/__tests__/codex-plugin-layout.test.js +7 -4
- package/dist/cli/__tests__/codex-plugin-layout.test.js.map +1 -1
- package/dist/cli/__tests__/doctor-context-window-warning.test.d.ts +2 -0
- package/dist/cli/__tests__/doctor-context-window-warning.test.d.ts.map +1 -0
- package/dist/cli/__tests__/doctor-context-window-warning.test.js +122 -0
- package/dist/cli/__tests__/doctor-context-window-warning.test.js.map +1 -0
- package/dist/cli/__tests__/doctor-warning-copy.test.js +25 -2
- package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
- package/dist/cli/__tests__/exec.test.js +1 -0
- package/dist/cli/__tests__/exec.test.js.map +1 -1
- package/dist/cli/__tests__/explore.test.js +48 -18
- package/dist/cli/__tests__/explore.test.js.map +1 -1
- package/dist/cli/__tests__/index.test.js +222 -10
- package/dist/cli/__tests__/index.test.js.map +1 -1
- package/dist/cli/__tests__/launch-fallback.test.js +58 -0
- package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
- package/dist/cli/__tests__/mcp-serve.test.js +27 -1
- package/dist/cli/__tests__/mcp-serve.test.js.map +1 -1
- package/dist/cli/__tests__/native-assets.test.js +26 -1
- package/dist/cli/__tests__/native-assets.test.js.map +1 -1
- package/dist/cli/__tests__/package-bin-contract.test.js +2 -2
- package/dist/cli/__tests__/package-bin-contract.test.js.map +1 -1
- package/dist/cli/__tests__/performance-goal.test.d.ts +2 -0
- package/dist/cli/__tests__/performance-goal.test.d.ts.map +1 -0
- package/dist/cli/__tests__/performance-goal.test.js +144 -0
- package/dist/cli/__tests__/performance-goal.test.js.map +1 -0
- package/dist/cli/__tests__/question.test.js +8 -0
- package/dist/cli/__tests__/question.test.js.map +1 -1
- package/dist/cli/__tests__/ralph-goal-mode-contract.test.d.ts +2 -0
- package/dist/cli/__tests__/ralph-goal-mode-contract.test.d.ts.map +1 -0
- package/dist/cli/__tests__/ralph-goal-mode-contract.test.js +31 -0
- package/dist/cli/__tests__/ralph-goal-mode-contract.test.js.map +1 -0
- package/dist/cli/__tests__/ralph-prd-deep-interview.test.js +5 -4
- package/dist/cli/__tests__/ralph-prd-deep-interview.test.js.map +1 -1
- package/dist/cli/__tests__/ralph-prd-smoke.test.js +7 -0
- package/dist/cli/__tests__/ralph-prd-smoke.test.js.map +1 -1
- package/dist/cli/__tests__/ralph.test.js +59 -1
- package/dist/cli/__tests__/ralph.test.js.map +1 -1
- package/dist/cli/__tests__/setup-install-mode.test.js +57 -21
- package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
- package/dist/cli/__tests__/setup-refresh.test.js +27 -8
- package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
- package/dist/cli/__tests__/setup-scope.test.js +20 -10
- package/dist/cli/__tests__/setup-scope.test.js.map +1 -1
- package/dist/cli/__tests__/setup-skill-validation.test.js +11 -11
- package/dist/cli/__tests__/setup-skill-validation.test.js.map +1 -1
- package/dist/cli/__tests__/setup-skills-overwrite.test.js +12 -12
- package/dist/cli/__tests__/setup-skills-overwrite.test.js.map +1 -1
- package/dist/cli/__tests__/team.test.js +242 -10
- package/dist/cli/__tests__/team.test.js.map +1 -1
- package/dist/cli/__tests__/ultragoal.test.d.ts +2 -0
- package/dist/cli/__tests__/ultragoal.test.d.ts.map +1 -0
- package/dist/cli/__tests__/ultragoal.test.js +106 -0
- package/dist/cli/__tests__/ultragoal.test.js.map +1 -0
- package/dist/cli/__tests__/uninstall.test.js +11 -0
- package/dist/cli/__tests__/uninstall.test.js.map +1 -1
- package/dist/cli/autoresearch-goal.d.ts +3 -0
- package/dist/cli/autoresearch-goal.d.ts.map +1 -0
- package/dist/cli/autoresearch-goal.js +175 -0
- package/dist/cli/autoresearch-goal.js.map +1 -0
- package/dist/cli/cleanup.d.ts +3 -1
- package/dist/cli/cleanup.d.ts.map +1 -1
- package/dist/cli/cleanup.js +42 -2
- package/dist/cli/cleanup.js.map +1 -1
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +95 -3
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/explore.d.ts.map +1 -1
- package/dist/cli/explore.js +10 -2
- package/dist/cli/explore.js.map +1 -1
- package/dist/cli/index.d.ts +21 -2
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +268 -30
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/mcp-serve.d.ts +1 -0
- package/dist/cli/mcp-serve.d.ts.map +1 -1
- package/dist/cli/mcp-serve.js +8 -0
- package/dist/cli/mcp-serve.js.map +1 -1
- package/dist/cli/native-assets.js +1 -1
- package/dist/cli/native-assets.js.map +1 -1
- package/dist/cli/performance-goal.d.ts +3 -0
- package/dist/cli/performance-goal.d.ts.map +1 -0
- package/dist/cli/performance-goal.js +186 -0
- package/dist/cli/performance-goal.js.map +1 -0
- package/dist/cli/ralph.d.ts +2 -0
- package/dist/cli/ralph.d.ts.map +1 -1
- package/dist/cli/ralph.js +25 -1
- package/dist/cli/ralph.js.map +1 -1
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +13 -6
- package/dist/cli/setup.js.map +1 -1
- package/dist/cli/team.d.ts +6 -0
- package/dist/cli/team.d.ts.map +1 -1
- package/dist/cli/team.js +113 -33
- package/dist/cli/team.js.map +1 -1
- package/dist/cli/tmux-hook.d.ts.map +1 -1
- package/dist/cli/tmux-hook.js +2 -1
- package/dist/cli/tmux-hook.js.map +1 -1
- package/dist/cli/ultragoal.d.ts +3 -0
- package/dist/cli/ultragoal.d.ts.map +1 -0
- package/dist/cli/ultragoal.js +191 -0
- package/dist/cli/ultragoal.js.map +1 -0
- package/dist/cli/uninstall.d.ts.map +1 -1
- package/dist/cli/uninstall.js +4 -2
- package/dist/cli/uninstall.js.map +1 -1
- package/dist/config/__tests__/generator-idempotent.test.js +39 -6
- package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
- package/dist/config/__tests__/generator-notify.test.js +5 -0
- package/dist/config/__tests__/generator-notify.test.js.map +1 -1
- package/dist/config/commit-lore-guard.d.ts +3 -0
- package/dist/config/commit-lore-guard.d.ts.map +1 -0
- package/dist/config/commit-lore-guard.js +9 -0
- package/dist/config/commit-lore-guard.js.map +1 -0
- package/dist/config/generator.d.ts +14 -4
- package/dist/config/generator.d.ts.map +1 -1
- package/dist/config/generator.js +166 -66
- package/dist/config/generator.js.map +1 -1
- package/dist/config/omx-first-party-mcp.d.ts +1 -0
- package/dist/config/omx-first-party-mcp.d.ts.map +1 -1
- package/dist/config/omx-first-party-mcp.js +4 -1
- package/dist/config/omx-first-party-mcp.js.map +1 -1
- package/dist/goal-workflows/__tests__/artifacts.test.d.ts +2 -0
- package/dist/goal-workflows/__tests__/artifacts.test.d.ts.map +1 -0
- package/dist/goal-workflows/__tests__/artifacts.test.js +96 -0
- package/dist/goal-workflows/__tests__/artifacts.test.js.map +1 -0
- package/dist/goal-workflows/__tests__/codex-goal-snapshot.test.d.ts +2 -0
- package/dist/goal-workflows/__tests__/codex-goal-snapshot.test.d.ts.map +1 -0
- package/dist/goal-workflows/__tests__/codex-goal-snapshot.test.js +54 -0
- package/dist/goal-workflows/__tests__/codex-goal-snapshot.test.js.map +1 -0
- package/dist/goal-workflows/artifacts.d.ts +62 -0
- package/dist/goal-workflows/artifacts.d.ts.map +1 -0
- package/dist/goal-workflows/artifacts.js +132 -0
- package/dist/goal-workflows/artifacts.js.map +1 -0
- package/dist/goal-workflows/codex-goal-snapshot.d.ts +28 -0
- package/dist/goal-workflows/codex-goal-snapshot.d.ts.map +1 -0
- package/dist/goal-workflows/codex-goal-snapshot.js +110 -0
- package/dist/goal-workflows/codex-goal-snapshot.js.map +1 -0
- package/dist/goal-workflows/handoff.d.ts +10 -0
- package/dist/goal-workflows/handoff.d.ts.map +1 -0
- package/dist/goal-workflows/handoff.js +31 -0
- package/dist/goal-workflows/handoff.js.map +1 -0
- package/dist/goal-workflows/validation.d.ts +13 -0
- package/dist/goal-workflows/validation.d.ts.map +1 -0
- package/dist/goal-workflows/validation.js +36 -0
- package/dist/goal-workflows/validation.js.map +1 -0
- package/dist/hooks/__tests__/agents-overlay.test.js +59 -0
- package/dist/hooks/__tests__/agents-overlay.test.js.map +1 -1
- package/dist/hooks/__tests__/anti-slop-workflow.test.js +109 -18
- package/dist/hooks/__tests__/anti-slop-workflow.test.js.map +1 -1
- package/dist/hooks/__tests__/keyword-detector.test.js +45 -32
- package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-fallback-watcher.test.js +3 -3
- package/dist/hooks/__tests__/notify-fallback-watcher.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js +2 -1
- package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js +17 -24
- package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js.map +1 -1
- package/dist/hooks/__tests__/prompt-guidance-wave-two.test.js +3 -3
- package/dist/hooks/__tests__/prompt-guidance-wave-two.test.js.map +1 -1
- package/dist/hooks/__tests__/task-size-detector.test.js +1 -1
- package/dist/hooks/__tests__/task-size-detector.test.js.map +1 -1
- package/dist/hooks/__tests__/visual-ralph-skill.test.js +3 -3
- package/dist/hooks/__tests__/visual-ralph-skill.test.js.map +1 -1
- package/dist/hooks/__tests__/visual-verdict-loop.test.js +7 -11
- package/dist/hooks/__tests__/visual-verdict-loop.test.js.map +1 -1
- package/dist/hooks/agents-overlay.d.ts.map +1 -1
- package/dist/hooks/agents-overlay.js +23 -2
- package/dist/hooks/agents-overlay.js.map +1 -1
- package/dist/hooks/keyword-detector.d.ts.map +1 -1
- package/dist/hooks/keyword-detector.js +12 -13
- 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 +2 -10
- 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 +0 -4
- package/dist/hooks/prompt-guidance-contract.js.map +1 -1
- package/dist/hooks/session.js +2 -2
- package/dist/hooks/session.js.map +1 -1
- package/dist/hooks/task-size-detector.d.ts.map +1 -1
- package/dist/hooks/task-size-detector.js +1 -0
- package/dist/hooks/task-size-detector.js.map +1 -1
- package/dist/hud/__tests__/index.test.js +30 -14
- package/dist/hud/__tests__/index.test.js.map +1 -1
- package/dist/hud/__tests__/reconcile.test.js +29 -7
- package/dist/hud/__tests__/reconcile.test.js.map +1 -1
- package/dist/hud/reconcile.d.ts +2 -1
- package/dist/hud/reconcile.d.ts.map +1 -1
- package/dist/hud/reconcile.js +12 -0
- package/dist/hud/reconcile.js.map +1 -1
- package/dist/mcp/__tests__/bootstrap.test.js +15 -2
- package/dist/mcp/__tests__/bootstrap.test.js.map +1 -1
- package/dist/mcp/__tests__/state-paths.test.js +54 -0
- package/dist/mcp/__tests__/state-paths.test.js.map +1 -1
- package/dist/mcp/__tests__/state-server.test.js +36 -0
- package/dist/mcp/__tests__/state-server.test.js.map +1 -1
- package/dist/mcp/bootstrap.d.ts +1 -1
- package/dist/mcp/bootstrap.d.ts.map +1 -1
- package/dist/mcp/bootstrap.js +9 -7
- package/dist/mcp/bootstrap.js.map +1 -1
- package/dist/mcp/state-paths.d.ts +17 -0
- package/dist/mcp/state-paths.d.ts.map +1 -1
- package/dist/mcp/state-paths.js +36 -2
- package/dist/mcp/state-paths.js.map +1 -1
- package/dist/modes/__tests__/base-session-scope.test.js +26 -0
- package/dist/modes/__tests__/base-session-scope.test.js.map +1 -1
- package/dist/modes/base.d.ts +1 -0
- package/dist/modes/base.d.ts.map +1 -1
- package/dist/modes/base.js +35 -5
- package/dist/modes/base.js.map +1 -1
- package/dist/notifications/__tests__/http-client.test.d.ts +2 -0
- package/dist/notifications/__tests__/http-client.test.d.ts.map +1 -0
- package/dist/notifications/__tests__/http-client.test.js +90 -0
- package/dist/notifications/__tests__/http-client.test.js.map +1 -0
- package/dist/notifications/__tests__/notifier.test.js +22 -60
- package/dist/notifications/__tests__/notifier.test.js.map +1 -1
- package/dist/notifications/dispatcher.d.ts.map +1 -1
- package/dist/notifications/dispatcher.js +35 -60
- package/dist/notifications/dispatcher.js.map +1 -1
- package/dist/notifications/http-client.d.ts +22 -0
- package/dist/notifications/http-client.d.ts.map +1 -0
- package/dist/notifications/http-client.js +298 -0
- package/dist/notifications/http-client.js.map +1 -0
- package/dist/notifications/notifier.d.ts +3 -2
- package/dist/notifications/notifier.d.ts.map +1 -1
- package/dist/notifications/notifier.js +17 -22
- package/dist/notifications/notifier.js.map +1 -1
- package/dist/openclaw/__tests__/dispatcher.test.js +63 -2
- package/dist/openclaw/__tests__/dispatcher.test.js.map +1 -1
- package/dist/openclaw/dispatcher.d.ts.map +1 -1
- package/dist/openclaw/dispatcher.js +3 -2
- package/dist/openclaw/dispatcher.js.map +1 -1
- package/dist/performance-goal/artifacts.d.ts +76 -0
- package/dist/performance-goal/artifacts.d.ts.map +1 -0
- package/dist/performance-goal/artifacts.js +221 -0
- package/dist/performance-goal/artifacts.js.map +1 -0
- package/dist/pipeline/__tests__/stages.test.js +423 -14
- package/dist/pipeline/__tests__/stages.test.js.map +1 -1
- package/dist/pipeline/stages/team-exec.d.ts +8 -4
- package/dist/pipeline/stages/team-exec.d.ts.map +1 -1
- package/dist/pipeline/stages/team-exec.js +181 -13
- package/dist/pipeline/stages/team-exec.js.map +1 -1
- package/dist/planning/__tests__/artifacts.test.js +261 -1
- package/dist/planning/__tests__/artifacts.test.js.map +1 -1
- package/dist/planning/artifact-names.d.ts +13 -0
- package/dist/planning/artifact-names.d.ts.map +1 -0
- package/dist/planning/artifact-names.js +108 -0
- package/dist/planning/artifact-names.js.map +1 -0
- package/dist/planning/artifacts.d.ts +23 -1
- package/dist/planning/artifacts.d.ts.map +1 -1
- package/dist/planning/artifacts.js +171 -59
- package/dist/planning/artifacts.js.map +1 -1
- package/dist/ralph/__tests__/persistence.test.js +21 -1
- package/dist/ralph/__tests__/persistence.test.js.map +1 -1
- package/dist/ralph/persistence.d.ts.map +1 -1
- package/dist/ralph/persistence.js +6 -4
- package/dist/ralph/persistence.js.map +1 -1
- package/dist/ralplan/__tests__/runtime.test.js +2 -0
- package/dist/ralplan/__tests__/runtime.test.js.map +1 -1
- package/dist/ralplan/runtime.d.ts.map +1 -1
- package/dist/ralplan/runtime.js +6 -0
- package/dist/ralplan/runtime.js.map +1 -1
- package/dist/scripts/__tests__/codex-native-hook.test.js +1749 -88
- package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
- package/dist/scripts/__tests__/hook-derived-watcher.test.js +33 -1
- package/dist/scripts/__tests__/hook-derived-watcher.test.js.map +1 -1
- package/dist/scripts/__tests__/run-test-files.test.js +36 -0
- package/dist/scripts/__tests__/run-test-files.test.js.map +1 -1
- package/dist/scripts/codex-native-hook.d.ts.map +1 -1
- package/dist/scripts/codex-native-hook.js +570 -45
- package/dist/scripts/codex-native-hook.js.map +1 -1
- package/dist/scripts/codex-native-pre-post.d.ts +7 -0
- package/dist/scripts/codex-native-pre-post.d.ts.map +1 -1
- package/dist/scripts/codex-native-pre-post.js +341 -15
- package/dist/scripts/codex-native-pre-post.js.map +1 -1
- package/dist/scripts/hook-derived-watcher.js +2 -1
- package/dist/scripts/hook-derived-watcher.js.map +1 -1
- package/dist/scripts/notify-fallback-watcher.js +2 -1
- package/dist/scripts/notify-fallback-watcher.js.map +1 -1
- package/dist/scripts/notify-hook/orchestration-intent.d.ts +1 -2
- package/dist/scripts/notify-hook/orchestration-intent.d.ts.map +1 -1
- package/dist/scripts/notify-hook/orchestration-intent.js +2 -3
- package/dist/scripts/notify-hook/orchestration-intent.js.map +1 -1
- package/dist/scripts/notify-hook/team-leader-nudge.d.ts +0 -2
- package/dist/scripts/notify-hook/team-leader-nudge.d.ts.map +1 -1
- package/dist/scripts/notify-hook/team-leader-nudge.js +8 -60
- package/dist/scripts/notify-hook/team-leader-nudge.js.map +1 -1
- package/dist/scripts/notify-hook/team-worker-posttooluse.js +1 -1
- package/dist/scripts/notify-hook/team-worker-posttooluse.js.map +1 -1
- package/dist/scripts/notify-hook/team-worker-stop.d.ts +15 -0
- package/dist/scripts/notify-hook/team-worker-stop.d.ts.map +1 -0
- package/dist/scripts/notify-hook/team-worker-stop.js +224 -0
- package/dist/scripts/notify-hook/team-worker-stop.js.map +1 -0
- package/dist/scripts/notify-hook/team-worker.d.ts.map +1 -1
- package/dist/scripts/notify-hook/team-worker.js +26 -18
- package/dist/scripts/notify-hook/team-worker.js.map +1 -1
- package/dist/scripts/notify-hook.js +1 -1
- package/dist/scripts/notify-hook.js.map +1 -1
- package/dist/scripts/run-test-files.js +17 -1
- package/dist/scripts/run-test-files.js.map +1 -1
- package/dist/scripts/sync-plugin-mirror.d.ts +1 -0
- package/dist/scripts/sync-plugin-mirror.d.ts.map +1 -1
- package/dist/scripts/sync-plugin-mirror.js +10 -4
- package/dist/scripts/sync-plugin-mirror.js.map +1 -1
- package/dist/state/__tests__/operations.test.js +26 -0
- package/dist/state/__tests__/operations.test.js.map +1 -1
- package/dist/state/__tests__/skill-active.test.js +76 -0
- package/dist/state/__tests__/skill-active.test.js.map +1 -1
- package/dist/state/operations.d.ts +3 -1
- package/dist/state/operations.d.ts.map +1 -1
- package/dist/state/operations.js +8 -4
- package/dist/state/operations.js.map +1 -1
- package/dist/state/skill-active.d.ts +1 -0
- package/dist/state/skill-active.d.ts.map +1 -1
- package/dist/state/skill-active.js +54 -13
- package/dist/state/skill-active.js.map +1 -1
- package/dist/team/__tests__/api-interop.test.js +279 -0
- package/dist/team/__tests__/api-interop.test.js.map +1 -1
- package/dist/team/__tests__/approved-execution.test.d.ts +2 -0
- package/dist/team/__tests__/approved-execution.test.d.ts.map +1 -0
- package/dist/team/__tests__/approved-execution.test.js +124 -0
- package/dist/team/__tests__/approved-execution.test.js.map +1 -0
- package/dist/team/__tests__/delivery-e2e-smoke.test.js +2 -4
- package/dist/team/__tests__/delivery-e2e-smoke.test.js.map +1 -1
- package/dist/team/__tests__/delivery-log.test.d.ts +2 -0
- package/dist/team/__tests__/delivery-log.test.d.ts.map +1 -0
- package/dist/team/__tests__/delivery-log.test.js +44 -0
- package/dist/team/__tests__/delivery-log.test.js.map +1 -0
- package/dist/team/__tests__/model-contract.test.js +40 -9
- package/dist/team/__tests__/model-contract.test.js.map +1 -1
- package/dist/team/__tests__/repo-aware-decomposition.test.js +41 -0
- package/dist/team/__tests__/repo-aware-decomposition.test.js.map +1 -1
- package/dist/team/__tests__/role-router.test.js +4 -4
- package/dist/team/__tests__/role-router.test.js.map +1 -1
- package/dist/team/__tests__/runtime-boxed-state.test.d.ts +2 -0
- package/dist/team/__tests__/runtime-boxed-state.test.d.ts.map +1 -0
- package/dist/team/__tests__/runtime-boxed-state.test.js +39 -0
- package/dist/team/__tests__/runtime-boxed-state.test.js.map +1 -0
- package/dist/team/__tests__/runtime-cli.test.js +24 -0
- package/dist/team/__tests__/runtime-cli.test.js.map +1 -1
- package/dist/team/__tests__/runtime.test.js +563 -72
- package/dist/team/__tests__/runtime.test.js.map +1 -1
- package/dist/team/__tests__/state-root.test.js +13 -0
- package/dist/team/__tests__/state-root.test.js.map +1 -1
- package/dist/team/__tests__/state.test.js +13 -0
- package/dist/team/__tests__/state.test.js.map +1 -1
- package/dist/team/__tests__/team-identity.test.d.ts +2 -0
- package/dist/team/__tests__/team-identity.test.d.ts.map +1 -0
- package/dist/team/__tests__/team-identity.test.js +166 -0
- package/dist/team/__tests__/team-identity.test.js.map +1 -0
- package/dist/team/__tests__/tmux-session.test.js +58 -1
- package/dist/team/__tests__/tmux-session.test.js.map +1 -1
- package/dist/team/__tests__/worker-bootstrap.test.js +62 -0
- package/dist/team/__tests__/worker-bootstrap.test.js.map +1 -1
- package/dist/team/api-interop.d.ts +1 -0
- package/dist/team/api-interop.d.ts.map +1 -1
- package/dist/team/api-interop.js +163 -132
- package/dist/team/api-interop.js.map +1 -1
- package/dist/team/approved-execution.d.ts +37 -0
- package/dist/team/approved-execution.d.ts.map +1 -0
- package/dist/team/approved-execution.js +136 -0
- package/dist/team/approved-execution.js.map +1 -0
- package/dist/team/delivery-log.d.ts +1 -1
- package/dist/team/delivery-log.d.ts.map +1 -1
- package/dist/team/delivery-log.js +2 -1
- package/dist/team/delivery-log.js.map +1 -1
- package/dist/team/followup-planner.js +2 -2
- package/dist/team/followup-planner.js.map +1 -1
- package/dist/team/goal-workflow.d.ts +20 -0
- package/dist/team/goal-workflow.d.ts.map +1 -0
- package/dist/team/goal-workflow.js +57 -0
- package/dist/team/goal-workflow.js.map +1 -0
- package/dist/team/orchestrator.js +2 -2
- package/dist/team/orchestrator.js.map +1 -1
- package/dist/team/repo-aware-decomposition.d.ts +3 -0
- package/dist/team/repo-aware-decomposition.d.ts.map +1 -1
- package/dist/team/repo-aware-decomposition.js +2 -0
- package/dist/team/repo-aware-decomposition.js.map +1 -1
- package/dist/team/role-router.js +5 -5
- package/dist/team/role-router.js.map +1 -1
- package/dist/team/runtime-cli.d.ts +32 -2
- package/dist/team/runtime-cli.d.ts.map +1 -1
- package/dist/team/runtime-cli.js +78 -26
- package/dist/team/runtime-cli.js.map +1 -1
- package/dist/team/runtime.d.ts +7 -1
- package/dist/team/runtime.d.ts.map +1 -1
- package/dist/team/runtime.js +383 -40
- package/dist/team/runtime.js.map +1 -1
- package/dist/team/scaling.d.ts.map +1 -1
- package/dist/team/scaling.js +2 -0
- package/dist/team/scaling.js.map +1 -1
- package/dist/team/state.d.ts +9 -0
- package/dist/team/state.d.ts.map +1 -1
- package/dist/team/state.js +21 -0
- package/dist/team/state.js.map +1 -1
- package/dist/team/team-identity.d.ts +26 -0
- package/dist/team/team-identity.d.ts.map +1 -0
- package/dist/team/team-identity.js +169 -0
- package/dist/team/team-identity.js.map +1 -0
- package/dist/team/tmux-session.d.ts +18 -0
- package/dist/team/tmux-session.d.ts.map +1 -1
- package/dist/team/tmux-session.js +65 -3
- package/dist/team/tmux-session.js.map +1 -1
- package/dist/team/worker-bootstrap.d.ts +4 -0
- package/dist/team/worker-bootstrap.d.ts.map +1 -1
- package/dist/team/worker-bootstrap.js +28 -2
- package/dist/team/worker-bootstrap.js.map +1 -1
- package/dist/ultragoal/__tests__/artifacts.test.d.ts +2 -0
- package/dist/ultragoal/__tests__/artifacts.test.d.ts.map +1 -0
- package/dist/ultragoal/__tests__/artifacts.test.js +93 -0
- package/dist/ultragoal/__tests__/artifacts.test.js.map +1 -0
- package/dist/ultragoal/artifacts.d.ts +89 -0
- package/dist/ultragoal/artifacts.d.ts.map +1 -0
- package/dist/ultragoal/artifacts.js +233 -0
- package/dist/ultragoal/artifacts.js.map +1 -0
- package/dist/utils/__tests__/agents-model-table.test.js +3 -1
- package/dist/utils/__tests__/agents-model-table.test.js.map +1 -1
- package/dist/utils/__tests__/paths.test.js +31 -1
- package/dist/utils/__tests__/paths.test.js.map +1 -1
- package/dist/utils/agents-model-table.d.ts.map +1 -1
- package/dist/utils/agents-model-table.js +12 -1
- package/dist/utils/agents-model-table.js.map +1 -1
- package/dist/utils/paths.d.ts +2 -0
- package/dist/utils/paths.d.ts.map +1 -1
- package/dist/utils/paths.js +23 -7
- package/dist/utils/paths.js.map +1 -1
- package/dist/verification/__tests__/ci-rust-gates.test.js +30 -19
- package/dist/verification/__tests__/ci-rust-gates.test.js.map +1 -1
- package/package.json +5 -5
- package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
- package/plugins/oh-my-codex/skills/ai-slop-cleaner/SKILL.md +30 -5
- package/plugins/oh-my-codex/skills/ask/SKILL.md +58 -0
- package/plugins/oh-my-codex/skills/autoresearch-goal/SKILL.md +36 -0
- package/plugins/oh-my-codex/skills/omx-setup/SKILL.md +2 -2
- package/plugins/oh-my-codex/skills/performance-goal/SKILL.md +65 -0
- package/plugins/oh-my-codex/skills/plan/SKILL.md +1 -1
- package/plugins/oh-my-codex/skills/ralph/SKILL.md +22 -3
- package/plugins/oh-my-codex/skills/team/SKILL.md +6 -2
- package/plugins/oh-my-codex/skills/ultragoal/SKILL.md +49 -0
- package/plugins/oh-my-codex/skills/visual-ralph/SKILL.md +9 -9
- package/prompts/api-reviewer.md +1 -1
- package/prompts/code-reviewer.md +2 -0
- package/prompts/performance-reviewer.md +1 -1
- package/prompts/quality-reviewer.md +1 -1
- package/prompts/quality-strategist.md +2 -2
- package/prompts/style-reviewer.md +1 -1
- package/prompts/test-engineer.md +1 -1
- package/skills/ai-slop-cleaner/SKILL.md +30 -5
- package/skills/ask/SKILL.md +58 -0
- package/skills/ask-claude/SKILL.md +3 -54
- package/skills/ask-gemini/SKILL.md +3 -54
- package/skills/autoresearch-goal/SKILL.md +36 -0
- package/skills/build-fix/SKILL.md +4 -139
- package/skills/deepsearch/SKILL.md +4 -32
- package/skills/ecomode/SKILL.md +4 -108
- package/skills/help/SKILL.md +4 -196
- package/skills/note/SKILL.md +4 -56
- package/skills/omx-setup/SKILL.md +2 -2
- package/skills/performance-goal/SKILL.md +65 -0
- package/skills/plan/SKILL.md +1 -1
- package/skills/ralph/SKILL.md +22 -3
- package/skills/ralph-init/SKILL.md +4 -40
- package/skills/review/SKILL.md +4 -32
- package/skills/security-review/SKILL.md +4 -294
- package/skills/swarm/SKILL.md +4 -19
- package/skills/tdd/SKILL.md +4 -100
- package/skills/team/SKILL.md +6 -2
- package/skills/trace/SKILL.md +4 -27
- package/skills/ultragoal/SKILL.md +49 -0
- package/skills/visual-ralph/SKILL.md +9 -9
- package/skills/visual-verdict/SKILL.md +4 -70
- package/skills/web-clone/SKILL.md +4 -18
- package/src/scripts/__tests__/codex-native-hook.test.ts +2923 -1030
- package/src/scripts/__tests__/hook-derived-watcher.test.ts +45 -1
- package/src/scripts/__tests__/run-test-files.test.ts +46 -0
- package/src/scripts/codex-native-hook.ts +696 -46
- package/src/scripts/codex-native-pre-post.ts +369 -16
- package/src/scripts/hook-derived-watcher.ts +2 -1
- package/src/scripts/notify-fallback-watcher.ts +2 -1
- package/src/scripts/notify-hook/orchestration-intent.ts +1 -3
- package/src/scripts/notify-hook/team-leader-nudge.ts +7 -63
- package/src/scripts/notify-hook/team-worker-posttooluse.ts +1 -1
- package/src/scripts/notify-hook/team-worker-stop.ts +246 -0
- package/src/scripts/notify-hook/team-worker.ts +23 -14
- package/src/scripts/notify-hook.ts +1 -1
- package/src/scripts/run-test-files.ts +20 -1
- package/src/scripts/sync-plugin-mirror.ts +13 -4
- package/templates/catalog-manifest.json +45 -27
- package/plugins/oh-my-codex/skills/ask-claude/SKILL.md +0 -61
- package/plugins/oh-my-codex/skills/ask-gemini/SKILL.md +0 -61
- package/plugins/oh-my-codex/skills/help/SKILL.md +0 -202
- package/plugins/oh-my-codex/skills/note/SKILL.md +0 -62
- package/plugins/oh-my-codex/skills/security-review/SKILL.md +0 -300
- package/plugins/oh-my-codex/skills/trace/SKILL.md +0 -33
- package/plugins/oh-my-codex/skills/visual-verdict/SKILL.md +0 -76
|
@@ -1,16 +1,22 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, it } from 'node:test';
|
|
2
2
|
import assert from 'node:assert/strict';
|
|
3
3
|
import { execFileSync, spawn } from 'child_process';
|
|
4
|
-
import { mkdtemp, rm, writeFile, readFile, mkdir, chmod } from 'fs/promises';
|
|
4
|
+
import { mkdtemp, rm, writeFile, readFile, mkdir, chmod, readdir } from 'fs/promises';
|
|
5
5
|
import { join } from 'path';
|
|
6
6
|
import { tmpdir } from 'os';
|
|
7
7
|
import { existsSync } from 'fs';
|
|
8
8
|
import { HUD_TMUX_TEAM_HEIGHT_LINES } from '../../hud/constants.js';
|
|
9
|
-
import { initTeamState, createTask, writeWorkerInbox, readTeamConfig, saveTeamConfig, listMailboxMessages, listDispatchRequests, transitionDispatchRequest, updateWorkerHeartbeat, writeAtomic, readTask, readMonitorSnapshot, claimTask, transitionTaskStatus, readWorkerStatus, writeWorkerStatus, } from '../state.js';
|
|
9
|
+
import { DEFAULT_MAX_WORKERS, initTeamState, createTask, writeWorkerInbox, readTeamConfig, saveTeamConfig, listMailboxMessages, listDispatchRequests, transitionDispatchRequest, updateWorkerHeartbeat, writeAtomic, readTask, readMonitorSnapshot, claimTask, transitionTaskStatus, readWorkerStatus, writeWorkerStatus, } from '../state.js';
|
|
10
10
|
import { monitorTeam, shutdownTeam, resumeTeam, startTeam, assignTask, sendWorkerMessage, applyCreatedInteractiveSessionToConfig, resolveWorkerLaunchArgsFromEnv, shouldPrekillInteractiveShutdownProcessTrees, waitForWorkerStartupEvidence, waitForClaudeStartupEvidence, cleanupTeamWorkerLaunchOrphanedMcpProcesses, settleStartupAttemptResults, } from '../runtime.js';
|
|
11
11
|
import { resolveAgentReasoningEffort, resolveTeamLowComplexityDefaultModel } from '../model-contract.js';
|
|
12
12
|
import { readTeamEvents } from '../state/events.js';
|
|
13
13
|
import { sanitizeTeamName } from '../tmux-session.js';
|
|
14
|
+
import { buildInternalTeamName, resolveTeamIdentityScope } from '../team-identity.js';
|
|
15
|
+
import { writePersistedApprovedTeamExecutionBinding } from '../approved-execution.js';
|
|
16
|
+
const coverageRun = process.env.NODE_V8_COVERAGE ? true : false;
|
|
17
|
+
const skipSlowLifecycleUnderCoverage = coverageRun
|
|
18
|
+
? 'covered by the team-state-runtime lane; skipped under c8 to keep the coverage gate bounded around slow process-lifecycle waits'
|
|
19
|
+
: false;
|
|
14
20
|
async function initRepo() {
|
|
15
21
|
const cwd = await mkdtemp(join(tmpdir(), 'omx-runtime-worktree-repo-'));
|
|
16
22
|
execFileSync('git', ['init'], { cwd, stdio: 'ignore' });
|
|
@@ -47,6 +53,56 @@ async function attachDirtyWorkerRepo(teamName, cwd, repoName) {
|
|
|
47
53
|
function expectedLowComplexityModel(codexHomeOverride) {
|
|
48
54
|
return resolveTeamLowComplexityDefaultModel(codexHomeOverride);
|
|
49
55
|
}
|
|
56
|
+
function withIsolatedDefaultModelEnv(run) {
|
|
57
|
+
const savedEnv = new Map();
|
|
58
|
+
for (const key of [
|
|
59
|
+
'CODEX_HOME',
|
|
60
|
+
'OMX_DEFAULT_FRONTIER_MODEL',
|
|
61
|
+
'OMX_DEFAULT_STANDARD_MODEL',
|
|
62
|
+
'OMX_DEFAULT_SPARK_MODEL',
|
|
63
|
+
'OMX_SPARK_MODEL',
|
|
64
|
+
]) {
|
|
65
|
+
savedEnv.set(key, process.env[key]);
|
|
66
|
+
delete process.env[key];
|
|
67
|
+
}
|
|
68
|
+
process.env.CODEX_HOME = join(tmpdir(), `omx-runtime-defaults-${process.pid}-${Date.now()}`);
|
|
69
|
+
try {
|
|
70
|
+
return run();
|
|
71
|
+
}
|
|
72
|
+
finally {
|
|
73
|
+
for (const [key, value] of savedEnv.entries()) {
|
|
74
|
+
if (typeof value === 'string')
|
|
75
|
+
process.env[key] = value;
|
|
76
|
+
else
|
|
77
|
+
delete process.env[key];
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
async function withIsolatedDefaultModelEnvAsync(run) {
|
|
82
|
+
const savedEnv = new Map();
|
|
83
|
+
for (const key of [
|
|
84
|
+
'CODEX_HOME',
|
|
85
|
+
'OMX_DEFAULT_FRONTIER_MODEL',
|
|
86
|
+
'OMX_DEFAULT_STANDARD_MODEL',
|
|
87
|
+
'OMX_DEFAULT_SPARK_MODEL',
|
|
88
|
+
'OMX_SPARK_MODEL',
|
|
89
|
+
]) {
|
|
90
|
+
savedEnv.set(key, process.env[key]);
|
|
91
|
+
delete process.env[key];
|
|
92
|
+
}
|
|
93
|
+
process.env.CODEX_HOME = join(tmpdir(), `omx-runtime-defaults-${process.pid}-${Date.now()}`);
|
|
94
|
+
try {
|
|
95
|
+
return await run();
|
|
96
|
+
}
|
|
97
|
+
finally {
|
|
98
|
+
for (const [key, value] of savedEnv.entries()) {
|
|
99
|
+
if (typeof value === 'string')
|
|
100
|
+
process.env[key] = value;
|
|
101
|
+
else
|
|
102
|
+
delete process.env[key];
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
50
106
|
async function readTeamDeliveryLog(cwd) {
|
|
51
107
|
const path = join(cwd, '.omx', 'logs', `team-delivery-${new Date().toISOString().slice(0, 10)}.jsonl`);
|
|
52
108
|
const raw = await readFile(path, 'utf-8').catch(() => '');
|
|
@@ -57,7 +113,7 @@ async function readTeamDeliveryLog(cwd) {
|
|
|
57
113
|
.map((line) => JSON.parse(line));
|
|
58
114
|
}
|
|
59
115
|
async function markPendingInboxDispatchesDelivered(teamName, cwd, opts = {}) {
|
|
60
|
-
const requests = await listDispatchRequests(teamName, cwd, { kind: 'inbox' }).catch(() => []);
|
|
116
|
+
const requests = await listDispatchRequests(await resolveRuntimeTeamName(cwd, teamName), cwd, { kind: 'inbox' }).catch(() => []);
|
|
61
117
|
for (const request of requests) {
|
|
62
118
|
if (request.status !== 'pending')
|
|
63
119
|
continue;
|
|
@@ -72,7 +128,7 @@ async function markPendingInboxDispatchesDelivered(teamName, cwd, opts = {}) {
|
|
|
72
128
|
}
|
|
73
129
|
}
|
|
74
130
|
async function markPendingInboxDispatchesNotified(teamName, cwd, opts = {}) {
|
|
75
|
-
const requests = await listDispatchRequests(teamName, cwd, { kind: 'inbox' }).catch(() => []);
|
|
131
|
+
const requests = await listDispatchRequests(await resolveRuntimeTeamName(cwd, teamName), cwd, { kind: 'inbox' }).catch(() => []);
|
|
76
132
|
for (const request of requests) {
|
|
77
133
|
if (request.status !== 'pending')
|
|
78
134
|
continue;
|
|
@@ -168,6 +224,16 @@ async function waitForFileText(filePath, matcher, timeoutMs = 3_000) {
|
|
|
168
224
|
}
|
|
169
225
|
throw new Error(`timed out waiting for ${filePath}`);
|
|
170
226
|
}
|
|
227
|
+
async function resolveRuntimeTeamName(cwd, requestedName) {
|
|
228
|
+
const teamsRoot = join(cwd, '.omx', 'state', 'team');
|
|
229
|
+
const entries = await readdir(teamsRoot, { withFileTypes: true }).catch(() => []);
|
|
230
|
+
const prefix = requestedName.slice(0, 18);
|
|
231
|
+
const names = entries
|
|
232
|
+
.filter((entry) => entry.isDirectory() && (entry.name === requestedName || entry.name.startsWith(`${requestedName}-`) || entry.name.startsWith(prefix)))
|
|
233
|
+
.map((entry) => entry.name)
|
|
234
|
+
.sort((a, b) => a.length - b.length || a.localeCompare(b));
|
|
235
|
+
return names[0] ?? requestedName;
|
|
236
|
+
}
|
|
171
237
|
async function writeFakePromptWorkerBinary(binaryPath, scriptBody, options = {}) {
|
|
172
238
|
const bootstrap = options.emitStartupEvidence === false
|
|
173
239
|
? ''
|
|
@@ -175,7 +241,7 @@ async function writeFakePromptWorkerBinary(binaryPath, scriptBody, options = {})
|
|
|
175
241
|
const fs = require('fs');
|
|
176
242
|
const path = require('path');
|
|
177
243
|
const stateRoot = process.env.OMX_TEAM_STATE_ROOT;
|
|
178
|
-
const worker = String(process.env.OMX_TEAM_WORKER || '');
|
|
244
|
+
const worker = String(process.env.OMX_TEAM_INTERNAL_WORKER || process.env.OMX_TEAM_WORKER || '');
|
|
179
245
|
const [teamName, workerName] = worker.split('/');
|
|
180
246
|
if (stateRoot && teamName && workerName) {
|
|
181
247
|
const workerDir = path.join(stateRoot, 'team', teamName, 'workers', workerName);
|
|
@@ -318,29 +384,35 @@ describe('runtime', () => {
|
|
|
318
384
|
assert.deepEqual(args, ['--no-alt-screen', '--model', expectedLowComplexityModel()]);
|
|
319
385
|
});
|
|
320
386
|
it('resolveWorkerLaunchArgsFromEnv reads low-complexity model from config when present', async () => {
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
387
|
+
await withIsolatedDefaultModelEnvAsync(async () => {
|
|
388
|
+
const previousCodexHome = process.env.CODEX_HOME;
|
|
389
|
+
const tempCodexHome = await mkdtemp(join(tmpdir(), 'omx-codex-home-'));
|
|
390
|
+
await writeFile(join(tempCodexHome, '.omx-config.json'), JSON.stringify({ models: { team_low_complexity: 'gpt-4.1-mini' } }));
|
|
391
|
+
process.env.CODEX_HOME = tempCodexHome;
|
|
392
|
+
try {
|
|
393
|
+
const args = resolveWorkerLaunchArgsFromEnv({ OMX_TEAM_WORKER_LAUNCH_ARGS: '--no-alt-screen' }, 'explore');
|
|
394
|
+
assert.deepEqual(args, ['--no-alt-screen', '--model', 'gpt-4.1-mini']);
|
|
395
|
+
}
|
|
396
|
+
finally {
|
|
397
|
+
if (typeof previousCodexHome === 'string')
|
|
398
|
+
process.env.CODEX_HOME = previousCodexHome;
|
|
399
|
+
else
|
|
400
|
+
delete process.env.CODEX_HOME;
|
|
401
|
+
await rm(tempCodexHome, { recursive: true, force: true });
|
|
402
|
+
}
|
|
403
|
+
});
|
|
336
404
|
});
|
|
337
405
|
it('resolveWorkerLaunchArgsFromEnv injects the frontier default model for executor workers', () => {
|
|
338
|
-
|
|
339
|
-
|
|
406
|
+
withIsolatedDefaultModelEnv(() => {
|
|
407
|
+
const args = resolveWorkerLaunchArgsFromEnv({ OMX_TEAM_WORKER_LAUNCH_ARGS: '--no-alt-screen' }, 'executor');
|
|
408
|
+
assert.deepEqual(args, ['--no-alt-screen', '--model', 'gpt-5.5']);
|
|
409
|
+
});
|
|
340
410
|
});
|
|
341
411
|
it('resolveWorkerLaunchArgsFromEnv uses medium reasoning for executor launch defaults', () => {
|
|
342
|
-
|
|
343
|
-
|
|
412
|
+
withIsolatedDefaultModelEnv(() => {
|
|
413
|
+
const args = resolveWorkerLaunchArgsFromEnv({ OMX_TEAM_WORKER_LAUNCH_ARGS: '--no-alt-screen' }, 'executor', undefined, resolveAgentReasoningEffort('executor'), 'codex');
|
|
414
|
+
assert.deepEqual(args, ['--no-alt-screen', '-c', 'model_reasoning_effort="medium"', '--model', 'gpt-5.5']);
|
|
415
|
+
});
|
|
344
416
|
});
|
|
345
417
|
it('resolveWorkerLaunchArgsFromEnv treats *-low aliases as low complexity', () => {
|
|
346
418
|
const args = resolveWorkerLaunchArgsFromEnv({ OMX_TEAM_WORKER_LAUNCH_ARGS: '--no-alt-screen' }, 'executor-low');
|
|
@@ -366,10 +438,12 @@ describe('runtime', () => {
|
|
|
366
438
|
const originalLog = console.log;
|
|
367
439
|
console.log = (...args) => { logs.push(args.join(' ')); };
|
|
368
440
|
try {
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
441
|
+
withIsolatedDefaultModelEnv(() => {
|
|
442
|
+
const lowArgs = resolveWorkerLaunchArgsFromEnv({ OMX_TEAM_WORKER_LAUNCH_ARGS: '--no-alt-screen' }, 'executor', undefined, 'low', 'codex');
|
|
443
|
+
const highArgs = resolveWorkerLaunchArgsFromEnv({ OMX_TEAM_WORKER_LAUNCH_ARGS: '--no-alt-screen' }, 'executor', undefined, 'high', 'codex');
|
|
444
|
+
assert.deepEqual(lowArgs, ['--no-alt-screen', '-c', 'model_reasoning_effort="low"', '--model', 'gpt-5.5']);
|
|
445
|
+
assert.deepEqual(highArgs, ['--no-alt-screen', '-c', 'model_reasoning_effort="high"', '--model', 'gpt-5.5']);
|
|
446
|
+
});
|
|
373
447
|
}
|
|
374
448
|
finally {
|
|
375
449
|
console.log = originalLog;
|
|
@@ -451,7 +525,10 @@ describe('runtime', () => {
|
|
|
451
525
|
const originalLog = console.log;
|
|
452
526
|
console.log = (...args) => { logs.push(args.join(' ')); };
|
|
453
527
|
try {
|
|
454
|
-
|
|
528
|
+
let codexArgs = [];
|
|
529
|
+
withIsolatedDefaultModelEnv(() => {
|
|
530
|
+
codexArgs = resolveWorkerLaunchArgsFromEnv({ OMX_TEAM_WORKER_LAUNCH_ARGS: '--no-alt-screen' }, 'executor', undefined, 'high', 'codex');
|
|
531
|
+
});
|
|
455
532
|
const claudeArgs = resolveWorkerLaunchArgsFromEnv({ OMX_TEAM_WORKER_LAUNCH_ARGS: '--no-alt-screen --model claude-3-7-sonnet' }, 'executor', undefined, 'low', 'claude');
|
|
456
533
|
const geminiArgs = resolveWorkerLaunchArgsFromEnv({ OMX_TEAM_WORKER_LAUNCH_ARGS: '--model gemini-2.0-pro' }, 'executor', undefined, 'low', 'gemini');
|
|
457
534
|
assert.deepEqual(codexArgs, ['--no-alt-screen', '-c', 'model_reasoning_effort="high"', '--model', 'gpt-5.5']);
|
|
@@ -589,7 +666,7 @@ describe('runtime', () => {
|
|
|
589
666
|
await rm(cwd, { recursive: true, force: true });
|
|
590
667
|
}
|
|
591
668
|
});
|
|
592
|
-
it('uses a production startup evidence window that can tolerate slow Codex startup', async () => {
|
|
669
|
+
it('uses a production startup evidence window that can tolerate slow Codex startup', { skip: skipSlowLifecycleUnderCoverage }, async () => {
|
|
593
670
|
const cwd = await mkdtemp(join(tmpdir(), 'omx-runtime-startup-window-'));
|
|
594
671
|
const prevTmux = process.env.TMUX;
|
|
595
672
|
const prevTmuxPane = process.env.TMUX_PANE;
|
|
@@ -680,22 +757,23 @@ esac
|
|
|
680
757
|
delete process.env.OMX_TEAM_STARTUP_EVIDENCE_TIMEOUT_MS;
|
|
681
758
|
process.env.OMX_TEAM_STARTUP_DISPATCH_RETRIES = '1';
|
|
682
759
|
process.env.OMX_TEAM_STARTUP_DISPATCH_RETRY_DELAY_MS = '50';
|
|
760
|
+
const expectedTeamName = buildInternalTeamName('team-startup-window', resolveTeamIdentityScope(process.env));
|
|
683
761
|
receiptNotifier = setInterval(() => {
|
|
684
|
-
void markPendingInboxDispatchesNotified(
|
|
762
|
+
void markPendingInboxDispatchesNotified(expectedTeamName, cwd, {
|
|
685
763
|
toWorker: 'worker-1',
|
|
686
764
|
lastReason: 'test_notified_receipt',
|
|
687
765
|
}).catch(() => { });
|
|
688
766
|
}, 20);
|
|
689
767
|
progressWriter = setTimeout(() => {
|
|
690
|
-
void writeWorkerStatus(
|
|
768
|
+
void writeWorkerStatus(expectedTeamName, 'worker-1', {
|
|
691
769
|
state: 'working',
|
|
692
770
|
current_task_id: '1',
|
|
693
771
|
updated_at: new Date().toISOString(),
|
|
694
772
|
}, cwd).catch(() => { });
|
|
695
773
|
}, 6_000);
|
|
696
774
|
const runtime = await withoutTeamWorkerEnv(() => startTeam('team-startup-window', 'interactive startup should wait for slow Codex evidence', 'executor', 1, [{ subject: 's', description: 'd', owner: 'worker-1' }], cwd));
|
|
697
|
-
assert.equal(runtime.teamName,
|
|
698
|
-
assert.ok(await readTeamConfig(
|
|
775
|
+
assert.equal(runtime.teamName, expectedTeamName);
|
|
776
|
+
assert.ok(await readTeamConfig(runtime.teamName, cwd));
|
|
699
777
|
});
|
|
700
778
|
}
|
|
701
779
|
finally {
|
|
@@ -744,7 +822,7 @@ esac
|
|
|
744
822
|
await rm(cwd, { recursive: true, force: true });
|
|
745
823
|
}
|
|
746
824
|
});
|
|
747
|
-
it('startTeam
|
|
825
|
+
it('startTeam records recoverable issue when tmux fallback never produces worker startup evidence', { skip: skipSlowLifecycleUnderCoverage }, async () => {
|
|
748
826
|
const cwd = await mkdtemp(join(tmpdir(), 'omx-runtime-startup-no-evidence-'));
|
|
749
827
|
const prevTmux = process.env.TMUX;
|
|
750
828
|
const prevTmuxPane = process.env.TMUX_PANE;
|
|
@@ -834,24 +912,28 @@ esac
|
|
|
834
912
|
process.env.OMX_TEAM_STARTUP_EVIDENCE_TIMEOUT_MS = '500';
|
|
835
913
|
process.env.OMX_TEAM_STARTUP_DISPATCH_RETRIES = '1';
|
|
836
914
|
process.env.OMX_TEAM_STARTUP_DISPATCH_RETRY_DELAY_MS = '50';
|
|
915
|
+
const expectedTeamName = buildInternalTeamName('team-startup-no-evidence', resolveTeamIdentityScope(process.env));
|
|
837
916
|
receiptFailer = setInterval(() => {
|
|
838
917
|
void (async () => {
|
|
839
|
-
const requests = await listDispatchRequests(
|
|
918
|
+
const requests = await listDispatchRequests(expectedTeamName, cwd, { kind: 'inbox' }).catch(() => []);
|
|
840
919
|
for (const request of requests) {
|
|
841
920
|
if (request.status !== 'pending')
|
|
842
921
|
continue;
|
|
843
|
-
await transitionDispatchRequest(
|
|
922
|
+
await transitionDispatchRequest(expectedTeamName, request.request_id, 'pending', 'failed', { last_reason: 'test_failed_receipt' }, cwd).catch(() => { });
|
|
844
923
|
}
|
|
845
924
|
})();
|
|
846
925
|
}, 20);
|
|
847
|
-
|
|
926
|
+
const runtime = await withoutTeamWorkerEnv(() => startTeam('team-startup-no-evidence', 'interactive startup records missing worker evidence without aborting live panes', 'executor', 1, [{ subject: 's', description: 'd', owner: 'worker-1' }], cwd));
|
|
848
927
|
if (receiptFailer) {
|
|
849
928
|
clearInterval(receiptFailer);
|
|
850
929
|
receiptFailer = null;
|
|
851
930
|
}
|
|
852
|
-
assert.
|
|
931
|
+
assert.ok(await readTeamConfig(runtime.teamName, cwd));
|
|
932
|
+
const workerStatus = await readWorkerStatus(runtime.teamName, 'worker-1', cwd);
|
|
933
|
+
assert.ok(['unknown', 'idle'].includes(workerStatus.state));
|
|
853
934
|
const tmuxLog = await readFile(tmuxLogPath, 'utf-8');
|
|
854
935
|
assert.match(tmuxLog, /send-keys -t %2 -l --/);
|
|
936
|
+
await shutdownTeam(runtime.teamName, cwd, { force: true }).catch(() => { });
|
|
855
937
|
});
|
|
856
938
|
}
|
|
857
939
|
finally {
|
|
@@ -956,7 +1038,8 @@ sleep 5
|
|
|
956
1038
|
let runtime = null;
|
|
957
1039
|
try {
|
|
958
1040
|
runtime = await startTeam('nested-allowed', 'nested task', 'explore', 1, [{ subject: 's', description: 'd', owner: 'worker-1' }], cwd);
|
|
959
|
-
assert.
|
|
1041
|
+
assert.match(runtime.teamName, /^nested-allowed-[a-f0-9]{8}$/);
|
|
1042
|
+
assert.equal(runtime.config.display_name, 'nested-allowed');
|
|
960
1043
|
await shutdownTeam(runtime.teamName, cwd, { force: true });
|
|
961
1044
|
runtime = null;
|
|
962
1045
|
}
|
|
@@ -1008,6 +1091,54 @@ sleep 5
|
|
|
1008
1091
|
await rm(cwd, { recursive: true, force: true });
|
|
1009
1092
|
}
|
|
1010
1093
|
});
|
|
1094
|
+
it('shutdownTeam with path-like display input cannot remove state outside the team directory', async () => {
|
|
1095
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-runtime-shutdown-unsafe-'));
|
|
1096
|
+
try {
|
|
1097
|
+
const victim = join(cwd, '.omx', 'state', 'victim');
|
|
1098
|
+
await mkdir(victim, { recursive: true });
|
|
1099
|
+
await writeFile(join(victim, 'keep.txt'), 'keep');
|
|
1100
|
+
await shutdownTeam('../../victim', cwd, { force: true });
|
|
1101
|
+
assert.equal(existsSync(join(victim, 'keep.txt')), true);
|
|
1102
|
+
}
|
|
1103
|
+
finally {
|
|
1104
|
+
await rm(cwd, { recursive: true, force: true });
|
|
1105
|
+
}
|
|
1106
|
+
});
|
|
1107
|
+
it('startTeam blocks duplicate no-session/no-tmux prompt-mode starts with stable cwd leader identity', async () => {
|
|
1108
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-runtime-prompt-duplicate-nosession-'));
|
|
1109
|
+
const binDir = join(cwd, 'bin');
|
|
1110
|
+
const fakeCodexPath = join(binDir, 'codex');
|
|
1111
|
+
await mkdir(binDir, { recursive: true });
|
|
1112
|
+
await writeFakePromptWorkerBinary(fakeCodexPath, `setTimeout(() => {}, 5000);
|
|
1113
|
+
process.on('SIGTERM', () => process.exit(0));`);
|
|
1114
|
+
let runtime = null;
|
|
1115
|
+
try {
|
|
1116
|
+
await withPromptModeCodexEnv(binDir, {
|
|
1117
|
+
OMX_SESSION_ID: undefined,
|
|
1118
|
+
CODEX_SESSION_ID: undefined,
|
|
1119
|
+
SESSION_ID: undefined,
|
|
1120
|
+
TMUX_PANE: undefined,
|
|
1121
|
+
}, async () => {
|
|
1122
|
+
runtime = await withoutTeamWorkerEnv(() => startTeam('first-prompt-team', 'first no-session prompt team', 'executor', 1, [{ subject: 's', description: 'd', owner: 'worker-1' }], cwd));
|
|
1123
|
+
assert.equal(runtime.config.worker_launch_mode, 'prompt');
|
|
1124
|
+
assert.match(runtime.teamName, /^first-prompt-team-[a-f0-9]{8}$/);
|
|
1125
|
+
assert.equal(runtime.config.display_name, 'first-prompt-team');
|
|
1126
|
+
assert.equal(runtime.config.identity_source, 'run-id');
|
|
1127
|
+
const manifest = JSON.parse(await readFile(join(cwd, '.omx', 'state', 'team', runtime.teamName, 'manifest.v2.json'), 'utf-8'));
|
|
1128
|
+
assert.equal(manifest.leader?.session_id, `cwd:${cwd}`);
|
|
1129
|
+
await assert.rejects(() => withoutTeamWorkerEnv(() => startTeam('second-prompt-team', 'second no-session prompt team must be blocked', 'executor', 1, [{ subject: 's2', description: 'd2', owner: 'worker-1' }], cwd)), /leader_session_conflict: active team exists \(first-prompt-team-[a-f0-9]{8}\)/);
|
|
1130
|
+
const teamEntries = await readdir(join(cwd, '.omx', 'state', 'team'), { withFileTypes: true });
|
|
1131
|
+
assert.equal(teamEntries.some((entry) => entry.isDirectory() && entry.name.startsWith('second-prompt-team-')), false, 'blocked duplicate start must not create a second prompt-mode team state directory');
|
|
1132
|
+
});
|
|
1133
|
+
}
|
|
1134
|
+
finally {
|
|
1135
|
+
const runtimeToShutdown = runtime;
|
|
1136
|
+
if (runtimeToShutdown) {
|
|
1137
|
+
await shutdownTeam(runtimeToShutdown.teamName, cwd, { force: true }).catch(() => { });
|
|
1138
|
+
}
|
|
1139
|
+
await rm(cwd, { recursive: true, force: true });
|
|
1140
|
+
}
|
|
1141
|
+
});
|
|
1011
1142
|
it('startTeam rejects duplicate active same-name team state without mutating existing files', async () => {
|
|
1012
1143
|
const cwd = await mkdtemp(join(tmpdir(), 'omx-runtime-duplicate-team-'));
|
|
1013
1144
|
const prevSessionId = process.env.OMX_SESSION_ID;
|
|
@@ -1256,8 +1387,9 @@ case "\${1:-}" in
|
|
|
1256
1387
|
split-window)
|
|
1257
1388
|
case "$*" in
|
|
1258
1389
|
*" -h "*)
|
|
1259
|
-
|
|
1260
|
-
|
|
1390
|
+
team_dir=$(find "${cwd}/.omx/state/team" -maxdepth 1 -type d -name 'team-interactive*' | head -n 1)
|
|
1391
|
+
mkdir -p "$team_dir/workers/worker-1"
|
|
1392
|
+
cat > "$team_dir/workers/worker-1/status.json" <<'EOF'
|
|
1261
1393
|
{
|
|
1262
1394
|
"state": "working",
|
|
1263
1395
|
"current_task_id": "1",
|
|
@@ -1382,8 +1514,9 @@ case "\${1:-}" in
|
|
|
1382
1514
|
split-window)
|
|
1383
1515
|
case "$*" in
|
|
1384
1516
|
*" -h "*)
|
|
1385
|
-
|
|
1386
|
-
|
|
1517
|
+
team_dir=$(find "${cwd}/.omx/state/team" -maxdepth 1 -type d -name 'team-pane-pid*' | head -n 1)
|
|
1518
|
+
mkdir -p "$team_dir/workers/worker-1"
|
|
1519
|
+
cat > "$team_dir/workers/worker-1/status.json" <<'EOF'
|
|
1387
1520
|
{
|
|
1388
1521
|
"state": "working",
|
|
1389
1522
|
"current_task_id": "1",
|
|
@@ -1464,6 +1597,253 @@ esac
|
|
|
1464
1597
|
assert.equal(applyIndex < saveIndex, true);
|
|
1465
1598
|
assert.equal(saveIndex < readyIndex, true);
|
|
1466
1599
|
});
|
|
1600
|
+
it('startTeam sends startup direct trigger before slow readiness wait when pane is safe', async () => {
|
|
1601
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-runtime-startup-direct-fast-'));
|
|
1602
|
+
const previousTmux = process.env.TMUX;
|
|
1603
|
+
const previousTmuxPane = process.env.TMUX_PANE;
|
|
1604
|
+
const previousLaunchMode = process.env.OMX_TEAM_WORKER_LAUNCH_MODE;
|
|
1605
|
+
const previousWorkerCli = process.env.OMX_TEAM_WORKER_CLI;
|
|
1606
|
+
const previousReadyTimeout = process.env.OMX_TEAM_READY_TIMEOUT_MS;
|
|
1607
|
+
const previousStartupEvidenceTimeout = process.env.OMX_TEAM_STARTUP_EVIDENCE_TIMEOUT_MS;
|
|
1608
|
+
const previousStartupDispatchRetries = process.env.OMX_TEAM_STARTUP_DISPATCH_RETRIES;
|
|
1609
|
+
let runtimeTeamName = null;
|
|
1610
|
+
try {
|
|
1611
|
+
await withMockTmuxFixture({
|
|
1612
|
+
dirPrefix: 'omx-runtime-startup-direct-fast-bin-',
|
|
1613
|
+
tmuxScript: () => `#!/bin/sh
|
|
1614
|
+
set -eu
|
|
1615
|
+
order_file="${cwd}/startup-order.log"
|
|
1616
|
+
case "$1" in
|
|
1617
|
+
-V)
|
|
1618
|
+
echo "tmux 3.4"
|
|
1619
|
+
exit 0
|
|
1620
|
+
;;
|
|
1621
|
+
display-message)
|
|
1622
|
+
case "$*" in
|
|
1623
|
+
*"#{window_width}"*) echo "120" ;;
|
|
1624
|
+
*) echo "leader:0 %1" ;;
|
|
1625
|
+
esac
|
|
1626
|
+
exit 0
|
|
1627
|
+
;;
|
|
1628
|
+
list-panes)
|
|
1629
|
+
case "$*" in
|
|
1630
|
+
*"pane_current_command"*) printf "%%1\tnode\t'codex'\n" ;;
|
|
1631
|
+
*"#{pane_dead} #{pane_pid}"*) echo "0 4242" ;;
|
|
1632
|
+
*"#{pane_dead}"*) echo "0" ;;
|
|
1633
|
+
*"#{pane_pid}"*) echo "4242" ;;
|
|
1634
|
+
*) exit 0 ;;
|
|
1635
|
+
esac
|
|
1636
|
+
exit 0
|
|
1637
|
+
;;
|
|
1638
|
+
capture-pane)
|
|
1639
|
+
printf '%s\n' capture >> "$order_file"
|
|
1640
|
+
printf 'OpenAI Codex\nmodel: test\ndirectory: /tmp/demo\n'
|
|
1641
|
+
exit 0
|
|
1642
|
+
;;
|
|
1643
|
+
send-keys)
|
|
1644
|
+
printf '%s\n' send-keys >> "$order_file"
|
|
1645
|
+
exit 0
|
|
1646
|
+
;;
|
|
1647
|
+
split-window)
|
|
1648
|
+
echo "%2"
|
|
1649
|
+
exit 0
|
|
1650
|
+
;;
|
|
1651
|
+
set-hook|run-shell|select-layout|set-window-option|select-pane|kill-pane|kill-session|resize-pane)
|
|
1652
|
+
exit 0
|
|
1653
|
+
;;
|
|
1654
|
+
*)
|
|
1655
|
+
exit 0
|
|
1656
|
+
;;
|
|
1657
|
+
esac
|
|
1658
|
+
`,
|
|
1659
|
+
binaries: [{ name: 'codex', content: '#!/usr/bin/env node\nprocess.stdin.resume();\n' }],
|
|
1660
|
+
}, async () => {
|
|
1661
|
+
delete process.env.TMUX;
|
|
1662
|
+
process.env.TMUX_PANE = '%1';
|
|
1663
|
+
process.env.OMX_TEAM_WORKER_LAUNCH_MODE = 'interactive';
|
|
1664
|
+
process.env.OMX_TEAM_WORKER_CLI = 'codex';
|
|
1665
|
+
process.env.OMX_TEAM_READY_TIMEOUT_MS = '5000';
|
|
1666
|
+
process.env.OMX_TEAM_STARTUP_EVIDENCE_TIMEOUT_MS = '50';
|
|
1667
|
+
process.env.OMX_TEAM_STARTUP_DISPATCH_RETRIES = '1';
|
|
1668
|
+
const runtime = await withoutTeamWorkerEnv(() => startTeam('team-startup-direct-fast', 'startup direct trigger falls back to evidence-gated dispatch', 'executor', 1, [{ subject: 'w1', description: 'worker one', owner: 'worker-1' }], cwd));
|
|
1669
|
+
runtimeTeamName = runtime.teamName;
|
|
1670
|
+
const order = (await readFile(join(cwd, 'startup-order.log'), 'utf-8')).trim().split('\n');
|
|
1671
|
+
assert.ok(order.includes('send-keys'), `expected direct send-keys, got ${order.join(',')}`);
|
|
1672
|
+
const timing = JSON.parse(await readFile(join(cwd, '.omx', 'state', 'team', runtime.teamName, 'startup-timing.json'), 'utf-8'));
|
|
1673
|
+
assert.ok(timing.events.some((event) => event.phase === 'split_returned'));
|
|
1674
|
+
assert.ok(timing.events.some((event) => event.phase === 'identity_inbox_written'));
|
|
1675
|
+
assert.ok(timing.events.some((event) => event.phase === 'direct_fallback' && /startup_direct_trigger_sent/.test(event.reason ?? '')));
|
|
1676
|
+
assert.ok(timing.events.some((event) => event.phase === 'startup_evidence' && event.reason === 'none' && event.ok === false));
|
|
1677
|
+
assert.equal(timing.events.some((event) => event.phase === 'ready_wait_start'), false);
|
|
1678
|
+
const workerStatus = await readWorkerStatus(runtime.teamName, 'worker-1', cwd);
|
|
1679
|
+
assert.equal(workerStatus?.state, 'unknown');
|
|
1680
|
+
assert.match(workerStatus?.reason ?? '', /startup_direct_no_evidence/);
|
|
1681
|
+
});
|
|
1682
|
+
}
|
|
1683
|
+
finally {
|
|
1684
|
+
if (runtimeTeamName)
|
|
1685
|
+
await shutdownTeam(runtimeTeamName, cwd, { force: true }).catch(() => { });
|
|
1686
|
+
if (typeof previousTmux === 'string')
|
|
1687
|
+
process.env.TMUX = previousTmux;
|
|
1688
|
+
else
|
|
1689
|
+
delete process.env.TMUX;
|
|
1690
|
+
if (typeof previousTmuxPane === 'string')
|
|
1691
|
+
process.env.TMUX_PANE = previousTmuxPane;
|
|
1692
|
+
else
|
|
1693
|
+
delete process.env.TMUX_PANE;
|
|
1694
|
+
if (typeof previousLaunchMode === 'string')
|
|
1695
|
+
process.env.OMX_TEAM_WORKER_LAUNCH_MODE = previousLaunchMode;
|
|
1696
|
+
else
|
|
1697
|
+
delete process.env.OMX_TEAM_WORKER_LAUNCH_MODE;
|
|
1698
|
+
if (typeof previousWorkerCli === 'string')
|
|
1699
|
+
process.env.OMX_TEAM_WORKER_CLI = previousWorkerCli;
|
|
1700
|
+
else
|
|
1701
|
+
delete process.env.OMX_TEAM_WORKER_CLI;
|
|
1702
|
+
if (typeof previousReadyTimeout === 'string')
|
|
1703
|
+
process.env.OMX_TEAM_READY_TIMEOUT_MS = previousReadyTimeout;
|
|
1704
|
+
else
|
|
1705
|
+
delete process.env.OMX_TEAM_READY_TIMEOUT_MS;
|
|
1706
|
+
if (typeof previousStartupEvidenceTimeout === 'string')
|
|
1707
|
+
process.env.OMX_TEAM_STARTUP_EVIDENCE_TIMEOUT_MS = previousStartupEvidenceTimeout;
|
|
1708
|
+
else
|
|
1709
|
+
delete process.env.OMX_TEAM_STARTUP_EVIDENCE_TIMEOUT_MS;
|
|
1710
|
+
if (typeof previousStartupDispatchRetries === 'string')
|
|
1711
|
+
process.env.OMX_TEAM_STARTUP_DISPATCH_RETRIES = previousStartupDispatchRetries;
|
|
1712
|
+
else
|
|
1713
|
+
delete process.env.OMX_TEAM_STARTUP_DISPATCH_RETRIES;
|
|
1714
|
+
await rm(cwd, { recursive: true, force: true });
|
|
1715
|
+
}
|
|
1716
|
+
});
|
|
1717
|
+
it('startTeam treats a confirmed ready prompt as startup evidence after hook notification', async () => {
|
|
1718
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-runtime-ready-prompt-evidence-'));
|
|
1719
|
+
const previousTmux = process.env.TMUX;
|
|
1720
|
+
const previousTmuxPane = process.env.TMUX_PANE;
|
|
1721
|
+
const previousLaunchMode = process.env.OMX_TEAM_WORKER_LAUNCH_MODE;
|
|
1722
|
+
const previousWorkerCli = process.env.OMX_TEAM_WORKER_CLI;
|
|
1723
|
+
const previousReadyTimeout = process.env.OMX_TEAM_READY_TIMEOUT_MS;
|
|
1724
|
+
const previousStartupEvidenceTimeout = process.env.OMX_TEAM_STARTUP_EVIDENCE_TIMEOUT_MS;
|
|
1725
|
+
const previousStartupDispatchRetries = process.env.OMX_TEAM_STARTUP_DISPATCH_RETRIES;
|
|
1726
|
+
let receiptNotifier = null;
|
|
1727
|
+
let runtimeTeamName = null;
|
|
1728
|
+
try {
|
|
1729
|
+
await withMockTmuxFixture({
|
|
1730
|
+
dirPrefix: 'omx-runtime-ready-prompt-evidence-bin-',
|
|
1731
|
+
tmuxScript: () => `#!/bin/sh
|
|
1732
|
+
set -eu
|
|
1733
|
+
count_file="${cwd}/capture-count"
|
|
1734
|
+
case "$1" in
|
|
1735
|
+
-V)
|
|
1736
|
+
echo "tmux 3.4"
|
|
1737
|
+
exit 0
|
|
1738
|
+
;;
|
|
1739
|
+
display-message)
|
|
1740
|
+
case "$*" in
|
|
1741
|
+
*"#{window_width}"*) echo "120" ;;
|
|
1742
|
+
*) echo "leader:0 %1" ;;
|
|
1743
|
+
esac
|
|
1744
|
+
exit 0
|
|
1745
|
+
;;
|
|
1746
|
+
list-panes)
|
|
1747
|
+
case "$*" in
|
|
1748
|
+
*"pane_current_command"*) printf "%%1\tnode\t'codex'\n" ;;
|
|
1749
|
+
*"#{pane_dead} #{pane_pid}"*) echo "0 4242" ;;
|
|
1750
|
+
*"-t %2"*"#{pane_pid}"*) echo "4242" ;;
|
|
1751
|
+
*"#{pane_dead}"*) echo "0" ;;
|
|
1752
|
+
*"#{pane_pid}"*) echo "4242" ;;
|
|
1753
|
+
*) exit 0 ;;
|
|
1754
|
+
esac
|
|
1755
|
+
exit 0
|
|
1756
|
+
;;
|
|
1757
|
+
capture-pane)
|
|
1758
|
+
count=0
|
|
1759
|
+
if [ -f "$count_file" ]; then count=$(cat "$count_file"); fi
|
|
1760
|
+
count=$((count + 1))
|
|
1761
|
+
printf '%s' "$count" > "$count_file"
|
|
1762
|
+
if [ "$count" -eq 1 ]; then
|
|
1763
|
+
printf 'OpenAI Codex\nmodel: loading\nLoading workspace...\n'
|
|
1764
|
+
else
|
|
1765
|
+
printf 'OpenAI Codex\nmodel: test\n› \n'
|
|
1766
|
+
fi
|
|
1767
|
+
exit 0
|
|
1768
|
+
;;
|
|
1769
|
+
split-window)
|
|
1770
|
+
echo "%2"
|
|
1771
|
+
exit 0
|
|
1772
|
+
;;
|
|
1773
|
+
set-hook|run-shell|select-layout|set-window-option|select-pane|send-keys|kill-pane|kill-session|resize-pane)
|
|
1774
|
+
exit 0
|
|
1775
|
+
;;
|
|
1776
|
+
*)
|
|
1777
|
+
exit 0
|
|
1778
|
+
;;
|
|
1779
|
+
esac
|
|
1780
|
+
`,
|
|
1781
|
+
binaries: [{ name: 'codex', content: '#!/usr/bin/env node\nprocess.stdin.resume();\n' }],
|
|
1782
|
+
}, async () => {
|
|
1783
|
+
delete process.env.TMUX;
|
|
1784
|
+
process.env.TMUX_PANE = '%1';
|
|
1785
|
+
process.env.OMX_TEAM_WORKER_LAUNCH_MODE = 'interactive';
|
|
1786
|
+
process.env.OMX_TEAM_WORKER_CLI = 'codex';
|
|
1787
|
+
process.env.OMX_TEAM_READY_TIMEOUT_MS = '5000';
|
|
1788
|
+
process.env.OMX_TEAM_STARTUP_EVIDENCE_TIMEOUT_MS = '500';
|
|
1789
|
+
process.env.OMX_TEAM_STARTUP_DISPATCH_RETRIES = '1';
|
|
1790
|
+
receiptNotifier = setInterval(() => {
|
|
1791
|
+
void markPendingInboxDispatchesNotified('team-ready-prompt-evidence', cwd);
|
|
1792
|
+
}, 20);
|
|
1793
|
+
const runtime = await withoutTeamWorkerEnv(() => startTeam('team-ready-prompt-evidence', 'interactive ready prompt should settle startup evidence after notification', 'executor', 1, [{ subject: 'w1', description: 'worker one', owner: 'worker-1' }], cwd));
|
|
1794
|
+
runtimeTeamName = runtime.teamName;
|
|
1795
|
+
const workerStatus = await readWorkerStatus(runtime.teamName, 'worker-1', cwd);
|
|
1796
|
+
assert.equal(workerStatus.state, 'unknown');
|
|
1797
|
+
assert.equal(workerStatus.reason, undefined);
|
|
1798
|
+
const requests = await listDispatchRequests(runtime.teamName, cwd, { kind: 'inbox' });
|
|
1799
|
+
assert.equal(requests.at(-1)?.status, 'notified');
|
|
1800
|
+
const captureCount = Number.parseInt(await readFile(join(cwd, 'capture-count'), 'utf-8'), 10);
|
|
1801
|
+
assert.ok(captureCount >= 2, `expected ready wait capture after bootstrapping, got ${captureCount}`);
|
|
1802
|
+
const timing = JSON.parse(await readFile(join(cwd, '.omx', 'state', 'team', runtime.teamName, 'startup-timing.json'), 'utf-8'));
|
|
1803
|
+
assert.ok(timing.events.some((event) => event.phase === 'ready_wait_start'));
|
|
1804
|
+
assert.ok(timing.events.some((event) => event.phase === 'ready_wait_end' && event.ok === true));
|
|
1805
|
+
});
|
|
1806
|
+
}
|
|
1807
|
+
finally {
|
|
1808
|
+
if (receiptNotifier)
|
|
1809
|
+
clearInterval(receiptNotifier);
|
|
1810
|
+
if (runtimeTeamName)
|
|
1811
|
+
await shutdownTeam(runtimeTeamName, cwd, { force: true }).catch(() => { });
|
|
1812
|
+
if (typeof previousTmux === 'string')
|
|
1813
|
+
process.env.TMUX = previousTmux;
|
|
1814
|
+
else
|
|
1815
|
+
delete process.env.TMUX;
|
|
1816
|
+
if (typeof previousTmuxPane === 'string')
|
|
1817
|
+
process.env.TMUX_PANE = previousTmuxPane;
|
|
1818
|
+
else
|
|
1819
|
+
delete process.env.TMUX_PANE;
|
|
1820
|
+
if (typeof previousLaunchMode === 'string')
|
|
1821
|
+
process.env.OMX_TEAM_WORKER_LAUNCH_MODE = previousLaunchMode;
|
|
1822
|
+
else
|
|
1823
|
+
delete process.env.OMX_TEAM_WORKER_LAUNCH_MODE;
|
|
1824
|
+
if (typeof previousWorkerCli === 'string')
|
|
1825
|
+
process.env.OMX_TEAM_WORKER_CLI = previousWorkerCli;
|
|
1826
|
+
else
|
|
1827
|
+
delete process.env.OMX_TEAM_WORKER_CLI;
|
|
1828
|
+
if (typeof previousReadyTimeout === 'string')
|
|
1829
|
+
process.env.OMX_TEAM_READY_TIMEOUT_MS = previousReadyTimeout;
|
|
1830
|
+
else
|
|
1831
|
+
delete process.env.OMX_TEAM_READY_TIMEOUT_MS;
|
|
1832
|
+
if (typeof previousStartupEvidenceTimeout === 'string') {
|
|
1833
|
+
process.env.OMX_TEAM_STARTUP_EVIDENCE_TIMEOUT_MS = previousStartupEvidenceTimeout;
|
|
1834
|
+
}
|
|
1835
|
+
else {
|
|
1836
|
+
delete process.env.OMX_TEAM_STARTUP_EVIDENCE_TIMEOUT_MS;
|
|
1837
|
+
}
|
|
1838
|
+
if (typeof previousStartupDispatchRetries === 'string') {
|
|
1839
|
+
process.env.OMX_TEAM_STARTUP_DISPATCH_RETRIES = previousStartupDispatchRetries;
|
|
1840
|
+
}
|
|
1841
|
+
else {
|
|
1842
|
+
delete process.env.OMX_TEAM_STARTUP_DISPATCH_RETRIES;
|
|
1843
|
+
}
|
|
1844
|
+
await rm(cwd, { recursive: true, force: true });
|
|
1845
|
+
}
|
|
1846
|
+
});
|
|
1467
1847
|
it('startTeam starts worker-2 readiness before delayed worker-1 readiness settles', async () => {
|
|
1468
1848
|
const cwd = await mkdtemp(join(tmpdir(), 'omx-runtime-parallel-ready-'));
|
|
1469
1849
|
const previousTmux = process.env.TMUX;
|
|
@@ -1606,7 +1986,7 @@ esac
|
|
|
1606
1986
|
await rm(cwd, { recursive: true, force: true });
|
|
1607
1987
|
}
|
|
1608
1988
|
});
|
|
1609
|
-
it('startTeam records recoverable startup issues per worker instead of failing launch early when panes stay alive', async () => {
|
|
1989
|
+
it('startTeam records recoverable startup issues per worker instead of failing launch early when panes stay alive', { skip: skipSlowLifecycleUnderCoverage }, async () => {
|
|
1610
1990
|
const cwd = await mkdtemp(join(tmpdir(), 'omx-runtime-no-startup-evidence-'));
|
|
1611
1991
|
const previousTmux = process.env.TMUX;
|
|
1612
1992
|
const previousTmuxPane = process.env.TMUX_PANE;
|
|
@@ -1704,7 +2084,7 @@ process.on('SIGTERM', () => process.exit(0));
|
|
|
1704
2084
|
process.env.OMX_TEAM_STARTUP_DISPATCH_RETRY_DELAY_MS = '50';
|
|
1705
2085
|
receiptFailer = setInterval(() => {
|
|
1706
2086
|
void (async () => {
|
|
1707
|
-
const requests = await listDispatchRequests(teamName, cwd, { kind: 'inbox' }).catch(() => []);
|
|
2087
|
+
const requests = await listDispatchRequests(await resolveRuntimeTeamName(cwd, teamName), cwd, { kind: 'inbox' }).catch(() => []);
|
|
1708
2088
|
for (const request of requests) {
|
|
1709
2089
|
if (request.status !== 'pending')
|
|
1710
2090
|
continue;
|
|
@@ -1716,14 +2096,15 @@ process.on('SIGTERM', () => process.exit(0));
|
|
|
1716
2096
|
{ subject: 'worker-1 task', description: 'd', owner: 'worker-1' },
|
|
1717
2097
|
{ subject: 'worker-2 task', description: 'd', owner: 'worker-2' },
|
|
1718
2098
|
], cwd));
|
|
1719
|
-
const
|
|
1720
|
-
const
|
|
2099
|
+
const runtimeTeamName = runtime.teamName;
|
|
2100
|
+
const worker1Status = await readWorkerStatus(runtimeTeamName, 'worker-1', cwd);
|
|
2101
|
+
const worker2Status = await readWorkerStatus(runtimeTeamName, 'worker-2', cwd);
|
|
1721
2102
|
assert.equal(worker1Status.state, 'unknown');
|
|
1722
2103
|
assert.equal(worker2Status.state, 'unknown');
|
|
1723
2104
|
assert.match(worker1Status.reason ?? '', /startup_no_evidence|fallback_attempted_but_unconfirmed/);
|
|
1724
2105
|
assert.match(worker2Status.reason ?? '', /startup_no_evidence|fallback_attempted_but_unconfirmed/);
|
|
1725
|
-
const task1 = await readTask(
|
|
1726
|
-
const task2 = await readTask(
|
|
2106
|
+
const task1 = await readTask(runtimeTeamName, '1', cwd);
|
|
2107
|
+
const task2 = await readTask(runtimeTeamName, '2', cwd);
|
|
1727
2108
|
assert.equal(task1?.status, 'pending');
|
|
1728
2109
|
assert.equal(task2?.status, 'pending');
|
|
1729
2110
|
});
|
|
@@ -1775,7 +2156,7 @@ process.on('SIGTERM', () => process.exit(0));
|
|
|
1775
2156
|
await rm(cwd, { recursive: true, force: true });
|
|
1776
2157
|
}
|
|
1777
2158
|
});
|
|
1778
|
-
it('startTeam attempts worker-2 before rejecting lowest-index unrecoverable startup failure', async () => {
|
|
2159
|
+
it('startTeam attempts worker-2 before rejecting lowest-index unrecoverable startup failure', { skip: skipSlowLifecycleUnderCoverage }, async () => {
|
|
1779
2160
|
const cwd = await mkdtemp(join(tmpdir(), 'omx-runtime-parallel-dead-pane-'));
|
|
1780
2161
|
const previousTmux = process.env.TMUX;
|
|
1781
2162
|
const previousTmuxPane = process.env.TMUX_PANE;
|
|
@@ -2052,7 +2433,7 @@ esac
|
|
|
2052
2433
|
await rm(cwd, { recursive: true, force: true });
|
|
2053
2434
|
}
|
|
2054
2435
|
});
|
|
2055
|
-
it('startTeam materializes all worker identity/inbox files before worker-1 startup evidence can block later workers', async () => {
|
|
2436
|
+
it('startTeam materializes all worker identity/inbox files before worker-1 startup evidence can block later workers', { skip: skipSlowLifecycleUnderCoverage }, async () => {
|
|
2056
2437
|
const cwd = await mkdtemp(join(tmpdir(), 'omx-runtime-materialize-before-evidence-'));
|
|
2057
2438
|
const previousTmux = process.env.TMUX;
|
|
2058
2439
|
const previousTmuxPane = process.env.TMUX_PANE;
|
|
@@ -2149,7 +2530,7 @@ process.on('SIGTERM', () => process.exit(0));
|
|
|
2149
2530
|
},
|
|
2150
2531
|
],
|
|
2151
2532
|
}, async () => {
|
|
2152
|
-
|
|
2533
|
+
let runtimeTeamName = sanitizeTeamName('team-materialize-before-evidence');
|
|
2153
2534
|
delete process.env.TMUX;
|
|
2154
2535
|
process.env.TMUX_PANE = '%1';
|
|
2155
2536
|
process.env.OMX_TEAM_WORKER_LAUNCH_MODE = 'interactive';
|
|
@@ -2160,11 +2541,13 @@ process.on('SIGTERM', () => process.exit(0));
|
|
|
2160
2541
|
process.env.OMX_TEAM_STARTUP_DISPATCH_RETRY_DELAY_MS = '50';
|
|
2161
2542
|
receiptFailer = setInterval(() => {
|
|
2162
2543
|
void (async () => {
|
|
2163
|
-
const
|
|
2544
|
+
const activeTeamName = await resolveRuntimeTeamName(cwd, 'team-materialize-before-evidence');
|
|
2545
|
+
runtimeTeamName = activeTeamName;
|
|
2546
|
+
const requests = await listDispatchRequests(activeTeamName, cwd, { kind: 'inbox' }).catch(() => []);
|
|
2164
2547
|
for (const request of requests) {
|
|
2165
2548
|
if (request.status !== 'pending')
|
|
2166
2549
|
continue;
|
|
2167
|
-
await transitionDispatchRequest(
|
|
2550
|
+
await transitionDispatchRequest(activeTeamName, request.request_id, 'pending', 'failed', { last_reason: 'test_failed_receipt' }, cwd).catch(() => { });
|
|
2168
2551
|
}
|
|
2169
2552
|
})();
|
|
2170
2553
|
}, 20);
|
|
@@ -2173,11 +2556,12 @@ process.on('SIGTERM', () => process.exit(0));
|
|
|
2173
2556
|
{ subject: 'w2', description: 'worker two', owner: 'worker-2' },
|
|
2174
2557
|
], cwd));
|
|
2175
2558
|
const observedTeamPromise = teamPromise.then((runtime) => ({ ok: true, runtime }), (error) => ({ ok: false, error }));
|
|
2176
|
-
const workerOneIdentity = join(cwd, '.omx', 'state', 'team', sanitizedTeamName, 'workers', 'worker-1', 'identity.json');
|
|
2177
|
-
const workerTwoIdentity = join(cwd, '.omx', 'state', 'team', sanitizedTeamName, 'workers', 'worker-2', 'identity.json');
|
|
2178
|
-
const workerTwoInbox = join(cwd, '.omx', 'state', 'team', sanitizedTeamName, 'workers', 'worker-2', 'inbox.md');
|
|
2179
2559
|
let materializedAllWorkers = false;
|
|
2180
2560
|
for (let attempt = 0; attempt < 200; attempt += 1) {
|
|
2561
|
+
runtimeTeamName = await resolveRuntimeTeamName(cwd, 'team-materialize-before-evidence');
|
|
2562
|
+
const workerOneIdentity = join(cwd, '.omx', 'state', 'team', runtimeTeamName, 'workers', 'worker-1', 'identity.json');
|
|
2563
|
+
const workerTwoIdentity = join(cwd, '.omx', 'state', 'team', runtimeTeamName, 'workers', 'worker-2', 'identity.json');
|
|
2564
|
+
const workerTwoInbox = join(cwd, '.omx', 'state', 'team', runtimeTeamName, 'workers', 'worker-2', 'inbox.md');
|
|
2181
2565
|
if (existsSync(workerOneIdentity)
|
|
2182
2566
|
&& existsSync(workerTwoIdentity)
|
|
2183
2567
|
&& existsSync(workerTwoInbox)) {
|
|
@@ -2190,7 +2574,7 @@ process.on('SIGTERM', () => process.exit(0));
|
|
|
2190
2574
|
const outcome = await observedTeamPromise;
|
|
2191
2575
|
assert.equal(outcome.ok, false);
|
|
2192
2576
|
assert.match(String(outcome.error), /worker_notify_failed:worker-1/);
|
|
2193
|
-
assert.equal(existsSync(join(cwd, '.omx', 'state', 'team',
|
|
2577
|
+
assert.equal(existsSync(join(cwd, '.omx', 'state', 'team', runtimeTeamName)), false);
|
|
2194
2578
|
});
|
|
2195
2579
|
}
|
|
2196
2580
|
finally {
|
|
@@ -2361,7 +2745,7 @@ sleep 5
|
|
|
2361
2745
|
assert.equal((runtime.config.workers[0]?.pid ?? 0) > 0, true);
|
|
2362
2746
|
const expectedArgv = [
|
|
2363
2747
|
'-i',
|
|
2364
|
-
|
|
2748
|
+
`Read .omx/state/team/${runtime.teamName}/workers/worker-1/inbox.md, start work now, report concrete progress, then continue assigned work or next feasible task.`,
|
|
2365
2749
|
];
|
|
2366
2750
|
let argv = null;
|
|
2367
2751
|
for (let attempt = 0; attempt < 50; attempt += 1) {
|
|
@@ -2491,10 +2875,10 @@ process.on('SIGTERM', () => process.exit(0));
|
|
|
2491
2875
|
delete process.env.OMX_DEFAULT_STANDARD_MODEL;
|
|
2492
2876
|
let runtime = null;
|
|
2493
2877
|
try {
|
|
2494
|
-
runtime = await withMockPromptModeCodexAllowed(() => withoutTeamWorkerEnv(() => startTeam('team-role-routing', 'heuristic routing handoff', 'executor', 2, [
|
|
2878
|
+
runtime = await withIsolatedDefaultModelEnvAsync(async () => await withMockPromptModeCodexAllowed(() => withoutTeamWorkerEnv(() => startTeam('team-role-routing', 'heuristic routing handoff', 'executor', 2, [
|
|
2495
2879
|
{ subject: 'test routing report only', description: 'test routing report only', owner: 'worker-1', role: 'test-engineer' },
|
|
2496
2880
|
{ subject: 'document routing report only', description: 'document routing report only', owner: 'worker-2', role: 'writer' },
|
|
2497
|
-
], cwd)));
|
|
2881
|
+
], cwd))));
|
|
2498
2882
|
assert.equal(runtime.config.worker_launch_mode, 'prompt');
|
|
2499
2883
|
assert.equal(runtime.config.workers[0]?.role, 'test-engineer');
|
|
2500
2884
|
assert.equal(runtime.config.workers[1]?.role, 'writer');
|
|
@@ -2872,20 +3256,20 @@ process.on('SIGTERM', () => process.exit(0));
|
|
|
2872
3256
|
assert.notEqual(workerPath, repo);
|
|
2873
3257
|
const workerAgents = await readFile(join(workerPath, 'AGENTS.md'), 'utf-8');
|
|
2874
3258
|
assert.match(workerAgents, /Team Worker Runtime Instructions/);
|
|
2875
|
-
assert.match(workerAgents,
|
|
3259
|
+
assert.match(workerAgents, new RegExp(runtime.teamName));
|
|
2876
3260
|
const startupLog = await waitForFileText(stdinLogPath, (content) => content.includes('/workers/worker-1/inbox.md'));
|
|
2877
|
-
assert.match(startupLog,
|
|
2878
|
-
assert.doesNotMatch(startupLog,
|
|
3261
|
+
assert.match(startupLog, new RegExp(`\\$OMX_TEAM_STATE_ROOT/team/${runtime.teamName}/workers/worker-1/inbox\\.md`));
|
|
3262
|
+
assert.doesNotMatch(startupLog, new RegExp(`Read \\.omx/state/team/${runtime.teamName}/workers/worker-1/inbox\\.md`));
|
|
2879
3263
|
const envLog = JSON.parse(await waitForFileText(envLogPath, (content) => content.includes('teamStateRoot')));
|
|
2880
3264
|
assert.equal(envLog.cwd, workerPath);
|
|
2881
3265
|
assert.equal(envLog.teamStateRoot, join(repo, '.omx', 'state'));
|
|
2882
3266
|
assert.equal(envLog.worker, 'team-detached-worktree-paths/worker-1');
|
|
2883
3267
|
const rootAgents = await readFile(join(workerPath, 'AGENTS.md'), 'utf-8');
|
|
2884
3268
|
assert.match(rootAgents, /Team Worker Runtime Instructions/);
|
|
2885
|
-
assert.match(rootAgents,
|
|
3269
|
+
assert.match(rootAgents, new RegExp(`Inbox path: .*${runtime.teamName}/workers/worker-1/inbox\\.md`));
|
|
2886
3270
|
await sendWorkerMessage(runtime.teamName, 'leader-fixed', 'worker-1', 'follow-up', repo);
|
|
2887
3271
|
const mailboxLog = await waitForFileText(stdinLogPath, (content) => content.includes('/mailbox/worker-1.json'));
|
|
2888
|
-
assert.match(mailboxLog,
|
|
3272
|
+
assert.match(mailboxLog, new RegExp(`\\$OMX_TEAM_STATE_ROOT/team/${runtime.teamName}/mailbox/worker-1\\.json`));
|
|
2889
3273
|
await shutdownTeam(runtime.teamName, repo, { force: true });
|
|
2890
3274
|
runtime = null;
|
|
2891
3275
|
}
|
|
@@ -4717,6 +5101,55 @@ esac
|
|
|
4717
5101
|
await rm(cwd, { recursive: true, force: true });
|
|
4718
5102
|
}
|
|
4719
5103
|
});
|
|
5104
|
+
it('resumeTeam fails closed when the persisted approved binding is stale', async () => {
|
|
5105
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-runtime-approved-resume-'));
|
|
5106
|
+
try {
|
|
5107
|
+
await initTeamState('team-approved-resume', 'approved resume test', 'executor', 1, cwd);
|
|
5108
|
+
await writePersistedApprovedTeamExecutionBinding('team-approved-resume', cwd, {
|
|
5109
|
+
prd_path: join(cwd, '.omx', 'plans', 'prd-missing.md'),
|
|
5110
|
+
task: 'Execute missing approved plan',
|
|
5111
|
+
});
|
|
5112
|
+
await assert.rejects(() => resumeTeam('team-approved-resume', cwd), /approved_execution_binding_stale:.*Execute missing approved plan/);
|
|
5113
|
+
}
|
|
5114
|
+
finally {
|
|
5115
|
+
await rm(cwd, { recursive: true, force: true });
|
|
5116
|
+
}
|
|
5117
|
+
});
|
|
5118
|
+
it('resumeTeam resolves approved binding continuity against the persisted leader cwd', async () => {
|
|
5119
|
+
const teamName = 'team-approved-shared-root';
|
|
5120
|
+
const leaderCwd = await mkdtemp(join(tmpdir(), 'omx-runtime-approved-leader-'));
|
|
5121
|
+
const resumeCwd = await mkdtemp(join(tmpdir(), 'omx-runtime-approved-resume-alt-'));
|
|
5122
|
+
const sharedStateRoot = await mkdtemp(join(tmpdir(), 'omx-runtime-approved-state-'));
|
|
5123
|
+
const previousTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT;
|
|
5124
|
+
process.env.OMX_TEAM_STATE_ROOT = sharedStateRoot;
|
|
5125
|
+
try {
|
|
5126
|
+
await initTeamState(teamName, 'approved resume shared-root test', 'executor', 1, leaderCwd, DEFAULT_MAX_WORKERS, process.env, {
|
|
5127
|
+
leader_cwd: leaderCwd,
|
|
5128
|
+
team_state_root: sharedStateRoot,
|
|
5129
|
+
});
|
|
5130
|
+
const plansDir = join(leaderCwd, '.omx', 'plans');
|
|
5131
|
+
await mkdir(plansDir, { recursive: true });
|
|
5132
|
+
const prdPath = join(plansDir, 'prd-issue-2110.md');
|
|
5133
|
+
await writeFile(prdPath, '# Approved plan\n\nLaunch via omx team 1:executor "Execute approved issue 2110 plan"\n');
|
|
5134
|
+
await writeFile(join(plansDir, 'test-spec-issue-2110.md'), '# Test spec\n');
|
|
5135
|
+
await writePersistedApprovedTeamExecutionBinding(teamName, leaderCwd, {
|
|
5136
|
+
prd_path: prdPath,
|
|
5137
|
+
task: 'Execute approved issue 2110 plan',
|
|
5138
|
+
command: 'omx team 1:executor "Execute approved issue 2110 plan"',
|
|
5139
|
+
}, sharedStateRoot);
|
|
5140
|
+
const resumed = await resumeTeam(teamName, resumeCwd);
|
|
5141
|
+
assert.equal(resumed, null);
|
|
5142
|
+
}
|
|
5143
|
+
finally {
|
|
5144
|
+
if (typeof previousTeamStateRoot === 'string')
|
|
5145
|
+
process.env.OMX_TEAM_STATE_ROOT = previousTeamStateRoot;
|
|
5146
|
+
else
|
|
5147
|
+
delete process.env.OMX_TEAM_STATE_ROOT;
|
|
5148
|
+
await rm(leaderCwd, { recursive: true, force: true });
|
|
5149
|
+
await rm(resumeCwd, { recursive: true, force: true });
|
|
5150
|
+
await rm(sharedStateRoot, { recursive: true, force: true });
|
|
5151
|
+
}
|
|
5152
|
+
});
|
|
4720
5153
|
it('resumeTeam returns null for prompt teams when worker handles are missing after restart', async () => {
|
|
4721
5154
|
const cwd = await mkdtemp(join(tmpdir(), 'omx-runtime-prompt-resume-'));
|
|
4722
5155
|
const sleeper = spawn(process.execPath, ['-e', 'setInterval(() => {}, 1000)'], {
|
|
@@ -4861,7 +5294,7 @@ esac
|
|
|
4861
5294
|
let runtime = null;
|
|
4862
5295
|
try {
|
|
4863
5296
|
runtime = await withPromptModeCodexEnv(binDir, {}, () => withoutTeamWorkerEnv(() => startTeam('team-delegation-persist', 'delegation persistence test', 'executor', 1, [{ subject: 'Investigate runtime assignment', description: 'Search runtime and debug assignTask behavior' }], cwd)));
|
|
4864
|
-
const task = await readTask(
|
|
5297
|
+
const task = await readTask(runtime.teamName, '1', cwd);
|
|
4865
5298
|
assert.equal(task?.delegation?.mode, 'auto');
|
|
4866
5299
|
assert.equal(task?.delegation?.child_model, 'gpt-5.4-mini');
|
|
4867
5300
|
assert.equal(task?.delegation?.required_parallel_probe, true);
|
|
@@ -4873,6 +5306,64 @@ esac
|
|
|
4873
5306
|
await rm(cwd, { recursive: true, force: true });
|
|
4874
5307
|
}
|
|
4875
5308
|
});
|
|
5309
|
+
it('startTeam persists approved execution binding under the team state root', async () => {
|
|
5310
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-runtime-approved-binding-'));
|
|
5311
|
+
const binDir = join(cwd, 'bin');
|
|
5312
|
+
const fakeCodexPath = join(binDir, 'codex');
|
|
5313
|
+
await mkdir(binDir, { recursive: true });
|
|
5314
|
+
await mkdir(join(cwd, '.omx', 'plans'), { recursive: true });
|
|
5315
|
+
await writeFile(join(cwd, '.omx', 'plans', 'prd-issue-1314.md'), '# Approved plan\n\nLaunch via omx team 1:executor "Execute approved issue 1314 plan"\n');
|
|
5316
|
+
await writeFile(join(cwd, '.omx', 'plans', 'test-spec-issue-1314.md'), '# Test spec\n');
|
|
5317
|
+
await writeFakePromptWorkerBinary(fakeCodexPath, `setTimeout(() => {}, 5000);`);
|
|
5318
|
+
let runtime = null;
|
|
5319
|
+
try {
|
|
5320
|
+
runtime = await withPromptModeCodexEnv(binDir, {}, () => withoutTeamWorkerEnv(() => startTeam('team-approved-binding', 'approved binding persistence test', 'executor', 1, [{ subject: 's', description: 'd', owner: 'worker-1' }], cwd, {
|
|
5321
|
+
approvedExecution: {
|
|
5322
|
+
prd_path: join(cwd, '.omx', 'plans', 'prd-issue-1314.md'),
|
|
5323
|
+
task: 'Execute approved issue 1314 plan',
|
|
5324
|
+
command: 'omx team 1:executor "Execute approved issue 1314 plan"',
|
|
5325
|
+
},
|
|
5326
|
+
})));
|
|
5327
|
+
const bindingPath = join(runtime.config.team_state_root ?? join(cwd, '.omx', 'state'), 'team', runtime.teamName, 'approved-execution.json');
|
|
5328
|
+
const binding = JSON.parse(await readFile(bindingPath, 'utf-8'));
|
|
5329
|
+
assert.deepEqual(binding, {
|
|
5330
|
+
prd_path: join(cwd, '.omx', 'plans', 'prd-issue-1314.md'),
|
|
5331
|
+
task: 'Execute approved issue 1314 plan',
|
|
5332
|
+
command: 'omx team 1:executor "Execute approved issue 1314 plan"',
|
|
5333
|
+
});
|
|
5334
|
+
assert.deepEqual(Object.keys(binding).sort(), ['command', 'prd_path', 'task']);
|
|
5335
|
+
}
|
|
5336
|
+
finally {
|
|
5337
|
+
if (runtime) {
|
|
5338
|
+
await shutdownTeam(runtime.teamName, cwd, { force: true }).catch(() => { });
|
|
5339
|
+
}
|
|
5340
|
+
await rm(cwd, { recursive: true, force: true });
|
|
5341
|
+
}
|
|
5342
|
+
});
|
|
5343
|
+
it('startTeam fails closed when an explicit approved execution binding is stale', async () => {
|
|
5344
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-runtime-approved-binding-stale-'));
|
|
5345
|
+
const binDir = join(cwd, 'bin');
|
|
5346
|
+
const fakeCodexPath = join(binDir, 'codex');
|
|
5347
|
+
await mkdir(binDir, { recursive: true });
|
|
5348
|
+
await mkdir(join(cwd, '.omx', 'plans'), { recursive: true });
|
|
5349
|
+
const stalePrdPath = join(cwd, '.omx', 'plans', 'prd-issue-1315.md');
|
|
5350
|
+
await writeFile(stalePrdPath, '# Approved plan\n\nLaunch via omx team 1:executor "Execute approved issue 1315 plan"\n');
|
|
5351
|
+
await writeFile(join(cwd, '.omx', 'plans', 'test-spec-issue-1315.md'), '# Test spec\n');
|
|
5352
|
+
await rm(stalePrdPath, { force: true });
|
|
5353
|
+
await writeFakePromptWorkerBinary(fakeCodexPath, `setTimeout(() => {}, 5000);`);
|
|
5354
|
+
try {
|
|
5355
|
+
await assert.rejects(() => withPromptModeCodexEnv(binDir, {}, () => withoutTeamWorkerEnv(() => startTeam('team-approved-binding-stale', 'approved binding stale start test', 'executor', 1, [{ subject: 's', description: 'd', owner: 'worker-1' }], cwd, {
|
|
5356
|
+
approvedExecution: {
|
|
5357
|
+
prd_path: stalePrdPath,
|
|
5358
|
+
task: 'Execute approved issue 1315 plan',
|
|
5359
|
+
},
|
|
5360
|
+
}))), /approved_execution_binding_stale:.*Execute approved issue 1315 plan/);
|
|
5361
|
+
assert.equal(existsSync(join(cwd, '.omx', 'state', 'team', 'team-approved-binding-stale')), false);
|
|
5362
|
+
}
|
|
5363
|
+
finally {
|
|
5364
|
+
await rm(cwd, { recursive: true, force: true });
|
|
5365
|
+
}
|
|
5366
|
+
});
|
|
4876
5367
|
it('startTeam remaps repo-aware DAG dependencies after concrete task IDs are created', async () => {
|
|
4877
5368
|
const cwd = await mkdtemp(join(tmpdir(), 'omx-runtime-'));
|
|
4878
5369
|
const binDir = join(cwd, 'bin');
|
|
@@ -4921,17 +5412,17 @@ esac
|
|
|
4921
5412
|
},
|
|
4922
5413
|
},
|
|
4923
5414
|
})));
|
|
4924
|
-
const first = await readTask(
|
|
4925
|
-
const second = await readTask(
|
|
5415
|
+
const first = await readTask(runtime.teamName, '1', cwd);
|
|
5416
|
+
const second = await readTask(runtime.teamName, '2', cwd);
|
|
4926
5417
|
assert.deepEqual(first?.depends_on, []);
|
|
4927
5418
|
assert.deepEqual(first?.blocked_by, undefined);
|
|
4928
5419
|
assert.deepEqual(second?.depends_on, ['1']);
|
|
4929
5420
|
assert.deepEqual(second?.blocked_by, ['1']);
|
|
4930
|
-
const report = JSON.parse(await readFile(join(cwd, '.omx', 'state', 'team',
|
|
5421
|
+
const report = JSON.parse(await readFile(join(cwd, '.omx', 'state', 'team', runtime.teamName, 'decomposition-report.json'), 'utf-8'));
|
|
4931
5422
|
assert.deepEqual(report.node_id_to_task_id, { impl: '1', verify: '2' });
|
|
4932
5423
|
assert.deepEqual(report.task_hints?.['2']?.depends_on, ['1']);
|
|
4933
5424
|
assert.deepEqual(report.task_hints?.['2']?.symbolic_depends_on, ['impl']);
|
|
4934
|
-
const inbox = await readFile(join(cwd, '.omx', 'state', 'team',
|
|
5425
|
+
const inbox = await readFile(join(cwd, '.omx', 'state', 'team', runtime.teamName, 'workers', 'worker-2', 'inbox.md'), 'utf-8');
|
|
4935
5426
|
assert.match(inbox, /Blocked by: 1/);
|
|
4936
5427
|
assert.doesNotMatch(inbox, /Blocked by: impl/);
|
|
4937
5428
|
assert.doesNotMatch(inbox, /Depends on: impl/);
|