oh-my-copilot 4.13.1 → 4.13.21
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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +3 -3
- package/bridge/cli.cjs +4709 -1961
- package/bridge/mcp-server.cjs +112 -11
- package/bridge/runtime-cli.cjs +4917 -2645
- package/bridge/team-bridge.cjs +6 -4
- package/bridge/team-mcp.cjs +19607 -0
- package/bridge/team.js +4895 -2215
- package/dist/__tests__/auto-slash-aliases.test.js +91 -0
- package/dist/__tests__/auto-slash-aliases.test.js.map +1 -1
- package/dist/__tests__/hooks/learner/bridge.test.js +25 -0
- package/dist/__tests__/hooks/learner/bridge.test.js.map +1 -1
- package/dist/__tests__/hooks/learner/parser.test.js +30 -0
- package/dist/__tests__/hooks/learner/parser.test.js.map +1 -1
- package/dist/__tests__/hud/extra-usage.test.js +42 -0
- package/dist/__tests__/hud/extra-usage.test.js.map +1 -1
- package/dist/__tests__/hud/labels.test.d.ts +2 -0
- package/dist/__tests__/hud/labels.test.d.ts.map +1 -0
- package/dist/__tests__/hud/labels.test.js +158 -0
- package/dist/__tests__/hud/labels.test.js.map +1 -0
- package/dist/__tests__/hud/render-enterprise.test.d.ts +5 -0
- package/dist/__tests__/hud/render-enterprise.test.d.ts.map +1 -0
- package/dist/__tests__/hud/render-enterprise.test.js +232 -0
- package/dist/__tests__/hud/render-enterprise.test.js.map +1 -0
- package/dist/__tests__/hud/render-rate-limits-priority.test.js +185 -0
- package/dist/__tests__/hud/render-rate-limits-priority.test.js.map +1 -1
- package/dist/__tests__/hud/stdin-session-scope.test.d.ts +2 -0
- package/dist/__tests__/hud/stdin-session-scope.test.d.ts.map +1 -0
- package/dist/__tests__/hud/stdin-session-scope.test.js +169 -0
- package/dist/__tests__/hud/stdin-session-scope.test.js.map +1 -0
- package/dist/__tests__/hud/usage-api.test.js +111 -1
- package/dist/__tests__/hud/usage-api.test.js.map +1 -1
- package/dist/__tests__/hud-marketplace-resolution.test.js +34 -0
- package/dist/__tests__/hud-marketplace-resolution.test.js.map +1 -1
- package/dist/__tests__/installer-hud-skip.test.js +6 -0
- package/dist/__tests__/installer-hud-skip.test.js.map +1 -1
- package/dist/__tests__/installer-omc-reference.test.js +1 -1
- package/dist/__tests__/installer-omc-reference.test.js.map +1 -1
- package/dist/__tests__/keyword-detector-echo-guard.test.d.ts +2 -0
- package/dist/__tests__/keyword-detector-echo-guard.test.d.ts.map +1 -0
- package/dist/__tests__/keyword-detector-echo-guard.test.js +218 -0
- package/dist/__tests__/keyword-detector-echo-guard.test.js.map +1 -0
- package/dist/__tests__/mnemosyne/detector.test.js +2 -0
- package/dist/__tests__/mnemosyne/detector.test.js.map +1 -1
- package/dist/__tests__/notepad.test.js +2 -2
- package/dist/__tests__/notepad.test.js.map +1 -1
- package/dist/__tests__/pre-tool-enforcer.test.js +84 -1
- package/dist/__tests__/pre-tool-enforcer.test.js.map +1 -1
- package/dist/__tests__/preemptive-compaction-hook.test.js +63 -0
- package/dist/__tests__/preemptive-compaction-hook.test.js.map +1 -1
- package/dist/__tests__/ralph-prd-mandatory.test.js +11 -1
- package/dist/__tests__/ralph-prd-mandatory.test.js.map +1 -1
- package/dist/__tests__/ralph-prd.test.js +40 -2
- package/dist/__tests__/ralph-prd.test.js.map +1 -1
- package/dist/__tests__/skills.test.js +363 -4
- package/dist/__tests__/skills.test.js.map +1 -1
- package/dist/__tests__/state-root-resolution.test.js +120 -6
- package/dist/__tests__/state-root-resolution.test.js.map +1 -1
- package/dist/autoresearch/__tests__/runtime.test.js +225 -28
- package/dist/autoresearch/__tests__/runtime.test.js.map +1 -1
- package/dist/autoresearch/runtime.d.ts.map +1 -1
- package/dist/autoresearch/runtime.js +9 -5
- package/dist/autoresearch/runtime.js.map +1 -1
- package/dist/cli/__tests__/team-status-worktree.test.d.ts +2 -0
- package/dist/cli/__tests__/team-status-worktree.test.d.ts.map +1 -0
- package/dist/cli/__tests__/team-status-worktree.test.js +96 -0
- package/dist/cli/__tests__/team-status-worktree.test.js.map +1 -0
- package/dist/cli/__tests__/team.test.js +164 -0
- package/dist/cli/__tests__/team.test.js.map +1 -1
- package/dist/cli/autoresearch.d.ts +1 -1
- package/dist/cli/autoresearch.d.ts.map +1 -1
- package/dist/cli/autoresearch.js +4 -4
- package/dist/cli/commands/team.d.ts +1 -0
- package/dist/cli/commands/team.d.ts.map +1 -1
- package/dist/cli/commands/team.js +15 -3
- package/dist/cli/commands/team.js.map +1 -1
- package/dist/cli/index.js +3 -0
- package/dist/cli/index.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 +126 -5
- package/dist/cli/team.js.map +1 -1
- package/dist/cli/utils/formatting.d.ts +0 -10
- package/dist/cli/utils/formatting.d.ts.map +1 -1
- package/dist/cli/utils/formatting.js +0 -52
- package/dist/cli/utils/formatting.js.map +1 -1
- package/dist/config/__tests__/loader.test.js.map +1 -1
- package/dist/features/boulder-state/constants.d.ts +1 -1
- package/dist/features/boulder-state/constants.d.ts.map +1 -1
- package/dist/features/builtin-skills/skills.d.ts.map +1 -1
- package/dist/features/builtin-skills/skills.js +11 -3
- package/dist/features/builtin-skills/skills.js.map +1 -1
- package/dist/features/builtin-skills/types.d.ts +7 -0
- package/dist/features/builtin-skills/types.d.ts.map +1 -1
- package/dist/features/delegation-enforcer.d.ts +2 -0
- package/dist/features/delegation-enforcer.d.ts.map +1 -1
- package/dist/features/delegation-enforcer.js +10 -0
- package/dist/features/delegation-enforcer.js.map +1 -1
- package/dist/features/magic-keywords.d.ts.map +1 -1
- package/dist/features/magic-keywords.js +30 -1
- package/dist/features/magic-keywords.js.map +1 -1
- package/dist/features/rate-limit-wait/tmux-detector.d.ts +4 -4
- package/dist/features/rate-limit-wait/tmux-detector.js +9 -9
- package/dist/features/rate-limit-wait/tmux-detector.js.map +1 -1
- package/dist/hooks/__tests__/bridge-routing.test.js +156 -0
- package/dist/hooks/__tests__/bridge-routing.test.js.map +1 -1
- package/dist/hooks/auto-slash-command/constants.d.ts.map +1 -1
- package/dist/hooks/auto-slash-command/constants.js +1 -0
- package/dist/hooks/auto-slash-command/constants.js.map +1 -1
- package/dist/hooks/auto-slash-command/executor.d.ts.map +1 -1
- package/dist/hooks/auto-slash-command/executor.js +29 -0
- package/dist/hooks/auto-slash-command/executor.js.map +1 -1
- package/dist/hooks/bridge.d.ts.map +1 -1
- package/dist/hooks/bridge.js +184 -1
- package/dist/hooks/bridge.js.map +1 -1
- package/dist/hooks/learner/bridge.d.ts.map +1 -1
- package/dist/hooks/learner/bridge.js +4 -109
- package/dist/hooks/learner/bridge.js.map +1 -1
- package/dist/hooks/learner/detector.js +1 -1
- package/dist/hooks/learner/parser.d.ts +9 -0
- package/dist/hooks/learner/parser.d.ts.map +1 -1
- package/dist/hooks/learner/parser.js +18 -5
- package/dist/hooks/learner/parser.js.map +1 -1
- package/dist/hooks/learner/types.d.ts +6 -0
- package/dist/hooks/learner/types.d.ts.map +1 -1
- package/dist/hooks/mode-registry/__tests__/session-isolation.test.js +16 -0
- package/dist/hooks/mode-registry/__tests__/session-isolation.test.js.map +1 -1
- package/dist/hooks/notepad/index.d.ts +4 -1
- package/dist/hooks/notepad/index.d.ts.map +1 -1
- package/dist/hooks/notepad/index.js +7 -4
- package/dist/hooks/notepad/index.js.map +1 -1
- package/dist/hooks/persistent-mode/__tests__/ralph-verification-flow.test.js +70 -3
- package/dist/hooks/persistent-mode/__tests__/ralph-verification-flow.test.js.map +1 -1
- package/dist/hooks/persistent-mode/index.d.ts.map +1 -1
- package/dist/hooks/persistent-mode/index.js +17 -13
- package/dist/hooks/persistent-mode/index.js.map +1 -1
- package/dist/hooks/persistent-mode/stop-hook-blocking.test.js +175 -3
- package/dist/hooks/persistent-mode/stop-hook-blocking.test.js.map +1 -1
- package/dist/hooks/project-memory/__tests__/integration.test.js +117 -3
- package/dist/hooks/project-memory/__tests__/integration.test.js.map +1 -1
- package/dist/hooks/project-memory/__tests__/storage.test.js +11 -11
- package/dist/hooks/project-memory/__tests__/storage.test.js.map +1 -1
- package/dist/hooks/project-memory/index.d.ts.map +1 -1
- package/dist/hooks/project-memory/index.js +24 -12
- package/dist/hooks/project-memory/index.js.map +1 -1
- package/dist/hooks/ralph/index.d.ts +1 -1
- package/dist/hooks/ralph/index.d.ts.map +1 -1
- package/dist/hooks/ralph/index.js +1 -1
- package/dist/hooks/ralph/index.js.map +1 -1
- package/dist/hooks/ralph/loop.d.ts +6 -6
- package/dist/hooks/ralph/loop.d.ts.map +1 -1
- package/dist/hooks/ralph/loop.js +18 -18
- package/dist/hooks/ralph/loop.js.map +1 -1
- package/dist/hooks/ralph/prd.d.ts +23 -12
- package/dist/hooks/ralph/prd.d.ts.map +1 -1
- package/dist/hooks/ralph/prd.js +88 -42
- package/dist/hooks/ralph/prd.js.map +1 -1
- package/dist/hooks/session-end/__tests__/mode-state-cleanup.test.js +20 -0
- package/dist/hooks/session-end/__tests__/mode-state-cleanup.test.js.map +1 -1
- package/dist/hooks/session-end/index.d.ts +9 -0
- package/dist/hooks/session-end/index.d.ts.map +1 -1
- package/dist/hooks/session-end/index.js +110 -0
- package/dist/hooks/session-end/index.js.map +1 -1
- package/dist/hooks/skill-bridge.cjs +114 -92
- package/dist/hooks/skill-state/index.d.ts.map +1 -1
- package/dist/hooks/skill-state/index.js +1 -0
- package/dist/hooks/skill-state/index.js.map +1 -1
- package/dist/hud/__tests__/enterprise-cost.test.d.ts +5 -0
- package/dist/hud/__tests__/enterprise-cost.test.d.ts.map +1 -0
- package/dist/hud/__tests__/enterprise-cost.test.js +151 -0
- package/dist/hud/__tests__/enterprise-cost.test.js.map +1 -0
- package/dist/hud/__tests__/git-status.test.js +18 -0
- package/dist/hud/__tests__/git-status.test.js.map +1 -1
- package/dist/hud/__tests__/usage-api-enterprise.test.d.ts +5 -0
- package/dist/hud/__tests__/usage-api-enterprise.test.d.ts.map +1 -0
- package/dist/hud/__tests__/usage-api-enterprise.test.js +249 -0
- package/dist/hud/__tests__/usage-api-enterprise.test.js.map +1 -0
- package/dist/hud/elements/background.d.ts +3 -3
- package/dist/hud/elements/background.d.ts.map +1 -1
- package/dist/hud/elements/background.js +5 -4
- package/dist/hud/elements/background.js.map +1 -1
- package/dist/hud/elements/call-counts.d.ts +2 -2
- package/dist/hud/elements/call-counts.d.ts.map +1 -1
- package/dist/hud/elements/call-counts.js +7 -6
- package/dist/hud/elements/call-counts.js.map +1 -1
- package/dist/hud/elements/context.d.ts +3 -3
- package/dist/hud/elements/context.d.ts.map +1 -1
- package/dist/hud/elements/context.js +5 -4
- package/dist/hud/elements/context.js.map +1 -1
- package/dist/hud/elements/enterprise-cost.d.ts +16 -0
- package/dist/hud/elements/enterprise-cost.d.ts.map +1 -0
- package/dist/hud/elements/enterprise-cost.js +63 -0
- package/dist/hud/elements/enterprise-cost.js.map +1 -0
- package/dist/hud/elements/git.d.ts +2 -1
- package/dist/hud/elements/git.d.ts.map +1 -1
- package/dist/hud/elements/git.js +7 -6
- package/dist/hud/elements/git.js.map +1 -1
- package/dist/hud/elements/permission.d.ts.map +1 -1
- package/dist/hud/elements/permission.js +2 -5
- package/dist/hud/elements/permission.js.map +1 -1
- package/dist/hud/elements/ralph.d.ts +2 -2
- package/dist/hud/elements/ralph.d.ts.map +1 -1
- package/dist/hud/elements/ralph.js +3 -2
- package/dist/hud/elements/ralph.js.map +1 -1
- package/dist/hud/elements/session.d.ts.map +1 -1
- package/dist/hud/elements/session.js +5 -9
- package/dist/hud/elements/session.js.map +1 -1
- package/dist/hud/elements/thinking.d.ts +2 -2
- package/dist/hud/elements/thinking.d.ts.map +1 -1
- package/dist/hud/elements/thinking.js +3 -2
- package/dist/hud/elements/thinking.js.map +1 -1
- package/dist/hud/elements/token-usage.d.ts +2 -2
- package/dist/hud/elements/token-usage.d.ts.map +1 -1
- package/dist/hud/elements/token-usage.js +3 -2
- package/dist/hud/elements/token-usage.js.map +1 -1
- package/dist/hud/index.d.ts.map +1 -1
- package/dist/hud/index.js +5 -1
- package/dist/hud/index.js.map +1 -1
- package/dist/hud/render.d.ts.map +1 -1
- package/dist/hud/render.js +43 -10
- package/dist/hud/render.js.map +1 -1
- package/dist/hud/state.d.ts.map +1 -1
- package/dist/hud/state.js +18 -1
- package/dist/hud/state.js.map +1 -1
- package/dist/hud/stdin.d.ts.map +1 -1
- package/dist/hud/stdin.js +49 -5
- package/dist/hud/stdin.js.map +1 -1
- package/dist/hud/types.d.ts +42 -0
- package/dist/hud/types.d.ts.map +1 -1
- package/dist/hud/types.js +59 -1
- package/dist/hud/types.js.map +1 -1
- package/dist/hud/usage-api.d.ts +19 -1
- package/dist/hud/usage-api.d.ts.map +1 -1
- package/dist/hud/usage-api.js +101 -18
- package/dist/hud/usage-api.js.map +1 -1
- package/dist/installer/__tests__/hud-cache-wrapper.test.d.ts +2 -0
- package/dist/installer/__tests__/hud-cache-wrapper.test.d.ts.map +1 -0
- package/dist/installer/__tests__/hud-cache-wrapper.test.js +235 -0
- package/dist/installer/__tests__/hud-cache-wrapper.test.js.map +1 -0
- package/dist/installer/__tests__/standalone-hook-reconcile.test.js +2 -0
- package/dist/installer/__tests__/standalone-hook-reconcile.test.js.map +1 -1
- package/dist/installer/index.d.ts +1 -0
- package/dist/installer/index.d.ts.map +1 -1
- package/dist/installer/index.js +83 -31
- package/dist/installer/index.js.map +1 -1
- package/dist/lib/__tests__/worktree-paths.test.js +162 -39
- package/dist/lib/__tests__/worktree-paths.test.js.map +1 -1
- package/dist/lib/worktree-paths.d.ts +87 -9
- package/dist/lib/worktree-paths.d.ts.map +1 -1
- package/dist/lib/worktree-paths.js +209 -27
- package/dist/lib/worktree-paths.js.map +1 -1
- package/dist/mcp/__tests__/team-server-artifact-convergence.test.js +156 -4
- package/dist/mcp/__tests__/team-server-artifact-convergence.test.js.map +1 -1
- package/dist/mcp/omc-tools-server.d.ts +11 -17
- package/dist/mcp/omc-tools-server.d.ts.map +1 -1
- package/dist/mcp/omc-tools-server.js +22 -32
- package/dist/mcp/omc-tools-server.js.map +1 -1
- package/dist/mcp/team-job-convergence.d.ts +9 -2
- package/dist/mcp/team-job-convergence.d.ts.map +1 -1
- package/dist/mcp/team-job-convergence.js +35 -14
- package/dist/mcp/team-job-convergence.js.map +1 -1
- package/dist/mcp/team-server.d.ts.map +1 -1
- package/dist/mcp/team-server.js +93 -13
- package/dist/mcp/team-server.js.map +1 -1
- package/dist/ralphthon/prd.d.ts.map +1 -1
- package/dist/ralphthon/prd.js +13 -17
- package/dist/ralphthon/prd.js.map +1 -1
- package/dist/shared/types.d.ts +2 -0
- package/dist/shared/types.d.ts.map +1 -1
- package/dist/team/__tests__/api-interop.cleanup.test.d.ts +2 -0
- package/dist/team/__tests__/api-interop.cleanup.test.d.ts.map +1 -0
- package/dist/team/__tests__/api-interop.cleanup.test.js +234 -0
- package/dist/team/__tests__/api-interop.cleanup.test.js.map +1 -0
- package/dist/team/__tests__/api-interop.dispatch.test.js +2 -1
- package/dist/team/__tests__/api-interop.dispatch.test.js.map +1 -1
- package/dist/team/__tests__/auto-merge-restart.test.d.ts +2 -0
- package/dist/team/__tests__/auto-merge-restart.test.d.ts.map +1 -0
- package/dist/team/__tests__/auto-merge-restart.test.js +128 -0
- package/dist/team/__tests__/auto-merge-restart.test.js.map +1 -0
- package/dist/team/__tests__/auto-merge.perf.test.d.ts +2 -0
- package/dist/team/__tests__/auto-merge.perf.test.d.ts.map +1 -0
- package/dist/team/__tests__/auto-merge.perf.test.js +85 -0
- package/dist/team/__tests__/auto-merge.perf.test.js.map +1 -0
- package/dist/team/__tests__/conflict-mailbox.test.d.ts +2 -0
- package/dist/team/__tests__/conflict-mailbox.test.d.ts.map +1 -0
- package/dist/team/__tests__/conflict-mailbox.test.js +238 -0
- package/dist/team/__tests__/conflict-mailbox.test.js.map +1 -0
- package/dist/team/__tests__/git-worktree.test.js +164 -3
- package/dist/team/__tests__/git-worktree.test.js.map +1 -1
- package/dist/team/__tests__/helpers/git-fixture.d.ts +72 -0
- package/dist/team/__tests__/helpers/git-fixture.d.ts.map +1 -0
- package/dist/team/__tests__/helpers/git-fixture.js +252 -0
- package/dist/team/__tests__/helpers/git-fixture.js.map +1 -0
- package/dist/team/__tests__/leader-inbox.test.d.ts +2 -0
- package/dist/team/__tests__/leader-inbox.test.d.ts.map +1 -0
- package/dist/team/__tests__/leader-inbox.test.js +152 -0
- package/dist/team/__tests__/leader-inbox.test.js.map +1 -0
- package/dist/team/__tests__/merge-orchestrator.test.d.ts +2 -0
- package/dist/team/__tests__/merge-orchestrator.test.d.ts.map +1 -0
- package/dist/team/__tests__/merge-orchestrator.test.js +779 -0
- package/dist/team/__tests__/merge-orchestrator.test.js.map +1 -0
- package/dist/team/__tests__/model-contract.test.js +35 -21
- package/dist/team/__tests__/model-contract.test.js.map +1 -1
- package/dist/team/__tests__/rebase-smoke.test.d.ts +2 -0
- package/dist/team/__tests__/rebase-smoke.test.d.ts.map +1 -0
- package/dist/team/__tests__/rebase-smoke.test.js +272 -0
- package/dist/team/__tests__/rebase-smoke.test.js.map +1 -0
- package/dist/team/__tests__/runtime-cli.test.js +11 -3
- package/dist/team/__tests__/runtime-cli.test.js.map +1 -1
- package/dist/team/__tests__/runtime-prompt-mode.test.js +14 -16
- package/dist/team/__tests__/runtime-prompt-mode.test.js.map +1 -1
- package/dist/team/__tests__/runtime-v2-role-routing.test.js +7 -1
- package/dist/team/__tests__/runtime-v2-role-routing.test.js.map +1 -1
- package/dist/team/__tests__/runtime-v2.dispatch.test.js +293 -15
- package/dist/team/__tests__/runtime-v2.dispatch.test.js.map +1 -1
- package/dist/team/__tests__/runtime-v2.monitor.test.d.ts +2 -0
- package/dist/team/__tests__/runtime-v2.monitor.test.d.ts.map +1 -0
- package/dist/team/__tests__/runtime-v2.monitor.test.js +219 -0
- package/dist/team/__tests__/runtime-v2.monitor.test.js.map +1 -0
- package/dist/team/__tests__/runtime-v2.shutdown.test.d.ts +2 -0
- package/dist/team/__tests__/runtime-v2.shutdown.test.d.ts.map +1 -0
- package/dist/team/__tests__/runtime-v2.shutdown.test.js +292 -0
- package/dist/team/__tests__/runtime-v2.shutdown.test.js.map +1 -0
- package/dist/team/__tests__/scaling-launch-config.test.js +203 -3
- package/dist/team/__tests__/scaling-launch-config.test.js.map +1 -1
- package/dist/team/__tests__/team-summary-worktree.test.d.ts +2 -0
- package/dist/team/__tests__/team-summary-worktree.test.d.ts.map +1 -0
- package/dist/team/__tests__/team-summary-worktree.test.js +85 -0
- package/dist/team/__tests__/team-summary-worktree.test.js.map +1 -0
- package/dist/team/__tests__/teardown-invariant.test.d.ts +2 -0
- package/dist/team/__tests__/teardown-invariant.test.d.ts.map +1 -0
- package/dist/team/__tests__/teardown-invariant.test.js +176 -0
- package/dist/team/__tests__/teardown-invariant.test.js.map +1 -0
- package/dist/team/__tests__/tmux-session-liveness.test.d.ts +2 -0
- package/dist/team/__tests__/tmux-session-liveness.test.d.ts.map +1 -0
- package/dist/team/__tests__/tmux-session-liveness.test.js +37 -0
- package/dist/team/__tests__/tmux-session-liveness.test.js.map +1 -0
- package/dist/team/__tests__/worker-bootstrap.test.js +47 -1
- package/dist/team/__tests__/worker-bootstrap.test.js.map +1 -1
- package/dist/team/__tests__/worker-commit-cadence.test.d.ts +2 -0
- package/dist/team/__tests__/worker-commit-cadence.test.d.ts.map +1 -0
- package/dist/team/__tests__/worker-commit-cadence.test.js +315 -0
- package/dist/team/__tests__/worker-commit-cadence.test.js.map +1 -0
- package/dist/team/__tests__/worktree-contract-fields.test.d.ts +2 -0
- package/dist/team/__tests__/worktree-contract-fields.test.d.ts.map +1 -0
- package/dist/team/__tests__/worktree-contract-fields.test.js +157 -0
- package/dist/team/__tests__/worktree-contract-fields.test.js.map +1 -0
- package/dist/team/__tests__/worktree-runtime-e2e.test.d.ts +2 -0
- package/dist/team/__tests__/worktree-runtime-e2e.test.d.ts.map +1 -0
- package/dist/team/__tests__/worktree-runtime-e2e.test.js +182 -0
- package/dist/team/__tests__/worktree-runtime-e2e.test.js.map +1 -0
- package/dist/team/api-interop.d.ts +2 -1
- package/dist/team/api-interop.d.ts.map +1 -1
- package/dist/team/api-interop.js +121 -14
- package/dist/team/api-interop.js.map +1 -1
- package/dist/team/capabilities.d.ts.map +1 -1
- package/dist/team/capabilities.js +2 -0
- package/dist/team/capabilities.js.map +1 -1
- package/dist/team/cli-worker-contract.d.ts +7 -6
- package/dist/team/cli-worker-contract.d.ts.map +1 -1
- package/dist/team/cli-worker-contract.js +7 -6
- package/dist/team/cli-worker-contract.js.map +1 -1
- package/dist/team/conflict-mailbox.d.ts +49 -0
- package/dist/team/conflict-mailbox.d.ts.map +1 -0
- package/dist/team/conflict-mailbox.js +98 -0
- package/dist/team/conflict-mailbox.js.map +1 -0
- package/dist/team/events.d.ts +3 -0
- package/dist/team/events.d.ts.map +1 -1
- package/dist/team/events.js +2 -1
- package/dist/team/events.js.map +1 -1
- package/dist/team/git-worktree.d.ts +78 -10
- package/dist/team/git-worktree.d.ts.map +1 -1
- package/dist/team/git-worktree.js +453 -97
- package/dist/team/git-worktree.js.map +1 -1
- package/dist/team/governance.d.ts +3 -2
- package/dist/team/governance.d.ts.map +1 -1
- package/dist/team/governance.js.map +1 -1
- package/dist/team/leader-inbox.d.ts +28 -0
- package/dist/team/leader-inbox.d.ts.map +1 -0
- package/dist/team/leader-inbox.js +68 -0
- package/dist/team/leader-inbox.js.map +1 -0
- package/dist/team/merge-coordinator.d.ts +6 -0
- package/dist/team/merge-coordinator.d.ts.map +1 -1
- package/dist/team/merge-coordinator.js +6 -2
- package/dist/team/merge-coordinator.js.map +1 -1
- package/dist/team/merge-orchestrator.d.ts +41 -0
- package/dist/team/merge-orchestrator.d.ts.map +1 -0
- package/dist/team/merge-orchestrator.js +736 -0
- package/dist/team/merge-orchestrator.js.map +1 -0
- package/dist/team/model-contract.d.ts +18 -2
- package/dist/team/model-contract.d.ts.map +1 -1
- package/dist/team/model-contract.js +55 -28
- package/dist/team/model-contract.js.map +1 -1
- package/dist/team/monitor.d.ts.map +1 -1
- package/dist/team/monitor.js +47 -3
- package/dist/team/monitor.js.map +1 -1
- package/dist/team/runtime-cli.d.ts +1 -0
- package/dist/team/runtime-cli.d.ts.map +1 -1
- package/dist/team/runtime-cli.js +15 -2
- package/dist/team/runtime-cli.js.map +1 -1
- package/dist/team/runtime-flags.d.ts +5 -0
- package/dist/team/runtime-flags.d.ts.map +1 -0
- package/dist/team/runtime-flags.js +12 -0
- package/dist/team/runtime-flags.js.map +1 -0
- package/dist/team/runtime-v2.d.ts +16 -1
- package/dist/team/runtime-v2.d.ts.map +1 -1
- package/dist/team/runtime-v2.js +531 -127
- package/dist/team/runtime-v2.js.map +1 -1
- package/dist/team/runtime.d.ts.map +1 -1
- package/dist/team/runtime.js +4 -1
- package/dist/team/runtime.js.map +1 -1
- package/dist/team/scaling.d.ts.map +1 -1
- package/dist/team/scaling.js +110 -13
- package/dist/team/scaling.js.map +1 -1
- package/dist/team/state/tasks.d.ts.map +1 -1
- package/dist/team/state/tasks.js +1 -0
- package/dist/team/state/tasks.js.map +1 -1
- package/dist/team/team-ops.d.ts.map +1 -1
- package/dist/team/team-ops.js +112 -16
- package/dist/team/team-ops.js.map +1 -1
- package/dist/team/tmux-session.d.ts +17 -29
- package/dist/team/tmux-session.d.ts.map +1 -1
- package/dist/team/tmux-session.js +117 -108
- package/dist/team/tmux-session.js.map +1 -1
- package/dist/team/types.d.ts +31 -17
- package/dist/team/types.d.ts.map +1 -1
- package/dist/team/types.js.map +1 -1
- package/dist/team/worker-bootstrap.d.ts +11 -0
- package/dist/team/worker-bootstrap.d.ts.map +1 -1
- package/dist/team/worker-bootstrap.js +37 -14
- package/dist/team/worker-bootstrap.js.map +1 -1
- package/dist/team/worker-canonicalization.d.ts.map +1 -1
- package/dist/team/worker-canonicalization.js +2 -0
- package/dist/team/worker-canonicalization.js.map +1 -1
- package/dist/team/worker-commit-cadence.d.ts +70 -0
- package/dist/team/worker-commit-cadence.d.ts.map +1 -0
- package/dist/team/worker-commit-cadence.js +271 -0
- package/dist/team/worker-commit-cadence.js.map +1 -0
- package/dist/tools/__tests__/memory-tools.test.js +4 -4
- package/dist/tools/__tests__/memory-tools.test.js.map +1 -1
- package/dist/tools/__tests__/state-tools.test.js +13 -1
- package/dist/tools/__tests__/state-tools.test.js.map +1 -1
- package/dist/utils/frontmatter.d.ts.map +1 -1
- package/dist/utils/frontmatter.js +1 -16
- package/dist/utils/frontmatter.js.map +1 -1
- package/dist/utils/resolve-node.d.ts.map +1 -1
- package/dist/utils/resolve-node.js +22 -28
- package/dist/utils/resolve-node.js.map +1 -1
- package/docs/GETTING-STARTED.md +415 -0
- package/docs/MIGRATION.md +1003 -0
- package/docs/REFERENCE.md +21 -4
- package/docs/TEAM-WORKTREE-MODE.md +86 -0
- package/docs/company-context-interface.md +5 -16
- package/docs/copilot-instructions.md +1 -1
- package/package.json +1 -1
- package/scripts/keyword-detector.mjs +168 -20
- package/scripts/lib/hud-cache-wrapper.sh +122 -0
- package/scripts/persistent-mode.cjs +28 -2
- package/scripts/persistent-mode.mjs +28 -2
- package/scripts/plugin-setup.mjs +10 -1
- package/scripts/post-tool-verifier.mjs +153 -3
- package/scripts/pre-tool-enforcer.mjs +54 -6
- package/scripts/session-start.mjs +194 -2
- package/skills/AGENTS.md +22 -91
- package/skills/ai-slop-cleaner/SKILL.md +118 -120
- package/skills/ask/SKILL.md +6 -8
- package/skills/autopilot/SKILL.md +1 -28
- package/skills/autoresearch/SKILL.md +5 -5
- package/skills/cancel/SKILL.md +13 -13
- package/skills/cccg/SKILL.md +1 -0
- package/skills/configure-notifications/SKILL.md +80 -215
- package/skills/debug/SKILL.md +3 -2
- package/skills/deep-dive/SKILL.md +20 -9
- package/skills/deep-interview/SKILL.md +32 -4
- package/skills/deepinit/SKILL.md +1 -0
- package/skills/external-context/SKILL.md +2 -1
- package/skills/hud/SKILL.md +22 -52
- package/skills/learner/SKILL.md +90 -57
- package/skills/mcp-setup/SKILL.md +45 -60
- package/skills/omc-doctor/SKILL.md +1 -1
- package/skills/omc-reference/SKILL.md +6 -6
- package/skills/omc-setup/SKILL.md +30 -74
- package/skills/omc-setup/phases/01-install-claude-md.md +97 -0
- package/skills/omc-setup/phases/02-configure.md +25 -40
- package/skills/omc-setup/phases/03-integrations.md +100 -69
- package/skills/omc-setup/phases/04-welcome.md +102 -44
- package/skills/omc-teams/SKILL.md +63 -26
- package/skills/plan/SKILL.md +11 -9
- package/skills/ralph/SKILL.md +42 -41
- package/skills/ralplan/SKILL.md +3 -10
- package/skills/release/SKILL.md +168 -57
- package/skills/remember/SKILL.md +2 -1
- package/skills/sciomc/SKILL.md +12 -11
- package/skills/self-improve/SKILL.md +4 -4
- package/skills/setup/SKILL.md +1 -0
- package/skills/skill/SKILL.md +21 -20
- package/skills/skillify/SKILL.md +34 -3
- package/skills/team/SKILL.md +62 -44
- package/skills/ultraqa/SKILL.md +1 -2
- package/skills/ultrawork/SKILL.md +28 -21
- package/skills/verify/SKILL.md +2 -1
- package/skills/writer-memory/SKILL.md +2 -1
- package/templates/hooks/keyword-detector.mjs +129 -11
- package/templates/hooks/persistent-mode.mjs +28 -2
- package/templates/hooks/pre-tool-use.mjs +144 -45
- package/templates/hooks/session-start.mjs +25 -1
package/dist/team/runtime-v2.js
CHANGED
|
@@ -15,10 +15,10 @@
|
|
|
15
15
|
* Architecture mirrors runtime.ts: startTeam, monitorTeam, shutdownTeam,
|
|
16
16
|
* assignTask, resumeTeam as discrete operations driven by the caller.
|
|
17
17
|
*/
|
|
18
|
-
import { join, resolve } from 'path';
|
|
19
18
|
import { tmuxExecAsync } from '../cli/tmux-utils.js';
|
|
19
|
+
import { join, resolve } from 'path';
|
|
20
20
|
import { existsSync } from 'fs';
|
|
21
|
-
import { mkdir, readdir, readFile, writeFile } from 'fs/promises';
|
|
21
|
+
import { mkdir, readdir, readFile, rm, writeFile } from 'fs/promises';
|
|
22
22
|
import { performance } from 'perf_hooks';
|
|
23
23
|
import { TeamPaths, absPath, teamStateRoot } from './state-paths.js';
|
|
24
24
|
import { allocateTasksToWorkers } from './allocation-policy.js';
|
|
@@ -28,12 +28,11 @@ import { DEFAULT_TEAM_GOVERNANCE, DEFAULT_TEAM_TRANSPORT_POLICY, getConfigGovern
|
|
|
28
28
|
import { inferPhase } from './phase-controller.js';
|
|
29
29
|
import { validateTeamName } from './team-name.js';
|
|
30
30
|
import { buildWorkerArgv, getContract, resolveValidatedBinaryPath, getWorkerEnv as getModelWorkerEnv, isPromptModeAgent, getPromptModeArgs, resolveClaudeWorkerModel, } from './model-contract.js';
|
|
31
|
-
import { createTeamSession, spawnWorkerInPane, sendToWorker, waitForPaneReady, paneHasActiveTask, paneLooksReady, applyMainVerticalLayout, } from './tmux-session.js';
|
|
31
|
+
import { createTeamSession, spawnWorkerInPane, sendToWorker, killTeamSession, waitForPaneReady, paneHasActiveTask, paneLooksReady, applyMainVerticalLayout, getWorkerLiveness, } from './tmux-session.js';
|
|
32
32
|
import { composeInitialInbox, ensureWorkerStateDir, writeWorkerOverlay, generateTriggerMessage, generatePromptModeStartupPrompt, } from './worker-bootstrap.js';
|
|
33
33
|
import { queueInboxInstruction } from './mcp-comm.js';
|
|
34
|
-
import { cleanupTeamWorktrees } from './git-worktree.js';
|
|
34
|
+
import { cleanupTeamWorktrees, inspectTeamWorktreeCleanupSafety, ensureWorkerWorktree, installWorktreeRootAgents, normalizeTeamWorktreeMode, } from './git-worktree.js';
|
|
35
35
|
import { formatOmcCliInvocation } from '../utils/omc-cli-rendering.js';
|
|
36
|
-
import { getHostCliType } from '../utils/host-detection.js';
|
|
37
36
|
import { createSwallowedErrorLogger } from '../lib/swallowed-error.js';
|
|
38
37
|
import { CANONICAL_TEAM_ROLES } from '../shared/types.js';
|
|
39
38
|
import { loadConfig } from '../config/loader.js';
|
|
@@ -41,16 +40,70 @@ import { buildResolvedRoutingSnapshot, getRoleRoutingSpec } from './stage-router
|
|
|
41
40
|
import { routeTaskToRole } from './role-router.js';
|
|
42
41
|
import { normalizeDelegationRole } from '../features/delegation-routing/types.js';
|
|
43
42
|
import { cliWorkerOutputFilePath, parseCliWorkerVerdict, renderCliWorkerOutputContract, shouldInjectContract, } from './cli-worker-contract.js';
|
|
43
|
+
import { startMergeOrchestrator, recoverFromRestart, } from './merge-orchestrator.js';
|
|
44
|
+
import { ensureLeaderInbox, extendLeaderBootstrapPrompt, appendToLeaderInbox } from './leader-inbox.js';
|
|
45
|
+
import { execFileSync } from 'node:child_process';
|
|
46
|
+
import { isRuntimeV2Enabled } from './runtime-flags.js';
|
|
47
|
+
import { installCommitCadence, startFallbackPoller, uninstallCommitCadence, } from './worker-commit-cadence.js';
|
|
44
48
|
// ---------------------------------------------------------------------------
|
|
45
|
-
//
|
|
49
|
+
// In-process orchestrator registry (per-team handle for the lifetime of the
|
|
50
|
+
// runtime-cli process). Lives at module scope so shutdownTeamV2 can find it.
|
|
46
51
|
// ---------------------------------------------------------------------------
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
52
|
+
const orchestratorByTeam = new Map();
|
|
53
|
+
const cadenceByTeam = new Map();
|
|
54
|
+
function registerTeamOrchestrator(teamName, handle) {
|
|
55
|
+
orchestratorByTeam.set(teamName, handle);
|
|
56
|
+
}
|
|
57
|
+
function getTeamOrchestrator(teamName) {
|
|
58
|
+
return orchestratorByTeam.get(teamName);
|
|
59
|
+
}
|
|
60
|
+
function unregisterTeamOrchestrator(teamName) {
|
|
61
|
+
orchestratorByTeam.delete(teamName);
|
|
62
|
+
}
|
|
63
|
+
function registerTeamCadence(teamName, context, poller) {
|
|
64
|
+
const entry = cadenceByTeam.get(teamName) ?? { pollers: [], contexts: [] };
|
|
65
|
+
entry.contexts.push(context);
|
|
66
|
+
if (poller)
|
|
67
|
+
entry.pollers.push(poller);
|
|
68
|
+
cadenceByTeam.set(teamName, entry);
|
|
69
|
+
}
|
|
70
|
+
async function stopTeamCadence(teamName) {
|
|
71
|
+
const entry = cadenceByTeam.get(teamName);
|
|
72
|
+
if (!entry)
|
|
73
|
+
return;
|
|
74
|
+
cadenceByTeam.delete(teamName);
|
|
75
|
+
for (const poller of entry.pollers) {
|
|
76
|
+
try {
|
|
77
|
+
poller.stop();
|
|
78
|
+
}
|
|
79
|
+
catch { /* best-effort cleanup */ }
|
|
80
|
+
}
|
|
81
|
+
for (const context of entry.contexts) {
|
|
82
|
+
try {
|
|
83
|
+
await uninstallCommitCadence(context);
|
|
84
|
+
}
|
|
85
|
+
catch { /* best-effort cleanup */ }
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Resolve the leader's current branch via `git branch --show-current` from cwd.
|
|
90
|
+
* Throws if not a git repo or HEAD is detached.
|
|
91
|
+
*/
|
|
92
|
+
function resolveLeaderBranch(cwd) {
|
|
93
|
+
const out = execFileSync('git', ['branch', '--show-current'], {
|
|
94
|
+
cwd,
|
|
95
|
+
encoding: 'utf-8',
|
|
96
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
97
|
+
}).trim();
|
|
98
|
+
if (!out) {
|
|
99
|
+
throw new Error('auto-merge requires a non-detached leader branch (git branch --show-current returned empty)');
|
|
100
|
+
}
|
|
101
|
+
return out;
|
|
53
102
|
}
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
// Feature flag
|
|
105
|
+
// ---------------------------------------------------------------------------
|
|
106
|
+
export { isRuntimeV2Enabled } from './runtime-flags.js';
|
|
54
107
|
const MONITOR_SIGNAL_STALE_MS = 30_000;
|
|
55
108
|
// ---------------------------------------------------------------------------
|
|
56
109
|
// Helper: sanitize team name
|
|
@@ -125,23 +178,17 @@ function resolvePreflightBinaryPath(agentType) {
|
|
|
125
178
|
// ---------------------------------------------------------------------------
|
|
126
179
|
// Helper: check worker liveness via tmux pane
|
|
127
180
|
// ---------------------------------------------------------------------------
|
|
128
|
-
async function
|
|
181
|
+
async function getWorkerPaneLiveness(paneId) {
|
|
129
182
|
if (!paneId)
|
|
130
|
-
return
|
|
131
|
-
|
|
132
|
-
const { isWorkerAlive } = await import('./tmux-session.js');
|
|
133
|
-
return await isWorkerAlive(paneId);
|
|
134
|
-
}
|
|
135
|
-
catch {
|
|
136
|
-
return false;
|
|
137
|
-
}
|
|
183
|
+
return 'dead';
|
|
184
|
+
return getWorkerLiveness(paneId);
|
|
138
185
|
}
|
|
139
186
|
async function captureWorkerPane(paneId) {
|
|
140
187
|
if (!paneId)
|
|
141
188
|
return '';
|
|
142
189
|
try {
|
|
143
|
-
const
|
|
144
|
-
return stdout;
|
|
190
|
+
const result = await tmuxExecAsync(['capture-pane', '-t', paneId, '-p', '-S', '-80']);
|
|
191
|
+
return result.stdout ?? '';
|
|
145
192
|
}
|
|
146
193
|
catch {
|
|
147
194
|
return '';
|
|
@@ -167,12 +214,18 @@ function findOutstandingWorkerTask(worker, taskById, inProgressByOwner) {
|
|
|
167
214
|
const owned = inProgressByOwner.get(worker.name) ?? [];
|
|
168
215
|
return owned[0] ?? null;
|
|
169
216
|
}
|
|
217
|
+
function getTaskDependencyIds(task) {
|
|
218
|
+
return task.depends_on ?? task.blocked_by ?? [];
|
|
219
|
+
}
|
|
220
|
+
function getMissingDependencyIds(task, taskById) {
|
|
221
|
+
return getTaskDependencyIds(task).filter((dependencyId) => !taskById.has(dependencyId));
|
|
222
|
+
}
|
|
170
223
|
// ---------------------------------------------------------------------------
|
|
171
224
|
// V2 task instruction builder — CLI API lifecycle, NO done.json
|
|
172
225
|
// ---------------------------------------------------------------------------
|
|
173
226
|
/**
|
|
174
227
|
* Build the initial task instruction for v2 workers.
|
|
175
|
-
* Workers use `
|
|
228
|
+
* Workers use `omc team api` CLI commands for all lifecycle transitions.
|
|
176
229
|
*/
|
|
177
230
|
function buildV2TaskInstruction(teamName, workerName, task, taskId, cliOutputContract) {
|
|
178
231
|
const claimTaskCommand = formatOmcCliInvocation(`team api claim-task --input '${JSON.stringify({ team_name: teamName, task_id: taskId, worker: workerName })}' --json`, {});
|
|
@@ -269,7 +322,7 @@ async function spawnV2Worker(opts) {
|
|
|
269
322
|
const splitResult = await tmuxExecAsync([
|
|
270
323
|
'split-window', splitType, '-t', splitTarget,
|
|
271
324
|
'-d', '-P', '-F', '#{pane_id}',
|
|
272
|
-
'-c', opts.cwd,
|
|
325
|
+
'-c', opts.workerCwd ?? opts.cwd,
|
|
273
326
|
]);
|
|
274
327
|
const paneId = splitResult.stdout.split('\n')[0]?.trim();
|
|
275
328
|
if (!paneId) {
|
|
@@ -288,8 +341,9 @@ async function spawnV2Worker(opts) {
|
|
|
288
341
|
: undefined;
|
|
289
342
|
// Build v2 task instruction (CLI API, NO done.json)
|
|
290
343
|
const instruction = buildV2TaskInstruction(opts.teamName, opts.workerName, opts.task, opts.taskId, cliOutputContract);
|
|
291
|
-
const
|
|
292
|
-
const
|
|
344
|
+
const instructionStateRoot = opts.worktreePath ? '$OMC_TEAM_STATE_ROOT' : undefined;
|
|
345
|
+
const inboxTriggerMessage = generateTriggerMessage(opts.teamName, opts.workerName, instructionStateRoot);
|
|
346
|
+
const promptModeStartupPrompt = generatePromptModeStartupPrompt(opts.teamName, opts.workerName, instructionStateRoot, cliOutputContract);
|
|
293
347
|
if (usePromptMode) {
|
|
294
348
|
await composeInitialInbox(opts.teamName, opts.workerName, instruction, opts.cwd, cliOutputContract);
|
|
295
349
|
}
|
|
@@ -298,10 +352,14 @@ async function spawnV2Worker(opts) {
|
|
|
298
352
|
...getModelWorkerEnv(opts.teamName, opts.workerName, opts.agentType),
|
|
299
353
|
OMC_TEAM_STATE_ROOT: teamStateRoot(opts.cwd, opts.teamName),
|
|
300
354
|
OMC_TEAM_LEADER_CWD: opts.cwd,
|
|
355
|
+
...(opts.worktreePath ? { OMC_TEAM_WORKTREE_PATH: opts.worktreePath } : {}),
|
|
356
|
+
...(opts.workerCwd ? { OMC_TEAM_WORKER_CWD: opts.workerCwd } : {}),
|
|
301
357
|
};
|
|
302
358
|
const resolvedBinaryPath = opts.resolvedBinaryPaths[opts.agentType]
|
|
303
359
|
?? resolveValidatedBinaryPath(opts.agentType);
|
|
304
360
|
// Resolve model from environment variables.
|
|
361
|
+
// For Claude agents on Bedrock/Vertex, resolve the provider-specific model
|
|
362
|
+
// so workers don't fall back to invalid Anthropic API model names. (#1695)
|
|
305
363
|
// Snapshot-provided model (from resolved_routing) takes precedence so
|
|
306
364
|
// per-role routing (codex/gemini/claude-tier) is honored at spawn time.
|
|
307
365
|
const modelForAgent = opts.model ?? (() => {
|
|
@@ -315,18 +373,35 @@ async function spawnV2Worker(opts) {
|
|
|
315
373
|
|| process.env.OMC_GEMINI_DEFAULT_MODEL
|
|
316
374
|
|| undefined;
|
|
317
375
|
}
|
|
376
|
+
// Claude agents: resolve Bedrock/Vertex model when on those providers
|
|
318
377
|
return resolveClaudeWorkerModel();
|
|
319
378
|
})();
|
|
320
379
|
const [launchBinary, ...launchArgs] = buildWorkerArgv(opts.agentType, {
|
|
321
380
|
teamName: opts.teamName,
|
|
322
381
|
workerName: opts.workerName,
|
|
323
|
-
cwd: opts.cwd,
|
|
382
|
+
cwd: opts.workerCwd ?? opts.cwd,
|
|
324
383
|
resolvedBinaryPath,
|
|
325
384
|
model: modelForAgent,
|
|
326
385
|
});
|
|
327
|
-
// For prompt-mode agents (
|
|
386
|
+
// For prompt-mode agents (currently gemini), keep the full instruction in
|
|
387
|
+
// inbox.md and pass only a short file-pointer prompt via CLI args. This
|
|
388
|
+
// avoids echoing reviewer/seed prompt text into tmux scrollback.
|
|
328
389
|
if (usePromptMode) {
|
|
329
|
-
launchArgs.push(...getPromptModeArgs(opts.agentType,
|
|
390
|
+
launchArgs.push(...getPromptModeArgs(opts.agentType, promptModeStartupPrompt));
|
|
391
|
+
}
|
|
392
|
+
if (opts.autoMerge && opts.worktreePath) {
|
|
393
|
+
const cadenceContext = {
|
|
394
|
+
teamName: opts.teamName,
|
|
395
|
+
workerName: opts.workerName,
|
|
396
|
+
worktreePath: opts.worktreePath,
|
|
397
|
+
agentType: opts.agentType,
|
|
398
|
+
enabled: true,
|
|
399
|
+
};
|
|
400
|
+
const cadence = await installCommitCadence(cadenceContext);
|
|
401
|
+
const poller = cadence.method === 'fallback-poll'
|
|
402
|
+
? startFallbackPoller(opts.worktreePath, opts.workerName)
|
|
403
|
+
: undefined;
|
|
404
|
+
registerTeamCadence(opts.teamName, cadenceContext, poller);
|
|
330
405
|
}
|
|
331
406
|
const paneConfig = {
|
|
332
407
|
teamName: opts.teamName,
|
|
@@ -334,7 +409,7 @@ async function spawnV2Worker(opts) {
|
|
|
334
409
|
envVars,
|
|
335
410
|
launchBinary,
|
|
336
411
|
launchArgs,
|
|
337
|
-
cwd: opts.cwd,
|
|
412
|
+
cwd: opts.workerCwd ?? opts.cwd,
|
|
338
413
|
};
|
|
339
414
|
await spawnWorkerInPane(opts.sessionName, paneId, paneConfig);
|
|
340
415
|
// Apply layout
|
|
@@ -385,7 +460,7 @@ async function spawnV2Worker(opts) {
|
|
|
385
460
|
startupFailureReason: dispatchOutcome.reason,
|
|
386
461
|
};
|
|
387
462
|
}
|
|
388
|
-
if (opts.agentType === 'claude'
|
|
463
|
+
if (opts.agentType === 'claude') {
|
|
389
464
|
const settled = await waitForWorkerStartupEvidence(opts.teamName, opts.workerName, opts.taskId, opts.cwd, 6);
|
|
390
465
|
if (!settled) {
|
|
391
466
|
return {
|
|
@@ -411,6 +486,46 @@ async function spawnV2Worker(opts) {
|
|
|
411
486
|
...(outputFile ? { outputFile } : {}),
|
|
412
487
|
};
|
|
413
488
|
}
|
|
489
|
+
async function rollbackUnpersistedNativeWorktreeStartup(teamName, cwd, cause) {
|
|
490
|
+
const safety = inspectTeamWorktreeCleanupSafety(teamName, cwd);
|
|
491
|
+
if (!safety.hasEvidence)
|
|
492
|
+
return;
|
|
493
|
+
const teamRoot = absPath(cwd, TeamPaths.root(teamName));
|
|
494
|
+
const errorMessage = cause instanceof Error ? cause.message : String(cause);
|
|
495
|
+
try {
|
|
496
|
+
const cleanup = cleanupTeamWorktrees(teamName, cwd);
|
|
497
|
+
if (cleanup.preserved.length === 0) {
|
|
498
|
+
await rm(teamRoot, { recursive: true, force: true });
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
await mkdir(teamRoot, { recursive: true });
|
|
502
|
+
await writeFile(join(teamRoot, 'startup-failure.json'), JSON.stringify({
|
|
503
|
+
reason: 'startup_failed_before_config_persisted',
|
|
504
|
+
error: errorMessage,
|
|
505
|
+
preserved: cleanup.preserved,
|
|
506
|
+
recorded_at: new Date().toISOString(),
|
|
507
|
+
}, null, 2), 'utf-8');
|
|
508
|
+
}
|
|
509
|
+
catch (rollbackError) {
|
|
510
|
+
await mkdir(teamRoot, { recursive: true });
|
|
511
|
+
await writeFile(join(teamRoot, 'startup-failure.json'), JSON.stringify({
|
|
512
|
+
reason: 'startup_failed_before_config_persisted',
|
|
513
|
+
error: errorMessage,
|
|
514
|
+
rollback_error: rollbackError instanceof Error ? rollbackError.message : String(rollbackError),
|
|
515
|
+
recorded_at: new Date().toISOString(),
|
|
516
|
+
}, null, 2), 'utf-8');
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
async function rollbackStartedNativeWorktreeStartup(args) {
|
|
520
|
+
try {
|
|
521
|
+
await killTeamSession(args.sessionName, args.workerPaneIds, args.leaderPaneId ?? undefined, { sessionMode: args.sessionMode });
|
|
522
|
+
}
|
|
523
|
+
catch (killError) {
|
|
524
|
+
process.stderr.write(`[team/runtime-v2] startup rollback tmux cleanup failed: ${killError instanceof Error ? killError.message : String(killError)}
|
|
525
|
+
`);
|
|
526
|
+
}
|
|
527
|
+
await rollbackUnpersistedNativeWorktreeStartup(args.teamName, args.cwd, args.cause);
|
|
528
|
+
}
|
|
414
529
|
// ---------------------------------------------------------------------------
|
|
415
530
|
// startTeamV2 — direct tmux creation, CLI API inbox, NO watchdog
|
|
416
531
|
// ---------------------------------------------------------------------------
|
|
@@ -430,6 +545,25 @@ export async function startTeamV2(config) {
|
|
|
430
545
|
// do NOT change routing — user must recreate the team to pick up changes.
|
|
431
546
|
const pluginCfg = config.pluginConfig ?? loadConfig();
|
|
432
547
|
const resolvedRouting = buildResolvedRoutingSnapshot(pluginCfg);
|
|
548
|
+
let worktreeMode = normalizeTeamWorktreeMode(process.env.OMC_TEAM_WORKTREE_MODE ?? pluginCfg.team?.ops?.worktreeMode);
|
|
549
|
+
// Auto-merge gate (M5 + M3 hardening). Forces worktreeMode='named' so each
|
|
550
|
+
// worker has a real branch the orchestrator can merge from.
|
|
551
|
+
let autoMergeLeaderBranch;
|
|
552
|
+
if (config.autoMerge) {
|
|
553
|
+
if (!isRuntimeV2Enabled()) {
|
|
554
|
+
throw new Error('auto-merge requires OMC_RUNTIME_V2=1 (this feature is v2-only).');
|
|
555
|
+
}
|
|
556
|
+
autoMergeLeaderBranch = resolveLeaderBranch(leaderCwd);
|
|
557
|
+
const stripped = autoMergeLeaderBranch.replace(/^refs\/heads\//i, '').toLowerCase();
|
|
558
|
+
if (stripped === 'main' || stripped === 'master') {
|
|
559
|
+
throw new Error('auto-merge refuses main/master leader branch — use a feature branch');
|
|
560
|
+
}
|
|
561
|
+
if (worktreeMode !== 'named') {
|
|
562
|
+
// Force named-branch worktree mode so workers get a real branch.
|
|
563
|
+
worktreeMode = 'named';
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
const workspaceMode = worktreeMode === 'disabled' ? 'single' : 'worktree';
|
|
433
567
|
// Validate CLIs and pin absolute binary paths for user-declared agentTypes.
|
|
434
568
|
// AC-8: missing/untrusted binaries fall back to the snapshot's Claude tuple at
|
|
435
569
|
// spawn time; emit a loud warning naming the binary so operators can fix it.
|
|
@@ -507,6 +641,23 @@ export async function startTeamV2(config) {
|
|
|
507
641
|
}
|
|
508
642
|
// Build allocation inputs for the new role-aware allocator
|
|
509
643
|
const workerNames = Array.from({ length: config.workerCount }, (_, index) => `worker-${index + 1}`);
|
|
644
|
+
const workerWorktrees = new Map();
|
|
645
|
+
try {
|
|
646
|
+
if (worktreeMode !== 'disabled') {
|
|
647
|
+
for (const workerName of workerNames) {
|
|
648
|
+
const worktree = ensureWorkerWorktree(sanitized, workerName, leaderCwd, {
|
|
649
|
+
mode: worktreeMode,
|
|
650
|
+
requireCleanLeader: true,
|
|
651
|
+
});
|
|
652
|
+
if (worktree)
|
|
653
|
+
workerWorktrees.set(workerName, worktree);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
catch (error) {
|
|
658
|
+
await rollbackUnpersistedNativeWorktreeStartup(sanitized, leaderCwd, error);
|
|
659
|
+
throw error;
|
|
660
|
+
}
|
|
510
661
|
const workerNameSet = new Set(workerNames);
|
|
511
662
|
// Respect explicit owner fields first, then allocate remaining tasks
|
|
512
663
|
const startupAllocations = [];
|
|
@@ -529,7 +680,7 @@ export async function startTeamV2(config) {
|
|
|
529
680
|
const allocationWorkers = workerNames.map((name, i) => ({
|
|
530
681
|
name,
|
|
531
682
|
role: config.workerRoles?.[i]
|
|
532
|
-
?? (agentTypes[i % agentTypes.length] ?? agentTypes[0] ??
|
|
683
|
+
?? (agentTypes[i % agentTypes.length] ?? agentTypes[0] ?? 'claude'),
|
|
533
684
|
currentLoad: 0,
|
|
534
685
|
}));
|
|
535
686
|
for (const r of allocateTasksToWorkers(allocationTasks, allocationWorkers)) {
|
|
@@ -537,41 +688,71 @@ export async function startTeamV2(config) {
|
|
|
537
688
|
}
|
|
538
689
|
}
|
|
539
690
|
// Set up worker state dirs and overlays (with v2 CLI API instructions)
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
691
|
+
try {
|
|
692
|
+
for (let i = 0; i < workerNames.length; i++) {
|
|
693
|
+
const wName = workerNames[i];
|
|
694
|
+
const agentType = (agentTypes[i % agentTypes.length] ?? agentTypes[0] ?? 'claude');
|
|
695
|
+
await ensureWorkerStateDir(sanitized, wName, leaderCwd);
|
|
696
|
+
const overlayPath = await writeWorkerOverlay({
|
|
697
|
+
teamName: sanitized, workerName: wName, agentType,
|
|
698
|
+
tasks: config.tasks.map((t, idx) => ({
|
|
699
|
+
id: String(idx + 1), subject: t.subject, description: t.description,
|
|
700
|
+
})),
|
|
701
|
+
cwd: leaderCwd,
|
|
702
|
+
...(config.rolePrompt ? { bootstrapInstructions: config.rolePrompt } : {}),
|
|
703
|
+
...(workerWorktrees.has(wName) ? { instructionStateRoot: '$OMC_TEAM_STATE_ROOT' } : {}),
|
|
704
|
+
});
|
|
705
|
+
const worktree = workerWorktrees.get(wName);
|
|
706
|
+
if (worktree) {
|
|
707
|
+
const overlayContent = await readFile(overlayPath, 'utf-8');
|
|
708
|
+
installWorktreeRootAgents(sanitized, wName, leaderCwd, worktree.path, overlayContent);
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
catch (error) {
|
|
713
|
+
await rollbackUnpersistedNativeWorktreeStartup(sanitized, leaderCwd, error);
|
|
714
|
+
throw error;
|
|
552
715
|
}
|
|
553
716
|
// Create tmux session (leader only — workers spawned below)
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
717
|
+
let session;
|
|
718
|
+
try {
|
|
719
|
+
session = await createTeamSession(sanitized, 0, leaderCwd, {
|
|
720
|
+
newWindow: Boolean(config.newWindow),
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
catch (error) {
|
|
724
|
+
await rollbackUnpersistedNativeWorktreeStartup(sanitized, leaderCwd, error);
|
|
725
|
+
throw error;
|
|
726
|
+
}
|
|
557
727
|
const sessionName = session.sessionName;
|
|
558
728
|
const leaderPaneId = session.leaderPaneId;
|
|
559
729
|
const ownsWindow = session.sessionMode !== 'split-pane';
|
|
560
730
|
const workerPaneIds = [];
|
|
561
731
|
// Build workers info for config
|
|
562
|
-
const workersInfo = workerNames.map((wName, i) =>
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
732
|
+
const workersInfo = workerNames.map((wName, i) => {
|
|
733
|
+
const worktree = workerWorktrees.get(wName);
|
|
734
|
+
return {
|
|
735
|
+
name: wName,
|
|
736
|
+
index: i + 1,
|
|
737
|
+
role: config.workerRoles?.[i]
|
|
738
|
+
?? (agentTypes[i % agentTypes.length] ?? agentTypes[0] ?? 'claude'),
|
|
739
|
+
assigned_tasks: [],
|
|
740
|
+
working_dir: worktree?.path ?? leaderCwd,
|
|
741
|
+
team_state_root: teamStateRoot(leaderCwd, sanitized),
|
|
742
|
+
...(worktree ? {
|
|
743
|
+
worktree_repo_root: leaderCwd,
|
|
744
|
+
worktree_path: worktree.path,
|
|
745
|
+
worktree_branch: worktree.branch,
|
|
746
|
+
worktree_detached: worktree.detached,
|
|
747
|
+
worktree_created: worktree.created,
|
|
748
|
+
} : {}),
|
|
749
|
+
};
|
|
750
|
+
});
|
|
570
751
|
// Write initial v2 config
|
|
571
752
|
const teamConfig = {
|
|
572
753
|
name: sanitized,
|
|
573
754
|
task: config.tasks.map(t => t.subject).join('; '),
|
|
574
|
-
agent_type: agentTypes[0] ||
|
|
755
|
+
agent_type: agentTypes[0] || 'claude',
|
|
575
756
|
worker_launch_mode: 'interactive',
|
|
576
757
|
policy: DEFAULT_TEAM_TRANSPORT_POLICY,
|
|
577
758
|
governance: DEFAULT_TEAM_GOVERNANCE,
|
|
@@ -589,9 +770,24 @@ export async function startTeamV2(config) {
|
|
|
589
770
|
resize_hook_name: null,
|
|
590
771
|
resize_hook_target: null,
|
|
591
772
|
resolved_routing: resolvedRouting,
|
|
592
|
-
|
|
773
|
+
workspace_mode: workspaceMode,
|
|
774
|
+
worktree_mode: worktreeMode,
|
|
593
775
|
};
|
|
594
|
-
|
|
776
|
+
try {
|
|
777
|
+
await saveTeamConfig(teamConfig, leaderCwd);
|
|
778
|
+
}
|
|
779
|
+
catch (error) {
|
|
780
|
+
await rollbackStartedNativeWorktreeStartup({
|
|
781
|
+
teamName: sanitized,
|
|
782
|
+
cwd: leaderCwd,
|
|
783
|
+
cause: error,
|
|
784
|
+
sessionName,
|
|
785
|
+
leaderPaneId,
|
|
786
|
+
workerPaneIds,
|
|
787
|
+
sessionMode: session.sessionMode,
|
|
788
|
+
});
|
|
789
|
+
throw error;
|
|
790
|
+
}
|
|
595
791
|
const permissionsSnapshot = {
|
|
596
792
|
approval_mode: process.env.OMC_APPROVAL_MODE || 'default',
|
|
597
793
|
sandbox_mode: process.env.OMC_SANDBOX_MODE || 'default',
|
|
@@ -606,7 +802,7 @@ export async function startTeamV2(config) {
|
|
|
606
802
|
worker_id: 'leader-fixed',
|
|
607
803
|
role: 'leader',
|
|
608
804
|
},
|
|
609
|
-
policy:
|
|
805
|
+
policy: DEFAULT_TEAM_TRANSPORT_POLICY,
|
|
610
806
|
governance: DEFAULT_TEAM_GOVERNANCE,
|
|
611
807
|
permissions_snapshot: permissionsSnapshot,
|
|
612
808
|
tmux_session: sessionName,
|
|
@@ -617,13 +813,28 @@ export async function startTeamV2(config) {
|
|
|
617
813
|
leader_cwd: leaderCwd,
|
|
618
814
|
team_state_root: teamConfig.team_state_root,
|
|
619
815
|
workspace_mode: teamConfig.workspace_mode,
|
|
816
|
+
worktree_mode: teamConfig.worktree_mode,
|
|
620
817
|
leader_pane_id: leaderPaneId,
|
|
621
818
|
hud_pane_id: null,
|
|
622
819
|
resize_hook_name: null,
|
|
623
820
|
resize_hook_target: null,
|
|
624
821
|
next_worker_index: teamConfig.next_worker_index,
|
|
625
822
|
};
|
|
626
|
-
|
|
823
|
+
try {
|
|
824
|
+
await writeFile(absPath(leaderCwd, TeamPaths.manifest(sanitized)), JSON.stringify(teamManifest, null, 2), 'utf-8');
|
|
825
|
+
}
|
|
826
|
+
catch (error) {
|
|
827
|
+
await rollbackStartedNativeWorktreeStartup({
|
|
828
|
+
teamName: sanitized,
|
|
829
|
+
cwd: leaderCwd,
|
|
830
|
+
cause: error,
|
|
831
|
+
sessionName,
|
|
832
|
+
leaderPaneId,
|
|
833
|
+
workerPaneIds,
|
|
834
|
+
sessionMode: session.sessionMode,
|
|
835
|
+
});
|
|
836
|
+
throw error;
|
|
837
|
+
}
|
|
627
838
|
// Spawn workers for initial tasks (at most one startup task per worker)
|
|
628
839
|
const initialStartupAllocations = [];
|
|
629
840
|
const seenStartupWorkers = new Set();
|
|
@@ -635,62 +846,146 @@ export async function startTeamV2(config) {
|
|
|
635
846
|
if (initialStartupAllocations.length >= config.workerCount)
|
|
636
847
|
break;
|
|
637
848
|
}
|
|
638
|
-
|
|
639
|
-
const
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
workerInfo
|
|
672
|
-
if (
|
|
673
|
-
workerInfo.
|
|
849
|
+
try {
|
|
850
|
+
for (const decision of initialStartupAllocations) {
|
|
851
|
+
const wName = decision.workerName;
|
|
852
|
+
const workerIndex = Number.parseInt(wName.replace('worker-', ''), 10) - 1;
|
|
853
|
+
const taskId = String(decision.taskIndex + 1);
|
|
854
|
+
const task = config.tasks[decision.taskIndex];
|
|
855
|
+
if (!task || workerIndex < 0)
|
|
856
|
+
continue;
|
|
857
|
+
// Route the task through the team's immutable snapshot (Option E).
|
|
858
|
+
// Falls back to the round-robin agentType when the inferred role is
|
|
859
|
+
// outside the canonical vocabulary (preserves pre-patch behavior).
|
|
860
|
+
const fallbackAgent = (agentTypes[workerIndex % agentTypes.length] ?? agentTypes[0] ?? 'claude');
|
|
861
|
+
const assignment = resolveTaskAssignment(task, resolvedRouting, pluginCfg.team?.roleRouting, resolvedBinaryPaths, fallbackAgent);
|
|
862
|
+
const workerLaunch = await spawnV2Worker({
|
|
863
|
+
sessionName,
|
|
864
|
+
leaderPaneId,
|
|
865
|
+
existingWorkerPaneIds: workerPaneIds,
|
|
866
|
+
teamName: sanitized,
|
|
867
|
+
workerName: wName,
|
|
868
|
+
workerIndex,
|
|
869
|
+
agentType: assignment.agentType,
|
|
870
|
+
task,
|
|
871
|
+
taskId,
|
|
872
|
+
cwd: leaderCwd,
|
|
873
|
+
workerCwd: workersInfo[workerIndex]?.working_dir ?? leaderCwd,
|
|
874
|
+
worktreePath: workersInfo[workerIndex]?.worktree_path,
|
|
875
|
+
autoMerge: Boolean(config.autoMerge),
|
|
876
|
+
resolvedBinaryPaths,
|
|
877
|
+
...(assignment.model ? { model: assignment.model } : {}),
|
|
878
|
+
...(assignment.role ? { role: assignment.role } : {}),
|
|
879
|
+
});
|
|
880
|
+
if (workerLaunch.paneId) {
|
|
881
|
+
workerPaneIds.push(workerLaunch.paneId);
|
|
882
|
+
const workerInfo = workersInfo[workerIndex];
|
|
883
|
+
if (workerInfo) {
|
|
884
|
+
workerInfo.pane_id = workerLaunch.paneId;
|
|
885
|
+
workerInfo.assigned_tasks = workerLaunch.startupAssigned ? [taskId] : [];
|
|
886
|
+
workerInfo.worker_cli = assignment.agentType;
|
|
887
|
+
if (workerLaunch.outputFile) {
|
|
888
|
+
workerInfo.output_file = workerLaunch.outputFile;
|
|
889
|
+
}
|
|
674
890
|
}
|
|
675
891
|
}
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
892
|
+
if (workerLaunch.startupFailureReason) {
|
|
893
|
+
const logEventFailure = createSwallowedErrorLogger('team.runtime-v2.startTeamV2 appendTeamEvent failed');
|
|
894
|
+
appendTeamEvent(sanitized, {
|
|
895
|
+
type: 'team_leader_nudge',
|
|
896
|
+
worker: 'leader-fixed',
|
|
897
|
+
reason: `startup_manual_intervention_required:${wName}:${workerLaunch.startupFailureReason}`,
|
|
898
|
+
}, leaderCwd).catch(logEventFailure);
|
|
899
|
+
}
|
|
683
900
|
}
|
|
684
901
|
}
|
|
902
|
+
catch (error) {
|
|
903
|
+
await rollbackStartedNativeWorktreeStartup({
|
|
904
|
+
teamName: sanitized,
|
|
905
|
+
cwd: leaderCwd,
|
|
906
|
+
cause: error,
|
|
907
|
+
sessionName,
|
|
908
|
+
leaderPaneId,
|
|
909
|
+
workerPaneIds,
|
|
910
|
+
sessionMode: session.sessionMode,
|
|
911
|
+
});
|
|
912
|
+
throw error;
|
|
913
|
+
}
|
|
685
914
|
// Persist config with pane IDs
|
|
686
915
|
teamConfig.workers = workersInfo;
|
|
687
|
-
|
|
916
|
+
try {
|
|
917
|
+
await saveTeamConfig(teamConfig, leaderCwd);
|
|
918
|
+
}
|
|
919
|
+
catch (error) {
|
|
920
|
+
await rollbackStartedNativeWorktreeStartup({
|
|
921
|
+
teamName: sanitized,
|
|
922
|
+
cwd: leaderCwd,
|
|
923
|
+
cause: error,
|
|
924
|
+
sessionName,
|
|
925
|
+
leaderPaneId,
|
|
926
|
+
workerPaneIds,
|
|
927
|
+
sessionMode: session.sessionMode,
|
|
928
|
+
});
|
|
929
|
+
throw error;
|
|
930
|
+
}
|
|
931
|
+
const logEventFailure = createSwallowedErrorLogger('team.runtime-v2.startTeamV2 appendTeamEvent failed');
|
|
688
932
|
// Emit start event — NO watchdog, leader drives via monitorTeamV2()
|
|
689
|
-
|
|
933
|
+
appendTeamEvent(sanitized, {
|
|
690
934
|
type: 'team_leader_nudge',
|
|
691
935
|
worker: 'leader-fixed',
|
|
692
936
|
reason: `start_team_v2: workers=${config.workerCount} tasks=${config.tasks.length} panes=${workerPaneIds.length}`,
|
|
693
|
-
}, leaderCwd);
|
|
937
|
+
}, leaderCwd).catch(logEventFailure);
|
|
938
|
+
// Auto-merge orchestrator startup. Because --auto-merge is an explicit
|
|
939
|
+
// safety opt-in, startup/registration failures are fatal: continuing would
|
|
940
|
+
// leave users believing worker edits are being merged when they are not.
|
|
941
|
+
if (config.autoMerge && autoMergeLeaderBranch) {
|
|
942
|
+
try {
|
|
943
|
+
await ensureLeaderInbox(sanitized, leaderCwd);
|
|
944
|
+
// Seed an introductory leader-inbox note so the leader knows the inbox
|
|
945
|
+
// exists and where to read it. This mirrors the worker bootstrap pattern.
|
|
946
|
+
await appendToLeaderInbox(sanitized, extendLeaderBootstrapPrompt(sanitized), leaderCwd);
|
|
947
|
+
// M6: try to recover from a previous run before starting fresh.
|
|
948
|
+
try {
|
|
949
|
+
await recoverFromRestart({
|
|
950
|
+
teamName: sanitized,
|
|
951
|
+
repoRoot: leaderCwd,
|
|
952
|
+
leaderBranch: autoMergeLeaderBranch,
|
|
953
|
+
cwd: leaderCwd,
|
|
954
|
+
});
|
|
955
|
+
}
|
|
956
|
+
catch (recErr) {
|
|
957
|
+
process.stderr.write(`[team/runtime-v2] auto-merge recover-from-restart failed: ${recErr}\n`);
|
|
958
|
+
}
|
|
959
|
+
const orchestrator = await startMergeOrchestrator({
|
|
960
|
+
teamName: sanitized,
|
|
961
|
+
repoRoot: leaderCwd,
|
|
962
|
+
leaderBranch: autoMergeLeaderBranch,
|
|
963
|
+
cwd: leaderCwd,
|
|
964
|
+
});
|
|
965
|
+
registerTeamOrchestrator(sanitized, orchestrator);
|
|
966
|
+
// Register every spawned worker (named worktree mode is enforced above
|
|
967
|
+
// when autoMerge is on, so worker branches exist). A single failed
|
|
968
|
+
// registration makes the auto-merge contract unsafe, so fail loudly.
|
|
969
|
+
for (const w of workersInfo) {
|
|
970
|
+
await orchestrator.registerWorker(w.name);
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
catch (orchErr) {
|
|
974
|
+
await stopTeamCadence(sanitized);
|
|
975
|
+
unregisterTeamOrchestrator(sanitized);
|
|
976
|
+
await rollbackStartedNativeWorktreeStartup({
|
|
977
|
+
teamName: sanitized,
|
|
978
|
+
cwd: leaderCwd,
|
|
979
|
+
cause: orchErr,
|
|
980
|
+
sessionName,
|
|
981
|
+
leaderPaneId,
|
|
982
|
+
workerPaneIds,
|
|
983
|
+
sessionMode: session.sessionMode,
|
|
984
|
+
});
|
|
985
|
+
const reason = orchErr instanceof Error ? orchErr.message : String(orchErr);
|
|
986
|
+
throw new Error(`auto-merge startup failed: ${reason}`);
|
|
987
|
+
}
|
|
988
|
+
}
|
|
694
989
|
return {
|
|
695
990
|
teamName: sanitized,
|
|
696
991
|
sanitizedName: sanitized,
|
|
@@ -837,8 +1132,8 @@ export async function processCliWorkerVerdicts(teamName, cwd) {
|
|
|
837
1132
|
const outputFile = worker.output_file;
|
|
838
1133
|
if (!outputFile)
|
|
839
1134
|
continue;
|
|
840
|
-
const
|
|
841
|
-
if (
|
|
1135
|
+
const liveness = await getWorkerPaneLiveness(worker.pane_id);
|
|
1136
|
+
if (liveness !== 'dead')
|
|
842
1137
|
continue;
|
|
843
1138
|
if (!fsExistsSync(outputFile)) {
|
|
844
1139
|
results.push({ workerName: worker.name, taskId: null, status: 'file_missing' });
|
|
@@ -1002,16 +1297,17 @@ export async function monitorTeamV2(teamName, cwd) {
|
|
|
1002
1297
|
const recommendations = [];
|
|
1003
1298
|
const workerScanStartMs = performance.now();
|
|
1004
1299
|
const workerSignals = await Promise.all(config.workers.map(async (worker) => {
|
|
1005
|
-
const
|
|
1300
|
+
const liveness = await getWorkerPaneLiveness(worker.pane_id);
|
|
1301
|
+
const alive = liveness === 'alive';
|
|
1006
1302
|
const [status, heartbeat, paneCapture] = await Promise.all([
|
|
1007
1303
|
readWorkerStatus(sanitized, worker.name, cwd),
|
|
1008
1304
|
readWorkerHeartbeat(sanitized, worker.name, cwd),
|
|
1009
1305
|
alive ? captureWorkerPane(worker.pane_id) : Promise.resolve(''),
|
|
1010
1306
|
]);
|
|
1011
|
-
return { worker, alive, status, heartbeat, paneCapture };
|
|
1307
|
+
return { worker, alive, liveness, status, heartbeat, paneCapture };
|
|
1012
1308
|
}));
|
|
1013
1309
|
const workerScanMs = performance.now() - workerScanStartMs;
|
|
1014
|
-
for (const { worker: w, alive, status, heartbeat, paneCapture } of workerSignals) {
|
|
1310
|
+
for (const { worker: w, alive, liveness, status, heartbeat, paneCapture } of workerSignals) {
|
|
1015
1311
|
const currentTask = status.current_task_id ? taskById.get(status.current_task_id) ?? null : null;
|
|
1016
1312
|
const outstandingTask = currentTask ?? findOutstandingWorkerTask(w, taskById, inProgressByOwner);
|
|
1017
1313
|
const expectedTaskId = status.current_task_id ?? outstandingTask?.id ?? w.assigned_tasks[0] ?? '';
|
|
@@ -1030,12 +1326,20 @@ export async function monitorTeamV2(teamName, cwd) {
|
|
|
1030
1326
|
workers.push({
|
|
1031
1327
|
name: w.name,
|
|
1032
1328
|
alive,
|
|
1329
|
+
liveness,
|
|
1033
1330
|
status,
|
|
1034
1331
|
heartbeat,
|
|
1035
1332
|
assignedTasks: w.assigned_tasks,
|
|
1333
|
+
working_dir: w.working_dir,
|
|
1334
|
+
worktree_repo_root: w.worktree_repo_root,
|
|
1335
|
+
worktree_path: w.worktree_path,
|
|
1336
|
+
worktree_branch: w.worktree_branch,
|
|
1337
|
+
worktree_detached: w.worktree_detached,
|
|
1338
|
+
worktree_created: w.worktree_created,
|
|
1339
|
+
team_state_root: w.team_state_root,
|
|
1036
1340
|
turnsWithoutProgress,
|
|
1037
1341
|
});
|
|
1038
|
-
if (
|
|
1342
|
+
if (liveness === 'dead') {
|
|
1039
1343
|
deadWorkers.push(w.name);
|
|
1040
1344
|
const deadWorkerTasks = inProgressByOwner.get(w.name) || [];
|
|
1041
1345
|
for (const t of deadWorkerTasks) {
|
|
@@ -1046,8 +1350,14 @@ export async function monitorTeamV2(teamName, cwd) {
|
|
|
1046
1350
|
const statusFresh = isFreshTimestamp(status.updated_at);
|
|
1047
1351
|
const heartbeatFresh = isFreshTimestamp(heartbeat?.last_turn_at);
|
|
1048
1352
|
const hasWorkStartEvidence = expectedTaskId !== '' && hasWorkerStatusProgress(status, expectedTaskId);
|
|
1353
|
+
const missingDependencyIds = outstandingTask
|
|
1354
|
+
? getMissingDependencyIds(outstandingTask, taskById)
|
|
1355
|
+
: [];
|
|
1049
1356
|
let stallReason = null;
|
|
1050
|
-
if (paneSuggestsIdle &&
|
|
1357
|
+
if (paneSuggestsIdle && missingDependencyIds.length > 0) {
|
|
1358
|
+
stallReason = 'missing_dependency';
|
|
1359
|
+
}
|
|
1360
|
+
else if (paneSuggestsIdle && expectedTaskId !== '' && !hasWorkStartEvidence) {
|
|
1051
1361
|
stallReason = 'no_work_start_evidence';
|
|
1052
1362
|
}
|
|
1053
1363
|
else if (paneSuggestsIdle && expectedTaskId !== '' && (!statusFresh || !heartbeatFresh)) {
|
|
@@ -1058,7 +1368,10 @@ export async function monitorTeamV2(teamName, cwd) {
|
|
|
1058
1368
|
}
|
|
1059
1369
|
if (stallReason) {
|
|
1060
1370
|
nonReportingWorkers.push(w.name);
|
|
1061
|
-
if (stallReason === '
|
|
1371
|
+
if (stallReason === 'missing_dependency') {
|
|
1372
|
+
recommendations.push(`Investigate ${w.name}: task-${outstandingTask?.id ?? expectedTaskId} is blocked by missing task ids [${missingDependencyIds.join(', ')}]; pane is idle at prompt`);
|
|
1373
|
+
}
|
|
1374
|
+
else if (stallReason === 'no_work_start_evidence') {
|
|
1062
1375
|
recommendations.push(`Investigate ${w.name}: assigned work but no work-start evidence; pane is idle at prompt`);
|
|
1063
1376
|
}
|
|
1064
1377
|
else if (stallReason === 'stale_or_missing_worker_reports') {
|
|
@@ -1079,19 +1392,27 @@ export async function monitorTeamV2(teamName, cwd) {
|
|
|
1079
1392
|
failed: allTasks.filter((t) => t.status === 'failed').length,
|
|
1080
1393
|
};
|
|
1081
1394
|
const allTasksTerminal = taskCounts.pending === 0 && taskCounts.blocked === 0 && taskCounts.in_progress === 0;
|
|
1395
|
+
for (const task of allTasks) {
|
|
1396
|
+
const missingDependencyIds = getMissingDependencyIds(task, taskById);
|
|
1397
|
+
if (missingDependencyIds.length === 0) {
|
|
1398
|
+
continue;
|
|
1399
|
+
}
|
|
1400
|
+
recommendations.push(`Investigate task-${task.id}: depends on missing task ids [${missingDependencyIds.join(', ')}]`);
|
|
1401
|
+
}
|
|
1082
1402
|
// Infer phase from task distribution
|
|
1083
1403
|
const phase = inferPhase(allTasks.map((t) => ({
|
|
1084
1404
|
status: t.status,
|
|
1085
1405
|
metadata: undefined,
|
|
1086
1406
|
})));
|
|
1087
1407
|
// Emit monitor-derived events (task completions, worker state changes)
|
|
1088
|
-
await emitMonitorDerivedEvents(sanitized, allTasks, workers.map((w) => ({ name: w.name, alive: w.alive, status: w.status })), previousSnapshot, cwd);
|
|
1408
|
+
await emitMonitorDerivedEvents(sanitized, allTasks, workers.map((w) => ({ name: w.name, alive: w.alive, liveness: w.liveness, status: w.status })), previousSnapshot, cwd);
|
|
1089
1409
|
// Persist snapshot for next cycle
|
|
1090
1410
|
const updatedAt = new Date().toISOString();
|
|
1091
1411
|
const totalMs = performance.now() - monitorStartMs;
|
|
1092
1412
|
await writeMonitorSnapshot(sanitized, {
|
|
1093
1413
|
taskStatusById: Object.fromEntries(allTasks.map((t) => [t.id, t.status])),
|
|
1094
1414
|
workerAliveByName: Object.fromEntries(workers.map((w) => [w.name, w.alive])),
|
|
1415
|
+
workerLivenessByName: Object.fromEntries(workers.map((w) => [w.name, w.liveness])),
|
|
1095
1416
|
workerStateByName: Object.fromEntries(workers.map((w) => [w.name, w.status.state])),
|
|
1096
1417
|
workerTurnCountByName: Object.fromEntries(workers.map((w) => [w.name, w.heartbeat?.turn_count ?? 0])),
|
|
1097
1418
|
workerTaskIdByName: Object.fromEntries(workers.map((w) => [w.name, w.status.current_task_id ?? ''])),
|
|
@@ -1143,9 +1464,48 @@ export async function shutdownTeamV2(teamName, cwd, options = {}) {
|
|
|
1143
1464
|
const timeoutMs = options.timeoutMs ?? 15_000;
|
|
1144
1465
|
const sanitized = sanitizeTeamName(teamName);
|
|
1145
1466
|
const config = await readTeamConfig(sanitized, cwd);
|
|
1467
|
+
const finalizeAutoMerge = async () => {
|
|
1468
|
+
const orchestrator = getTeamOrchestrator(sanitized);
|
|
1469
|
+
if (orchestrator) {
|
|
1470
|
+
try {
|
|
1471
|
+
const drainResult = await orchestrator.drainAndStop();
|
|
1472
|
+
if (drainResult.unmerged.length > 0) {
|
|
1473
|
+
await appendTeamEvent(sanitized, {
|
|
1474
|
+
type: 'team_leader_nudge',
|
|
1475
|
+
worker: 'leader-fixed',
|
|
1476
|
+
reason: `auto_merge_drain_unmerged:${drainResult.unmerged.map((u) => `${u.workerName}:${u.reason}`).join(',')}`,
|
|
1477
|
+
}, cwd).catch(logEventFailure);
|
|
1478
|
+
}
|
|
1479
|
+
for (const w of config?.workers ?? []) {
|
|
1480
|
+
try {
|
|
1481
|
+
await orchestrator.unregisterWorker(w.name);
|
|
1482
|
+
}
|
|
1483
|
+
catch (err) {
|
|
1484
|
+
process.stderr.write(`[team/runtime-v2] orchestrator.unregisterWorker(${w.name}) failed: ${err}\n`);
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
catch (err) {
|
|
1489
|
+
process.stderr.write(`[team/runtime-v2] orchestrator drainAndStop: ${err}\n`);
|
|
1490
|
+
}
|
|
1491
|
+
finally {
|
|
1492
|
+
await stopTeamCadence(sanitized);
|
|
1493
|
+
unregisterTeamOrchestrator(sanitized);
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
else {
|
|
1497
|
+
await stopTeamCadence(sanitized);
|
|
1498
|
+
}
|
|
1499
|
+
};
|
|
1146
1500
|
if (!config) {
|
|
1147
|
-
// No config
|
|
1148
|
-
//
|
|
1501
|
+
// No config means worker liveness cannot be proven. Worktree metadata and
|
|
1502
|
+
// root AGENTS backups live under the scoped state tree, so use non-mutating
|
|
1503
|
+
// inspection and preserve state whenever any worktree recovery evidence exists.
|
|
1504
|
+
const cleanupSafety = inspectTeamWorktreeCleanupSafety(sanitized, cwd);
|
|
1505
|
+
if (cleanupSafety.hasEvidence) {
|
|
1506
|
+
process.stderr.write('[team/runtime-v2] preserving team state because config is missing and worktree cleanup evidence remains\n');
|
|
1507
|
+
return;
|
|
1508
|
+
}
|
|
1149
1509
|
await cleanupTeamState(sanitized, cwd);
|
|
1150
1510
|
return;
|
|
1151
1511
|
}
|
|
@@ -1205,7 +1565,10 @@ export async function shutdownTeamV2(teamName, cwd, options = {}) {
|
|
|
1205
1565
|
await writeShutdownRequest(sanitized, w.name, 'leader-fixed', cwd);
|
|
1206
1566
|
shutdownRequestTimes.set(w.name, requestedAt);
|
|
1207
1567
|
// Write shutdown inbox
|
|
1208
|
-
const
|
|
1568
|
+
const shutdownAckPath = w.worktree_path
|
|
1569
|
+
? `$OMC_TEAM_STATE_ROOT/workers/${w.name}/shutdown-ack.json`
|
|
1570
|
+
: TeamPaths.shutdownAck(sanitized, w.name);
|
|
1571
|
+
const shutdownInbox = `# Shutdown Request\n\nAll tasks are complete. Please wrap up and respond with a shutdown acknowledgement.\n\nWrite your ack to: ${shutdownAckPath}\nFormat: {"status":"accept","reason":"ok","updated_at":"<iso>"}\n\nThen exit your session.\n`;
|
|
1209
1572
|
await writeWorkerInbox(sanitized, w.name, shutdownInbox, cwd);
|
|
1210
1573
|
}
|
|
1211
1574
|
catch (err) {
|
|
@@ -1244,11 +1607,11 @@ export async function shutdownTeamV2(teamName, cwd, options = {}) {
|
|
|
1244
1607
|
await new Promise((r) => setTimeout(r, 2_000));
|
|
1245
1608
|
}
|
|
1246
1609
|
// 4. Force kill remaining tmux panes
|
|
1610
|
+
const recordedWorkerPaneIds = config.workers
|
|
1611
|
+
.map((w) => w.pane_id)
|
|
1612
|
+
.filter((p) => typeof p === 'string' && p.trim().length > 0);
|
|
1247
1613
|
try {
|
|
1248
|
-
const { killWorkerPanes, killTeamSession, resolveSplitPaneWorkerPaneIds } = await import('./tmux-session.js');
|
|
1249
|
-
const recordedWorkerPaneIds = config.workers
|
|
1250
|
-
.map((w) => w.pane_id)
|
|
1251
|
-
.filter((p) => typeof p === 'string' && p.trim().length > 0);
|
|
1614
|
+
const { killWorkerPanes, killTeamSession, resolveSplitPaneWorkerPaneIds, getWorkerLiveness } = await import('./tmux-session.js');
|
|
1252
1615
|
const ownsWindow = config.tmux_window_owned === true;
|
|
1253
1616
|
const workerPaneIds = ownsWindow
|
|
1254
1617
|
? recordedWorkerPaneIds
|
|
@@ -1265,9 +1628,36 @@ export async function shutdownTeamV2(teamName, cwd, options = {}) {
|
|
|
1265
1628
|
: 'detached-session';
|
|
1266
1629
|
await killTeamSession(config.tmux_session, workerPaneIds, config.leader_pane_id ?? undefined, { sessionMode });
|
|
1267
1630
|
}
|
|
1631
|
+
const paneById = new Map(config.workers
|
|
1632
|
+
.filter((w) => typeof w.pane_id === 'string' && w.pane_id.trim().length > 0)
|
|
1633
|
+
.map((w) => [w.pane_id, w.name]));
|
|
1634
|
+
const liveness = await Promise.all(workerPaneIds.map(async (paneId) => [paneId, await getWorkerLiveness(paneId)]));
|
|
1635
|
+
const aliveWorkers = liveness
|
|
1636
|
+
.filter(([, state]) => state === 'alive')
|
|
1637
|
+
.map(([paneId]) => paneById.get(paneId) ?? paneId);
|
|
1638
|
+
if (aliveWorkers.length > 0) {
|
|
1639
|
+
process.stderr.write(`[team/runtime-v2] preserving worktrees/state because worker pane(s) are still alive: ${aliveWorkers.join(', ')}
|
|
1640
|
+
`);
|
|
1641
|
+
await finalizeAutoMerge();
|
|
1642
|
+
return;
|
|
1643
|
+
}
|
|
1644
|
+
const unknownWorkers = liveness
|
|
1645
|
+
.filter(([, state]) => state === 'unknown')
|
|
1646
|
+
.map(([paneId]) => paneById.get(paneId) ?? paneId);
|
|
1647
|
+
if (unknownWorkers.length > 0) {
|
|
1648
|
+
process.stderr.write(`[team/runtime-v2] preserving worktrees/state because worker pane liveness is unknown: ${unknownWorkers.join(', ')}
|
|
1649
|
+
`);
|
|
1650
|
+
await finalizeAutoMerge();
|
|
1651
|
+
return;
|
|
1652
|
+
}
|
|
1268
1653
|
}
|
|
1269
1654
|
catch (err) {
|
|
1270
1655
|
process.stderr.write(`[team/runtime-v2] tmux cleanup: ${err}\n`);
|
|
1656
|
+
if (recordedWorkerPaneIds.length > 0) {
|
|
1657
|
+
process.stderr.write('[team/runtime-v2] preserving worktrees/state because tmux cleanup did not prove worker panes exited\n');
|
|
1658
|
+
await finalizeAutoMerge();
|
|
1659
|
+
return;
|
|
1660
|
+
}
|
|
1271
1661
|
}
|
|
1272
1662
|
// 5. Ralph completion logging
|
|
1273
1663
|
if (ralph) {
|
|
@@ -1281,14 +1671,28 @@ export async function shutdownTeamV2(teamName, cwd, options = {}) {
|
|
|
1281
1671
|
reason: `ralph_cleanup_summary: total=${finalTasks.length} completed=${completed} failed=${failed} pending=${pending} force=${force}`,
|
|
1282
1672
|
}, cwd).catch(logEventFailure);
|
|
1283
1673
|
}
|
|
1284
|
-
//
|
|
1674
|
+
// 6a. Drain the merge orchestrator (if attached). Final merge sweep before
|
|
1675
|
+
// cleanupTeamWorktrees touches per-worker worktrees. Also used by preserve-state
|
|
1676
|
+
// exits above so auto-merge shutdown is not skipped when pane liveness is unknown.
|
|
1677
|
+
await finalizeAutoMerge();
|
|
1678
|
+
// 6. Clean up state. If worktree cleanup preserved dirty worktrees, keep the
|
|
1679
|
+
// team state directory too; it contains the metadata and root AGENTS.md backups
|
|
1680
|
+
// needed for a later safe cleanup attempt.
|
|
1681
|
+
let preservedWorktrees = 0;
|
|
1285
1682
|
try {
|
|
1286
|
-
cleanupTeamWorktrees(sanitized, cwd);
|
|
1683
|
+
const worktreeCleanup = cleanupTeamWorktrees(sanitized, cwd);
|
|
1684
|
+
preservedWorktrees = worktreeCleanup.preserved.length;
|
|
1287
1685
|
}
|
|
1288
1686
|
catch (err) {
|
|
1687
|
+
preservedWorktrees = 1;
|
|
1289
1688
|
process.stderr.write(`[team/runtime-v2] worktree cleanup: ${err}\n`);
|
|
1290
1689
|
}
|
|
1291
|
-
|
|
1690
|
+
if (preservedWorktrees === 0) {
|
|
1691
|
+
await cleanupTeamState(sanitized, cwd);
|
|
1692
|
+
}
|
|
1693
|
+
else {
|
|
1694
|
+
process.stderr.write(`[team/runtime-v2] preserved ${preservedWorktrees} worktree(s); keeping team state for follow-up cleanup\n`);
|
|
1695
|
+
}
|
|
1292
1696
|
}
|
|
1293
1697
|
// ---------------------------------------------------------------------------
|
|
1294
1698
|
// resumeTeam — reconstruct runtime from persisted state
|
|
@@ -1300,7 +1704,7 @@ export async function resumeTeamV2(teamName, cwd) {
|
|
|
1300
1704
|
return null;
|
|
1301
1705
|
// Verify tmux session is alive
|
|
1302
1706
|
try {
|
|
1303
|
-
const sessionName = config.tmux_session || `
|
|
1707
|
+
const sessionName = config.tmux_session || `omc-team-${sanitized}`;
|
|
1304
1708
|
await tmuxExecAsync(['has-session', '-t', sessionName.split(':')[0]]);
|
|
1305
1709
|
return {
|
|
1306
1710
|
teamName: sanitized,
|