oh-my-copilot 4.13.2 → 4.13.45
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 +9 -1
- package/agents/code-reviewer.agent.md +24 -6
- package/agents/designer.agent.md +12 -0
- package/bridge/cli.cjs +4069 -1732
- package/bridge/mcp-server.cjs +774 -269
- package/bridge/runtime-cli.cjs +1311 -141
- package/bridge/team.js +1778 -133
- package/dist/__tests__/agent-registry.test.js +92 -1
- package/dist/__tests__/agent-registry.test.js.map +1 -1
- package/dist/__tests__/auto-slash-aliases.test.js +91 -0
- package/dist/__tests__/auto-slash-aliases.test.js.map +1 -1
- package/dist/__tests__/bedrock-model-routing.test.js +18 -17
- package/dist/__tests__/bedrock-model-routing.test.js.map +1 -1
- package/dist/__tests__/delegation-enforcer.test.js +46 -6
- package/dist/__tests__/delegation-enforcer.test.js.map +1 -1
- package/dist/__tests__/hooks/session-end-cleanup.test.d.ts +2 -0
- package/dist/__tests__/hooks/session-end-cleanup.test.d.ts.map +1 -0
- package/dist/__tests__/hooks/session-end-cleanup.test.js +93 -0
- package/dist/__tests__/hooks/session-end-cleanup.test.js.map +1 -0
- 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-rate-limits-priority.test.js +26 -0
- package/dist/__tests__/hud/render-rate-limits-priority.test.js.map +1 -1
- package/dist/__tests__/hud/stdin.test.d.ts +2 -0
- package/dist/__tests__/hud/stdin.test.d.ts.map +1 -0
- package/dist/__tests__/hud/stdin.test.js +556 -0
- package/dist/__tests__/hud/stdin.test.js.map +1 -0
- 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__/mnemosyne/detector.test.js +2 -0
- package/dist/__tests__/mnemosyne/detector.test.js.map +1 -1
- package/dist/__tests__/model-routing-override-three-site-sync.test.d.ts +2 -0
- package/dist/__tests__/model-routing-override-three-site-sync.test.d.ts.map +1 -0
- package/dist/__tests__/model-routing-override-three-site-sync.test.js +75 -0
- package/dist/__tests__/model-routing-override-three-site-sync.test.js.map +1 -0
- package/dist/__tests__/non-claude-provider-detection.test.js +37 -0
- package/dist/__tests__/non-claude-provider-detection.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__/package-dir-resolution-regression.test.js +25 -0
- package/dist/__tests__/package-dir-resolution-regression.test.js.map +1 -1
- package/dist/__tests__/pre-tool-enforcer.test.js +135 -1
- package/dist/__tests__/pre-tool-enforcer.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__/session-start-script-context.test.js +121 -0
- package/dist/__tests__/session-start-script-context.test.js.map +1 -1
- package/dist/__tests__/setup-claude-md-script.test.js +44 -1
- package/dist/__tests__/setup-claude-md-script.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 +56 -6
- package/dist/__tests__/state-root-resolution.test.js.map +1 -1
- package/dist/agents/definitions.d.ts.map +1 -1
- package/dist/agents/definitions.js +2 -1
- package/dist/agents/definitions.js.map +1 -1
- package/dist/autoresearch/__tests__/decide-outcome.test.d.ts +2 -0
- package/dist/autoresearch/__tests__/decide-outcome.test.d.ts.map +1 -0
- package/dist/autoresearch/__tests__/decide-outcome.test.js +69 -0
- package/dist/autoresearch/__tests__/decide-outcome.test.js.map +1 -0
- 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 +25 -7
- package/dist/autoresearch/runtime.js.map +1 -1
- package/dist/cli/__tests__/launch.test.js +23 -0
- package/dist/cli/__tests__/launch.test.js.map +1 -1
- package/dist/cli/__tests__/self-improve-paths.test.js +3 -2
- package/dist/cli/__tests__/self-improve-paths.test.js.map +1 -1
- package/dist/cli/__tests__/team.test.js +100 -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/__tests__/team-role-shorthand.test.js +30 -0
- package/dist/cli/commands/__tests__/team-role-shorthand.test.js.map +1 -1
- package/dist/cli/commands/__tests__/team.test.js +48 -1
- package/dist/cli/commands/__tests__/team.test.js.map +1 -1
- package/dist/cli/commands/team.d.ts +3 -0
- package/dist/cli/commands/team.d.ts.map +1 -1
- package/dist/cli/commands/team.js +23 -4
- 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/launch.d.ts.map +1 -1
- package/dist/cli/launch.js +7 -0
- package/dist/cli/launch.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 +57 -4
- package/dist/cli/team.js.map +1 -1
- package/dist/config/__tests__/loader.test.js +35 -4
- package/dist/config/__tests__/loader.test.js.map +1 -1
- package/dist/config/__tests__/models.test.js +126 -3
- package/dist/config/__tests__/models.test.js.map +1 -1
- package/dist/config/models.d.ts +8 -0
- package/dist/config/models.d.ts.map +1 -1
- package/dist/config/models.js +119 -8
- package/dist/config/models.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.map +1 -1
- package/dist/features/delegation-enforcer.js +10 -9
- 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 +157 -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/autopilot/enforcement.d.ts.map +1 -1
- package/dist/hooks/autopilot/enforcement.js +19 -0
- package/dist/hooks/autopilot/enforcement.js.map +1 -1
- package/dist/hooks/bridge.d.ts.map +1 -1
- package/dist/hooks/bridge.js +210 -5
- package/dist/hooks/bridge.js.map +1 -1
- package/dist/hooks/learner/detector.js +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 +10 -9
- package/dist/hooks/persistent-mode/index.js.map +1 -1
- package/dist/hooks/persistent-mode/stop-hook-blocking.test.js +101 -10
- 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 +18 -2
- package/dist/hooks/session-end/index.d.ts.map +1 -1
- package/dist/hooks/session-end/index.js +157 -35
- package/dist/hooks/session-end/index.js.map +1 -1
- package/dist/hooks/skill-bridge.cjs +6 -5
- 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__/git-status.test.js +18 -0
- package/dist/hud/__tests__/git-status.test.js.map +1 -1
- 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/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/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/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/render.d.ts.map +1 -1
- package/dist/hud/render.js +11 -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 +12 -1
- package/dist/hud/stdin.d.ts.map +1 -1
- package/dist/hud/stdin.js +123 -18
- package/dist/hud/stdin.js.map +1 -1
- package/dist/hud/types.d.ts +27 -0
- package/dist/hud/types.d.ts.map +1 -1
- package/dist/hud/types.js +58 -0
- package/dist/hud/types.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__/session-start-template.test.d.ts +2 -0
- package/dist/installer/__tests__/session-start-template.test.d.ts.map +1 -0
- package/dist/installer/__tests__/session-start-template.test.js +174 -0
- package/dist/installer/__tests__/session-start-template.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.map +1 -1
- package/dist/installer/index.js +19 -7
- package/dist/installer/index.js.map +1 -1
- package/dist/lib/__tests__/mode-state-io.test.js +75 -12
- package/dist/lib/__tests__/mode-state-io.test.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/mode-state-io.d.ts +11 -0
- package/dist/lib/mode-state-io.d.ts.map +1 -1
- package/dist/lib/mode-state-io.js +64 -0
- package/dist/lib/mode-state-io.js.map +1 -1
- package/dist/lib/worktree-paths.d.ts +99 -9
- package/dist/lib/worktree-paths.d.ts.map +1 -1
- package/dist/lib/worktree-paths.js +284 -27
- package/dist/lib/worktree-paths.js.map +1 -1
- package/dist/planning/__tests__/artifacts.test.d.ts +2 -0
- package/dist/planning/__tests__/artifacts.test.d.ts.map +1 -0
- package/dist/planning/__tests__/artifacts.test.js +486 -0
- package/dist/planning/__tests__/artifacts.test.js.map +1 -0
- 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 +47 -0
- package/dist/planning/artifacts.d.ts.map +1 -0
- package/dist/planning/artifacts.js +221 -0
- package/dist/planning/artifacts.js.map +1 -0
- package/dist/team/__tests__/api-interop.compatibility.test.js +194 -5
- package/dist/team/__tests__/api-interop.compatibility.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__/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 +92 -2
- 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 +9 -1
- package/dist/team/__tests__/runtime-cli.test.js.map +1 -1
- package/dist/team/__tests__/runtime-v2.dispatch.test.js +180 -1
- package/dist/team/__tests__/runtime-v2.dispatch.test.js.map +1 -1
- package/dist/team/__tests__/scaling.test.js +39 -1
- package/dist/team/__tests__/scaling.test.js.map +1 -1
- 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__/worker-bootstrap.test.js +6 -0
- 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-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.map +1 -1
- package/dist/team/api-interop.js +89 -2
- package/dist/team/api-interop.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/delegation-evidence.d.ts +5 -0
- package/dist/team/delegation-evidence.d.ts.map +1 -0
- package/dist/team/delegation-evidence.js +29 -0
- package/dist/team/delegation-evidence.js.map +1 -0
- 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 +8 -0
- package/dist/team/model-contract.d.ts.map +1 -1
- package/dist/team/model-contract.js +13 -0
- package/dist/team/model-contract.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 +9 -2
- package/dist/team/runtime-v2.d.ts.map +1 -1
- package/dist/team/runtime-v2.js +188 -9
- package/dist/team/runtime-v2.js.map +1 -1
- package/dist/team/state/tasks.d.ts +4 -1
- package/dist/team/state/tasks.d.ts.map +1 -1
- package/dist/team/state/tasks.js +38 -1
- package/dist/team/state/tasks.js.map +1 -1
- package/dist/team/team-ops.d.ts +4 -1
- package/dist/team/team-ops.d.ts.map +1 -1
- package/dist/team/team-ops.js +2 -2
- package/dist/team/team-ops.js.map +1 -1
- package/dist/team/types.d.ts +22 -1
- package/dist/team/types.d.ts.map +1 -1
- package/dist/team/types.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 +16 -2
- package/dist/team/worker-bootstrap.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 +335 -6
- package/dist/tools/__tests__/state-tools.test.js.map +1 -1
- package/dist/tools/__tests__/wiki-tools-working-directory.test.d.ts +2 -0
- package/dist/tools/__tests__/wiki-tools-working-directory.test.d.ts.map +1 -0
- package/dist/tools/__tests__/wiki-tools-working-directory.test.js +66 -0
- package/dist/tools/__tests__/wiki-tools-working-directory.test.js.map +1 -0
- package/dist/tools/state-tools.d.ts +3 -10
- package/dist/tools/state-tools.d.ts.map +1 -1
- package/dist/tools/state-tools.js +393 -91
- package/dist/tools/state-tools.js.map +1 -1
- package/dist/tools/wiki-tools.js +8 -8
- package/dist/tools/wiki-tools.js.map +1 -1
- package/docs/GETTING-STARTED.md +422 -0
- package/docs/MIGRATION.md +66 -66
- package/docs/REFERENCE.md +21 -4
- package/docs/company-context-interface.md +5 -16
- package/docs/copilot-instructions.md +1 -1
- package/package.json +1 -1
- package/scripts/lib/hud-cache-wrapper.sh +122 -0
- package/scripts/lib/model-routing-override-message.mjs +21 -0
- package/scripts/persistent-mode.cjs +36 -0
- package/scripts/persistent-mode.mjs +36 -0
- package/scripts/plugin-setup.mjs +10 -1
- package/scripts/post-tool-verifier.mjs +119 -5
- package/scripts/pre-tool-enforcer.mjs +114 -6
- package/scripts/session-start.mjs +234 -3
- package/scripts/setup-copilot-instructions.sh +12 -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 +77 -212
- 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/omc-doctor/SKILL.md +1 -1
- package/skills/omc-reference/SKILL.md +6 -6
- package/skills/omc-setup/SKILL.md +29 -73
- 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 +104 -46
- package/skills/omc-teams/SKILL.md +63 -26
- package/skills/plan/SKILL.md +11 -9
- package/skills/project-session-manager/SKILL.md +2 -2
- package/skills/project-session-manager/psm.sh +1 -1
- 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/lib/model-routing-override-message.mjs +21 -0
- package/templates/hooks/pre-tool-use.mjs +144 -45
- package/templates/hooks/session-start.mjs +62 -0
package/bridge/runtime-cli.cjs
CHANGED
|
@@ -207,7 +207,7 @@ function getDefaultShell() {
|
|
|
207
207
|
return process.env.COMSPEC || "cmd.exe";
|
|
208
208
|
}
|
|
209
209
|
const shell = process.env.SHELL || "/bin/bash";
|
|
210
|
-
const name = (0,
|
|
210
|
+
const name = (0, import_path10.basename)(shell.replace(/\\/g, "/")).replace(/\.(exe|cmd|bat)$/i, "");
|
|
211
211
|
if (!SUPPORTED_POSIX_SHELLS.has(name)) {
|
|
212
212
|
return "/bin/sh";
|
|
213
213
|
}
|
|
@@ -217,7 +217,7 @@ function pathEntries(envPath) {
|
|
|
217
217
|
return (envPath ?? "").split(process.platform === "win32" ? ";" : ":").map((entry) => entry.trim()).filter(Boolean);
|
|
218
218
|
}
|
|
219
219
|
function pathCandidateNames(candidatePath) {
|
|
220
|
-
const base = (0,
|
|
220
|
+
const base = (0, import_path10.basename)(candidatePath.replace(/\\/g, "/"));
|
|
221
221
|
const bare = base.replace(/\.(exe|cmd|bat)$/i, "");
|
|
222
222
|
if (process.platform === "win32") {
|
|
223
223
|
return Array.from(/* @__PURE__ */ new Set([`${bare}.exe`, `${bare}.cmd`, `${bare}.bat`, bare]));
|
|
@@ -227,7 +227,7 @@ function pathCandidateNames(candidatePath) {
|
|
|
227
227
|
function resolveShellFromPath(candidatePath) {
|
|
228
228
|
for (const dir of pathEntries(process.env.PATH)) {
|
|
229
229
|
for (const name of pathCandidateNames(candidatePath)) {
|
|
230
|
-
const full = (0,
|
|
230
|
+
const full = (0, import_path10.join)(dir, name);
|
|
231
231
|
if ((0, import_fs7.existsSync)(full)) return full;
|
|
232
232
|
}
|
|
233
233
|
}
|
|
@@ -243,7 +243,7 @@ function resolveShellFromCandidates(paths, rcFile) {
|
|
|
243
243
|
}
|
|
244
244
|
function resolveSupportedShellAffinity(shellPath) {
|
|
245
245
|
if (!shellPath) return null;
|
|
246
|
-
const name = (0,
|
|
246
|
+
const name = (0, import_path10.basename)(shellPath.replace(/\\/g, "/")).replace(/\.(exe|cmd|bat)$/i, "");
|
|
247
247
|
if (name !== "zsh" && name !== "bash") return null;
|
|
248
248
|
if (!(0, import_fs7.existsSync)(shellPath)) return null;
|
|
249
249
|
const home = process.env.HOME ?? "";
|
|
@@ -269,7 +269,7 @@ function escapeForCmdSet(value) {
|
|
|
269
269
|
return value.replace(/"/g, '""');
|
|
270
270
|
}
|
|
271
271
|
function shellNameFromPath(shellPath) {
|
|
272
|
-
const shellName = (0,
|
|
272
|
+
const shellName = (0, import_path10.basename)(shellPath.replace(/\\/g, "/"));
|
|
273
273
|
return shellName.replace(/\.(exe|cmd|bat)$/i, "");
|
|
274
274
|
}
|
|
275
275
|
function shellEscape(value) {
|
|
@@ -281,7 +281,7 @@ function assertSafeEnvKey(key) {
|
|
|
281
281
|
}
|
|
282
282
|
}
|
|
283
283
|
function isAbsoluteLaunchBinaryPath(value) {
|
|
284
|
-
return (0,
|
|
284
|
+
return (0, import_path10.isAbsolute)(value) || import_path10.win32.isAbsolute(value);
|
|
285
285
|
}
|
|
286
286
|
function assertSafeLaunchBinary(launchBinary) {
|
|
287
287
|
if (launchBinary.trim().length === 0) {
|
|
@@ -794,9 +794,9 @@ async function isWorkerAlive(paneId) {
|
|
|
794
794
|
async function killWorkerPanes(opts) {
|
|
795
795
|
const { paneIds, leaderPaneId, teamName, cwd, graceMs = 1e4 } = opts;
|
|
796
796
|
if (!paneIds.length) return;
|
|
797
|
-
const shutdownPath = (0,
|
|
797
|
+
const shutdownPath = (0, import_path10.join)(cwd, ".omc", "state", "team", teamName, "shutdown.json");
|
|
798
798
|
try {
|
|
799
|
-
await
|
|
799
|
+
await import_promises.default.writeFile(shutdownPath, JSON.stringify({ requestedAt: Date.now() }));
|
|
800
800
|
const aliveChecks = await Promise.all(paneIds.map((id) => isWorkerAlive(id)));
|
|
801
801
|
if (aliveChecks.some((alive) => alive)) {
|
|
802
802
|
await sleep(graceMs);
|
|
@@ -873,13 +873,13 @@ async function killTeamSession(sessionName2, workerPaneIds, leaderPaneId, option
|
|
|
873
873
|
} catch {
|
|
874
874
|
}
|
|
875
875
|
}
|
|
876
|
-
var import_fs7,
|
|
876
|
+
var import_fs7, import_path10, import_promises, sleep, TMUX_SESSION_PREFIX, SUPPORTED_POSIX_SHELLS, ZSH_CANDIDATES, BASH_CANDIDATES, DANGEROUS_LAUNCH_BINARY_CHARS;
|
|
877
877
|
var init_tmux_session = __esm({
|
|
878
878
|
"src/team/tmux-session.ts"() {
|
|
879
879
|
"use strict";
|
|
880
880
|
import_fs7 = require("fs");
|
|
881
|
-
|
|
882
|
-
|
|
881
|
+
import_path10 = require("path");
|
|
882
|
+
import_promises = __toESM(require("fs/promises"), 1);
|
|
883
883
|
init_team_name();
|
|
884
884
|
init_tmux_utils();
|
|
885
885
|
sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
@@ -1133,6 +1133,7 @@ var init_file_lock = __esm({
|
|
|
1133
1133
|
// src/team/runtime-cli.ts
|
|
1134
1134
|
var runtime_cli_exports = {};
|
|
1135
1135
|
__export(runtime_cli_exports, {
|
|
1136
|
+
assertAutoMergeRuntimeSupported: () => assertAutoMergeRuntimeSupported,
|
|
1136
1137
|
buildCliOutput: () => buildCliOutput,
|
|
1137
1138
|
buildTerminalCliResult: () => buildTerminalCliResult,
|
|
1138
1139
|
checkWatchdogFailedMarker: () => checkWatchdogFailedMarker,
|
|
@@ -1140,9 +1141,9 @@ __export(runtime_cli_exports, {
|
|
|
1140
1141
|
writeResultArtifact: () => writeResultArtifact
|
|
1141
1142
|
});
|
|
1142
1143
|
module.exports = __toCommonJS(runtime_cli_exports);
|
|
1143
|
-
var
|
|
1144
|
-
var
|
|
1145
|
-
var
|
|
1144
|
+
var import_fs21 = require("fs");
|
|
1145
|
+
var import_promises11 = require("fs/promises");
|
|
1146
|
+
var import_path23 = require("path");
|
|
1146
1147
|
|
|
1147
1148
|
// src/team/runtime.ts
|
|
1148
1149
|
var import_promises3 = require("fs/promises");
|
|
@@ -1417,6 +1418,8 @@ function validateAnthropicBaseUrl(urlString) {
|
|
|
1417
1418
|
}
|
|
1418
1419
|
|
|
1419
1420
|
// src/config/models.ts
|
|
1421
|
+
var DIRECT_MODEL_ENV_KEYS = ["CLAUDE_MODEL", "ANTHROPIC_MODEL"];
|
|
1422
|
+
var INHERIT_TIER_PRIORITY = ["MEDIUM", "HIGH", "LOW"];
|
|
1420
1423
|
var TIER_ENV_KEYS = {
|
|
1421
1424
|
LOW: [
|
|
1422
1425
|
"OMC_MODEL_LOW",
|
|
@@ -1453,15 +1456,42 @@ var BUILTIN_EXTERNAL_MODEL_DEFAULTS = {
|
|
|
1453
1456
|
codexModel: "gpt-5.3-codex",
|
|
1454
1457
|
geminiModel: "gemini-3.1-pro-preview"
|
|
1455
1458
|
};
|
|
1459
|
+
function readEnvValue(key) {
|
|
1460
|
+
const value = process.env[key]?.trim();
|
|
1461
|
+
return value || void 0;
|
|
1462
|
+
}
|
|
1456
1463
|
function resolveTierModelFromEnv(tier) {
|
|
1457
1464
|
for (const key of TIER_ENV_KEYS[tier]) {
|
|
1458
|
-
const value =
|
|
1465
|
+
const value = readEnvValue(key);
|
|
1466
|
+
if (value) {
|
|
1467
|
+
return value;
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
return void 0;
|
|
1471
|
+
}
|
|
1472
|
+
function getDirectModelEnvValue() {
|
|
1473
|
+
for (const key of DIRECT_MODEL_ENV_KEYS) {
|
|
1474
|
+
const value = readEnvValue(key);
|
|
1459
1475
|
if (value) {
|
|
1460
1476
|
return value;
|
|
1461
1477
|
}
|
|
1462
1478
|
}
|
|
1463
1479
|
return void 0;
|
|
1464
1480
|
}
|
|
1481
|
+
function getProviderDetectionModelEnvValues() {
|
|
1482
|
+
const directModel = getDirectModelEnvValue();
|
|
1483
|
+
if (directModel) {
|
|
1484
|
+
return [directModel];
|
|
1485
|
+
}
|
|
1486
|
+
const values = /* @__PURE__ */ new Set();
|
|
1487
|
+
for (const tier of INHERIT_TIER_PRIORITY) {
|
|
1488
|
+
const value = resolveTierModelFromEnv(tier);
|
|
1489
|
+
if (value) {
|
|
1490
|
+
values.add(value);
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
return [...values];
|
|
1494
|
+
}
|
|
1465
1495
|
function getDefaultModelHigh() {
|
|
1466
1496
|
return resolveTierModelFromEnv("HIGH") || BUILTIN_TIER_MODEL_DEFAULTS.HIGH;
|
|
1467
1497
|
}
|
|
@@ -1494,17 +1524,19 @@ function isBedrock() {
|
|
|
1494
1524
|
if (modelId && /^((us|eu|ap|global)\.anthropic\.|anthropic\.claude)/i.test(modelId)) {
|
|
1495
1525
|
return true;
|
|
1496
1526
|
}
|
|
1527
|
+
if (modelId && /^arn:aws(-[^:]+)?:bedrock:/i.test(modelId) && /:(inference-profile|application-inference-profile)\//i.test(modelId) && modelId.toLowerCase().includes("claude")) {
|
|
1528
|
+
return true;
|
|
1529
|
+
}
|
|
1497
1530
|
return false;
|
|
1498
1531
|
}
|
|
1499
1532
|
function isVertexAI() {
|
|
1500
1533
|
if (process.env.CLAUDE_CODE_USE_VERTEX === "1") {
|
|
1501
1534
|
return true;
|
|
1502
1535
|
}
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
return false;
|
|
1536
|
+
return hasVertexModelId(getProviderDetectionModelEnvValues());
|
|
1537
|
+
}
|
|
1538
|
+
function hasVertexModelId(modelIds) {
|
|
1539
|
+
return modelIds.some((modelId) => modelId.toLowerCase().startsWith("vertex_ai/"));
|
|
1508
1540
|
}
|
|
1509
1541
|
function isProviderSpecificModelId(modelId) {
|
|
1510
1542
|
return isBedrockModelId(modelId) || isVertexModelId(modelId);
|
|
@@ -2462,6 +2494,9 @@ var CC_FAMILY_TO_ALIAS = {
|
|
|
2462
2494
|
HAIKU: "haiku"
|
|
2463
2495
|
};
|
|
2464
2496
|
function normalizeToCcAlias(model) {
|
|
2497
|
+
if (isProviderSpecificModelId(model)) {
|
|
2498
|
+
return model;
|
|
2499
|
+
}
|
|
2465
2500
|
const family = resolveClaudeFamily(model);
|
|
2466
2501
|
return family ? CC_FAMILY_TO_ALIAS[family] ?? model : model;
|
|
2467
2502
|
}
|
|
@@ -2614,6 +2649,9 @@ function resolveCliBinaryPath(binary) {
|
|
|
2614
2649
|
resolvedPathCache.set(binary, resolvedPath);
|
|
2615
2650
|
return resolvedPath;
|
|
2616
2651
|
}
|
|
2652
|
+
function shouldUseClaudeBareMode(env = process.env) {
|
|
2653
|
+
return typeof env.ANTHROPIC_API_KEY === "string" && env.ANTHROPIC_API_KEY.trim().length > 0;
|
|
2654
|
+
}
|
|
2617
2655
|
var CONTRACTS = {
|
|
2618
2656
|
claude: {
|
|
2619
2657
|
agentType: "claude",
|
|
@@ -2621,6 +2659,9 @@ var CONTRACTS = {
|
|
|
2621
2659
|
installInstructions: "Install Claude CLI: https://claude.ai/download",
|
|
2622
2660
|
buildLaunchArgs(model, extraFlags = []) {
|
|
2623
2661
|
const args = ["--dangerously-skip-permissions"];
|
|
2662
|
+
if (shouldUseClaudeBareMode() && !extraFlags.includes("--bare")) {
|
|
2663
|
+
args.push("--bare");
|
|
2664
|
+
}
|
|
2624
2665
|
if (model) {
|
|
2625
2666
|
const resolved = isProviderSpecificModelId(model) ? model : normalizeToCcAlias(model);
|
|
2626
2667
|
args.push("--model", resolved);
|
|
@@ -2819,8 +2860,8 @@ function getPromptModeArgs(agentType, instruction) {
|
|
|
2819
2860
|
init_team_name();
|
|
2820
2861
|
|
|
2821
2862
|
// src/team/worker-bootstrap.ts
|
|
2822
|
-
var
|
|
2823
|
-
var
|
|
2863
|
+
var import_promises2 = require("fs/promises");
|
|
2864
|
+
var import_path12 = require("path");
|
|
2824
2865
|
|
|
2825
2866
|
// src/agents/prompt-helpers.ts
|
|
2826
2867
|
var import_fs6 = require("fs");
|
|
@@ -2923,10 +2964,74 @@ function formatOmcCliInvocation(commandSuffix, options = {}) {
|
|
|
2923
2964
|
return `${resolveInvocationPrefix(suffix, options)} ${suffix}`.trim();
|
|
2924
2965
|
}
|
|
2925
2966
|
|
|
2967
|
+
// src/team/worker-bootstrap.ts
|
|
2968
|
+
init_tmux_session();
|
|
2969
|
+
|
|
2970
|
+
// src/team/fs-utils.ts
|
|
2971
|
+
var import_fs8 = require("fs");
|
|
2972
|
+
var import_path11 = require("path");
|
|
2973
|
+
function atomicWriteJson(filePath, data, mode = 384) {
|
|
2974
|
+
const dir = (0, import_path11.dirname)(filePath);
|
|
2975
|
+
if (!(0, import_fs8.existsSync)(dir)) (0, import_fs8.mkdirSync)(dir, { recursive: true, mode: 448 });
|
|
2976
|
+
const tmpPath = `${filePath}.tmp.${process.pid}.${Date.now()}`;
|
|
2977
|
+
const serialized = JSON.stringify(data, null, 2) + "\n";
|
|
2978
|
+
(0, import_fs8.writeFileSync)(tmpPath, serialized, { encoding: "utf-8", mode });
|
|
2979
|
+
try {
|
|
2980
|
+
(0, import_fs8.renameSync)(tmpPath, filePath);
|
|
2981
|
+
} catch (err) {
|
|
2982
|
+
if (process.platform === "win32" && err && typeof err === "object" && "code" in err && err.code === "EPERM") {
|
|
2983
|
+
try {
|
|
2984
|
+
(0, import_fs8.writeFileSync)(filePath, serialized, { encoding: "utf-8", mode });
|
|
2985
|
+
} finally {
|
|
2986
|
+
try {
|
|
2987
|
+
(0, import_fs8.unlinkSync)(tmpPath);
|
|
2988
|
+
} catch {
|
|
2989
|
+
}
|
|
2990
|
+
}
|
|
2991
|
+
} else {
|
|
2992
|
+
try {
|
|
2993
|
+
(0, import_fs8.unlinkSync)(tmpPath);
|
|
2994
|
+
} catch {
|
|
2995
|
+
}
|
|
2996
|
+
throw err;
|
|
2997
|
+
}
|
|
2998
|
+
}
|
|
2999
|
+
}
|
|
3000
|
+
function ensureDirWithMode(dirPath, mode = 448) {
|
|
3001
|
+
if (!(0, import_fs8.existsSync)(dirPath)) (0, import_fs8.mkdirSync)(dirPath, { recursive: true, mode });
|
|
3002
|
+
}
|
|
3003
|
+
function safeRealpath(p) {
|
|
3004
|
+
try {
|
|
3005
|
+
return (0, import_fs8.realpathSync)(p);
|
|
3006
|
+
} catch {
|
|
3007
|
+
const segments = [];
|
|
3008
|
+
let current = (0, import_path11.resolve)(p);
|
|
3009
|
+
while (!(0, import_fs8.existsSync)(current)) {
|
|
3010
|
+
segments.unshift((0, import_path11.basename)(current));
|
|
3011
|
+
const parent = (0, import_path11.dirname)(current);
|
|
3012
|
+
if (parent === current) break;
|
|
3013
|
+
current = parent;
|
|
3014
|
+
}
|
|
3015
|
+
try {
|
|
3016
|
+
return (0, import_path11.join)((0, import_fs8.realpathSync)(current), ...segments);
|
|
3017
|
+
} catch {
|
|
3018
|
+
return (0, import_path11.resolve)(p);
|
|
3019
|
+
}
|
|
3020
|
+
}
|
|
3021
|
+
}
|
|
3022
|
+
function validateResolvedPath(resolvedPath, expectedBase) {
|
|
3023
|
+
const absResolved = safeRealpath(resolvedPath);
|
|
3024
|
+
const absBase = safeRealpath(expectedBase);
|
|
3025
|
+
const rel = (0, import_path11.relative)(absBase, absResolved);
|
|
3026
|
+
if (rel.startsWith("..") || (0, import_path11.resolve)(absBase, rel) !== absResolved) {
|
|
3027
|
+
throw new Error(`Path traversal detected: "${resolvedPath}" escapes base "${expectedBase}"`);
|
|
3028
|
+
}
|
|
3029
|
+
}
|
|
3030
|
+
|
|
2926
3031
|
// src/team/worker-bootstrap.ts
|
|
2927
3032
|
var DEFAULT_INSTRUCTION_STATE_ROOT = ".omcp/state";
|
|
2928
3033
|
function buildInstructionPath(...parts) {
|
|
2929
|
-
return (0,
|
|
3034
|
+
return (0, import_path12.join)(...parts).replaceAll("\\", "/");
|
|
2930
3035
|
}
|
|
2931
3036
|
function buildTeamStateInstructionPath(teamName, instructionStateRoot, ...teamRelativeParts) {
|
|
2932
3037
|
const baseParts = instructionStateRoot === DEFAULT_INSTRUCTION_STATE_ROOT ? [instructionStateRoot, "team", teamName] : [instructionStateRoot];
|
|
@@ -2995,7 +3100,7 @@ function generateWorkerOverlay(params) {
|
|
|
2995
3100
|
const shutdownAckPath = buildTeamStateInstructionPath(teamName, instructionStateRoot, "workers", workerName2, "shutdown-ack.json");
|
|
2996
3101
|
const claimTaskCommand = formatOmcCliInvocation(`team api claim-task --input "{\\"team_name\\":\\"${teamName}\\",\\"task_id\\":\\"<id>\\",\\"worker\\":\\"${workerName2}\\"}" --json`);
|
|
2997
3102
|
const sendAckCommand = formatOmcCliInvocation(`team api send-message --input "{\\"team_name\\":\\"${teamName}\\",\\"from_worker\\":\\"${workerName2}\\",\\"to_worker\\":\\"leader-fixed\\",\\"body\\":\\"ACK: ${workerName2} initialized\\"}" --json`);
|
|
2998
|
-
const completeTaskCommand = formatOmcCliInvocation(`team api transition-task-status --input "{\\"team_name\\":\\"${teamName}\\",\\"task_id\\":\\"<id>\\",\\"from\\":\\"in_progress\\",\\"to\\":\\"completed\\",\\"claim_token\\":\\"<claim_token>\\"}" --json`);
|
|
3103
|
+
const completeTaskCommand = formatOmcCliInvocation(`team api transition-task-status --input "{\\"team_name\\":\\"${teamName}\\",\\"task_id\\":\\"<id>\\",\\"from\\":\\"in_progress\\",\\"to\\":\\"completed\\",\\"claim_token\\":\\"<claim_token>\\",\\"result\\":\\"Summary: <what changed>\\\\nVerification: <tests/checks run>\\\\nSubagent skip reason: worker protocol forbids nested subagents; completed focused probe in-session\\"}" --json`);
|
|
2999
3104
|
const failTaskCommand = formatOmcCliInvocation(`team api transition-task-status --input "{\\"team_name\\":\\"${teamName}\\",\\"task_id\\":\\"<id>\\",\\"from\\":\\"in_progress\\",\\"to\\":\\"failed\\",\\"claim_token\\":\\"<claim_token>\\"}" --json`);
|
|
3000
3105
|
const readTaskCommand = formatOmcCliInvocation(`team api read-task --input "{\\"team_name\\":\\"${teamName}\\",\\"task_id\\":\\"<id>\\"}" --json`);
|
|
3001
3106
|
const releaseClaimCommand = formatOmcCliInvocation(`team api release-task-claim --input "{\\"team_name\\":\\"${teamName}\\",\\"task_id\\":\\"<id>\\",\\"claim_token\\":\\"<claim_token>\\",\\"worker\\":\\"${workerName2}\\"}" --json`);
|
|
@@ -3048,6 +3153,11 @@ Use the CLI API for all task lifecycle operations. Do NOT directly edit task fil
|
|
|
3048
3153
|
- Complete task: \`${completeTaskCommand}\`
|
|
3049
3154
|
- Fail task: \`${failTaskCommand}\`
|
|
3050
3155
|
- Release claim (rollback): \`${releaseClaimCommand}\`
|
|
3156
|
+
- Delegation compliance evidence (required for broad delegated tasks):
|
|
3157
|
+
- The completion command MUST include a \`result\` string with summary and verification evidence.
|
|
3158
|
+
- Because worker protocol forbids nested sub-agents, use: \`Subagent skip reason: <why in-session execution was safer/sufficient>\`
|
|
3159
|
+
- Only if the leader explicitly grants an exception to spawn nested help, use: \`Subagent spawn evidence: <count, child task names/thread ids, and integrated findings>\`
|
|
3160
|
+
- Completion is rejected with \`missing_delegation_compliance_evidence\` when required evidence is absent.
|
|
3051
3161
|
|
|
3052
3162
|
## Canonical Team State Root
|
|
3053
3163
|
- Resolve the team state root in this order: \`OMC_TEAM_STATE_ROOT\` env -> worker identity \`team_state_root\` -> config/manifest \`team_state_root\` -> ${params.cwd}/.omcp/state/team/${teamName}.
|
|
@@ -3105,26 +3215,37 @@ ${bootstrapInstructions}
|
|
|
3105
3215
|
` : ""}`;
|
|
3106
3216
|
}
|
|
3107
3217
|
async function composeInitialInbox(teamName, workerName2, content, cwd, cliOutputContract) {
|
|
3108
|
-
const inboxPath = (0,
|
|
3109
|
-
await (0,
|
|
3218
|
+
const inboxPath = (0, import_path12.join)(cwd, `.omcp/state/team/${teamName}/workers/${workerName2}/inbox.md`);
|
|
3219
|
+
await (0, import_promises2.mkdir)((0, import_path12.dirname)(inboxPath), { recursive: true });
|
|
3110
3220
|
const finalContent = cliOutputContract && !content.includes(cliOutputContract) ? `${content}
|
|
3111
3221
|
${cliOutputContract}` : content;
|
|
3112
|
-
await (0,
|
|
3222
|
+
await (0, import_promises2.writeFile)(inboxPath, finalContent, "utf-8");
|
|
3223
|
+
}
|
|
3224
|
+
async function appendToInbox(teamName, workerName2, message, cwd) {
|
|
3225
|
+
const safeTeam = sanitizeName(teamName);
|
|
3226
|
+
const safeWorker = sanitizeName(workerName2);
|
|
3227
|
+
const inboxPath = (0, import_path12.join)(cwd, `.omcp/state/team/${safeTeam}/workers/${safeWorker}/inbox.md`);
|
|
3228
|
+
validateResolvedPath(inboxPath, cwd);
|
|
3229
|
+
await (0, import_promises2.mkdir)((0, import_path12.dirname)(inboxPath), { recursive: true });
|
|
3230
|
+
await (0, import_promises2.appendFile)(inboxPath, `
|
|
3231
|
+
|
|
3232
|
+
---
|
|
3233
|
+
${message}`, "utf-8");
|
|
3113
3234
|
}
|
|
3114
3235
|
async function ensureWorkerStateDir(teamName, workerName2, cwd) {
|
|
3115
|
-
const workerDir = (0,
|
|
3116
|
-
await (0,
|
|
3117
|
-
const mailboxDir = (0,
|
|
3118
|
-
await (0,
|
|
3119
|
-
const tasksDir = (0,
|
|
3120
|
-
await (0,
|
|
3236
|
+
const workerDir = (0, import_path12.join)(cwd, `.omcp/state/team/${teamName}/workers/${workerName2}`);
|
|
3237
|
+
await (0, import_promises2.mkdir)(workerDir, { recursive: true });
|
|
3238
|
+
const mailboxDir = (0, import_path12.join)(cwd, `.omcp/state/team/${teamName}/mailbox`);
|
|
3239
|
+
await (0, import_promises2.mkdir)(mailboxDir, { recursive: true });
|
|
3240
|
+
const tasksDir = (0, import_path12.join)(cwd, `.omcp/state/team/${teamName}/tasks`);
|
|
3241
|
+
await (0, import_promises2.mkdir)(tasksDir, { recursive: true });
|
|
3121
3242
|
}
|
|
3122
3243
|
async function writeWorkerOverlay(params) {
|
|
3123
3244
|
const { teamName, workerName: workerName2, cwd } = params;
|
|
3124
3245
|
const overlay = generateWorkerOverlay(params);
|
|
3125
|
-
const overlayPath = (0,
|
|
3126
|
-
await (0,
|
|
3127
|
-
await (0,
|
|
3246
|
+
const overlayPath = (0, import_path12.join)(cwd, `.omcp/state/team/${teamName}/workers/${workerName2}/AGENTS.md`);
|
|
3247
|
+
await (0, import_promises2.mkdir)((0, import_path12.dirname)(overlayPath), { recursive: true });
|
|
3248
|
+
await (0, import_promises2.writeFile)(overlayPath, overlay, "utf-8");
|
|
3128
3249
|
return overlayPath;
|
|
3129
3250
|
}
|
|
3130
3251
|
|
|
@@ -3141,69 +3262,6 @@ init_tmux_session();
|
|
|
3141
3262
|
var import_node_fs = require("node:fs");
|
|
3142
3263
|
var import_node_path = require("node:path");
|
|
3143
3264
|
var import_node_child_process = require("node:child_process");
|
|
3144
|
-
|
|
3145
|
-
// src/team/fs-utils.ts
|
|
3146
|
-
var import_fs8 = require("fs");
|
|
3147
|
-
var import_path12 = require("path");
|
|
3148
|
-
function atomicWriteJson(filePath, data, mode = 384) {
|
|
3149
|
-
const dir = (0, import_path12.dirname)(filePath);
|
|
3150
|
-
if (!(0, import_fs8.existsSync)(dir)) (0, import_fs8.mkdirSync)(dir, { recursive: true, mode: 448 });
|
|
3151
|
-
const tmpPath = `${filePath}.tmp.${process.pid}.${Date.now()}`;
|
|
3152
|
-
const serialized = JSON.stringify(data, null, 2) + "\n";
|
|
3153
|
-
(0, import_fs8.writeFileSync)(tmpPath, serialized, { encoding: "utf-8", mode });
|
|
3154
|
-
try {
|
|
3155
|
-
(0, import_fs8.renameSync)(tmpPath, filePath);
|
|
3156
|
-
} catch (err) {
|
|
3157
|
-
if (process.platform === "win32" && err && typeof err === "object" && "code" in err && err.code === "EPERM") {
|
|
3158
|
-
try {
|
|
3159
|
-
(0, import_fs8.writeFileSync)(filePath, serialized, { encoding: "utf-8", mode });
|
|
3160
|
-
} finally {
|
|
3161
|
-
try {
|
|
3162
|
-
(0, import_fs8.unlinkSync)(tmpPath);
|
|
3163
|
-
} catch {
|
|
3164
|
-
}
|
|
3165
|
-
}
|
|
3166
|
-
} else {
|
|
3167
|
-
try {
|
|
3168
|
-
(0, import_fs8.unlinkSync)(tmpPath);
|
|
3169
|
-
} catch {
|
|
3170
|
-
}
|
|
3171
|
-
throw err;
|
|
3172
|
-
}
|
|
3173
|
-
}
|
|
3174
|
-
}
|
|
3175
|
-
function ensureDirWithMode(dirPath, mode = 448) {
|
|
3176
|
-
if (!(0, import_fs8.existsSync)(dirPath)) (0, import_fs8.mkdirSync)(dirPath, { recursive: true, mode });
|
|
3177
|
-
}
|
|
3178
|
-
function safeRealpath(p) {
|
|
3179
|
-
try {
|
|
3180
|
-
return (0, import_fs8.realpathSync)(p);
|
|
3181
|
-
} catch {
|
|
3182
|
-
const segments = [];
|
|
3183
|
-
let current = (0, import_path12.resolve)(p);
|
|
3184
|
-
while (!(0, import_fs8.existsSync)(current)) {
|
|
3185
|
-
segments.unshift((0, import_path12.basename)(current));
|
|
3186
|
-
const parent = (0, import_path12.dirname)(current);
|
|
3187
|
-
if (parent === current) break;
|
|
3188
|
-
current = parent;
|
|
3189
|
-
}
|
|
3190
|
-
try {
|
|
3191
|
-
return (0, import_path12.join)((0, import_fs8.realpathSync)(current), ...segments);
|
|
3192
|
-
} catch {
|
|
3193
|
-
return (0, import_path12.resolve)(p);
|
|
3194
|
-
}
|
|
3195
|
-
}
|
|
3196
|
-
}
|
|
3197
|
-
function validateResolvedPath(resolvedPath, expectedBase) {
|
|
3198
|
-
const absResolved = safeRealpath(resolvedPath);
|
|
3199
|
-
const absBase = safeRealpath(expectedBase);
|
|
3200
|
-
const rel = (0, import_path12.relative)(absBase, absResolved);
|
|
3201
|
-
if (rel.startsWith("..") || (0, import_path12.resolve)(absBase, rel) !== absResolved) {
|
|
3202
|
-
throw new Error(`Path traversal detected: "${resolvedPath}" escapes base "${expectedBase}"`);
|
|
3203
|
-
}
|
|
3204
|
-
}
|
|
3205
|
-
|
|
3206
|
-
// src/team/git-worktree.ts
|
|
3207
3265
|
init_tmux_session();
|
|
3208
3266
|
init_file_lock();
|
|
3209
3267
|
function getWorktreePath(repoRoot, teamName, workerName2) {
|
|
@@ -3587,6 +3645,9 @@ function removeWorkerWorktree(teamName, workerName2, repoRoot) {
|
|
|
3587
3645
|
forgetMetadataUnlocked(repoRoot, teamName, workerName2);
|
|
3588
3646
|
});
|
|
3589
3647
|
}
|
|
3648
|
+
function listTeamWorktrees(teamName, repoRoot) {
|
|
3649
|
+
return readMetadata(repoRoot, teamName);
|
|
3650
|
+
}
|
|
3590
3651
|
function inspectTeamWorktreeCleanupSafety(teamName, repoRoot) {
|
|
3591
3652
|
const metadata = readMetadataResult(repoRoot, teamName);
|
|
3592
3653
|
const entries = metadata.entries;
|
|
@@ -4157,8 +4218,8 @@ function watchdogCliWorkers(runtime, intervalMs) {
|
|
|
4157
4218
|
unresponsiveCounts.delete(wName);
|
|
4158
4219
|
await markTaskFromDone(root, runtime.teamName, runtime.cwd, signal.taskId || active.taskId, signal.status, signal.summary);
|
|
4159
4220
|
try {
|
|
4160
|
-
const { unlink:
|
|
4161
|
-
await
|
|
4221
|
+
const { unlink: unlink4 } = await import("fs/promises");
|
|
4222
|
+
await unlink4(donePath);
|
|
4162
4223
|
} catch {
|
|
4163
4224
|
}
|
|
4164
4225
|
await killWorkerPane(runtime, wName, active.paneId);
|
|
@@ -5056,9 +5117,9 @@ async function waitForSentinelReadiness(options = {}) {
|
|
|
5056
5117
|
|
|
5057
5118
|
// src/team/runtime-v2.ts
|
|
5058
5119
|
init_tmux_utils();
|
|
5059
|
-
var
|
|
5060
|
-
var
|
|
5061
|
-
var
|
|
5120
|
+
var import_path22 = require("path");
|
|
5121
|
+
var import_fs20 = require("fs");
|
|
5122
|
+
var import_promises10 = require("fs/promises");
|
|
5062
5123
|
var import_perf_hooks = require("perf_hooks");
|
|
5063
5124
|
|
|
5064
5125
|
// src/team/allocation-policy.ts
|
|
@@ -5271,10 +5332,10 @@ async function readJsonSafe2(filePath) {
|
|
|
5271
5332
|
}
|
|
5272
5333
|
}
|
|
5273
5334
|
async function writeAtomic(filePath, data) {
|
|
5274
|
-
const { writeFile:
|
|
5335
|
+
const { writeFile: writeFile8 } = await import("fs/promises");
|
|
5275
5336
|
await (0, import_promises5.mkdir)((0, import_path18.dirname)(filePath), { recursive: true });
|
|
5276
5337
|
const tmpPath = `${filePath}.tmp.${process.pid}.${Date.now()}`;
|
|
5277
|
-
await
|
|
5338
|
+
await writeFile8(tmpPath, data, "utf-8");
|
|
5278
5339
|
const { rename: rename4 } = await import("fs/promises");
|
|
5279
5340
|
await rename4(tmpPath, filePath);
|
|
5280
5341
|
}
|
|
@@ -6198,13 +6259,996 @@ function cliWorkerOutputFilePath(teamStateRootAbs, workerName2) {
|
|
|
6198
6259
|
return `${teamStateRootAbs.replaceAll("\\", "/")}/workers/${workerName2}/verdict.json`;
|
|
6199
6260
|
}
|
|
6200
6261
|
|
|
6201
|
-
// src/team/
|
|
6262
|
+
// src/team/merge-orchestrator.ts
|
|
6263
|
+
var import_node_child_process3 = require("node:child_process");
|
|
6264
|
+
var import_node_fs2 = require("node:fs");
|
|
6265
|
+
var import_promises9 = require("node:fs/promises");
|
|
6266
|
+
var import_node_path2 = require("node:path");
|
|
6267
|
+
|
|
6268
|
+
// src/team/runtime-flags.ts
|
|
6202
6269
|
function isRuntimeV2Enabled(env = process.env) {
|
|
6203
6270
|
const raw = env.OMC_RUNTIME_V2;
|
|
6204
6271
|
if (!raw) return true;
|
|
6205
6272
|
const normalized = raw.trim().toLowerCase();
|
|
6206
6273
|
return !["0", "false", "no", "off"].includes(normalized);
|
|
6207
6274
|
}
|
|
6275
|
+
|
|
6276
|
+
// src/team/merge-orchestrator.ts
|
|
6277
|
+
init_tmux_session();
|
|
6278
|
+
|
|
6279
|
+
// src/team/merge-coordinator.ts
|
|
6280
|
+
var import_node_child_process2 = require("node:child_process");
|
|
6281
|
+
var BRANCH_NAME_RE = /^[a-zA-Z0-9][a-zA-Z0-9/_.-]*$/;
|
|
6282
|
+
function validateBranchName(branch) {
|
|
6283
|
+
if (!BRANCH_NAME_RE.test(branch)) {
|
|
6284
|
+
throw new Error(`Invalid branch name: "${branch}" \u2014 must match ${BRANCH_NAME_RE}`);
|
|
6285
|
+
}
|
|
6286
|
+
}
|
|
6287
|
+
function checkMergeConflicts(workerBranch, baseBranch, repoRoot) {
|
|
6288
|
+
validateBranchName(workerBranch);
|
|
6289
|
+
validateBranchName(baseBranch);
|
|
6290
|
+
try {
|
|
6291
|
+
(0, import_node_child_process2.execFileSync)(
|
|
6292
|
+
"git",
|
|
6293
|
+
["merge-tree", "--write-tree", baseBranch, workerBranch],
|
|
6294
|
+
{ cwd: repoRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
6295
|
+
);
|
|
6296
|
+
return [];
|
|
6297
|
+
} catch (err) {
|
|
6298
|
+
const error = err;
|
|
6299
|
+
if (error.status === 1 && typeof error.stdout === "string") {
|
|
6300
|
+
const lines = error.stdout.split("\n");
|
|
6301
|
+
const conflicts = [];
|
|
6302
|
+
for (const line of lines) {
|
|
6303
|
+
const match = line.match(/^CONFLICT\s.*?:\s+.*?\s+in\s+(.+)$/);
|
|
6304
|
+
if (match) {
|
|
6305
|
+
conflicts.push(match[1].trim());
|
|
6306
|
+
}
|
|
6307
|
+
}
|
|
6308
|
+
return conflicts.length > 0 ? conflicts : ["(merge-tree reported conflicts)"];
|
|
6309
|
+
}
|
|
6310
|
+
}
|
|
6311
|
+
const mergeBase = (0, import_node_child_process2.execFileSync)(
|
|
6312
|
+
"git",
|
|
6313
|
+
["merge-base", baseBranch, workerBranch],
|
|
6314
|
+
{ cwd: repoRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
6315
|
+
).trim();
|
|
6316
|
+
const baseDiff = (0, import_node_child_process2.execFileSync)(
|
|
6317
|
+
"git",
|
|
6318
|
+
["diff", "--name-only", mergeBase, baseBranch],
|
|
6319
|
+
{ cwd: repoRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
6320
|
+
).trim();
|
|
6321
|
+
const workerDiff = (0, import_node_child_process2.execFileSync)(
|
|
6322
|
+
"git",
|
|
6323
|
+
["diff", "--name-only", mergeBase, workerBranch],
|
|
6324
|
+
{ cwd: repoRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
6325
|
+
).trim();
|
|
6326
|
+
if (!baseDiff || !workerDiff) {
|
|
6327
|
+
return [];
|
|
6328
|
+
}
|
|
6329
|
+
const baseFiles = new Set(baseDiff.split("\n").filter((f) => f));
|
|
6330
|
+
const workerFiles = workerDiff.split("\n").filter((f) => f);
|
|
6331
|
+
return workerFiles.filter((f) => baseFiles.has(f));
|
|
6332
|
+
}
|
|
6333
|
+
function mergeWorkerBranch(workerBranch, baseBranch, repoRoot) {
|
|
6334
|
+
validateBranchName(workerBranch);
|
|
6335
|
+
validateBranchName(baseBranch);
|
|
6336
|
+
const workerName2 = workerBranch.split("/").pop() || workerBranch;
|
|
6337
|
+
try {
|
|
6338
|
+
try {
|
|
6339
|
+
(0, import_node_child_process2.execFileSync)("git", ["diff-index", "--quiet", "HEAD", "--"], {
|
|
6340
|
+
cwd: repoRoot,
|
|
6341
|
+
stdio: "pipe"
|
|
6342
|
+
});
|
|
6343
|
+
} catch {
|
|
6344
|
+
throw new Error("Working tree has uncommitted changes \u2014 commit or stash before merging");
|
|
6345
|
+
}
|
|
6346
|
+
(0, import_node_child_process2.execFileSync)("git", ["checkout", baseBranch], {
|
|
6347
|
+
cwd: repoRoot,
|
|
6348
|
+
stdio: "pipe"
|
|
6349
|
+
});
|
|
6350
|
+
(0, import_node_child_process2.execFileSync)("git", ["merge", "--no-ff", "-m", `Merge ${workerBranch} into ${baseBranch}`, workerBranch], {
|
|
6351
|
+
cwd: repoRoot,
|
|
6352
|
+
stdio: "pipe"
|
|
6353
|
+
});
|
|
6354
|
+
const mergeCommit = (0, import_node_child_process2.execFileSync)("git", ["rev-parse", "HEAD"], {
|
|
6355
|
+
cwd: repoRoot,
|
|
6356
|
+
encoding: "utf-8",
|
|
6357
|
+
stdio: "pipe"
|
|
6358
|
+
}).trim();
|
|
6359
|
+
return {
|
|
6360
|
+
workerName: workerName2,
|
|
6361
|
+
branch: workerBranch,
|
|
6362
|
+
success: true,
|
|
6363
|
+
conflicts: [],
|
|
6364
|
+
mergeCommit
|
|
6365
|
+
};
|
|
6366
|
+
} catch (_err) {
|
|
6367
|
+
try {
|
|
6368
|
+
(0, import_node_child_process2.execFileSync)("git", ["merge", "--abort"], { cwd: repoRoot, stdio: "pipe" });
|
|
6369
|
+
} catch {
|
|
6370
|
+
}
|
|
6371
|
+
const conflicts = checkMergeConflicts(workerBranch, baseBranch, repoRoot);
|
|
6372
|
+
return {
|
|
6373
|
+
workerName: workerName2,
|
|
6374
|
+
branch: workerBranch,
|
|
6375
|
+
success: false,
|
|
6376
|
+
conflicts
|
|
6377
|
+
};
|
|
6378
|
+
}
|
|
6379
|
+
}
|
|
6380
|
+
|
|
6381
|
+
// src/team/leader-inbox.ts
|
|
6382
|
+
var import_promises7 = require("fs/promises");
|
|
6383
|
+
var import_fs18 = require("fs");
|
|
6384
|
+
var import_path20 = require("path");
|
|
6385
|
+
init_tmux_session();
|
|
6386
|
+
var LEADER_INBOX_HEADER = `# Leader Inbox
|
|
6387
|
+
|
|
6388
|
+
Runtime notifications (merge conflicts, rebase events, etc.) appear here.
|
|
6389
|
+
Check this file periodically and after long-running operations.
|
|
6390
|
+
|
|
6391
|
+
---
|
|
6392
|
+
`;
|
|
6393
|
+
function leaderInboxPath(teamName, cwd) {
|
|
6394
|
+
const safe = sanitizeName(teamName);
|
|
6395
|
+
return (0, import_path20.join)(cwd, `.omcp/state/team/${safe}/leader/inbox.md`);
|
|
6396
|
+
}
|
|
6397
|
+
async function ensureLeaderInbox(teamName, cwd) {
|
|
6398
|
+
const inboxPath = leaderInboxPath(teamName, cwd);
|
|
6399
|
+
validateResolvedPath(inboxPath, cwd);
|
|
6400
|
+
await (0, import_promises7.mkdir)((0, import_path20.dirname)(inboxPath), { recursive: true });
|
|
6401
|
+
if (!(0, import_fs18.existsSync)(inboxPath)) {
|
|
6402
|
+
await (0, import_promises7.writeFile)(inboxPath, LEADER_INBOX_HEADER, "utf-8");
|
|
6403
|
+
}
|
|
6404
|
+
return inboxPath;
|
|
6405
|
+
}
|
|
6406
|
+
async function appendToLeaderInbox(teamName, message, cwd) {
|
|
6407
|
+
const inboxPath = leaderInboxPath(teamName, cwd);
|
|
6408
|
+
validateResolvedPath(inboxPath, cwd);
|
|
6409
|
+
await (0, import_promises7.mkdir)((0, import_path20.dirname)(inboxPath), { recursive: true });
|
|
6410
|
+
await (0, import_promises7.appendFile)(inboxPath, `
|
|
6411
|
+
|
|
6412
|
+
---
|
|
6413
|
+
${message}`, "utf-8");
|
|
6414
|
+
}
|
|
6415
|
+
function extendLeaderBootstrapPrompt(teamName) {
|
|
6416
|
+
const safe = sanitizeName(teamName);
|
|
6417
|
+
const path4 = `.omcp/state/team/${safe}/leader/inbox.md`;
|
|
6418
|
+
return `Runtime notifications appear at ${path4} \u2014 check this file periodically and after long-running operations.`;
|
|
6419
|
+
}
|
|
6420
|
+
|
|
6421
|
+
// src/team/conflict-mailbox.ts
|
|
6422
|
+
function sanitizeConflictPath(path4) {
|
|
6423
|
+
return path4.replace(/[`\r\n]/g, "?");
|
|
6424
|
+
}
|
|
6425
|
+
function formatMergeConflictForLeader(args) {
|
|
6426
|
+
const { workerName: workerName2, workerBranch, leaderBranch, conflictingFiles, mergeBaseSha, observedAt } = args;
|
|
6427
|
+
const ts = new Date(observedAt).toISOString();
|
|
6428
|
+
const safeFiles = conflictingFiles.map(sanitizeConflictPath);
|
|
6429
|
+
const fileList = safeFiles.map((f) => `- \`${f}\``).join("\n");
|
|
6430
|
+
return `### Merge conflict: ${workerName2} \u2192 ${leaderBranch}
|
|
6431
|
+
|
|
6432
|
+
**Worker branch:** \`${workerBranch}\`
|
|
6433
|
+
**Leader branch:** \`${leaderBranch}\`
|
|
6434
|
+
**Merge base:** \`${mergeBaseSha}\`
|
|
6435
|
+
**Observed at:** ${ts}
|
|
6436
|
+
|
|
6437
|
+
**Conflicting files:**
|
|
6438
|
+
${fileList}
|
|
6439
|
+
|
|
6440
|
+
**Leader: choose strategy.** To resolve, run:
|
|
6441
|
+
|
|
6442
|
+
\`\`\`sh
|
|
6443
|
+
git checkout ${leaderBranch} && git merge --no-ff ${workerBranch}
|
|
6444
|
+
# resolve conflicts in the files listed above
|
|
6445
|
+
git add ${safeFiles.join(" ")}
|
|
6446
|
+
git commit
|
|
6447
|
+
\`\`\`
|
|
6448
|
+
|
|
6449
|
+
Or abort with \`git merge --abort\` to defer resolution.`;
|
|
6450
|
+
}
|
|
6451
|
+
function formatRebaseConflictForWorker(args) {
|
|
6452
|
+
const { workerName: workerName2, workerBranch, leaderBranch, conflictingFiles, baseSha, worktreePath, observedAt } = args;
|
|
6453
|
+
const ts = new Date(observedAt).toISOString();
|
|
6454
|
+
const safeFiles = conflictingFiles.map(sanitizeConflictPath);
|
|
6455
|
+
const fileList = safeFiles.map((f) => `- \`${f}\``).join("\n");
|
|
6456
|
+
return `### Rebase conflict: ${workerName2} onto ${leaderBranch}
|
|
6457
|
+
|
|
6458
|
+
**Worker branch:** \`${workerBranch}\`
|
|
6459
|
+
**Base branch:** \`${leaderBranch}\`
|
|
6460
|
+
**Base SHA:** \`${baseSha}\`
|
|
6461
|
+
**Worktree:** \`${worktreePath}\`
|
|
6462
|
+
**Observed at:** ${ts}
|
|
6463
|
+
|
|
6464
|
+
**Conflicting files:**
|
|
6465
|
+
${fileList}
|
|
6466
|
+
|
|
6467
|
+
Resolve conflicts in your own pane, then \`git add <files>\` and \`git rebase --continue\`.
|
|
6468
|
+
Cadence stays paused until \`.git/rebase-merge\` is gone.
|
|
6469
|
+
|
|
6470
|
+
Or run \`git rebase --abort\` to bail and return to the pre-rebase state.`;
|
|
6471
|
+
}
|
|
6472
|
+
|
|
6473
|
+
// src/team/worker-commit-cadence.ts
|
|
6474
|
+
var import_fs19 = require("fs");
|
|
6475
|
+
var import_promises8 = require("fs/promises");
|
|
6476
|
+
var import_path21 = require("path");
|
|
6477
|
+
var import_child_process5 = require("child_process");
|
|
6478
|
+
var SENTINEL_FILENAME = ".hook-paused";
|
|
6479
|
+
var HOOK_MATCHER = "Write|Edit|MultiEdit";
|
|
6480
|
+
var DEFAULT_POLL_DEBOUNCE_MS = 3e3;
|
|
6481
|
+
var WORKER_NAME_RE = /^[A-Za-z0-9_-]{1,50}$/;
|
|
6482
|
+
function assertSafeWorkerName(workerName2) {
|
|
6483
|
+
if (!WORKER_NAME_RE.test(workerName2)) {
|
|
6484
|
+
throw new Error(
|
|
6485
|
+
`Invalid worker name for shell hook: "${workerName2}" \u2014 must match ${WORKER_NAME_RE}`
|
|
6486
|
+
);
|
|
6487
|
+
}
|
|
6488
|
+
}
|
|
6489
|
+
function buildHookCommand(workerName2) {
|
|
6490
|
+
assertSafeWorkerName(workerName2);
|
|
6491
|
+
return `sh -c 'rebase_dir=$(git rev-parse --git-path rebase-merge 2>/dev/null || printf %s .git/rebase-merge); merge_head=$(git rev-parse --git-path MERGE_HEAD 2>/dev/null || printf %s .git/MERGE_HEAD); if [ -d "$rebase_dir" ] || [ -f "$merge_head" ] || [ -e ${SENTINEL_FILENAME} ]; then exit 0; fi; git add -A && (git diff --cached --quiet || git commit -m "auto-commit by worker ${workerName2} at $(date -Iseconds)")'`;
|
|
6492
|
+
}
|
|
6493
|
+
async function mergeSettingsWithHook(settingsPath, hookCommand) {
|
|
6494
|
+
let existing = { hooks: { PostToolUse: [] } };
|
|
6495
|
+
try {
|
|
6496
|
+
const raw = await (0, import_promises8.readFile)(settingsPath, "utf-8");
|
|
6497
|
+
const parsed = JSON.parse(raw);
|
|
6498
|
+
existing = {
|
|
6499
|
+
...parsed,
|
|
6500
|
+
hooks: {
|
|
6501
|
+
PostToolUse: [],
|
|
6502
|
+
...parsed.hooks ?? {}
|
|
6503
|
+
}
|
|
6504
|
+
};
|
|
6505
|
+
} catch {
|
|
6506
|
+
}
|
|
6507
|
+
const filteredHooks = (existing.hooks.PostToolUse ?? []).filter(
|
|
6508
|
+
(h) => h.matcher !== HOOK_MATCHER
|
|
6509
|
+
);
|
|
6510
|
+
const newEntry = {
|
|
6511
|
+
matcher: HOOK_MATCHER,
|
|
6512
|
+
hooks: [{ type: "command", command: hookCommand }]
|
|
6513
|
+
};
|
|
6514
|
+
return {
|
|
6515
|
+
...existing,
|
|
6516
|
+
hooks: {
|
|
6517
|
+
...existing.hooks,
|
|
6518
|
+
PostToolUse: [...filteredHooks, newEntry]
|
|
6519
|
+
}
|
|
6520
|
+
};
|
|
6521
|
+
}
|
|
6522
|
+
async function installPostToolUseHook(worktreePath, workerName2) {
|
|
6523
|
+
assertSafeWorkerName(workerName2);
|
|
6524
|
+
if (isHookPaused(worktreePath)) {
|
|
6525
|
+
return;
|
|
6526
|
+
}
|
|
6527
|
+
const claudeDir = (0, import_path21.join)(worktreePath, ".claude");
|
|
6528
|
+
await (0, import_promises8.mkdir)(claudeDir, { recursive: true });
|
|
6529
|
+
const settingsPath = (0, import_path21.join)(claudeDir, "settings.json");
|
|
6530
|
+
const hookCommand = buildHookCommand(workerName2);
|
|
6531
|
+
const merged = await mergeSettingsWithHook(settingsPath, hookCommand);
|
|
6532
|
+
await (0, import_promises8.writeFile)(settingsPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
|
|
6533
|
+
}
|
|
6534
|
+
async function pauseHookViaSentinel(worktreePath) {
|
|
6535
|
+
const sentinelPath = (0, import_path21.join)(worktreePath, SENTINEL_FILENAME);
|
|
6536
|
+
await (0, import_promises8.mkdir)((0, import_path21.dirname)(sentinelPath), { recursive: true });
|
|
6537
|
+
await (0, import_promises8.writeFile)(sentinelPath, "", "utf-8");
|
|
6538
|
+
}
|
|
6539
|
+
async function resumeHookViaSentinel(worktreePath) {
|
|
6540
|
+
const sentinelPath = (0, import_path21.join)(worktreePath, SENTINEL_FILENAME);
|
|
6541
|
+
try {
|
|
6542
|
+
await (0, import_promises8.unlink)(sentinelPath);
|
|
6543
|
+
} catch {
|
|
6544
|
+
}
|
|
6545
|
+
}
|
|
6546
|
+
function isHookPaused(worktreePath) {
|
|
6547
|
+
return (0, import_fs19.existsSync)((0, import_path21.join)(worktreePath, SENTINEL_FILENAME));
|
|
6548
|
+
}
|
|
6549
|
+
function startFallbackPoller(worktreePath, workerName2, opts) {
|
|
6550
|
+
assertSafeWorkerName(workerName2);
|
|
6551
|
+
const debounceMs = opts?.intervalMs ?? DEFAULT_POLL_DEBOUNCE_MS;
|
|
6552
|
+
let debounceTimer = null;
|
|
6553
|
+
let stopped = false;
|
|
6554
|
+
const runAutoCommit = () => {
|
|
6555
|
+
if (stopped) return;
|
|
6556
|
+
if (isHookPaused(worktreePath)) return;
|
|
6557
|
+
const cmd = buildHookCommand(workerName2);
|
|
6558
|
+
(0, import_child_process5.exec)(cmd, { cwd: worktreePath }, (_err) => {
|
|
6559
|
+
});
|
|
6560
|
+
};
|
|
6561
|
+
const scheduleDebounce = () => {
|
|
6562
|
+
if (stopped) return;
|
|
6563
|
+
if (debounceTimer !== null) {
|
|
6564
|
+
clearTimeout(debounceTimer);
|
|
6565
|
+
}
|
|
6566
|
+
debounceTimer = setTimeout(() => {
|
|
6567
|
+
debounceTimer = null;
|
|
6568
|
+
runAutoCommit();
|
|
6569
|
+
}, debounceMs);
|
|
6570
|
+
};
|
|
6571
|
+
const watcher = (0, import_fs19.watch)(worktreePath, { recursive: true }, (eventType, filename) => {
|
|
6572
|
+
if (stopped) return;
|
|
6573
|
+
if (filename && (filename.startsWith(".git") || filename.startsWith(".git/"))) return;
|
|
6574
|
+
scheduleDebounce();
|
|
6575
|
+
});
|
|
6576
|
+
return {
|
|
6577
|
+
stop() {
|
|
6578
|
+
stopped = true;
|
|
6579
|
+
if (debounceTimer !== null) {
|
|
6580
|
+
clearTimeout(debounceTimer);
|
|
6581
|
+
debounceTimer = null;
|
|
6582
|
+
}
|
|
6583
|
+
watcher.close();
|
|
6584
|
+
}
|
|
6585
|
+
};
|
|
6586
|
+
}
|
|
6587
|
+
async function installCommitCadence(ctx) {
|
|
6588
|
+
if (!ctx.enabled) {
|
|
6589
|
+
return { method: "none" };
|
|
6590
|
+
}
|
|
6591
|
+
if (ctx.agentType === "claude") {
|
|
6592
|
+
await installPostToolUseHook(ctx.worktreePath, ctx.workerName);
|
|
6593
|
+
return { method: "hook" };
|
|
6594
|
+
}
|
|
6595
|
+
return { method: "fallback-poll" };
|
|
6596
|
+
}
|
|
6597
|
+
async function uninstallCommitCadence(ctx) {
|
|
6598
|
+
if (ctx.agentType !== "claude") return;
|
|
6599
|
+
const settingsPath = (0, import_path21.join)(ctx.worktreePath, ".claude", "settings.json");
|
|
6600
|
+
try {
|
|
6601
|
+
const raw = await (0, import_promises8.readFile)(settingsPath, "utf-8");
|
|
6602
|
+
const parsed = JSON.parse(raw);
|
|
6603
|
+
const filtered = (parsed.hooks?.PostToolUse ?? []).filter(
|
|
6604
|
+
(h) => h.matcher !== HOOK_MATCHER
|
|
6605
|
+
);
|
|
6606
|
+
const updated = {
|
|
6607
|
+
...parsed,
|
|
6608
|
+
hooks: {
|
|
6609
|
+
...parsed.hooks,
|
|
6610
|
+
PostToolUse: filtered
|
|
6611
|
+
}
|
|
6612
|
+
};
|
|
6613
|
+
await (0, import_promises8.writeFile)(settingsPath, JSON.stringify(updated, null, 2) + "\n", "utf-8");
|
|
6614
|
+
} catch {
|
|
6615
|
+
}
|
|
6616
|
+
}
|
|
6617
|
+
|
|
6618
|
+
// src/team/merge-orchestrator.ts
|
|
6619
|
+
var DEFAULT_POLL_INTERVAL_MS = 1e3;
|
|
6620
|
+
var DEFAULT_DRAIN_TIMEOUT_MS = 1e4;
|
|
6621
|
+
function mergerWorktreePathFor(repoRoot, teamName) {
|
|
6622
|
+
return (0, import_node_path2.join)(repoRoot, ".omcp", "team", sanitizeName(teamName), "merger");
|
|
6623
|
+
}
|
|
6624
|
+
function persistedStatePath(repoRoot, teamName) {
|
|
6625
|
+
return (0, import_node_path2.join)(
|
|
6626
|
+
repoRoot,
|
|
6627
|
+
".omcp",
|
|
6628
|
+
"state",
|
|
6629
|
+
"team",
|
|
6630
|
+
sanitizeName(teamName),
|
|
6631
|
+
"auto-merge-state.json"
|
|
6632
|
+
);
|
|
6633
|
+
}
|
|
6634
|
+
function teardownAuditPath(repoRoot, teamName) {
|
|
6635
|
+
return (0, import_node_path2.join)(
|
|
6636
|
+
repoRoot,
|
|
6637
|
+
".omcp",
|
|
6638
|
+
"state",
|
|
6639
|
+
"team",
|
|
6640
|
+
sanitizeName(teamName),
|
|
6641
|
+
"teardown-audit.jsonl"
|
|
6642
|
+
);
|
|
6643
|
+
}
|
|
6644
|
+
function orchestratorEventLogPath(repoRoot, teamName) {
|
|
6645
|
+
return (0, import_node_path2.join)(
|
|
6646
|
+
repoRoot,
|
|
6647
|
+
".omcp",
|
|
6648
|
+
"state",
|
|
6649
|
+
"team",
|
|
6650
|
+
sanitizeName(teamName),
|
|
6651
|
+
"orchestrator-events.jsonl"
|
|
6652
|
+
);
|
|
6653
|
+
}
|
|
6654
|
+
function assertLeaderBranchAllowed(leaderBranch) {
|
|
6655
|
+
const stripped = leaderBranch.replace(/^refs\/heads\//i, "").toLowerCase();
|
|
6656
|
+
if (stripped === "main" || stripped === "master") {
|
|
6657
|
+
throw new Error("auto-merge refuses main/master leader branch \u2014 use a feature branch");
|
|
6658
|
+
}
|
|
6659
|
+
}
|
|
6660
|
+
function assertRuntimeV2Gate() {
|
|
6661
|
+
if (!isRuntimeV2Enabled()) {
|
|
6662
|
+
throw new Error("auto-merge requires runtime v2 (OMC_RUNTIME_V2 is explicitly disabled).");
|
|
6663
|
+
}
|
|
6664
|
+
}
|
|
6665
|
+
async function appendEvent(repoRoot, teamName, event) {
|
|
6666
|
+
const path4 = orchestratorEventLogPath(repoRoot, teamName);
|
|
6667
|
+
await (0, import_promises9.mkdir)((0, import_node_path2.dirname)(path4), { recursive: true });
|
|
6668
|
+
const full = {
|
|
6669
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6670
|
+
team: teamName,
|
|
6671
|
+
...event
|
|
6672
|
+
};
|
|
6673
|
+
await (0, import_promises9.appendFile)(path4, `${JSON.stringify(full)}
|
|
6674
|
+
`, "utf-8");
|
|
6675
|
+
}
|
|
6676
|
+
function createMutex() {
|
|
6677
|
+
let lock = Promise.resolve();
|
|
6678
|
+
return (fn) => {
|
|
6679
|
+
const next = lock.then(fn, fn);
|
|
6680
|
+
lock = next.catch(() => void 0);
|
|
6681
|
+
return next;
|
|
6682
|
+
};
|
|
6683
|
+
}
|
|
6684
|
+
function gitRevParseHead(repoRoot, branch) {
|
|
6685
|
+
return (0, import_node_child_process3.execFileSync)("git", ["rev-parse", `refs/heads/${branch}`], {
|
|
6686
|
+
cwd: repoRoot,
|
|
6687
|
+
encoding: "utf-8",
|
|
6688
|
+
stdio: "pipe"
|
|
6689
|
+
}).trim();
|
|
6690
|
+
}
|
|
6691
|
+
function gitPath(worktreePath, gitPathName) {
|
|
6692
|
+
try {
|
|
6693
|
+
const resolved = (0, import_node_child_process3.execFileSync)("git", ["rev-parse", "--git-path", gitPathName], {
|
|
6694
|
+
cwd: worktreePath,
|
|
6695
|
+
encoding: "utf-8",
|
|
6696
|
+
stdio: "pipe"
|
|
6697
|
+
}).trim();
|
|
6698
|
+
if (resolved) return resolved;
|
|
6699
|
+
} catch {
|
|
6700
|
+
}
|
|
6701
|
+
return (0, import_node_path2.join)(worktreePath, ".git", gitPathName);
|
|
6702
|
+
}
|
|
6703
|
+
function isRebaseInProgress(worktreePath) {
|
|
6704
|
+
return (0, import_node_fs2.existsSync)(gitPath(worktreePath, "rebase-merge"));
|
|
6705
|
+
}
|
|
6706
|
+
function isWorktreeRegistered(repoRoot, wtPath) {
|
|
6707
|
+
try {
|
|
6708
|
+
const out = (0, import_node_child_process3.execFileSync)("git", ["worktree", "list", "--porcelain"], {
|
|
6709
|
+
cwd: repoRoot,
|
|
6710
|
+
encoding: "utf-8",
|
|
6711
|
+
stdio: "pipe"
|
|
6712
|
+
});
|
|
6713
|
+
for (const line of out.split("\n")) {
|
|
6714
|
+
if (line.startsWith("worktree ")) {
|
|
6715
|
+
if (line.slice("worktree ".length).trim() === wtPath) return true;
|
|
6716
|
+
}
|
|
6717
|
+
}
|
|
6718
|
+
} catch {
|
|
6719
|
+
}
|
|
6720
|
+
return false;
|
|
6721
|
+
}
|
|
6722
|
+
function ensureMergerWorktree(repoRoot, mergerPath, leaderBranch) {
|
|
6723
|
+
ensureDirWithMode((0, import_node_path2.dirname)(mergerPath));
|
|
6724
|
+
if ((0, import_node_fs2.existsSync)(mergerPath) && isWorktreeRegistered(repoRoot, mergerPath)) {
|
|
6725
|
+
return;
|
|
6726
|
+
}
|
|
6727
|
+
(0, import_node_child_process3.execFileSync)("git", ["worktree", "add", "--force", mergerPath, leaderBranch], {
|
|
6728
|
+
cwd: repoRoot,
|
|
6729
|
+
stdio: "pipe"
|
|
6730
|
+
});
|
|
6731
|
+
}
|
|
6732
|
+
function preflightMergerWorktree(mergerPath, leaderBranch) {
|
|
6733
|
+
try {
|
|
6734
|
+
(0, import_node_child_process3.execFileSync)("git", ["fetch", "--no-tags", "origin", leaderBranch], {
|
|
6735
|
+
cwd: mergerPath,
|
|
6736
|
+
stdio: "pipe"
|
|
6737
|
+
});
|
|
6738
|
+
} catch {
|
|
6739
|
+
}
|
|
6740
|
+
(0, import_node_child_process3.execFileSync)("git", ["reset", "--hard", leaderBranch], {
|
|
6741
|
+
cwd: mergerPath,
|
|
6742
|
+
stdio: "pipe"
|
|
6743
|
+
});
|
|
6744
|
+
}
|
|
6745
|
+
function parseUUFiles(porcelainOutput) {
|
|
6746
|
+
const files = [];
|
|
6747
|
+
for (const line of porcelainOutput.split("\n")) {
|
|
6748
|
+
if (line.startsWith("UU ")) {
|
|
6749
|
+
files.push(line.slice(3).trim());
|
|
6750
|
+
} else if (line.startsWith("AA ") || line.startsWith("DD ")) {
|
|
6751
|
+
files.push(line.slice(3).trim());
|
|
6752
|
+
}
|
|
6753
|
+
}
|
|
6754
|
+
return files;
|
|
6755
|
+
}
|
|
6756
|
+
async function startMergeOrchestrator(config) {
|
|
6757
|
+
assertRuntimeV2Gate();
|
|
6758
|
+
assertLeaderBranchAllowed(config.leaderBranch);
|
|
6759
|
+
validateBranchName(config.leaderBranch);
|
|
6760
|
+
const pollIntervalMs = config.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
6761
|
+
const drainTimeoutMs = config.drainTimeoutMs ?? DEFAULT_DRAIN_TIMEOUT_MS;
|
|
6762
|
+
const mergerPath = mergerWorktreePathFor(config.repoRoot, config.teamName);
|
|
6763
|
+
validateResolvedPath(mergerPath, config.repoRoot);
|
|
6764
|
+
ensureMergerWorktree(config.repoRoot, mergerPath, config.leaderBranch);
|
|
6765
|
+
await ensureLeaderInbox(config.teamName, config.cwd);
|
|
6766
|
+
const persistedPath = persistedStatePath(config.repoRoot, config.teamName);
|
|
6767
|
+
let persisted = { lastShas: {} };
|
|
6768
|
+
if ((0, import_node_fs2.existsSync)(persistedPath)) {
|
|
6769
|
+
try {
|
|
6770
|
+
const { readFileSync: readFileSync12 } = await import("node:fs");
|
|
6771
|
+
persisted = JSON.parse(readFileSync12(persistedPath, "utf-8"));
|
|
6772
|
+
} catch {
|
|
6773
|
+
persisted = { lastShas: {} };
|
|
6774
|
+
}
|
|
6775
|
+
}
|
|
6776
|
+
const workers = /* @__PURE__ */ new Map();
|
|
6777
|
+
const pausedWorkers = /* @__PURE__ */ new Set();
|
|
6778
|
+
const mutex = createMutex();
|
|
6779
|
+
let stopped = false;
|
|
6780
|
+
function persistState() {
|
|
6781
|
+
const payload = {
|
|
6782
|
+
lastShas: Object.fromEntries(
|
|
6783
|
+
Array.from(workers.values()).map((w) => [w.workerName, w.lastObservedSha])
|
|
6784
|
+
)
|
|
6785
|
+
};
|
|
6786
|
+
atomicWriteJson(persistedPath, payload);
|
|
6787
|
+
}
|
|
6788
|
+
async function fanOutRebase(triggeringWorker) {
|
|
6789
|
+
for (const other of workers.values()) {
|
|
6790
|
+
if (other.workerName === triggeringWorker) continue;
|
|
6791
|
+
const wtPath = other.workerWorktreePath;
|
|
6792
|
+
if (isRebaseInProgress(wtPath)) {
|
|
6793
|
+
await appendEvent(config.repoRoot, config.teamName, {
|
|
6794
|
+
type: "rebase_skipped_in_progress",
|
|
6795
|
+
worker: other.workerName,
|
|
6796
|
+
reason: "rebase-already-in-progress"
|
|
6797
|
+
});
|
|
6798
|
+
continue;
|
|
6799
|
+
}
|
|
6800
|
+
await appendEvent(config.repoRoot, config.teamName, {
|
|
6801
|
+
type: "rebase_triggered",
|
|
6802
|
+
worker: other.workerName
|
|
6803
|
+
});
|
|
6804
|
+
await pauseHookViaSentinel(wtPath);
|
|
6805
|
+
pausedWorkers.add(other.workerName);
|
|
6806
|
+
try {
|
|
6807
|
+
(0, import_node_child_process3.execFileSync)("git", ["fetch", "--no-tags", "origin", config.leaderBranch], {
|
|
6808
|
+
cwd: wtPath,
|
|
6809
|
+
stdio: "pipe"
|
|
6810
|
+
});
|
|
6811
|
+
} catch {
|
|
6812
|
+
}
|
|
6813
|
+
try {
|
|
6814
|
+
(0, import_node_child_process3.execFileSync)("git", ["rebase", config.leaderBranch], {
|
|
6815
|
+
cwd: wtPath,
|
|
6816
|
+
stdio: "pipe"
|
|
6817
|
+
});
|
|
6818
|
+
await resumeHookViaSentinel(wtPath);
|
|
6819
|
+
pausedWorkers.delete(other.workerName);
|
|
6820
|
+
await appendEvent(config.repoRoot, config.teamName, {
|
|
6821
|
+
type: "rebase_succeeded",
|
|
6822
|
+
worker: other.workerName
|
|
6823
|
+
});
|
|
6824
|
+
} catch {
|
|
6825
|
+
let conflictingFiles = [];
|
|
6826
|
+
try {
|
|
6827
|
+
const status = (0, import_node_child_process3.execFileSync)("git", ["status", "--porcelain"], {
|
|
6828
|
+
cwd: wtPath,
|
|
6829
|
+
encoding: "utf-8",
|
|
6830
|
+
stdio: "pipe"
|
|
6831
|
+
});
|
|
6832
|
+
conflictingFiles = parseUUFiles(status);
|
|
6833
|
+
} catch {
|
|
6834
|
+
conflictingFiles = ["(rebase status unavailable)"];
|
|
6835
|
+
}
|
|
6836
|
+
const baseSha = (() => {
|
|
6837
|
+
try {
|
|
6838
|
+
return (0, import_node_child_process3.execFileSync)("git", ["rev-parse", `refs/heads/${config.leaderBranch}`], {
|
|
6839
|
+
cwd: config.repoRoot,
|
|
6840
|
+
encoding: "utf-8",
|
|
6841
|
+
stdio: "pipe"
|
|
6842
|
+
}).trim();
|
|
6843
|
+
} catch {
|
|
6844
|
+
return "unknown";
|
|
6845
|
+
}
|
|
6846
|
+
})();
|
|
6847
|
+
const message = formatRebaseConflictForWorker({
|
|
6848
|
+
workerName: other.workerName,
|
|
6849
|
+
workerBranch: other.workerBranch,
|
|
6850
|
+
leaderBranch: config.leaderBranch,
|
|
6851
|
+
conflictingFiles,
|
|
6852
|
+
baseSha,
|
|
6853
|
+
worktreePath: wtPath,
|
|
6854
|
+
observedAt: Date.now()
|
|
6855
|
+
});
|
|
6856
|
+
try {
|
|
6857
|
+
await appendToInbox(config.teamName, other.workerName, message, config.cwd);
|
|
6858
|
+
} catch {
|
|
6859
|
+
}
|
|
6860
|
+
await appendEvent(config.repoRoot, config.teamName, {
|
|
6861
|
+
type: "rebase_conflict",
|
|
6862
|
+
worker: other.workerName,
|
|
6863
|
+
data: { conflictingFiles }
|
|
6864
|
+
});
|
|
6865
|
+
}
|
|
6866
|
+
}
|
|
6867
|
+
}
|
|
6868
|
+
async function attemptMergeForWorker(entry) {
|
|
6869
|
+
await mutex(async () => {
|
|
6870
|
+
const targetSha = entry.lastObservedSha;
|
|
6871
|
+
await appendEvent(config.repoRoot, config.teamName, {
|
|
6872
|
+
type: "merge_attempted",
|
|
6873
|
+
worker: entry.workerName,
|
|
6874
|
+
data: { targetSha }
|
|
6875
|
+
});
|
|
6876
|
+
try {
|
|
6877
|
+
preflightMergerWorktree(mergerPath, config.leaderBranch);
|
|
6878
|
+
} catch (err) {
|
|
6879
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
6880
|
+
entry.consecutiveFailures += 1;
|
|
6881
|
+
await appendEvent(config.repoRoot, config.teamName, {
|
|
6882
|
+
type: "merge_conflict",
|
|
6883
|
+
worker: entry.workerName,
|
|
6884
|
+
reason: `preflight_failed:${reason}`
|
|
6885
|
+
});
|
|
6886
|
+
return;
|
|
6887
|
+
}
|
|
6888
|
+
const conflicts = checkMergeConflicts(
|
|
6889
|
+
entry.workerBranch,
|
|
6890
|
+
config.leaderBranch,
|
|
6891
|
+
mergerPath
|
|
6892
|
+
);
|
|
6893
|
+
if (conflicts.length > 0) {
|
|
6894
|
+
let mergeBaseSha = "unknown";
|
|
6895
|
+
try {
|
|
6896
|
+
mergeBaseSha = (0, import_node_child_process3.execFileSync)(
|
|
6897
|
+
"git",
|
|
6898
|
+
["merge-base", config.leaderBranch, entry.workerBranch],
|
|
6899
|
+
{ cwd: mergerPath, encoding: "utf-8", stdio: "pipe" }
|
|
6900
|
+
).trim();
|
|
6901
|
+
} catch {
|
|
6902
|
+
}
|
|
6903
|
+
const message = formatMergeConflictForLeader({
|
|
6904
|
+
workerName: entry.workerName,
|
|
6905
|
+
workerBranch: entry.workerBranch,
|
|
6906
|
+
leaderBranch: config.leaderBranch,
|
|
6907
|
+
conflictingFiles: conflicts,
|
|
6908
|
+
mergeBaseSha,
|
|
6909
|
+
observedAt: Date.now()
|
|
6910
|
+
});
|
|
6911
|
+
try {
|
|
6912
|
+
await appendToLeaderInbox(config.teamName, message, config.cwd);
|
|
6913
|
+
} catch {
|
|
6914
|
+
}
|
|
6915
|
+
await appendEvent(config.repoRoot, config.teamName, {
|
|
6916
|
+
type: "merge_conflict",
|
|
6917
|
+
worker: entry.workerName,
|
|
6918
|
+
data: { conflictingFiles: conflicts, mergeBaseSha }
|
|
6919
|
+
});
|
|
6920
|
+
entry.consecutiveFailures += 1;
|
|
6921
|
+
return;
|
|
6922
|
+
}
|
|
6923
|
+
const result = mergeWorkerBranch(
|
|
6924
|
+
entry.workerBranch,
|
|
6925
|
+
config.leaderBranch,
|
|
6926
|
+
mergerPath
|
|
6927
|
+
);
|
|
6928
|
+
if (!result.success) {
|
|
6929
|
+
const message = formatMergeConflictForLeader({
|
|
6930
|
+
workerName: entry.workerName,
|
|
6931
|
+
workerBranch: entry.workerBranch,
|
|
6932
|
+
leaderBranch: config.leaderBranch,
|
|
6933
|
+
conflictingFiles: result.conflicts.length > 0 ? result.conflicts : ["(merge failed after clean check)"],
|
|
6934
|
+
mergeBaseSha: "unknown",
|
|
6935
|
+
observedAt: Date.now()
|
|
6936
|
+
});
|
|
6937
|
+
try {
|
|
6938
|
+
await appendToLeaderInbox(config.teamName, message, config.cwd);
|
|
6939
|
+
} catch {
|
|
6940
|
+
}
|
|
6941
|
+
await appendEvent(config.repoRoot, config.teamName, {
|
|
6942
|
+
type: "merge_conflict",
|
|
6943
|
+
worker: entry.workerName,
|
|
6944
|
+
data: { conflictingFiles: result.conflicts }
|
|
6945
|
+
});
|
|
6946
|
+
entry.consecutiveFailures += 1;
|
|
6947
|
+
return;
|
|
6948
|
+
}
|
|
6949
|
+
entry.lastMergedSha = targetSha;
|
|
6950
|
+
entry.consecutiveFailures = 0;
|
|
6951
|
+
await appendEvent(config.repoRoot, config.teamName, {
|
|
6952
|
+
type: "merge_succeeded",
|
|
6953
|
+
worker: entry.workerName,
|
|
6954
|
+
data: { mergeCommit: result.mergeCommit, targetSha }
|
|
6955
|
+
});
|
|
6956
|
+
if (stopped) return;
|
|
6957
|
+
await fanOutRebase(entry.workerName);
|
|
6958
|
+
});
|
|
6959
|
+
}
|
|
6960
|
+
async function runPollOnce() {
|
|
6961
|
+
if (stopped) return;
|
|
6962
|
+
for (const entry of workers.values()) {
|
|
6963
|
+
const skipModulo = Math.min(30, Math.pow(2, entry.consecutiveFailures));
|
|
6964
|
+
if (skipModulo > 1 && pollTickCount % skipModulo !== 0) {
|
|
6965
|
+
continue;
|
|
6966
|
+
}
|
|
6967
|
+
if (pausedWorkers.has(entry.workerName)) {
|
|
6968
|
+
if (!isRebaseInProgress(entry.workerWorktreePath)) {
|
|
6969
|
+
await handleRebaseResolution(entry);
|
|
6970
|
+
} else {
|
|
6971
|
+
continue;
|
|
6972
|
+
}
|
|
6973
|
+
}
|
|
6974
|
+
let currentSha;
|
|
6975
|
+
try {
|
|
6976
|
+
currentSha = gitRevParseHead(config.repoRoot, entry.workerBranch);
|
|
6977
|
+
} catch (err) {
|
|
6978
|
+
entry.consecutiveFailures += 1;
|
|
6979
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
6980
|
+
await appendEvent(config.repoRoot, config.teamName, {
|
|
6981
|
+
type: "commit_observed",
|
|
6982
|
+
worker: entry.workerName,
|
|
6983
|
+
reason: `rev_parse_failed:${reason}`
|
|
6984
|
+
});
|
|
6985
|
+
continue;
|
|
6986
|
+
}
|
|
6987
|
+
if (currentSha && currentSha !== entry.lastObservedSha) {
|
|
6988
|
+
entry.lastObservedSha = currentSha;
|
|
6989
|
+
try {
|
|
6990
|
+
persistState();
|
|
6991
|
+
} catch {
|
|
6992
|
+
}
|
|
6993
|
+
await appendEvent(config.repoRoot, config.teamName, {
|
|
6994
|
+
type: "commit_observed",
|
|
6995
|
+
worker: entry.workerName,
|
|
6996
|
+
data: { sha: currentSha }
|
|
6997
|
+
});
|
|
6998
|
+
try {
|
|
6999
|
+
await attemptMergeForWorker(entry);
|
|
7000
|
+
} catch (err) {
|
|
7001
|
+
entry.consecutiveFailures += 1;
|
|
7002
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
7003
|
+
await appendEvent(config.repoRoot, config.teamName, {
|
|
7004
|
+
type: "merge_conflict",
|
|
7005
|
+
worker: entry.workerName,
|
|
7006
|
+
reason: `merge_threw:${reason}`
|
|
7007
|
+
});
|
|
7008
|
+
}
|
|
7009
|
+
}
|
|
7010
|
+
}
|
|
7011
|
+
}
|
|
7012
|
+
async function handleRebaseResolution(entry) {
|
|
7013
|
+
pausedWorkers.delete(entry.workerName);
|
|
7014
|
+
try {
|
|
7015
|
+
const status = (0, import_node_child_process3.execFileSync)("git", ["status", "--porcelain"], {
|
|
7016
|
+
cwd: entry.workerWorktreePath,
|
|
7017
|
+
encoding: "utf-8",
|
|
7018
|
+
stdio: "pipe"
|
|
7019
|
+
}).trim();
|
|
7020
|
+
if (status.length > 0) {
|
|
7021
|
+
const dirtyFiles = status.split("\n").map((l) => l.trim().replace(/^\S+\s+/, "")).filter((s) => s.length > 0);
|
|
7022
|
+
const audit = `## Auto-commit audit: the following files were modified during rebase pause and will be folded into the next auto-commit:
|
|
7023
|
+
${dirtyFiles.map((f) => `- \`${f}\``).join("\n")}`;
|
|
7024
|
+
try {
|
|
7025
|
+
await appendToInbox(config.teamName, entry.workerName, audit, config.cwd);
|
|
7026
|
+
} catch {
|
|
7027
|
+
}
|
|
7028
|
+
}
|
|
7029
|
+
} catch {
|
|
7030
|
+
}
|
|
7031
|
+
await resumeHookViaSentinel(entry.workerWorktreePath);
|
|
7032
|
+
await appendEvent(config.repoRoot, config.teamName, {
|
|
7033
|
+
type: "rebase_resolved",
|
|
7034
|
+
worker: entry.workerName
|
|
7035
|
+
});
|
|
7036
|
+
}
|
|
7037
|
+
let pollTickCount = 0;
|
|
7038
|
+
const interval = setInterval(() => {
|
|
7039
|
+
pollTickCount += 1;
|
|
7040
|
+
void runPollOnce().catch(() => {
|
|
7041
|
+
});
|
|
7042
|
+
}, pollIntervalMs);
|
|
7043
|
+
if (typeof interval.unref === "function") interval.unref();
|
|
7044
|
+
return {
|
|
7045
|
+
async registerWorker(workerName2) {
|
|
7046
|
+
if (workers.has(workerName2)) return;
|
|
7047
|
+
const workerBranch = getBranchName(config.teamName, workerName2);
|
|
7048
|
+
validateBranchName(workerBranch);
|
|
7049
|
+
const wtPath = getWorktreePath(config.repoRoot, config.teamName, workerName2);
|
|
7050
|
+
let seedSha = persisted.lastShas[workerName2] ?? "";
|
|
7051
|
+
if (!seedSha) {
|
|
7052
|
+
try {
|
|
7053
|
+
seedSha = gitRevParseHead(config.repoRoot, workerBranch);
|
|
7054
|
+
} catch {
|
|
7055
|
+
seedSha = "";
|
|
7056
|
+
}
|
|
7057
|
+
}
|
|
7058
|
+
workers.set(workerName2, {
|
|
7059
|
+
workerName: workerName2,
|
|
7060
|
+
workerBranch,
|
|
7061
|
+
workerWorktreePath: wtPath,
|
|
7062
|
+
lastObservedSha: seedSha,
|
|
7063
|
+
lastMergedSha: seedSha,
|
|
7064
|
+
consecutiveFailures: 0
|
|
7065
|
+
});
|
|
7066
|
+
try {
|
|
7067
|
+
persistState();
|
|
7068
|
+
} catch {
|
|
7069
|
+
}
|
|
7070
|
+
},
|
|
7071
|
+
async unregisterWorker(workerName2) {
|
|
7072
|
+
workers.delete(workerName2);
|
|
7073
|
+
pausedWorkers.delete(workerName2);
|
|
7074
|
+
try {
|
|
7075
|
+
persistState();
|
|
7076
|
+
} catch {
|
|
7077
|
+
}
|
|
7078
|
+
},
|
|
7079
|
+
async pollOnce() {
|
|
7080
|
+
await runPollOnce();
|
|
7081
|
+
},
|
|
7082
|
+
async drainAndStop() {
|
|
7083
|
+
stopped = true;
|
|
7084
|
+
clearInterval(interval);
|
|
7085
|
+
const start = Date.now();
|
|
7086
|
+
const unmerged = [];
|
|
7087
|
+
const candidates = Array.from(workers.values()).filter(
|
|
7088
|
+
(w) => w.lastObservedSha && w.lastObservedSha !== w.lastMergedSha
|
|
7089
|
+
);
|
|
7090
|
+
for (const entry of candidates) {
|
|
7091
|
+
const remaining = drainTimeoutMs - (Date.now() - start);
|
|
7092
|
+
if (remaining <= 0) {
|
|
7093
|
+
unmerged.push({ workerName: entry.workerName, reason: "drain-timeout" });
|
|
7094
|
+
continue;
|
|
7095
|
+
}
|
|
7096
|
+
const merged = await Promise.race([
|
|
7097
|
+
(async () => {
|
|
7098
|
+
try {
|
|
7099
|
+
await attemptMergeForWorker(entry);
|
|
7100
|
+
return true;
|
|
7101
|
+
} catch {
|
|
7102
|
+
return false;
|
|
7103
|
+
}
|
|
7104
|
+
})(),
|
|
7105
|
+
new Promise((resolve6) => {
|
|
7106
|
+
const t = setTimeout(() => resolve6(false), remaining);
|
|
7107
|
+
if (typeof t.unref === "function") t.unref();
|
|
7108
|
+
})
|
|
7109
|
+
]);
|
|
7110
|
+
if (!merged || entry.lastMergedSha !== entry.lastObservedSha) {
|
|
7111
|
+
unmerged.push({
|
|
7112
|
+
workerName: entry.workerName,
|
|
7113
|
+
reason: merged ? "merge-conflict" : "drain-timeout"
|
|
7114
|
+
});
|
|
7115
|
+
}
|
|
7116
|
+
}
|
|
7117
|
+
if (unmerged.length > 0) {
|
|
7118
|
+
const auditPath = teardownAuditPath(config.repoRoot, config.teamName);
|
|
7119
|
+
await (0, import_promises9.mkdir)((0, import_node_path2.dirname)(auditPath), { recursive: true });
|
|
7120
|
+
for (const u of unmerged) {
|
|
7121
|
+
const row = JSON.stringify({
|
|
7122
|
+
type: "unmerged_at_shutdown",
|
|
7123
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7124
|
+
team: config.teamName,
|
|
7125
|
+
worker: u.workerName,
|
|
7126
|
+
reason: u.reason
|
|
7127
|
+
});
|
|
7128
|
+
try {
|
|
7129
|
+
await (0, import_promises9.appendFile)(auditPath, `${row}
|
|
7130
|
+
`, "utf-8");
|
|
7131
|
+
} catch {
|
|
7132
|
+
}
|
|
7133
|
+
}
|
|
7134
|
+
const message = `## Teardown audit: unmerged worker branches at shutdown
|
|
7135
|
+
|
|
7136
|
+
${unmerged.map((u) => `- ${u.workerName}: ${u.reason}`).join("\n")}`;
|
|
7137
|
+
try {
|
|
7138
|
+
await appendToLeaderInbox(config.teamName, message, config.cwd);
|
|
7139
|
+
} catch {
|
|
7140
|
+
}
|
|
7141
|
+
}
|
|
7142
|
+
return { unmerged };
|
|
7143
|
+
},
|
|
7144
|
+
getState() {
|
|
7145
|
+
return {
|
|
7146
|
+
workers: Array.from(workers.keys()),
|
|
7147
|
+
lastShas: Object.fromEntries(
|
|
7148
|
+
Array.from(workers.values()).map((w) => [w.workerName, w.lastObservedSha])
|
|
7149
|
+
),
|
|
7150
|
+
mergerWorktreePath: mergerPath
|
|
7151
|
+
};
|
|
7152
|
+
}
|
|
7153
|
+
};
|
|
7154
|
+
}
|
|
7155
|
+
async function recoverFromRestart(config) {
|
|
7156
|
+
const persistedPath = persistedStatePath(config.repoRoot, config.teamName);
|
|
7157
|
+
let persistedShasLoaded = 0;
|
|
7158
|
+
if ((0, import_node_fs2.existsSync)(persistedPath)) {
|
|
7159
|
+
try {
|
|
7160
|
+
const { readFileSync: readFileSync12 } = await import("node:fs");
|
|
7161
|
+
const persisted = JSON.parse(readFileSync12(persistedPath, "utf-8"));
|
|
7162
|
+
persistedShasLoaded = Object.keys(persisted.lastShas ?? {}).length;
|
|
7163
|
+
} catch {
|
|
7164
|
+
persistedShasLoaded = 0;
|
|
7165
|
+
}
|
|
7166
|
+
}
|
|
7167
|
+
const orphanedRebases = [];
|
|
7168
|
+
let entries = [];
|
|
7169
|
+
try {
|
|
7170
|
+
entries = listTeamWorktrees(config.teamName, config.repoRoot).map((w) => ({
|
|
7171
|
+
workerName: w.workerName,
|
|
7172
|
+
path: w.path
|
|
7173
|
+
}));
|
|
7174
|
+
} catch {
|
|
7175
|
+
entries = [];
|
|
7176
|
+
}
|
|
7177
|
+
for (const entry of entries) {
|
|
7178
|
+
if (!isRebaseInProgress(entry.path)) continue;
|
|
7179
|
+
orphanedRebases.push(entry.workerName);
|
|
7180
|
+
const message = `### Runtime restart recovery \u2014 your branch is mid-rebase
|
|
7181
|
+
|
|
7182
|
+
Runtime restarted while your branch was mid-rebase onto \`${config.leaderBranch}\`.
|
|
7183
|
+
|
|
7184
|
+
**Worktree:** \`${entry.path}\`
|
|
7185
|
+
|
|
7186
|
+
Cadence remains paused. Resolve and \`git rebase --continue\`, or \`git rebase --abort\` to bail.
|
|
7187
|
+
Cadence resumes once the git rebase state is gone.`;
|
|
7188
|
+
try {
|
|
7189
|
+
await appendToInbox(config.teamName, entry.workerName, message, config.cwd);
|
|
7190
|
+
} catch {
|
|
7191
|
+
}
|
|
7192
|
+
}
|
|
7193
|
+
if (orphanedRebases.length > 0 || persistedShasLoaded > 0) {
|
|
7194
|
+
try {
|
|
7195
|
+
await appendEvent(config.repoRoot, config.teamName, {
|
|
7196
|
+
type: "restart_recovery",
|
|
7197
|
+
data: { orphanedRebases, persistedShasLoaded }
|
|
7198
|
+
});
|
|
7199
|
+
} catch {
|
|
7200
|
+
}
|
|
7201
|
+
}
|
|
7202
|
+
return { orphanedRebases, persistedShasLoaded };
|
|
7203
|
+
}
|
|
7204
|
+
|
|
7205
|
+
// src/team/runtime-v2.ts
|
|
7206
|
+
var import_node_child_process4 = require("node:child_process");
|
|
7207
|
+
var orchestratorByTeam = /* @__PURE__ */ new Map();
|
|
7208
|
+
var cadenceByTeam = /* @__PURE__ */ new Map();
|
|
7209
|
+
function registerTeamOrchestrator(teamName, handle) {
|
|
7210
|
+
orchestratorByTeam.set(teamName, handle);
|
|
7211
|
+
}
|
|
7212
|
+
function getTeamOrchestrator(teamName) {
|
|
7213
|
+
return orchestratorByTeam.get(teamName);
|
|
7214
|
+
}
|
|
7215
|
+
function unregisterTeamOrchestrator(teamName) {
|
|
7216
|
+
orchestratorByTeam.delete(teamName);
|
|
7217
|
+
}
|
|
7218
|
+
function registerTeamCadence(teamName, context, poller) {
|
|
7219
|
+
const entry = cadenceByTeam.get(teamName) ?? { pollers: [], contexts: [] };
|
|
7220
|
+
entry.contexts.push(context);
|
|
7221
|
+
if (poller) entry.pollers.push(poller);
|
|
7222
|
+
cadenceByTeam.set(teamName, entry);
|
|
7223
|
+
}
|
|
7224
|
+
async function stopTeamCadence(teamName) {
|
|
7225
|
+
const entry = cadenceByTeam.get(teamName);
|
|
7226
|
+
if (!entry) return;
|
|
7227
|
+
cadenceByTeam.delete(teamName);
|
|
7228
|
+
for (const poller of entry.pollers) {
|
|
7229
|
+
try {
|
|
7230
|
+
poller.stop();
|
|
7231
|
+
} catch {
|
|
7232
|
+
}
|
|
7233
|
+
}
|
|
7234
|
+
for (const context of entry.contexts) {
|
|
7235
|
+
try {
|
|
7236
|
+
await uninstallCommitCadence(context);
|
|
7237
|
+
} catch {
|
|
7238
|
+
}
|
|
7239
|
+
}
|
|
7240
|
+
}
|
|
7241
|
+
function resolveLeaderBranch(cwd) {
|
|
7242
|
+
const out = (0, import_node_child_process4.execFileSync)("git", ["branch", "--show-current"], {
|
|
7243
|
+
cwd,
|
|
7244
|
+
encoding: "utf-8",
|
|
7245
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
7246
|
+
}).trim();
|
|
7247
|
+
if (!out) {
|
|
7248
|
+
throw new Error("auto-merge requires a non-detached leader branch (git branch --show-current returned empty)");
|
|
7249
|
+
}
|
|
7250
|
+
return out;
|
|
7251
|
+
}
|
|
6208
7252
|
var MONITOR_SIGNAL_STALE_MS = 3e4;
|
|
6209
7253
|
function resolveTaskAssignment(task, resolvedRouting, roleRoutingConfig, resolvedBinaryPaths, fallbackAgent) {
|
|
6210
7254
|
const canonicalRoles = new Set(CANONICAL_TEAM_ROLES);
|
|
@@ -6296,7 +7340,7 @@ function buildV2TaskInstruction(teamName, workerName2, task, taskId, cliOutputCo
|
|
|
6296
7340
|
{}
|
|
6297
7341
|
);
|
|
6298
7342
|
const completeTaskCommand = formatOmcCliInvocation(
|
|
6299
|
-
`team api transition-task-status --input '${JSON.stringify({ team_name: teamName, task_id: taskId, from: "in_progress", to: "completed", claim_token: "<claim_token>" })}' --json`
|
|
7343
|
+
`team api transition-task-status --input '${JSON.stringify({ team_name: teamName, task_id: taskId, from: "in_progress", to: "completed", claim_token: "<claim_token>", result: "Summary: <what changed>\\nVerification: <tests/checks run>\\nSubagent skip reason: worker protocol forbids nested subagents; completed focused probe in-session" })}' --json`
|
|
6300
7344
|
);
|
|
6301
7345
|
const failTaskCommand = formatOmcCliInvocation(
|
|
6302
7346
|
`team api transition-task-status --input '${JSON.stringify({ team_name: teamName, task_id: taskId, from: "in_progress", to: "failed", claim_token: "<claim_token>" })}' --json`
|
|
@@ -6311,6 +7355,7 @@ function buildV2TaskInstruction(teamName, workerName2, task, taskId, cliOutputCo
|
|
|
6311
7355
|
`2. Do the work described below.`,
|
|
6312
7356
|
`3. On completion (use claim_token from step 1):`,
|
|
6313
7357
|
` ${completeTaskCommand}`,
|
|
7358
|
+
` The result field is required for completion evidence. For broad delegated tasks, include either "Subagent skip reason: <why no nested worker was needed/allowed>" or, only when explicitly allowed by the leader, "Subagent spawn evidence: <child task names/thread ids and integrated findings>".`,
|
|
6314
7359
|
`4. On failure (use claim_token from step 1):`,
|
|
6315
7360
|
` ${failTaskCommand}`,
|
|
6316
7361
|
`5. ACK/progress replies are not a stop signal. Keep executing your assigned or next feasible work until the task is actually complete or failed, then transition and exit.`,
|
|
@@ -6347,7 +7392,7 @@ function hasWorkerStatusProgress(status, taskId) {
|
|
|
6347
7392
|
}
|
|
6348
7393
|
async function hasWorkerTaskClaimEvidence(teamName, workerName2, cwd, taskId) {
|
|
6349
7394
|
try {
|
|
6350
|
-
const raw = await (0,
|
|
7395
|
+
const raw = await (0, import_promises10.readFile)(absPath(cwd, TeamPaths.taskFile(teamName, taskId)), "utf-8");
|
|
6351
7396
|
const task = JSON.parse(raw);
|
|
6352
7397
|
return task.owner === workerName2 && ["in_progress", "completed", "failed"].includes(task.status);
|
|
6353
7398
|
} catch {
|
|
@@ -6446,6 +7491,18 @@ async function spawnV2Worker(opts) {
|
|
|
6446
7491
|
if (usePromptMode) {
|
|
6447
7492
|
launchArgs.push(...getPromptModeArgs(opts.agentType, promptModeStartupPrompt));
|
|
6448
7493
|
}
|
|
7494
|
+
if (opts.autoMerge && opts.worktreePath) {
|
|
7495
|
+
const cadenceContext = {
|
|
7496
|
+
teamName: opts.teamName,
|
|
7497
|
+
workerName: opts.workerName,
|
|
7498
|
+
worktreePath: opts.worktreePath,
|
|
7499
|
+
agentType: opts.agentType,
|
|
7500
|
+
enabled: true
|
|
7501
|
+
};
|
|
7502
|
+
const cadence = await installCommitCadence(cadenceContext);
|
|
7503
|
+
const poller = cadence.method === "fallback-poll" ? startFallbackPoller(opts.worktreePath, opts.workerName) : void 0;
|
|
7504
|
+
registerTeamCadence(opts.teamName, cadenceContext, poller);
|
|
7505
|
+
}
|
|
6449
7506
|
const paneConfig = {
|
|
6450
7507
|
teamName: opts.teamName,
|
|
6451
7508
|
workerName: opts.workerName,
|
|
@@ -6546,19 +7603,19 @@ async function rollbackUnpersistedNativeWorktreeStartup(teamName, cwd, cause) {
|
|
|
6546
7603
|
try {
|
|
6547
7604
|
const cleanup = cleanupTeamWorktrees(teamName, cwd);
|
|
6548
7605
|
if (cleanup.preserved.length === 0) {
|
|
6549
|
-
await (0,
|
|
7606
|
+
await (0, import_promises10.rm)(teamRoot, { recursive: true, force: true });
|
|
6550
7607
|
return;
|
|
6551
7608
|
}
|
|
6552
|
-
await (0,
|
|
6553
|
-
await (0,
|
|
7609
|
+
await (0, import_promises10.mkdir)(teamRoot, { recursive: true });
|
|
7610
|
+
await (0, import_promises10.writeFile)((0, import_path22.join)(teamRoot, "startup-failure.json"), JSON.stringify({
|
|
6554
7611
|
reason: "startup_failed_before_config_persisted",
|
|
6555
7612
|
error: errorMessage,
|
|
6556
7613
|
preserved: cleanup.preserved,
|
|
6557
7614
|
recorded_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
6558
7615
|
}, null, 2), "utf-8");
|
|
6559
7616
|
} catch (rollbackError) {
|
|
6560
|
-
await (0,
|
|
6561
|
-
await (0,
|
|
7617
|
+
await (0, import_promises10.mkdir)(teamRoot, { recursive: true });
|
|
7618
|
+
await (0, import_promises10.writeFile)((0, import_path22.join)(teamRoot, "startup-failure.json"), JSON.stringify({
|
|
6562
7619
|
reason: "startup_failed_before_config_persisted",
|
|
6563
7620
|
error: errorMessage,
|
|
6564
7621
|
rollback_error: rollbackError instanceof Error ? rollbackError.message : String(rollbackError),
|
|
@@ -6584,13 +7641,27 @@ async function rollbackStartedNativeWorktreeStartup(args) {
|
|
|
6584
7641
|
}
|
|
6585
7642
|
async function startTeamV2(config) {
|
|
6586
7643
|
const sanitized = sanitizeTeamName(config.teamName);
|
|
6587
|
-
const leaderCwd = (0,
|
|
7644
|
+
const leaderCwd = (0, import_path22.resolve)(config.cwd);
|
|
6588
7645
|
validateTeamName(sanitized);
|
|
6589
7646
|
const pluginCfg = config.pluginConfig ?? loadConfig();
|
|
6590
7647
|
const resolvedRouting = buildResolvedRoutingSnapshot(pluginCfg);
|
|
6591
|
-
|
|
7648
|
+
let worktreeMode = normalizeTeamWorktreeMode(
|
|
6592
7649
|
process.env.OMC_TEAM_WORKTREE_MODE ?? pluginCfg.team?.ops?.worktreeMode
|
|
6593
7650
|
);
|
|
7651
|
+
let autoMergeLeaderBranch;
|
|
7652
|
+
if (config.autoMerge) {
|
|
7653
|
+
if (!isRuntimeV2Enabled()) {
|
|
7654
|
+
throw new Error("auto-merge requires OMC_RUNTIME_V2=1 (this feature is v2-only).");
|
|
7655
|
+
}
|
|
7656
|
+
autoMergeLeaderBranch = resolveLeaderBranch(leaderCwd);
|
|
7657
|
+
const stripped = autoMergeLeaderBranch.replace(/^refs\/heads\//i, "").toLowerCase();
|
|
7658
|
+
if (stripped === "main" || stripped === "master") {
|
|
7659
|
+
throw new Error("auto-merge refuses main/master leader branch \u2014 use a feature branch");
|
|
7660
|
+
}
|
|
7661
|
+
if (worktreeMode !== "named") {
|
|
7662
|
+
worktreeMode = "named";
|
|
7663
|
+
}
|
|
7664
|
+
}
|
|
6594
7665
|
const workspaceMode = worktreeMode === "disabled" ? "single" : "worktree";
|
|
6595
7666
|
const agentTypes = config.agentTypes;
|
|
6596
7667
|
const resolvedBinaryPaths = {};
|
|
@@ -6620,9 +7691,9 @@ async function startTeamV2(config) {
|
|
|
6620
7691
|
} catch {
|
|
6621
7692
|
}
|
|
6622
7693
|
}
|
|
6623
|
-
await (0,
|
|
6624
|
-
await (0,
|
|
6625
|
-
await (0,
|
|
7694
|
+
await (0, import_promises10.mkdir)(absPath(leaderCwd, TeamPaths.tasks(sanitized)), { recursive: true });
|
|
7695
|
+
await (0, import_promises10.mkdir)(absPath(leaderCwd, TeamPaths.workers(sanitized)), { recursive: true });
|
|
7696
|
+
await (0, import_promises10.mkdir)((0, import_path22.join)(leaderCwd, ".omcp", "state", "team", sanitized, "mailbox"), { recursive: true });
|
|
6626
7697
|
const missingBinaryLogFailure = createSwallowedErrorLogger(
|
|
6627
7698
|
"team.runtime-v2.startTeamV2 cli_binary_missing event failed"
|
|
6628
7699
|
);
|
|
@@ -6640,14 +7711,15 @@ async function startTeamV2(config) {
|
|
|
6640
7711
|
for (let i = 0; i < config.tasks.length; i++) {
|
|
6641
7712
|
const taskId = String(i + 1);
|
|
6642
7713
|
const taskFilePath = absPath(leaderCwd, TeamPaths.taskFile(sanitized, taskId));
|
|
6643
|
-
await (0,
|
|
6644
|
-
await (0,
|
|
7714
|
+
await (0, import_promises10.mkdir)((0, import_path22.join)(taskFilePath, ".."), { recursive: true });
|
|
7715
|
+
await (0, import_promises10.writeFile)(taskFilePath, JSON.stringify({
|
|
6645
7716
|
id: taskId,
|
|
6646
7717
|
subject: config.tasks[i].subject,
|
|
6647
7718
|
description: config.tasks[i].description,
|
|
6648
7719
|
status: "pending",
|
|
6649
7720
|
owner: null,
|
|
6650
7721
|
result: null,
|
|
7722
|
+
...config.tasks[i].delegation ? { delegation: config.tasks[i].delegation } : {},
|
|
6651
7723
|
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
6652
7724
|
}, null, 2), "utf-8");
|
|
6653
7725
|
}
|
|
@@ -6713,7 +7785,7 @@ async function startTeamV2(config) {
|
|
|
6713
7785
|
});
|
|
6714
7786
|
const worktree = workerWorktrees.get(wName);
|
|
6715
7787
|
if (worktree) {
|
|
6716
|
-
const overlayContent = await (0,
|
|
7788
|
+
const overlayContent = await (0, import_promises10.readFile)(overlayPath, "utf-8");
|
|
6717
7789
|
installWorktreeRootAgents(sanitized, wName, leaderCwd, worktree.path, overlayContent);
|
|
6718
7790
|
}
|
|
6719
7791
|
}
|
|
@@ -6823,7 +7895,7 @@ async function startTeamV2(config) {
|
|
|
6823
7895
|
next_worker_index: teamConfig.next_worker_index
|
|
6824
7896
|
};
|
|
6825
7897
|
try {
|
|
6826
|
-
await (0,
|
|
7898
|
+
await (0, import_promises10.writeFile)(absPath(leaderCwd, TeamPaths.manifest(sanitized)), JSON.stringify(teamManifest, null, 2), "utf-8");
|
|
6827
7899
|
} catch (error) {
|
|
6828
7900
|
await rollbackStartedNativeWorktreeStartup({
|
|
6829
7901
|
teamName: sanitized,
|
|
@@ -6872,6 +7944,7 @@ async function startTeamV2(config) {
|
|
|
6872
7944
|
cwd: leaderCwd,
|
|
6873
7945
|
workerCwd: workersInfo[workerIndex]?.working_dir ?? leaderCwd,
|
|
6874
7946
|
worktreePath: workersInfo[workerIndex]?.worktree_path,
|
|
7947
|
+
autoMerge: Boolean(config.autoMerge),
|
|
6875
7948
|
resolvedBinaryPaths,
|
|
6876
7949
|
...assignment.model ? { model: assignment.model } : {},
|
|
6877
7950
|
...assignment.role ? { role: assignment.role } : {}
|
|
@@ -6934,6 +8007,51 @@ async function startTeamV2(config) {
|
|
|
6934
8007
|
worker: "leader-fixed",
|
|
6935
8008
|
reason: `start_team_v2: workers=${config.workerCount} tasks=${config.tasks.length} panes=${workerPaneIds.length}`
|
|
6936
8009
|
}, leaderCwd).catch(logEventFailure);
|
|
8010
|
+
if (config.autoMerge && autoMergeLeaderBranch) {
|
|
8011
|
+
try {
|
|
8012
|
+
await ensureLeaderInbox(sanitized, leaderCwd);
|
|
8013
|
+
await appendToLeaderInbox(
|
|
8014
|
+
sanitized,
|
|
8015
|
+
extendLeaderBootstrapPrompt(sanitized),
|
|
8016
|
+
leaderCwd
|
|
8017
|
+
);
|
|
8018
|
+
try {
|
|
8019
|
+
await recoverFromRestart({
|
|
8020
|
+
teamName: sanitized,
|
|
8021
|
+
repoRoot: leaderCwd,
|
|
8022
|
+
leaderBranch: autoMergeLeaderBranch,
|
|
8023
|
+
cwd: leaderCwd
|
|
8024
|
+
});
|
|
8025
|
+
} catch (recErr) {
|
|
8026
|
+
process.stderr.write(`[team/runtime-v2] auto-merge recover-from-restart failed: ${recErr}
|
|
8027
|
+
`);
|
|
8028
|
+
}
|
|
8029
|
+
const orchestrator = await startMergeOrchestrator({
|
|
8030
|
+
teamName: sanitized,
|
|
8031
|
+
repoRoot: leaderCwd,
|
|
8032
|
+
leaderBranch: autoMergeLeaderBranch,
|
|
8033
|
+
cwd: leaderCwd
|
|
8034
|
+
});
|
|
8035
|
+
registerTeamOrchestrator(sanitized, orchestrator);
|
|
8036
|
+
for (const w of workersInfo) {
|
|
8037
|
+
await orchestrator.registerWorker(w.name);
|
|
8038
|
+
}
|
|
8039
|
+
} catch (orchErr) {
|
|
8040
|
+
await stopTeamCadence(sanitized);
|
|
8041
|
+
unregisterTeamOrchestrator(sanitized);
|
|
8042
|
+
await rollbackStartedNativeWorktreeStartup({
|
|
8043
|
+
teamName: sanitized,
|
|
8044
|
+
cwd: leaderCwd,
|
|
8045
|
+
cause: orchErr,
|
|
8046
|
+
sessionName: sessionName2,
|
|
8047
|
+
leaderPaneId,
|
|
8048
|
+
workerPaneIds,
|
|
8049
|
+
sessionMode: session.sessionMode
|
|
8050
|
+
});
|
|
8051
|
+
const reason = orchErr instanceof Error ? orchErr.message : String(orchErr);
|
|
8052
|
+
throw new Error(`auto-merge startup failed: ${reason}`);
|
|
8053
|
+
}
|
|
8054
|
+
}
|
|
6937
8055
|
return {
|
|
6938
8056
|
teamName: sanitized,
|
|
6939
8057
|
sanitizedName: sanitized,
|
|
@@ -6965,7 +8083,7 @@ async function processCliWorkerVerdicts(teamName, cwd) {
|
|
|
6965
8083
|
}
|
|
6966
8084
|
let payload;
|
|
6967
8085
|
try {
|
|
6968
|
-
const raw = await (0,
|
|
8086
|
+
const raw = await (0, import_promises10.readFile)(outputFile, "utf-8");
|
|
6969
8087
|
payload = parseCliWorkerVerdict(raw);
|
|
6970
8088
|
} catch (err) {
|
|
6971
8089
|
const reason = err instanceof Error ? err.message : String(err);
|
|
@@ -7248,6 +8366,39 @@ async function shutdownTeamV2(teamName, cwd, options = {}) {
|
|
|
7248
8366
|
const timeoutMs = options.timeoutMs ?? 15e3;
|
|
7249
8367
|
const sanitized = sanitizeTeamName(teamName);
|
|
7250
8368
|
const config = await readTeamConfig(sanitized, cwd);
|
|
8369
|
+
const finalizeAutoMerge = async () => {
|
|
8370
|
+
const orchestrator = getTeamOrchestrator(sanitized);
|
|
8371
|
+
if (orchestrator) {
|
|
8372
|
+
try {
|
|
8373
|
+
const drainResult = await orchestrator.drainAndStop();
|
|
8374
|
+
if (drainResult.unmerged.length > 0) {
|
|
8375
|
+
await appendTeamEvent(sanitized, {
|
|
8376
|
+
type: "team_leader_nudge",
|
|
8377
|
+
worker: "leader-fixed",
|
|
8378
|
+
reason: `auto_merge_drain_unmerged:${drainResult.unmerged.map((u) => `${u.workerName}:${u.reason}`).join(",")}`
|
|
8379
|
+
}, cwd).catch(logEventFailure);
|
|
8380
|
+
}
|
|
8381
|
+
for (const w of config?.workers ?? []) {
|
|
8382
|
+
try {
|
|
8383
|
+
await orchestrator.unregisterWorker(w.name);
|
|
8384
|
+
} catch (err) {
|
|
8385
|
+
process.stderr.write(
|
|
8386
|
+
`[team/runtime-v2] orchestrator.unregisterWorker(${w.name}) failed: ${err}
|
|
8387
|
+
`
|
|
8388
|
+
);
|
|
8389
|
+
}
|
|
8390
|
+
}
|
|
8391
|
+
} catch (err) {
|
|
8392
|
+
process.stderr.write(`[team/runtime-v2] orchestrator drainAndStop: ${err}
|
|
8393
|
+
`);
|
|
8394
|
+
} finally {
|
|
8395
|
+
await stopTeamCadence(sanitized);
|
|
8396
|
+
unregisterTeamOrchestrator(sanitized);
|
|
8397
|
+
}
|
|
8398
|
+
} else {
|
|
8399
|
+
await stopTeamCadence(sanitized);
|
|
8400
|
+
}
|
|
8401
|
+
};
|
|
7251
8402
|
if (!config) {
|
|
7252
8403
|
const cleanupSafety = inspectTeamWorktreeCleanupSafety(sanitized, cwd);
|
|
7253
8404
|
if (cleanupSafety.hasEvidence) {
|
|
@@ -7382,12 +8533,14 @@ Then exit your session.
|
|
|
7382
8533
|
if (aliveWorkers.length > 0) {
|
|
7383
8534
|
process.stderr.write(`[team/runtime-v2] preserving worktrees/state because worker pane(s) are still alive: ${aliveWorkers.join(", ")}
|
|
7384
8535
|
`);
|
|
8536
|
+
await finalizeAutoMerge();
|
|
7385
8537
|
return;
|
|
7386
8538
|
}
|
|
7387
8539
|
const unknownWorkers = liveness.filter(([, state]) => state === "unknown").map(([paneId]) => paneById.get(paneId) ?? paneId);
|
|
7388
8540
|
if (unknownWorkers.length > 0) {
|
|
7389
8541
|
process.stderr.write(`[team/runtime-v2] preserving worktrees/state because worker pane liveness is unknown: ${unknownWorkers.join(", ")}
|
|
7390
8542
|
`);
|
|
8543
|
+
await finalizeAutoMerge();
|
|
7391
8544
|
return;
|
|
7392
8545
|
}
|
|
7393
8546
|
} catch (err) {
|
|
@@ -7395,6 +8548,7 @@ Then exit your session.
|
|
|
7395
8548
|
`);
|
|
7396
8549
|
if (recordedWorkerPaneIds.length > 0) {
|
|
7397
8550
|
process.stderr.write("[team/runtime-v2] preserving worktrees/state because tmux cleanup did not prove worker panes exited\n");
|
|
8551
|
+
await finalizeAutoMerge();
|
|
7398
8552
|
return;
|
|
7399
8553
|
}
|
|
7400
8554
|
}
|
|
@@ -7409,6 +8563,7 @@ Then exit your session.
|
|
|
7409
8563
|
reason: `ralph_cleanup_summary: total=${finalTasks.length} completed=${completed} failed=${failed} pending=${pending} force=${force}`
|
|
7410
8564
|
}, cwd).catch(logEventFailure);
|
|
7411
8565
|
}
|
|
8566
|
+
await finalizeAutoMerge();
|
|
7412
8567
|
let preservedWorktrees = 0;
|
|
7413
8568
|
try {
|
|
7414
8569
|
const worktreeCleanup = cleanupTeamWorktrees(sanitized, cwd);
|
|
@@ -7427,6 +8582,11 @@ Then exit your session.
|
|
|
7427
8582
|
}
|
|
7428
8583
|
|
|
7429
8584
|
// src/team/runtime-cli.ts
|
|
8585
|
+
function assertAutoMergeRuntimeSupported(useV2, autoMerge) {
|
|
8586
|
+
if (autoMerge && !useV2) {
|
|
8587
|
+
throw new Error("--auto-merge requires runtime v2; unset OMC_RUNTIME_V2=0 or disable --auto-merge");
|
|
8588
|
+
}
|
|
8589
|
+
}
|
|
7430
8590
|
function getTerminalStatus(taskCounts, expectedTaskCount) {
|
|
7431
8591
|
const active = taskCounts.pending + taskCounts.inProgress;
|
|
7432
8592
|
const terminal = taskCounts.completed + taskCounts.failed;
|
|
@@ -7444,10 +8604,10 @@ function parseWatchdogFailedAt(marker) {
|
|
|
7444
8604
|
throw new Error("watchdog marker missing valid failedAt");
|
|
7445
8605
|
}
|
|
7446
8606
|
async function checkWatchdogFailedMarker(stateRoot2, startTime) {
|
|
7447
|
-
const markerPath = (0,
|
|
8607
|
+
const markerPath = (0, import_path23.join)(stateRoot2, "watchdog-failed.json");
|
|
7448
8608
|
let raw;
|
|
7449
8609
|
try {
|
|
7450
|
-
raw = await (0,
|
|
8610
|
+
raw = await (0, import_promises11.readFile)(markerPath, "utf-8");
|
|
7451
8611
|
} catch (err) {
|
|
7452
8612
|
const code = err.code;
|
|
7453
8613
|
if (code === "ENOENT") return { failed: false };
|
|
@@ -7469,21 +8629,21 @@ async function checkWatchdogFailedMarker(stateRoot2, startTime) {
|
|
|
7469
8629
|
return { failed: true, reason: `Watchdog marked team failed at ${new Date(failedAt).toISOString()}` };
|
|
7470
8630
|
}
|
|
7471
8631
|
try {
|
|
7472
|
-
await (0,
|
|
8632
|
+
await (0, import_promises11.unlink)(markerPath);
|
|
7473
8633
|
} catch {
|
|
7474
8634
|
}
|
|
7475
8635
|
return { failed: false };
|
|
7476
8636
|
}
|
|
7477
8637
|
async function writeResultArtifact(output, finishedAt, jobId = process.env.OMC_JOB_ID, omcJobsDir = process.env.OMC_JOBS_DIR) {
|
|
7478
8638
|
if (!jobId || !omcJobsDir) return;
|
|
7479
|
-
const resultPath = (0,
|
|
8639
|
+
const resultPath = (0, import_path23.join)(omcJobsDir, `${jobId}-result.json`);
|
|
7480
8640
|
const tmpPath = `${resultPath}.tmp`;
|
|
7481
|
-
await (0,
|
|
8641
|
+
await (0, import_promises11.writeFile)(
|
|
7482
8642
|
tmpPath,
|
|
7483
8643
|
JSON.stringify({ ...output, finishedAt }),
|
|
7484
8644
|
"utf-8"
|
|
7485
8645
|
);
|
|
7486
|
-
await (0,
|
|
8646
|
+
await (0, import_promises11.rename)(tmpPath, resultPath);
|
|
7487
8647
|
}
|
|
7488
8648
|
function buildCliOutput(stateRoot2, teamName, status, workerCount, startTimeMs) {
|
|
7489
8649
|
const taskResults = collectTaskResults(stateRoot2);
|
|
@@ -7508,20 +8668,20 @@ function buildTerminalCliResult(stateRoot2, teamName, phase, workerCount, startT
|
|
|
7508
8668
|
async function writePanesFile(jobId, paneIds, leaderPaneId, sessionName2, ownsWindow) {
|
|
7509
8669
|
const omcJobsDir = process.env.OMC_JOBS_DIR;
|
|
7510
8670
|
if (!jobId || !omcJobsDir) return;
|
|
7511
|
-
const panesPath = (0,
|
|
7512
|
-
await (0,
|
|
8671
|
+
const panesPath = (0, import_path23.join)(omcJobsDir, `${jobId}-panes.json`);
|
|
8672
|
+
await (0, import_promises11.writeFile)(
|
|
7513
8673
|
panesPath + ".tmp",
|
|
7514
8674
|
JSON.stringify({ paneIds: [...paneIds], leaderPaneId, sessionName: sessionName2, ownsWindow })
|
|
7515
8675
|
);
|
|
7516
|
-
await (0,
|
|
8676
|
+
await (0, import_promises11.rename)(panesPath + ".tmp", panesPath);
|
|
7517
8677
|
}
|
|
7518
8678
|
function collectTaskResults(stateRoot2) {
|
|
7519
|
-
const tasksDir = (0,
|
|
8679
|
+
const tasksDir = (0, import_path23.join)(stateRoot2, "tasks");
|
|
7520
8680
|
try {
|
|
7521
|
-
const files = (0,
|
|
8681
|
+
const files = (0, import_fs21.readdirSync)(tasksDir).filter((f) => f.endsWith(".json"));
|
|
7522
8682
|
return files.map((f) => {
|
|
7523
8683
|
try {
|
|
7524
|
-
const raw = (0,
|
|
8684
|
+
const raw = (0, import_fs21.readFileSync)((0, import_path23.join)(tasksDir, f), "utf-8");
|
|
7525
8685
|
const task = JSON.parse(raw);
|
|
7526
8686
|
return {
|
|
7527
8687
|
taskId: task.id ?? f.replace(".json", ""),
|
|
@@ -7572,10 +8732,11 @@ async function main() {
|
|
|
7572
8732
|
newWindow = false,
|
|
7573
8733
|
pollIntervalMs = 5e3,
|
|
7574
8734
|
sentinelGateTimeoutMs = 3e4,
|
|
7575
|
-
sentinelGatePollIntervalMs = 250
|
|
8735
|
+
sentinelGatePollIntervalMs = 250,
|
|
8736
|
+
autoMerge = false
|
|
7576
8737
|
} = input;
|
|
7577
8738
|
const workerCount = input.workerCount ?? agentTypes.length;
|
|
7578
|
-
const stateRoot2 = (0,
|
|
8739
|
+
const stateRoot2 = (0, import_path23.join)(cwd, `.omcp/state/team/${teamName}`);
|
|
7579
8740
|
const config = {
|
|
7580
8741
|
teamName,
|
|
7581
8742
|
workerCount,
|
|
@@ -7585,6 +8746,13 @@ async function main() {
|
|
|
7585
8746
|
newWindow
|
|
7586
8747
|
};
|
|
7587
8748
|
const useV2 = isRuntimeV2Enabled();
|
|
8749
|
+
try {
|
|
8750
|
+
assertAutoMergeRuntimeSupported(useV2, autoMerge);
|
|
8751
|
+
} catch (err) {
|
|
8752
|
+
process.stderr.write(`[runtime-cli] ${err instanceof Error ? err.message : String(err)}
|
|
8753
|
+
`);
|
|
8754
|
+
process.exit(1);
|
|
8755
|
+
}
|
|
7588
8756
|
let runtime = null;
|
|
7589
8757
|
let finalStatus = "failed";
|
|
7590
8758
|
let pollActive = true;
|
|
@@ -7649,7 +8817,8 @@ async function main() {
|
|
|
7649
8817
|
agentTypes,
|
|
7650
8818
|
tasks,
|
|
7651
8819
|
cwd,
|
|
7652
|
-
newWindow
|
|
8820
|
+
newWindow,
|
|
8821
|
+
autoMerge
|
|
7653
8822
|
});
|
|
7654
8823
|
const v2PaneIds = v2Runtime.config.workers.map((w) => w.pane_id).filter((p) => typeof p === "string");
|
|
7655
8824
|
runtime = {
|
|
@@ -7769,7 +8938,7 @@ async function main() {
|
|
|
7769
8938
|
if (snap.allTasksTerminal) {
|
|
7770
8939
|
const hasFailures = snap.tasks.failed > 0;
|
|
7771
8940
|
if (!hasFailures) {
|
|
7772
|
-
const sentinelLogPath = (0,
|
|
8941
|
+
const sentinelLogPath = (0, import_path23.join)(cwd, "sentinel_stop.jsonl");
|
|
7773
8942
|
const gateResult = await waitForSentinelReadiness({
|
|
7774
8943
|
workspace: cwd,
|
|
7775
8944
|
logPath: sentinelLogPath,
|
|
@@ -7846,7 +9015,7 @@ async function main() {
|
|
|
7846
9015
|
mismatchStreak = 0;
|
|
7847
9016
|
const terminalStatus = getTerminalStatus(snap.taskCounts, expectedTaskCount);
|
|
7848
9017
|
if (terminalStatus === "completed") {
|
|
7849
|
-
const sentinelLogPath = (0,
|
|
9018
|
+
const sentinelLogPath = (0, import_path23.join)(cwd, "sentinel_stop.jsonl");
|
|
7850
9019
|
const gateResult = await waitForSentinelReadiness({
|
|
7851
9020
|
workspace: cwd,
|
|
7852
9021
|
logPath: sentinelLogPath,
|
|
@@ -7890,6 +9059,7 @@ if (require.main === module) {
|
|
|
7890
9059
|
}
|
|
7891
9060
|
// Annotate the CommonJS export names for ESM import in node:
|
|
7892
9061
|
0 && (module.exports = {
|
|
9062
|
+
assertAutoMergeRuntimeSupported,
|
|
7893
9063
|
buildCliOutput,
|
|
7894
9064
|
buildTerminalCliResult,
|
|
7895
9065
|
checkWatchdogFailedMarker,
|