oh-my-copilot 4.13.2 → 4.13.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +1 -1
- package/bridge/cli.cjs +2522 -949
- package/bridge/mcp-server.cjs +97 -9
- package/bridge/runtime-cli.cjs +1256 -133
- package/bridge/team.js +1241 -65
- package/dist/__tests__/auto-slash-aliases.test.js +91 -0
- package/dist/__tests__/auto-slash-aliases.test.js.map +1 -1
- package/dist/__tests__/hud/labels.test.d.ts +2 -0
- package/dist/__tests__/hud/labels.test.d.ts.map +1 -0
- package/dist/__tests__/hud/labels.test.js +158 -0
- package/dist/__tests__/hud/labels.test.js.map +1 -0
- package/dist/__tests__/hud/render-rate-limits-priority.test.js +26 -0
- package/dist/__tests__/hud/render-rate-limits-priority.test.js.map +1 -1
- package/dist/__tests__/hud-marketplace-resolution.test.js +34 -0
- package/dist/__tests__/hud-marketplace-resolution.test.js.map +1 -1
- package/dist/__tests__/installer-hud-skip.test.js +6 -0
- package/dist/__tests__/installer-hud-skip.test.js.map +1 -1
- package/dist/__tests__/installer-omc-reference.test.js +1 -1
- package/dist/__tests__/installer-omc-reference.test.js.map +1 -1
- package/dist/__tests__/mnemosyne/detector.test.js +2 -0
- package/dist/__tests__/mnemosyne/detector.test.js.map +1 -1
- package/dist/__tests__/notepad.test.js +2 -2
- package/dist/__tests__/notepad.test.js.map +1 -1
- package/dist/__tests__/pre-tool-enforcer.test.js +84 -1
- package/dist/__tests__/pre-tool-enforcer.test.js.map +1 -1
- package/dist/__tests__/ralph-prd-mandatory.test.js +11 -1
- package/dist/__tests__/ralph-prd-mandatory.test.js.map +1 -1
- package/dist/__tests__/ralph-prd.test.js +40 -2
- package/dist/__tests__/ralph-prd.test.js.map +1 -1
- package/dist/__tests__/skills.test.js +363 -4
- package/dist/__tests__/skills.test.js.map +1 -1
- package/dist/__tests__/state-root-resolution.test.js +56 -6
- package/dist/__tests__/state-root-resolution.test.js.map +1 -1
- package/dist/autoresearch/__tests__/runtime.test.js +225 -28
- package/dist/autoresearch/__tests__/runtime.test.js.map +1 -1
- package/dist/autoresearch/runtime.d.ts.map +1 -1
- package/dist/autoresearch/runtime.js +9 -5
- package/dist/autoresearch/runtime.js.map +1 -1
- package/dist/cli/autoresearch.d.ts +1 -1
- package/dist/cli/autoresearch.d.ts.map +1 -1
- package/dist/cli/autoresearch.js +4 -4
- package/dist/cli/commands/team.d.ts +1 -0
- package/dist/cli/commands/team.d.ts.map +1 -1
- package/dist/cli/commands/team.js +14 -2
- package/dist/cli/commands/team.js.map +1 -1
- package/dist/cli/index.js +3 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/team.d.ts +6 -0
- package/dist/cli/team.d.ts.map +1 -1
- package/dist/cli/team.js +23 -1
- package/dist/cli/team.js.map +1 -1
- package/dist/config/__tests__/loader.test.js.map +1 -1
- package/dist/features/boulder-state/constants.d.ts +1 -1
- package/dist/features/boulder-state/constants.d.ts.map +1 -1
- package/dist/features/builtin-skills/skills.d.ts.map +1 -1
- package/dist/features/builtin-skills/skills.js +11 -3
- package/dist/features/builtin-skills/skills.js.map +1 -1
- package/dist/features/builtin-skills/types.d.ts +7 -0
- package/dist/features/builtin-skills/types.d.ts.map +1 -1
- package/dist/features/magic-keywords.d.ts.map +1 -1
- package/dist/features/magic-keywords.js +30 -1
- package/dist/features/magic-keywords.js.map +1 -1
- package/dist/features/rate-limit-wait/tmux-detector.d.ts +4 -4
- package/dist/features/rate-limit-wait/tmux-detector.js +9 -9
- package/dist/features/rate-limit-wait/tmux-detector.js.map +1 -1
- package/dist/hooks/__tests__/bridge-routing.test.js +156 -0
- package/dist/hooks/__tests__/bridge-routing.test.js.map +1 -1
- package/dist/hooks/auto-slash-command/constants.d.ts.map +1 -1
- package/dist/hooks/auto-slash-command/constants.js +1 -0
- package/dist/hooks/auto-slash-command/constants.js.map +1 -1
- package/dist/hooks/auto-slash-command/executor.d.ts.map +1 -1
- package/dist/hooks/auto-slash-command/executor.js +29 -0
- package/dist/hooks/auto-slash-command/executor.js.map +1 -1
- package/dist/hooks/bridge.d.ts.map +1 -1
- package/dist/hooks/bridge.js +184 -1
- package/dist/hooks/bridge.js.map +1 -1
- package/dist/hooks/learner/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 +64 -9
- package/dist/hooks/persistent-mode/stop-hook-blocking.test.js.map +1 -1
- package/dist/hooks/project-memory/__tests__/integration.test.js +117 -3
- package/dist/hooks/project-memory/__tests__/integration.test.js.map +1 -1
- package/dist/hooks/project-memory/__tests__/storage.test.js +11 -11
- package/dist/hooks/project-memory/__tests__/storage.test.js.map +1 -1
- package/dist/hooks/project-memory/index.d.ts.map +1 -1
- package/dist/hooks/project-memory/index.js +24 -12
- package/dist/hooks/project-memory/index.js.map +1 -1
- package/dist/hooks/ralph/index.d.ts +1 -1
- package/dist/hooks/ralph/index.d.ts.map +1 -1
- package/dist/hooks/ralph/index.js +1 -1
- package/dist/hooks/ralph/index.js.map +1 -1
- package/dist/hooks/ralph/loop.d.ts +6 -6
- package/dist/hooks/ralph/loop.d.ts.map +1 -1
- package/dist/hooks/ralph/loop.js +18 -18
- package/dist/hooks/ralph/loop.js.map +1 -1
- package/dist/hooks/ralph/prd.d.ts +23 -12
- package/dist/hooks/ralph/prd.d.ts.map +1 -1
- package/dist/hooks/ralph/prd.js +88 -42
- package/dist/hooks/ralph/prd.js.map +1 -1
- package/dist/hooks/session-end/__tests__/mode-state-cleanup.test.js +20 -0
- package/dist/hooks/session-end/__tests__/mode-state-cleanup.test.js.map +1 -1
- package/dist/hooks/session-end/index.d.ts +9 -0
- package/dist/hooks/session-end/index.d.ts.map +1 -1
- package/dist/hooks/session-end/index.js +67 -0
- 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/types.d.ts +26 -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__/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__/worktree-paths.test.js +162 -39
- package/dist/lib/__tests__/worktree-paths.test.js.map +1 -1
- package/dist/lib/worktree-paths.d.ts +87 -9
- package/dist/lib/worktree-paths.d.ts.map +1 -1
- package/dist/lib/worktree-paths.js +209 -27
- package/dist/lib/worktree-paths.js.map +1 -1
- package/dist/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__/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 +150 -1
- package/dist/team/__tests__/runtime-v2.dispatch.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-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/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/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/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 +7 -1
- package/dist/team/runtime-v2.d.ts.map +1 -1
- package/dist/team/runtime-v2.js +185 -8
- package/dist/team/runtime-v2.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 +10 -1
- 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 +13 -1
- package/dist/tools/__tests__/state-tools.test.js.map +1 -1
- package/docs/GETTING-STARTED.md +415 -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/plugin-setup.mjs +10 -1
- package/scripts/post-tool-verifier.mjs +118 -5
- package/scripts/pre-tool-enforcer.mjs +54 -6
- package/scripts/session-start.mjs +171 -3
- package/skills/AGENTS.md +22 -91
- package/skills/ai-slop-cleaner/SKILL.md +118 -120
- package/skills/ask/SKILL.md +6 -8
- package/skills/autopilot/SKILL.md +1 -28
- package/skills/autoresearch/SKILL.md +5 -5
- package/skills/cancel/SKILL.md +13 -13
- package/skills/cccg/SKILL.md +1 -0
- package/skills/configure-notifications/SKILL.md +80 -215
- package/skills/debug/SKILL.md +3 -2
- package/skills/deep-dive/SKILL.md +20 -9
- package/skills/deep-interview/SKILL.md +32 -4
- package/skills/deepinit/SKILL.md +1 -0
- package/skills/external-context/SKILL.md +2 -1
- package/skills/hud/SKILL.md +22 -52
- package/skills/learner/SKILL.md +90 -57
- package/skills/mcp-setup/SKILL.md +45 -60
- package/skills/omc-doctor/SKILL.md +1 -1
- package/skills/omc-reference/SKILL.md +6 -6
- package/skills/omc-setup/SKILL.md +30 -74
- package/skills/omc-setup/phases/01-install-claude-md.md +97 -0
- package/skills/omc-setup/phases/02-configure.md +25 -40
- package/skills/omc-setup/phases/03-integrations.md +100 -69
- package/skills/omc-setup/phases/04-welcome.md +102 -44
- package/skills/omc-teams/SKILL.md +63 -26
- package/skills/plan/SKILL.md +11 -9
- package/skills/ralph/SKILL.md +42 -41
- package/skills/ralplan/SKILL.md +3 -10
- package/skills/release/SKILL.md +168 -57
- package/skills/remember/SKILL.md +2 -1
- package/skills/sciomc/SKILL.md +12 -11
- package/skills/self-improve/SKILL.md +4 -4
- package/skills/setup/SKILL.md +1 -0
- package/skills/skill/SKILL.md +21 -20
- package/skills/skillify/SKILL.md +34 -3
- package/skills/team/SKILL.md +62 -44
- package/skills/ultraqa/SKILL.md +1 -2
- package/skills/ultrawork/SKILL.md +28 -21
- package/skills/verify/SKILL.md +2 -1
- package/skills/writer-memory/SKILL.md +2 -1
- package/templates/hooks/pre-tool-use.mjs +144 -45
package/bridge/team.js
CHANGED
|
@@ -3865,6 +3865,17 @@ async function composeInitialInbox(teamName, workerName, content, cwd, cliOutput
|
|
|
3865
3865
|
${cliOutputContract}` : content;
|
|
3866
3866
|
await writeFile3(inboxPath, finalContent, "utf-8");
|
|
3867
3867
|
}
|
|
3868
|
+
async function appendToInbox(teamName, workerName, message, cwd) {
|
|
3869
|
+
const safeTeam = sanitizeName(teamName);
|
|
3870
|
+
const safeWorker = sanitizeName(workerName);
|
|
3871
|
+
const inboxPath = join14(cwd, `.omcp/state/team/${safeTeam}/workers/${safeWorker}/inbox.md`);
|
|
3872
|
+
validateResolvedPath(inboxPath, cwd);
|
|
3873
|
+
await mkdir3(dirname7(inboxPath), { recursive: true });
|
|
3874
|
+
await appendFile2(inboxPath, `
|
|
3875
|
+
|
|
3876
|
+
---
|
|
3877
|
+
${message}`, "utf-8");
|
|
3878
|
+
}
|
|
3868
3879
|
async function ensureWorkerStateDir(teamName, workerName, cwd) {
|
|
3869
3880
|
const workerDir = join14(cwd, `.omcp/state/team/${teamName}/workers/${workerName}`);
|
|
3870
3881
|
await mkdir3(workerDir, { recursive: true });
|
|
@@ -3887,6 +3898,8 @@ var init_worker_bootstrap = __esm({
|
|
|
3887
3898
|
"use strict";
|
|
3888
3899
|
init_prompt_helpers();
|
|
3889
3900
|
init_omc_cli_rendering();
|
|
3901
|
+
init_tmux_session();
|
|
3902
|
+
init_fs_utils();
|
|
3890
3903
|
init_model_contract();
|
|
3891
3904
|
DEFAULT_INSTRUCTION_STATE_ROOT = ".omcp/state";
|
|
3892
3905
|
}
|
|
@@ -4523,6 +4536,9 @@ function removeWorkerWorktree(teamName, workerName, repoRoot) {
|
|
|
4523
4536
|
forgetMetadataUnlocked(repoRoot, teamName, workerName);
|
|
4524
4537
|
});
|
|
4525
4538
|
}
|
|
4539
|
+
function listTeamWorktrees(teamName, repoRoot) {
|
|
4540
|
+
return readMetadata(repoRoot, teamName);
|
|
4541
|
+
}
|
|
4526
4542
|
function inspectTeamWorktreeCleanupSafety(teamName, repoRoot) {
|
|
4527
4543
|
const metadata = readMetadataResult(repoRoot, teamName);
|
|
4528
4544
|
const entries = metadata.entries;
|
|
@@ -4650,10 +4666,10 @@ async function readJsonSafe3(filePath) {
|
|
|
4650
4666
|
}
|
|
4651
4667
|
}
|
|
4652
4668
|
async function writeAtomic2(filePath, data) {
|
|
4653
|
-
const { writeFile:
|
|
4669
|
+
const { writeFile: writeFile8 } = await import("fs/promises");
|
|
4654
4670
|
await mkdir5(dirname10(filePath), { recursive: true });
|
|
4655
4671
|
const tmpPath = `${filePath}.tmp.${process.pid}.${Date.now()}`;
|
|
4656
|
-
await
|
|
4672
|
+
await writeFile8(tmpPath, data, "utf-8");
|
|
4657
4673
|
const { rename: rename3 } = await import("fs/promises");
|
|
4658
4674
|
await rename3(tmpPath, filePath);
|
|
4659
4675
|
}
|
|
@@ -5385,6 +5401,991 @@ var init_cli_worker_contract = __esm({
|
|
|
5385
5401
|
}
|
|
5386
5402
|
});
|
|
5387
5403
|
|
|
5404
|
+
// src/team/runtime-flags.ts
|
|
5405
|
+
function isRuntimeV2Enabled(env = process.env) {
|
|
5406
|
+
const raw = env.OMC_RUNTIME_V2;
|
|
5407
|
+
if (!raw) return true;
|
|
5408
|
+
const normalized = raw.trim().toLowerCase();
|
|
5409
|
+
return !["0", "false", "no", "off"].includes(normalized);
|
|
5410
|
+
}
|
|
5411
|
+
var init_runtime_flags = __esm({
|
|
5412
|
+
"src/team/runtime-flags.ts"() {
|
|
5413
|
+
"use strict";
|
|
5414
|
+
}
|
|
5415
|
+
});
|
|
5416
|
+
|
|
5417
|
+
// src/team/merge-coordinator.ts
|
|
5418
|
+
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
5419
|
+
function validateBranchName(branch) {
|
|
5420
|
+
if (!BRANCH_NAME_RE.test(branch)) {
|
|
5421
|
+
throw new Error(`Invalid branch name: "${branch}" \u2014 must match ${BRANCH_NAME_RE}`);
|
|
5422
|
+
}
|
|
5423
|
+
}
|
|
5424
|
+
function checkMergeConflicts(workerBranch, baseBranch, repoRoot) {
|
|
5425
|
+
validateBranchName(workerBranch);
|
|
5426
|
+
validateBranchName(baseBranch);
|
|
5427
|
+
try {
|
|
5428
|
+
execFileSync3(
|
|
5429
|
+
"git",
|
|
5430
|
+
["merge-tree", "--write-tree", baseBranch, workerBranch],
|
|
5431
|
+
{ cwd: repoRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
5432
|
+
);
|
|
5433
|
+
return [];
|
|
5434
|
+
} catch (err) {
|
|
5435
|
+
const error = err;
|
|
5436
|
+
if (error.status === 1 && typeof error.stdout === "string") {
|
|
5437
|
+
const lines = error.stdout.split("\n");
|
|
5438
|
+
const conflicts = [];
|
|
5439
|
+
for (const line of lines) {
|
|
5440
|
+
const match = line.match(/^CONFLICT\s.*?:\s+.*?\s+in\s+(.+)$/);
|
|
5441
|
+
if (match) {
|
|
5442
|
+
conflicts.push(match[1].trim());
|
|
5443
|
+
}
|
|
5444
|
+
}
|
|
5445
|
+
return conflicts.length > 0 ? conflicts : ["(merge-tree reported conflicts)"];
|
|
5446
|
+
}
|
|
5447
|
+
}
|
|
5448
|
+
const mergeBase = execFileSync3(
|
|
5449
|
+
"git",
|
|
5450
|
+
["merge-base", baseBranch, workerBranch],
|
|
5451
|
+
{ cwd: repoRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
5452
|
+
).trim();
|
|
5453
|
+
const baseDiff = execFileSync3(
|
|
5454
|
+
"git",
|
|
5455
|
+
["diff", "--name-only", mergeBase, baseBranch],
|
|
5456
|
+
{ cwd: repoRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
5457
|
+
).trim();
|
|
5458
|
+
const workerDiff = execFileSync3(
|
|
5459
|
+
"git",
|
|
5460
|
+
["diff", "--name-only", mergeBase, workerBranch],
|
|
5461
|
+
{ cwd: repoRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
5462
|
+
).trim();
|
|
5463
|
+
if (!baseDiff || !workerDiff) {
|
|
5464
|
+
return [];
|
|
5465
|
+
}
|
|
5466
|
+
const baseFiles = new Set(baseDiff.split("\n").filter((f) => f));
|
|
5467
|
+
const workerFiles = workerDiff.split("\n").filter((f) => f);
|
|
5468
|
+
return workerFiles.filter((f) => baseFiles.has(f));
|
|
5469
|
+
}
|
|
5470
|
+
function mergeWorkerBranch(workerBranch, baseBranch, repoRoot) {
|
|
5471
|
+
validateBranchName(workerBranch);
|
|
5472
|
+
validateBranchName(baseBranch);
|
|
5473
|
+
const workerName = workerBranch.split("/").pop() || workerBranch;
|
|
5474
|
+
try {
|
|
5475
|
+
try {
|
|
5476
|
+
execFileSync3("git", ["diff-index", "--quiet", "HEAD", "--"], {
|
|
5477
|
+
cwd: repoRoot,
|
|
5478
|
+
stdio: "pipe"
|
|
5479
|
+
});
|
|
5480
|
+
} catch {
|
|
5481
|
+
throw new Error("Working tree has uncommitted changes \u2014 commit or stash before merging");
|
|
5482
|
+
}
|
|
5483
|
+
execFileSync3("git", ["checkout", baseBranch], {
|
|
5484
|
+
cwd: repoRoot,
|
|
5485
|
+
stdio: "pipe"
|
|
5486
|
+
});
|
|
5487
|
+
execFileSync3("git", ["merge", "--no-ff", "-m", `Merge ${workerBranch} into ${baseBranch}`, workerBranch], {
|
|
5488
|
+
cwd: repoRoot,
|
|
5489
|
+
stdio: "pipe"
|
|
5490
|
+
});
|
|
5491
|
+
const mergeCommit = execFileSync3("git", ["rev-parse", "HEAD"], {
|
|
5492
|
+
cwd: repoRoot,
|
|
5493
|
+
encoding: "utf-8",
|
|
5494
|
+
stdio: "pipe"
|
|
5495
|
+
}).trim();
|
|
5496
|
+
return {
|
|
5497
|
+
workerName,
|
|
5498
|
+
branch: workerBranch,
|
|
5499
|
+
success: true,
|
|
5500
|
+
conflicts: [],
|
|
5501
|
+
mergeCommit
|
|
5502
|
+
};
|
|
5503
|
+
} catch (_err) {
|
|
5504
|
+
try {
|
|
5505
|
+
execFileSync3("git", ["merge", "--abort"], { cwd: repoRoot, stdio: "pipe" });
|
|
5506
|
+
} catch {
|
|
5507
|
+
}
|
|
5508
|
+
const conflicts = checkMergeConflicts(workerBranch, baseBranch, repoRoot);
|
|
5509
|
+
return {
|
|
5510
|
+
workerName,
|
|
5511
|
+
branch: workerBranch,
|
|
5512
|
+
success: false,
|
|
5513
|
+
conflicts
|
|
5514
|
+
};
|
|
5515
|
+
}
|
|
5516
|
+
}
|
|
5517
|
+
var BRANCH_NAME_RE;
|
|
5518
|
+
var init_merge_coordinator = __esm({
|
|
5519
|
+
"src/team/merge-coordinator.ts"() {
|
|
5520
|
+
"use strict";
|
|
5521
|
+
init_git_worktree();
|
|
5522
|
+
BRANCH_NAME_RE = /^[a-zA-Z0-9][a-zA-Z0-9/_.-]*$/;
|
|
5523
|
+
}
|
|
5524
|
+
});
|
|
5525
|
+
|
|
5526
|
+
// src/team/leader-inbox.ts
|
|
5527
|
+
import { appendFile as appendFile4, mkdir as mkdir7, writeFile as writeFile5 } from "fs/promises";
|
|
5528
|
+
import { existsSync as existsSync16 } from "fs";
|
|
5529
|
+
import { dirname as dirname12, join as join19 } from "path";
|
|
5530
|
+
function leaderInboxPath(teamName, cwd) {
|
|
5531
|
+
const safe = sanitizeName(teamName);
|
|
5532
|
+
return join19(cwd, `.omcp/state/team/${safe}/leader/inbox.md`);
|
|
5533
|
+
}
|
|
5534
|
+
async function ensureLeaderInbox(teamName, cwd) {
|
|
5535
|
+
const inboxPath = leaderInboxPath(teamName, cwd);
|
|
5536
|
+
validateResolvedPath(inboxPath, cwd);
|
|
5537
|
+
await mkdir7(dirname12(inboxPath), { recursive: true });
|
|
5538
|
+
if (!existsSync16(inboxPath)) {
|
|
5539
|
+
await writeFile5(inboxPath, LEADER_INBOX_HEADER, "utf-8");
|
|
5540
|
+
}
|
|
5541
|
+
return inboxPath;
|
|
5542
|
+
}
|
|
5543
|
+
async function appendToLeaderInbox(teamName, message, cwd) {
|
|
5544
|
+
const inboxPath = leaderInboxPath(teamName, cwd);
|
|
5545
|
+
validateResolvedPath(inboxPath, cwd);
|
|
5546
|
+
await mkdir7(dirname12(inboxPath), { recursive: true });
|
|
5547
|
+
await appendFile4(inboxPath, `
|
|
5548
|
+
|
|
5549
|
+
---
|
|
5550
|
+
${message}`, "utf-8");
|
|
5551
|
+
}
|
|
5552
|
+
function extendLeaderBootstrapPrompt(teamName) {
|
|
5553
|
+
const safe = sanitizeName(teamName);
|
|
5554
|
+
const path4 = `.omcp/state/team/${safe}/leader/inbox.md`;
|
|
5555
|
+
return `Runtime notifications appear at ${path4} \u2014 check this file periodically and after long-running operations.`;
|
|
5556
|
+
}
|
|
5557
|
+
var LEADER_INBOX_HEADER;
|
|
5558
|
+
var init_leader_inbox = __esm({
|
|
5559
|
+
"src/team/leader-inbox.ts"() {
|
|
5560
|
+
"use strict";
|
|
5561
|
+
init_tmux_session();
|
|
5562
|
+
init_fs_utils();
|
|
5563
|
+
LEADER_INBOX_HEADER = `# Leader Inbox
|
|
5564
|
+
|
|
5565
|
+
Runtime notifications (merge conflicts, rebase events, etc.) appear here.
|
|
5566
|
+
Check this file periodically and after long-running operations.
|
|
5567
|
+
|
|
5568
|
+
---
|
|
5569
|
+
`;
|
|
5570
|
+
}
|
|
5571
|
+
});
|
|
5572
|
+
|
|
5573
|
+
// src/team/conflict-mailbox.ts
|
|
5574
|
+
function sanitizeConflictPath(path4) {
|
|
5575
|
+
return path4.replace(/[`\r\n]/g, "?");
|
|
5576
|
+
}
|
|
5577
|
+
function formatMergeConflictForLeader(args) {
|
|
5578
|
+
const { workerName, workerBranch, leaderBranch, conflictingFiles, mergeBaseSha, observedAt } = args;
|
|
5579
|
+
const ts = new Date(observedAt).toISOString();
|
|
5580
|
+
const safeFiles = conflictingFiles.map(sanitizeConflictPath);
|
|
5581
|
+
const fileList = safeFiles.map((f) => `- \`${f}\``).join("\n");
|
|
5582
|
+
return `### Merge conflict: ${workerName} \u2192 ${leaderBranch}
|
|
5583
|
+
|
|
5584
|
+
**Worker branch:** \`${workerBranch}\`
|
|
5585
|
+
**Leader branch:** \`${leaderBranch}\`
|
|
5586
|
+
**Merge base:** \`${mergeBaseSha}\`
|
|
5587
|
+
**Observed at:** ${ts}
|
|
5588
|
+
|
|
5589
|
+
**Conflicting files:**
|
|
5590
|
+
${fileList}
|
|
5591
|
+
|
|
5592
|
+
**Leader: choose strategy.** To resolve, run:
|
|
5593
|
+
|
|
5594
|
+
\`\`\`sh
|
|
5595
|
+
git checkout ${leaderBranch} && git merge --no-ff ${workerBranch}
|
|
5596
|
+
# resolve conflicts in the files listed above
|
|
5597
|
+
git add ${safeFiles.join(" ")}
|
|
5598
|
+
git commit
|
|
5599
|
+
\`\`\`
|
|
5600
|
+
|
|
5601
|
+
Or abort with \`git merge --abort\` to defer resolution.`;
|
|
5602
|
+
}
|
|
5603
|
+
function formatRebaseConflictForWorker(args) {
|
|
5604
|
+
const { workerName, workerBranch, leaderBranch, conflictingFiles, baseSha, worktreePath, observedAt } = args;
|
|
5605
|
+
const ts = new Date(observedAt).toISOString();
|
|
5606
|
+
const safeFiles = conflictingFiles.map(sanitizeConflictPath);
|
|
5607
|
+
const fileList = safeFiles.map((f) => `- \`${f}\``).join("\n");
|
|
5608
|
+
return `### Rebase conflict: ${workerName} onto ${leaderBranch}
|
|
5609
|
+
|
|
5610
|
+
**Worker branch:** \`${workerBranch}\`
|
|
5611
|
+
**Base branch:** \`${leaderBranch}\`
|
|
5612
|
+
**Base SHA:** \`${baseSha}\`
|
|
5613
|
+
**Worktree:** \`${worktreePath}\`
|
|
5614
|
+
**Observed at:** ${ts}
|
|
5615
|
+
|
|
5616
|
+
**Conflicting files:**
|
|
5617
|
+
${fileList}
|
|
5618
|
+
|
|
5619
|
+
Resolve conflicts in your own pane, then \`git add <files>\` and \`git rebase --continue\`.
|
|
5620
|
+
Cadence stays paused until \`.git/rebase-merge\` is gone.
|
|
5621
|
+
|
|
5622
|
+
Or run \`git rebase --abort\` to bail and return to the pre-rebase state.`;
|
|
5623
|
+
}
|
|
5624
|
+
var init_conflict_mailbox = __esm({
|
|
5625
|
+
"src/team/conflict-mailbox.ts"() {
|
|
5626
|
+
"use strict";
|
|
5627
|
+
init_worker_bootstrap();
|
|
5628
|
+
init_leader_inbox();
|
|
5629
|
+
}
|
|
5630
|
+
});
|
|
5631
|
+
|
|
5632
|
+
// src/team/worker-commit-cadence.ts
|
|
5633
|
+
import { existsSync as existsSync17, watch as fsWatch } from "fs";
|
|
5634
|
+
import { readFile as readFile9, writeFile as writeFile6, mkdir as mkdir8, unlink as unlink2 } from "fs/promises";
|
|
5635
|
+
import { join as join20, dirname as dirname13 } from "path";
|
|
5636
|
+
import { exec as exec2 } from "child_process";
|
|
5637
|
+
function assertSafeWorkerName(workerName) {
|
|
5638
|
+
if (!WORKER_NAME_RE.test(workerName)) {
|
|
5639
|
+
throw new Error(
|
|
5640
|
+
`Invalid worker name for shell hook: "${workerName}" \u2014 must match ${WORKER_NAME_RE}`
|
|
5641
|
+
);
|
|
5642
|
+
}
|
|
5643
|
+
}
|
|
5644
|
+
function buildHookCommand(workerName) {
|
|
5645
|
+
assertSafeWorkerName(workerName);
|
|
5646
|
+
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 ${workerName} at $(date -Iseconds)")'`;
|
|
5647
|
+
}
|
|
5648
|
+
async function mergeSettingsWithHook(settingsPath, hookCommand) {
|
|
5649
|
+
let existing = { hooks: { PostToolUse: [] } };
|
|
5650
|
+
try {
|
|
5651
|
+
const raw = await readFile9(settingsPath, "utf-8");
|
|
5652
|
+
const parsed = JSON.parse(raw);
|
|
5653
|
+
existing = {
|
|
5654
|
+
...parsed,
|
|
5655
|
+
hooks: {
|
|
5656
|
+
PostToolUse: [],
|
|
5657
|
+
...parsed.hooks ?? {}
|
|
5658
|
+
}
|
|
5659
|
+
};
|
|
5660
|
+
} catch {
|
|
5661
|
+
}
|
|
5662
|
+
const filteredHooks = (existing.hooks.PostToolUse ?? []).filter(
|
|
5663
|
+
(h) => h.matcher !== HOOK_MATCHER
|
|
5664
|
+
);
|
|
5665
|
+
const newEntry = {
|
|
5666
|
+
matcher: HOOK_MATCHER,
|
|
5667
|
+
hooks: [{ type: "command", command: hookCommand }]
|
|
5668
|
+
};
|
|
5669
|
+
return {
|
|
5670
|
+
...existing,
|
|
5671
|
+
hooks: {
|
|
5672
|
+
...existing.hooks,
|
|
5673
|
+
PostToolUse: [...filteredHooks, newEntry]
|
|
5674
|
+
}
|
|
5675
|
+
};
|
|
5676
|
+
}
|
|
5677
|
+
async function installPostToolUseHook(worktreePath, workerName) {
|
|
5678
|
+
assertSafeWorkerName(workerName);
|
|
5679
|
+
if (isHookPaused(worktreePath)) {
|
|
5680
|
+
return;
|
|
5681
|
+
}
|
|
5682
|
+
const claudeDir = join20(worktreePath, ".claude");
|
|
5683
|
+
await mkdir8(claudeDir, { recursive: true });
|
|
5684
|
+
const settingsPath = join20(claudeDir, "settings.json");
|
|
5685
|
+
const hookCommand = buildHookCommand(workerName);
|
|
5686
|
+
const merged = await mergeSettingsWithHook(settingsPath, hookCommand);
|
|
5687
|
+
await writeFile6(settingsPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
|
|
5688
|
+
}
|
|
5689
|
+
async function pauseHookViaSentinel(worktreePath) {
|
|
5690
|
+
const sentinelPath = join20(worktreePath, SENTINEL_FILENAME);
|
|
5691
|
+
await mkdir8(dirname13(sentinelPath), { recursive: true });
|
|
5692
|
+
await writeFile6(sentinelPath, "", "utf-8");
|
|
5693
|
+
}
|
|
5694
|
+
async function resumeHookViaSentinel(worktreePath) {
|
|
5695
|
+
const sentinelPath = join20(worktreePath, SENTINEL_FILENAME);
|
|
5696
|
+
try {
|
|
5697
|
+
await unlink2(sentinelPath);
|
|
5698
|
+
} catch {
|
|
5699
|
+
}
|
|
5700
|
+
}
|
|
5701
|
+
function isHookPaused(worktreePath) {
|
|
5702
|
+
return existsSync17(join20(worktreePath, SENTINEL_FILENAME));
|
|
5703
|
+
}
|
|
5704
|
+
function startFallbackPoller(worktreePath, workerName, opts) {
|
|
5705
|
+
assertSafeWorkerName(workerName);
|
|
5706
|
+
const debounceMs = opts?.intervalMs ?? DEFAULT_POLL_DEBOUNCE_MS;
|
|
5707
|
+
let debounceTimer = null;
|
|
5708
|
+
let stopped = false;
|
|
5709
|
+
const runAutoCommit = () => {
|
|
5710
|
+
if (stopped) return;
|
|
5711
|
+
if (isHookPaused(worktreePath)) return;
|
|
5712
|
+
const cmd = buildHookCommand(workerName);
|
|
5713
|
+
exec2(cmd, { cwd: worktreePath }, (_err) => {
|
|
5714
|
+
});
|
|
5715
|
+
};
|
|
5716
|
+
const scheduleDebounce = () => {
|
|
5717
|
+
if (stopped) return;
|
|
5718
|
+
if (debounceTimer !== null) {
|
|
5719
|
+
clearTimeout(debounceTimer);
|
|
5720
|
+
}
|
|
5721
|
+
debounceTimer = setTimeout(() => {
|
|
5722
|
+
debounceTimer = null;
|
|
5723
|
+
runAutoCommit();
|
|
5724
|
+
}, debounceMs);
|
|
5725
|
+
};
|
|
5726
|
+
const watcher = fsWatch(worktreePath, { recursive: true }, (eventType, filename) => {
|
|
5727
|
+
if (stopped) return;
|
|
5728
|
+
if (filename && (filename.startsWith(".git") || filename.startsWith(".git/"))) return;
|
|
5729
|
+
scheduleDebounce();
|
|
5730
|
+
});
|
|
5731
|
+
return {
|
|
5732
|
+
stop() {
|
|
5733
|
+
stopped = true;
|
|
5734
|
+
if (debounceTimer !== null) {
|
|
5735
|
+
clearTimeout(debounceTimer);
|
|
5736
|
+
debounceTimer = null;
|
|
5737
|
+
}
|
|
5738
|
+
watcher.close();
|
|
5739
|
+
}
|
|
5740
|
+
};
|
|
5741
|
+
}
|
|
5742
|
+
async function installCommitCadence(ctx) {
|
|
5743
|
+
if (!ctx.enabled) {
|
|
5744
|
+
return { method: "none" };
|
|
5745
|
+
}
|
|
5746
|
+
if (ctx.agentType === "claude") {
|
|
5747
|
+
await installPostToolUseHook(ctx.worktreePath, ctx.workerName);
|
|
5748
|
+
return { method: "hook" };
|
|
5749
|
+
}
|
|
5750
|
+
return { method: "fallback-poll" };
|
|
5751
|
+
}
|
|
5752
|
+
async function uninstallCommitCadence(ctx) {
|
|
5753
|
+
if (ctx.agentType !== "claude") return;
|
|
5754
|
+
const settingsPath = join20(ctx.worktreePath, ".claude", "settings.json");
|
|
5755
|
+
try {
|
|
5756
|
+
const raw = await readFile9(settingsPath, "utf-8");
|
|
5757
|
+
const parsed = JSON.parse(raw);
|
|
5758
|
+
const filtered = (parsed.hooks?.PostToolUse ?? []).filter(
|
|
5759
|
+
(h) => h.matcher !== HOOK_MATCHER
|
|
5760
|
+
);
|
|
5761
|
+
const updated = {
|
|
5762
|
+
...parsed,
|
|
5763
|
+
hooks: {
|
|
5764
|
+
...parsed.hooks,
|
|
5765
|
+
PostToolUse: filtered
|
|
5766
|
+
}
|
|
5767
|
+
};
|
|
5768
|
+
await writeFile6(settingsPath, JSON.stringify(updated, null, 2) + "\n", "utf-8");
|
|
5769
|
+
} catch {
|
|
5770
|
+
}
|
|
5771
|
+
}
|
|
5772
|
+
var SENTINEL_FILENAME, HOOK_MATCHER, DEFAULT_POLL_DEBOUNCE_MS, WORKER_NAME_RE;
|
|
5773
|
+
var init_worker_commit_cadence = __esm({
|
|
5774
|
+
"src/team/worker-commit-cadence.ts"() {
|
|
5775
|
+
"use strict";
|
|
5776
|
+
SENTINEL_FILENAME = ".hook-paused";
|
|
5777
|
+
HOOK_MATCHER = "Write|Edit|MultiEdit";
|
|
5778
|
+
DEFAULT_POLL_DEBOUNCE_MS = 3e3;
|
|
5779
|
+
WORKER_NAME_RE = /^[A-Za-z0-9_-]{1,50}$/;
|
|
5780
|
+
}
|
|
5781
|
+
});
|
|
5782
|
+
|
|
5783
|
+
// src/team/merge-orchestrator.ts
|
|
5784
|
+
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
5785
|
+
import { existsSync as existsSync18 } from "node:fs";
|
|
5786
|
+
import { mkdir as mkdir9, appendFile as appendFile5 } from "node:fs/promises";
|
|
5787
|
+
import { dirname as dirname14, join as join21 } from "node:path";
|
|
5788
|
+
function mergerWorktreePathFor(repoRoot, teamName) {
|
|
5789
|
+
return join21(repoRoot, ".omcp", "team", sanitizeName(teamName), "merger");
|
|
5790
|
+
}
|
|
5791
|
+
function persistedStatePath(repoRoot, teamName) {
|
|
5792
|
+
return join21(
|
|
5793
|
+
repoRoot,
|
|
5794
|
+
".omcp",
|
|
5795
|
+
"state",
|
|
5796
|
+
"team",
|
|
5797
|
+
sanitizeName(teamName),
|
|
5798
|
+
"auto-merge-state.json"
|
|
5799
|
+
);
|
|
5800
|
+
}
|
|
5801
|
+
function teardownAuditPath(repoRoot, teamName) {
|
|
5802
|
+
return join21(
|
|
5803
|
+
repoRoot,
|
|
5804
|
+
".omcp",
|
|
5805
|
+
"state",
|
|
5806
|
+
"team",
|
|
5807
|
+
sanitizeName(teamName),
|
|
5808
|
+
"teardown-audit.jsonl"
|
|
5809
|
+
);
|
|
5810
|
+
}
|
|
5811
|
+
function orchestratorEventLogPath(repoRoot, teamName) {
|
|
5812
|
+
return join21(
|
|
5813
|
+
repoRoot,
|
|
5814
|
+
".omcp",
|
|
5815
|
+
"state",
|
|
5816
|
+
"team",
|
|
5817
|
+
sanitizeName(teamName),
|
|
5818
|
+
"orchestrator-events.jsonl"
|
|
5819
|
+
);
|
|
5820
|
+
}
|
|
5821
|
+
function assertLeaderBranchAllowed(leaderBranch) {
|
|
5822
|
+
const stripped = leaderBranch.replace(/^refs\/heads\//i, "").toLowerCase();
|
|
5823
|
+
if (stripped === "main" || stripped === "master") {
|
|
5824
|
+
throw new Error("auto-merge refuses main/master leader branch \u2014 use a feature branch");
|
|
5825
|
+
}
|
|
5826
|
+
}
|
|
5827
|
+
function assertRuntimeV2Gate() {
|
|
5828
|
+
if (!isRuntimeV2Enabled()) {
|
|
5829
|
+
throw new Error("auto-merge requires runtime v2 (OMC_RUNTIME_V2 is explicitly disabled).");
|
|
5830
|
+
}
|
|
5831
|
+
}
|
|
5832
|
+
async function appendEvent(repoRoot, teamName, event) {
|
|
5833
|
+
const path4 = orchestratorEventLogPath(repoRoot, teamName);
|
|
5834
|
+
await mkdir9(dirname14(path4), { recursive: true });
|
|
5835
|
+
const full = {
|
|
5836
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5837
|
+
team: teamName,
|
|
5838
|
+
...event
|
|
5839
|
+
};
|
|
5840
|
+
await appendFile5(path4, `${JSON.stringify(full)}
|
|
5841
|
+
`, "utf-8");
|
|
5842
|
+
}
|
|
5843
|
+
function createMutex() {
|
|
5844
|
+
let lock = Promise.resolve();
|
|
5845
|
+
return (fn) => {
|
|
5846
|
+
const next = lock.then(fn, fn);
|
|
5847
|
+
lock = next.catch(() => void 0);
|
|
5848
|
+
return next;
|
|
5849
|
+
};
|
|
5850
|
+
}
|
|
5851
|
+
function gitRevParseHead(repoRoot, branch) {
|
|
5852
|
+
return execFileSync4("git", ["rev-parse", `refs/heads/${branch}`], {
|
|
5853
|
+
cwd: repoRoot,
|
|
5854
|
+
encoding: "utf-8",
|
|
5855
|
+
stdio: "pipe"
|
|
5856
|
+
}).trim();
|
|
5857
|
+
}
|
|
5858
|
+
function gitPath(worktreePath, gitPathName) {
|
|
5859
|
+
try {
|
|
5860
|
+
const resolved = execFileSync4("git", ["rev-parse", "--git-path", gitPathName], {
|
|
5861
|
+
cwd: worktreePath,
|
|
5862
|
+
encoding: "utf-8",
|
|
5863
|
+
stdio: "pipe"
|
|
5864
|
+
}).trim();
|
|
5865
|
+
if (resolved) return resolved;
|
|
5866
|
+
} catch {
|
|
5867
|
+
}
|
|
5868
|
+
return join21(worktreePath, ".git", gitPathName);
|
|
5869
|
+
}
|
|
5870
|
+
function isRebaseInProgress(worktreePath) {
|
|
5871
|
+
return existsSync18(gitPath(worktreePath, "rebase-merge"));
|
|
5872
|
+
}
|
|
5873
|
+
function isWorktreeRegistered(repoRoot, wtPath) {
|
|
5874
|
+
try {
|
|
5875
|
+
const out = execFileSync4("git", ["worktree", "list", "--porcelain"], {
|
|
5876
|
+
cwd: repoRoot,
|
|
5877
|
+
encoding: "utf-8",
|
|
5878
|
+
stdio: "pipe"
|
|
5879
|
+
});
|
|
5880
|
+
for (const line of out.split("\n")) {
|
|
5881
|
+
if (line.startsWith("worktree ")) {
|
|
5882
|
+
if (line.slice("worktree ".length).trim() === wtPath) return true;
|
|
5883
|
+
}
|
|
5884
|
+
}
|
|
5885
|
+
} catch {
|
|
5886
|
+
}
|
|
5887
|
+
return false;
|
|
5888
|
+
}
|
|
5889
|
+
function ensureMergerWorktree(repoRoot, mergerPath, leaderBranch) {
|
|
5890
|
+
ensureDirWithMode(dirname14(mergerPath));
|
|
5891
|
+
if (existsSync18(mergerPath) && isWorktreeRegistered(repoRoot, mergerPath)) {
|
|
5892
|
+
return;
|
|
5893
|
+
}
|
|
5894
|
+
execFileSync4("git", ["worktree", "add", "--force", mergerPath, leaderBranch], {
|
|
5895
|
+
cwd: repoRoot,
|
|
5896
|
+
stdio: "pipe"
|
|
5897
|
+
});
|
|
5898
|
+
}
|
|
5899
|
+
function preflightMergerWorktree(mergerPath, leaderBranch) {
|
|
5900
|
+
try {
|
|
5901
|
+
execFileSync4("git", ["fetch", "--no-tags", "origin", leaderBranch], {
|
|
5902
|
+
cwd: mergerPath,
|
|
5903
|
+
stdio: "pipe"
|
|
5904
|
+
});
|
|
5905
|
+
} catch {
|
|
5906
|
+
}
|
|
5907
|
+
execFileSync4("git", ["reset", "--hard", leaderBranch], {
|
|
5908
|
+
cwd: mergerPath,
|
|
5909
|
+
stdio: "pipe"
|
|
5910
|
+
});
|
|
5911
|
+
}
|
|
5912
|
+
function parseUUFiles(porcelainOutput) {
|
|
5913
|
+
const files = [];
|
|
5914
|
+
for (const line of porcelainOutput.split("\n")) {
|
|
5915
|
+
if (line.startsWith("UU ")) {
|
|
5916
|
+
files.push(line.slice(3).trim());
|
|
5917
|
+
} else if (line.startsWith("AA ") || line.startsWith("DD ")) {
|
|
5918
|
+
files.push(line.slice(3).trim());
|
|
5919
|
+
}
|
|
5920
|
+
}
|
|
5921
|
+
return files;
|
|
5922
|
+
}
|
|
5923
|
+
async function startMergeOrchestrator(config) {
|
|
5924
|
+
assertRuntimeV2Gate();
|
|
5925
|
+
assertLeaderBranchAllowed(config.leaderBranch);
|
|
5926
|
+
validateBranchName(config.leaderBranch);
|
|
5927
|
+
const pollIntervalMs = config.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
5928
|
+
const drainTimeoutMs = config.drainTimeoutMs ?? DEFAULT_DRAIN_TIMEOUT_MS;
|
|
5929
|
+
const mergerPath = mergerWorktreePathFor(config.repoRoot, config.teamName);
|
|
5930
|
+
validateResolvedPath(mergerPath, config.repoRoot);
|
|
5931
|
+
ensureMergerWorktree(config.repoRoot, mergerPath, config.leaderBranch);
|
|
5932
|
+
await ensureLeaderInbox(config.teamName, config.cwd);
|
|
5933
|
+
const persistedPath = persistedStatePath(config.repoRoot, config.teamName);
|
|
5934
|
+
let persisted = { lastShas: {} };
|
|
5935
|
+
if (existsSync18(persistedPath)) {
|
|
5936
|
+
try {
|
|
5937
|
+
const { readFileSync: readFileSync12 } = await import("node:fs");
|
|
5938
|
+
persisted = JSON.parse(readFileSync12(persistedPath, "utf-8"));
|
|
5939
|
+
} catch {
|
|
5940
|
+
persisted = { lastShas: {} };
|
|
5941
|
+
}
|
|
5942
|
+
}
|
|
5943
|
+
const workers = /* @__PURE__ */ new Map();
|
|
5944
|
+
const pausedWorkers = /* @__PURE__ */ new Set();
|
|
5945
|
+
const mutex = createMutex();
|
|
5946
|
+
let stopped = false;
|
|
5947
|
+
function persistState() {
|
|
5948
|
+
const payload = {
|
|
5949
|
+
lastShas: Object.fromEntries(
|
|
5950
|
+
Array.from(workers.values()).map((w) => [w.workerName, w.lastObservedSha])
|
|
5951
|
+
)
|
|
5952
|
+
};
|
|
5953
|
+
atomicWriteJson(persistedPath, payload);
|
|
5954
|
+
}
|
|
5955
|
+
async function fanOutRebase(triggeringWorker) {
|
|
5956
|
+
for (const other of workers.values()) {
|
|
5957
|
+
if (other.workerName === triggeringWorker) continue;
|
|
5958
|
+
const wtPath = other.workerWorktreePath;
|
|
5959
|
+
if (isRebaseInProgress(wtPath)) {
|
|
5960
|
+
await appendEvent(config.repoRoot, config.teamName, {
|
|
5961
|
+
type: "rebase_skipped_in_progress",
|
|
5962
|
+
worker: other.workerName,
|
|
5963
|
+
reason: "rebase-already-in-progress"
|
|
5964
|
+
});
|
|
5965
|
+
continue;
|
|
5966
|
+
}
|
|
5967
|
+
await appendEvent(config.repoRoot, config.teamName, {
|
|
5968
|
+
type: "rebase_triggered",
|
|
5969
|
+
worker: other.workerName
|
|
5970
|
+
});
|
|
5971
|
+
await pauseHookViaSentinel(wtPath);
|
|
5972
|
+
pausedWorkers.add(other.workerName);
|
|
5973
|
+
try {
|
|
5974
|
+
execFileSync4("git", ["fetch", "--no-tags", "origin", config.leaderBranch], {
|
|
5975
|
+
cwd: wtPath,
|
|
5976
|
+
stdio: "pipe"
|
|
5977
|
+
});
|
|
5978
|
+
} catch {
|
|
5979
|
+
}
|
|
5980
|
+
try {
|
|
5981
|
+
execFileSync4("git", ["rebase", config.leaderBranch], {
|
|
5982
|
+
cwd: wtPath,
|
|
5983
|
+
stdio: "pipe"
|
|
5984
|
+
});
|
|
5985
|
+
await resumeHookViaSentinel(wtPath);
|
|
5986
|
+
pausedWorkers.delete(other.workerName);
|
|
5987
|
+
await appendEvent(config.repoRoot, config.teamName, {
|
|
5988
|
+
type: "rebase_succeeded",
|
|
5989
|
+
worker: other.workerName
|
|
5990
|
+
});
|
|
5991
|
+
} catch {
|
|
5992
|
+
let conflictingFiles = [];
|
|
5993
|
+
try {
|
|
5994
|
+
const status = execFileSync4("git", ["status", "--porcelain"], {
|
|
5995
|
+
cwd: wtPath,
|
|
5996
|
+
encoding: "utf-8",
|
|
5997
|
+
stdio: "pipe"
|
|
5998
|
+
});
|
|
5999
|
+
conflictingFiles = parseUUFiles(status);
|
|
6000
|
+
} catch {
|
|
6001
|
+
conflictingFiles = ["(rebase status unavailable)"];
|
|
6002
|
+
}
|
|
6003
|
+
const baseSha = (() => {
|
|
6004
|
+
try {
|
|
6005
|
+
return execFileSync4("git", ["rev-parse", `refs/heads/${config.leaderBranch}`], {
|
|
6006
|
+
cwd: config.repoRoot,
|
|
6007
|
+
encoding: "utf-8",
|
|
6008
|
+
stdio: "pipe"
|
|
6009
|
+
}).trim();
|
|
6010
|
+
} catch {
|
|
6011
|
+
return "unknown";
|
|
6012
|
+
}
|
|
6013
|
+
})();
|
|
6014
|
+
const message = formatRebaseConflictForWorker({
|
|
6015
|
+
workerName: other.workerName,
|
|
6016
|
+
workerBranch: other.workerBranch,
|
|
6017
|
+
leaderBranch: config.leaderBranch,
|
|
6018
|
+
conflictingFiles,
|
|
6019
|
+
baseSha,
|
|
6020
|
+
worktreePath: wtPath,
|
|
6021
|
+
observedAt: Date.now()
|
|
6022
|
+
});
|
|
6023
|
+
try {
|
|
6024
|
+
await appendToInbox(config.teamName, other.workerName, message, config.cwd);
|
|
6025
|
+
} catch {
|
|
6026
|
+
}
|
|
6027
|
+
await appendEvent(config.repoRoot, config.teamName, {
|
|
6028
|
+
type: "rebase_conflict",
|
|
6029
|
+
worker: other.workerName,
|
|
6030
|
+
data: { conflictingFiles }
|
|
6031
|
+
});
|
|
6032
|
+
}
|
|
6033
|
+
}
|
|
6034
|
+
}
|
|
6035
|
+
async function attemptMergeForWorker(entry) {
|
|
6036
|
+
await mutex(async () => {
|
|
6037
|
+
const targetSha = entry.lastObservedSha;
|
|
6038
|
+
await appendEvent(config.repoRoot, config.teamName, {
|
|
6039
|
+
type: "merge_attempted",
|
|
6040
|
+
worker: entry.workerName,
|
|
6041
|
+
data: { targetSha }
|
|
6042
|
+
});
|
|
6043
|
+
try {
|
|
6044
|
+
preflightMergerWorktree(mergerPath, config.leaderBranch);
|
|
6045
|
+
} catch (err) {
|
|
6046
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
6047
|
+
entry.consecutiveFailures += 1;
|
|
6048
|
+
await appendEvent(config.repoRoot, config.teamName, {
|
|
6049
|
+
type: "merge_conflict",
|
|
6050
|
+
worker: entry.workerName,
|
|
6051
|
+
reason: `preflight_failed:${reason}`
|
|
6052
|
+
});
|
|
6053
|
+
return;
|
|
6054
|
+
}
|
|
6055
|
+
const conflicts = checkMergeConflicts(
|
|
6056
|
+
entry.workerBranch,
|
|
6057
|
+
config.leaderBranch,
|
|
6058
|
+
mergerPath
|
|
6059
|
+
);
|
|
6060
|
+
if (conflicts.length > 0) {
|
|
6061
|
+
let mergeBaseSha = "unknown";
|
|
6062
|
+
try {
|
|
6063
|
+
mergeBaseSha = execFileSync4(
|
|
6064
|
+
"git",
|
|
6065
|
+
["merge-base", config.leaderBranch, entry.workerBranch],
|
|
6066
|
+
{ cwd: mergerPath, encoding: "utf-8", stdio: "pipe" }
|
|
6067
|
+
).trim();
|
|
6068
|
+
} catch {
|
|
6069
|
+
}
|
|
6070
|
+
const message = formatMergeConflictForLeader({
|
|
6071
|
+
workerName: entry.workerName,
|
|
6072
|
+
workerBranch: entry.workerBranch,
|
|
6073
|
+
leaderBranch: config.leaderBranch,
|
|
6074
|
+
conflictingFiles: conflicts,
|
|
6075
|
+
mergeBaseSha,
|
|
6076
|
+
observedAt: Date.now()
|
|
6077
|
+
});
|
|
6078
|
+
try {
|
|
6079
|
+
await appendToLeaderInbox(config.teamName, message, config.cwd);
|
|
6080
|
+
} catch {
|
|
6081
|
+
}
|
|
6082
|
+
await appendEvent(config.repoRoot, config.teamName, {
|
|
6083
|
+
type: "merge_conflict",
|
|
6084
|
+
worker: entry.workerName,
|
|
6085
|
+
data: { conflictingFiles: conflicts, mergeBaseSha }
|
|
6086
|
+
});
|
|
6087
|
+
entry.consecutiveFailures += 1;
|
|
6088
|
+
return;
|
|
6089
|
+
}
|
|
6090
|
+
const result = mergeWorkerBranch(
|
|
6091
|
+
entry.workerBranch,
|
|
6092
|
+
config.leaderBranch,
|
|
6093
|
+
mergerPath
|
|
6094
|
+
);
|
|
6095
|
+
if (!result.success) {
|
|
6096
|
+
const message = formatMergeConflictForLeader({
|
|
6097
|
+
workerName: entry.workerName,
|
|
6098
|
+
workerBranch: entry.workerBranch,
|
|
6099
|
+
leaderBranch: config.leaderBranch,
|
|
6100
|
+
conflictingFiles: result.conflicts.length > 0 ? result.conflicts : ["(merge failed after clean check)"],
|
|
6101
|
+
mergeBaseSha: "unknown",
|
|
6102
|
+
observedAt: Date.now()
|
|
6103
|
+
});
|
|
6104
|
+
try {
|
|
6105
|
+
await appendToLeaderInbox(config.teamName, message, config.cwd);
|
|
6106
|
+
} catch {
|
|
6107
|
+
}
|
|
6108
|
+
await appendEvent(config.repoRoot, config.teamName, {
|
|
6109
|
+
type: "merge_conflict",
|
|
6110
|
+
worker: entry.workerName,
|
|
6111
|
+
data: { conflictingFiles: result.conflicts }
|
|
6112
|
+
});
|
|
6113
|
+
entry.consecutiveFailures += 1;
|
|
6114
|
+
return;
|
|
6115
|
+
}
|
|
6116
|
+
entry.lastMergedSha = targetSha;
|
|
6117
|
+
entry.consecutiveFailures = 0;
|
|
6118
|
+
await appendEvent(config.repoRoot, config.teamName, {
|
|
6119
|
+
type: "merge_succeeded",
|
|
6120
|
+
worker: entry.workerName,
|
|
6121
|
+
data: { mergeCommit: result.mergeCommit, targetSha }
|
|
6122
|
+
});
|
|
6123
|
+
if (stopped) return;
|
|
6124
|
+
await fanOutRebase(entry.workerName);
|
|
6125
|
+
});
|
|
6126
|
+
}
|
|
6127
|
+
async function runPollOnce() {
|
|
6128
|
+
if (stopped) return;
|
|
6129
|
+
for (const entry of workers.values()) {
|
|
6130
|
+
const skipModulo = Math.min(30, Math.pow(2, entry.consecutiveFailures));
|
|
6131
|
+
if (skipModulo > 1 && pollTickCount % skipModulo !== 0) {
|
|
6132
|
+
continue;
|
|
6133
|
+
}
|
|
6134
|
+
if (pausedWorkers.has(entry.workerName)) {
|
|
6135
|
+
if (!isRebaseInProgress(entry.workerWorktreePath)) {
|
|
6136
|
+
await handleRebaseResolution(entry);
|
|
6137
|
+
} else {
|
|
6138
|
+
continue;
|
|
6139
|
+
}
|
|
6140
|
+
}
|
|
6141
|
+
let currentSha;
|
|
6142
|
+
try {
|
|
6143
|
+
currentSha = gitRevParseHead(config.repoRoot, entry.workerBranch);
|
|
6144
|
+
} catch (err) {
|
|
6145
|
+
entry.consecutiveFailures += 1;
|
|
6146
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
6147
|
+
await appendEvent(config.repoRoot, config.teamName, {
|
|
6148
|
+
type: "commit_observed",
|
|
6149
|
+
worker: entry.workerName,
|
|
6150
|
+
reason: `rev_parse_failed:${reason}`
|
|
6151
|
+
});
|
|
6152
|
+
continue;
|
|
6153
|
+
}
|
|
6154
|
+
if (currentSha && currentSha !== entry.lastObservedSha) {
|
|
6155
|
+
entry.lastObservedSha = currentSha;
|
|
6156
|
+
try {
|
|
6157
|
+
persistState();
|
|
6158
|
+
} catch {
|
|
6159
|
+
}
|
|
6160
|
+
await appendEvent(config.repoRoot, config.teamName, {
|
|
6161
|
+
type: "commit_observed",
|
|
6162
|
+
worker: entry.workerName,
|
|
6163
|
+
data: { sha: currentSha }
|
|
6164
|
+
});
|
|
6165
|
+
try {
|
|
6166
|
+
await attemptMergeForWorker(entry);
|
|
6167
|
+
} catch (err) {
|
|
6168
|
+
entry.consecutiveFailures += 1;
|
|
6169
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
6170
|
+
await appendEvent(config.repoRoot, config.teamName, {
|
|
6171
|
+
type: "merge_conflict",
|
|
6172
|
+
worker: entry.workerName,
|
|
6173
|
+
reason: `merge_threw:${reason}`
|
|
6174
|
+
});
|
|
6175
|
+
}
|
|
6176
|
+
}
|
|
6177
|
+
}
|
|
6178
|
+
}
|
|
6179
|
+
async function handleRebaseResolution(entry) {
|
|
6180
|
+
pausedWorkers.delete(entry.workerName);
|
|
6181
|
+
try {
|
|
6182
|
+
const status = execFileSync4("git", ["status", "--porcelain"], {
|
|
6183
|
+
cwd: entry.workerWorktreePath,
|
|
6184
|
+
encoding: "utf-8",
|
|
6185
|
+
stdio: "pipe"
|
|
6186
|
+
}).trim();
|
|
6187
|
+
if (status.length > 0) {
|
|
6188
|
+
const dirtyFiles = status.split("\n").map((l) => l.trim().replace(/^\S+\s+/, "")).filter((s) => s.length > 0);
|
|
6189
|
+
const audit = `## Auto-commit audit: the following files were modified during rebase pause and will be folded into the next auto-commit:
|
|
6190
|
+
${dirtyFiles.map((f) => `- \`${f}\``).join("\n")}`;
|
|
6191
|
+
try {
|
|
6192
|
+
await appendToInbox(config.teamName, entry.workerName, audit, config.cwd);
|
|
6193
|
+
} catch {
|
|
6194
|
+
}
|
|
6195
|
+
}
|
|
6196
|
+
} catch {
|
|
6197
|
+
}
|
|
6198
|
+
await resumeHookViaSentinel(entry.workerWorktreePath);
|
|
6199
|
+
await appendEvent(config.repoRoot, config.teamName, {
|
|
6200
|
+
type: "rebase_resolved",
|
|
6201
|
+
worker: entry.workerName
|
|
6202
|
+
});
|
|
6203
|
+
}
|
|
6204
|
+
let pollTickCount = 0;
|
|
6205
|
+
const interval = setInterval(() => {
|
|
6206
|
+
pollTickCount += 1;
|
|
6207
|
+
void runPollOnce().catch(() => {
|
|
6208
|
+
});
|
|
6209
|
+
}, pollIntervalMs);
|
|
6210
|
+
if (typeof interval.unref === "function") interval.unref();
|
|
6211
|
+
return {
|
|
6212
|
+
async registerWorker(workerName) {
|
|
6213
|
+
if (workers.has(workerName)) return;
|
|
6214
|
+
const workerBranch = getBranchName(config.teamName, workerName);
|
|
6215
|
+
validateBranchName(workerBranch);
|
|
6216
|
+
const wtPath = getWorktreePath(config.repoRoot, config.teamName, workerName);
|
|
6217
|
+
let seedSha = persisted.lastShas[workerName] ?? "";
|
|
6218
|
+
if (!seedSha) {
|
|
6219
|
+
try {
|
|
6220
|
+
seedSha = gitRevParseHead(config.repoRoot, workerBranch);
|
|
6221
|
+
} catch {
|
|
6222
|
+
seedSha = "";
|
|
6223
|
+
}
|
|
6224
|
+
}
|
|
6225
|
+
workers.set(workerName, {
|
|
6226
|
+
workerName,
|
|
6227
|
+
workerBranch,
|
|
6228
|
+
workerWorktreePath: wtPath,
|
|
6229
|
+
lastObservedSha: seedSha,
|
|
6230
|
+
lastMergedSha: seedSha,
|
|
6231
|
+
consecutiveFailures: 0
|
|
6232
|
+
});
|
|
6233
|
+
try {
|
|
6234
|
+
persistState();
|
|
6235
|
+
} catch {
|
|
6236
|
+
}
|
|
6237
|
+
},
|
|
6238
|
+
async unregisterWorker(workerName) {
|
|
6239
|
+
workers.delete(workerName);
|
|
6240
|
+
pausedWorkers.delete(workerName);
|
|
6241
|
+
try {
|
|
6242
|
+
persistState();
|
|
6243
|
+
} catch {
|
|
6244
|
+
}
|
|
6245
|
+
},
|
|
6246
|
+
async pollOnce() {
|
|
6247
|
+
await runPollOnce();
|
|
6248
|
+
},
|
|
6249
|
+
async drainAndStop() {
|
|
6250
|
+
stopped = true;
|
|
6251
|
+
clearInterval(interval);
|
|
6252
|
+
const start = Date.now();
|
|
6253
|
+
const unmerged = [];
|
|
6254
|
+
const candidates = Array.from(workers.values()).filter(
|
|
6255
|
+
(w) => w.lastObservedSha && w.lastObservedSha !== w.lastMergedSha
|
|
6256
|
+
);
|
|
6257
|
+
for (const entry of candidates) {
|
|
6258
|
+
const remaining = drainTimeoutMs - (Date.now() - start);
|
|
6259
|
+
if (remaining <= 0) {
|
|
6260
|
+
unmerged.push({ workerName: entry.workerName, reason: "drain-timeout" });
|
|
6261
|
+
continue;
|
|
6262
|
+
}
|
|
6263
|
+
const merged = await Promise.race([
|
|
6264
|
+
(async () => {
|
|
6265
|
+
try {
|
|
6266
|
+
await attemptMergeForWorker(entry);
|
|
6267
|
+
return true;
|
|
6268
|
+
} catch {
|
|
6269
|
+
return false;
|
|
6270
|
+
}
|
|
6271
|
+
})(),
|
|
6272
|
+
new Promise((resolve5) => {
|
|
6273
|
+
const t = setTimeout(() => resolve5(false), remaining);
|
|
6274
|
+
if (typeof t.unref === "function") t.unref();
|
|
6275
|
+
})
|
|
6276
|
+
]);
|
|
6277
|
+
if (!merged || entry.lastMergedSha !== entry.lastObservedSha) {
|
|
6278
|
+
unmerged.push({
|
|
6279
|
+
workerName: entry.workerName,
|
|
6280
|
+
reason: merged ? "merge-conflict" : "drain-timeout"
|
|
6281
|
+
});
|
|
6282
|
+
}
|
|
6283
|
+
}
|
|
6284
|
+
if (unmerged.length > 0) {
|
|
6285
|
+
const auditPath = teardownAuditPath(config.repoRoot, config.teamName);
|
|
6286
|
+
await mkdir9(dirname14(auditPath), { recursive: true });
|
|
6287
|
+
for (const u of unmerged) {
|
|
6288
|
+
const row = JSON.stringify({
|
|
6289
|
+
type: "unmerged_at_shutdown",
|
|
6290
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6291
|
+
team: config.teamName,
|
|
6292
|
+
worker: u.workerName,
|
|
6293
|
+
reason: u.reason
|
|
6294
|
+
});
|
|
6295
|
+
try {
|
|
6296
|
+
await appendFile5(auditPath, `${row}
|
|
6297
|
+
`, "utf-8");
|
|
6298
|
+
} catch {
|
|
6299
|
+
}
|
|
6300
|
+
}
|
|
6301
|
+
const message = `## Teardown audit: unmerged worker branches at shutdown
|
|
6302
|
+
|
|
6303
|
+
${unmerged.map((u) => `- ${u.workerName}: ${u.reason}`).join("\n")}`;
|
|
6304
|
+
try {
|
|
6305
|
+
await appendToLeaderInbox(config.teamName, message, config.cwd);
|
|
6306
|
+
} catch {
|
|
6307
|
+
}
|
|
6308
|
+
}
|
|
6309
|
+
return { unmerged };
|
|
6310
|
+
},
|
|
6311
|
+
getState() {
|
|
6312
|
+
return {
|
|
6313
|
+
workers: Array.from(workers.keys()),
|
|
6314
|
+
lastShas: Object.fromEntries(
|
|
6315
|
+
Array.from(workers.values()).map((w) => [w.workerName, w.lastObservedSha])
|
|
6316
|
+
),
|
|
6317
|
+
mergerWorktreePath: mergerPath
|
|
6318
|
+
};
|
|
6319
|
+
}
|
|
6320
|
+
};
|
|
6321
|
+
}
|
|
6322
|
+
async function recoverFromRestart(config) {
|
|
6323
|
+
const persistedPath = persistedStatePath(config.repoRoot, config.teamName);
|
|
6324
|
+
let persistedShasLoaded = 0;
|
|
6325
|
+
if (existsSync18(persistedPath)) {
|
|
6326
|
+
try {
|
|
6327
|
+
const { readFileSync: readFileSync12 } = await import("node:fs");
|
|
6328
|
+
const persisted = JSON.parse(readFileSync12(persistedPath, "utf-8"));
|
|
6329
|
+
persistedShasLoaded = Object.keys(persisted.lastShas ?? {}).length;
|
|
6330
|
+
} catch {
|
|
6331
|
+
persistedShasLoaded = 0;
|
|
6332
|
+
}
|
|
6333
|
+
}
|
|
6334
|
+
const orphanedRebases = [];
|
|
6335
|
+
let entries = [];
|
|
6336
|
+
try {
|
|
6337
|
+
entries = listTeamWorktrees(config.teamName, config.repoRoot).map((w) => ({
|
|
6338
|
+
workerName: w.workerName,
|
|
6339
|
+
path: w.path
|
|
6340
|
+
}));
|
|
6341
|
+
} catch {
|
|
6342
|
+
entries = [];
|
|
6343
|
+
}
|
|
6344
|
+
for (const entry of entries) {
|
|
6345
|
+
if (!isRebaseInProgress(entry.path)) continue;
|
|
6346
|
+
orphanedRebases.push(entry.workerName);
|
|
6347
|
+
const message = `### Runtime restart recovery \u2014 your branch is mid-rebase
|
|
6348
|
+
|
|
6349
|
+
Runtime restarted while your branch was mid-rebase onto \`${config.leaderBranch}\`.
|
|
6350
|
+
|
|
6351
|
+
**Worktree:** \`${entry.path}\`
|
|
6352
|
+
|
|
6353
|
+
Cadence remains paused. Resolve and \`git rebase --continue\`, or \`git rebase --abort\` to bail.
|
|
6354
|
+
Cadence resumes once the git rebase state is gone.`;
|
|
6355
|
+
try {
|
|
6356
|
+
await appendToInbox(config.teamName, entry.workerName, message, config.cwd);
|
|
6357
|
+
} catch {
|
|
6358
|
+
}
|
|
6359
|
+
}
|
|
6360
|
+
if (orphanedRebases.length > 0 || persistedShasLoaded > 0) {
|
|
6361
|
+
try {
|
|
6362
|
+
await appendEvent(config.repoRoot, config.teamName, {
|
|
6363
|
+
type: "restart_recovery",
|
|
6364
|
+
data: { orphanedRebases, persistedShasLoaded }
|
|
6365
|
+
});
|
|
6366
|
+
} catch {
|
|
6367
|
+
}
|
|
6368
|
+
}
|
|
6369
|
+
return { orphanedRebases, persistedShasLoaded };
|
|
6370
|
+
}
|
|
6371
|
+
var DEFAULT_POLL_INTERVAL_MS, DEFAULT_DRAIN_TIMEOUT_MS;
|
|
6372
|
+
var init_merge_orchestrator = __esm({
|
|
6373
|
+
"src/team/merge-orchestrator.ts"() {
|
|
6374
|
+
"use strict";
|
|
6375
|
+
init_fs_utils();
|
|
6376
|
+
init_runtime_flags();
|
|
6377
|
+
init_tmux_session();
|
|
6378
|
+
init_git_worktree();
|
|
6379
|
+
init_merge_coordinator();
|
|
6380
|
+
init_worker_bootstrap();
|
|
6381
|
+
init_leader_inbox();
|
|
6382
|
+
init_conflict_mailbox();
|
|
6383
|
+
init_worker_commit_cadence();
|
|
6384
|
+
DEFAULT_POLL_INTERVAL_MS = 1e3;
|
|
6385
|
+
DEFAULT_DRAIN_TIMEOUT_MS = 1e4;
|
|
6386
|
+
}
|
|
6387
|
+
});
|
|
6388
|
+
|
|
5388
6389
|
// src/team/runtime-v2.ts
|
|
5389
6390
|
var runtime_v2_exports = {};
|
|
5390
6391
|
__export(runtime_v2_exports, {
|
|
@@ -5399,15 +6400,53 @@ __export(runtime_v2_exports, {
|
|
|
5399
6400
|
startTeamV2: () => startTeamV2,
|
|
5400
6401
|
writeWatchdogFailedMarker: () => writeWatchdogFailedMarker
|
|
5401
6402
|
});
|
|
5402
|
-
import { join as
|
|
5403
|
-
import { existsSync as
|
|
5404
|
-
import { mkdir as
|
|
6403
|
+
import { join as join22, resolve as resolve4 } from "path";
|
|
6404
|
+
import { existsSync as existsSync19 } from "fs";
|
|
6405
|
+
import { mkdir as mkdir10, readdir as readdir2, readFile as readFile10, rm as rm4, writeFile as writeFile7 } from "fs/promises";
|
|
5405
6406
|
import { performance } from "perf_hooks";
|
|
5406
|
-
|
|
5407
|
-
|
|
5408
|
-
|
|
5409
|
-
|
|
5410
|
-
|
|
6407
|
+
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
6408
|
+
function registerTeamOrchestrator(teamName, handle) {
|
|
6409
|
+
orchestratorByTeam.set(teamName, handle);
|
|
6410
|
+
}
|
|
6411
|
+
function getTeamOrchestrator(teamName) {
|
|
6412
|
+
return orchestratorByTeam.get(teamName);
|
|
6413
|
+
}
|
|
6414
|
+
function unregisterTeamOrchestrator(teamName) {
|
|
6415
|
+
orchestratorByTeam.delete(teamName);
|
|
6416
|
+
}
|
|
6417
|
+
function registerTeamCadence(teamName, context, poller) {
|
|
6418
|
+
const entry = cadenceByTeam.get(teamName) ?? { pollers: [], contexts: [] };
|
|
6419
|
+
entry.contexts.push(context);
|
|
6420
|
+
if (poller) entry.pollers.push(poller);
|
|
6421
|
+
cadenceByTeam.set(teamName, entry);
|
|
6422
|
+
}
|
|
6423
|
+
async function stopTeamCadence(teamName) {
|
|
6424
|
+
const entry = cadenceByTeam.get(teamName);
|
|
6425
|
+
if (!entry) return;
|
|
6426
|
+
cadenceByTeam.delete(teamName);
|
|
6427
|
+
for (const poller of entry.pollers) {
|
|
6428
|
+
try {
|
|
6429
|
+
poller.stop();
|
|
6430
|
+
} catch {
|
|
6431
|
+
}
|
|
6432
|
+
}
|
|
6433
|
+
for (const context of entry.contexts) {
|
|
6434
|
+
try {
|
|
6435
|
+
await uninstallCommitCadence(context);
|
|
6436
|
+
} catch {
|
|
6437
|
+
}
|
|
6438
|
+
}
|
|
6439
|
+
}
|
|
6440
|
+
function resolveLeaderBranch(cwd) {
|
|
6441
|
+
const out = execFileSync5("git", ["branch", "--show-current"], {
|
|
6442
|
+
cwd,
|
|
6443
|
+
encoding: "utf-8",
|
|
6444
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
6445
|
+
}).trim();
|
|
6446
|
+
if (!out) {
|
|
6447
|
+
throw new Error("auto-merge requires a non-detached leader branch (git branch --show-current returned empty)");
|
|
6448
|
+
}
|
|
6449
|
+
return out;
|
|
5411
6450
|
}
|
|
5412
6451
|
function resolveTaskAssignment(task, resolvedRouting, roleRoutingConfig, resolvedBinaryPaths, fallbackAgent) {
|
|
5413
6452
|
const canonicalRoles = new Set(CANONICAL_TEAM_ROLES);
|
|
@@ -5550,7 +6589,7 @@ function hasWorkerStatusProgress(status, taskId) {
|
|
|
5550
6589
|
}
|
|
5551
6590
|
async function hasWorkerTaskClaimEvidence(teamName, workerName, cwd, taskId) {
|
|
5552
6591
|
try {
|
|
5553
|
-
const raw = await
|
|
6592
|
+
const raw = await readFile10(absPath(cwd, TeamPaths.taskFile(teamName, taskId)), "utf-8");
|
|
5554
6593
|
const task = JSON.parse(raw);
|
|
5555
6594
|
return task.owner === workerName && ["in_progress", "completed", "failed"].includes(task.status);
|
|
5556
6595
|
} catch {
|
|
@@ -5649,6 +6688,18 @@ async function spawnV2Worker(opts) {
|
|
|
5649
6688
|
if (usePromptMode) {
|
|
5650
6689
|
launchArgs.push(...getPromptModeArgs(opts.agentType, promptModeStartupPrompt));
|
|
5651
6690
|
}
|
|
6691
|
+
if (opts.autoMerge && opts.worktreePath) {
|
|
6692
|
+
const cadenceContext = {
|
|
6693
|
+
teamName: opts.teamName,
|
|
6694
|
+
workerName: opts.workerName,
|
|
6695
|
+
worktreePath: opts.worktreePath,
|
|
6696
|
+
agentType: opts.agentType,
|
|
6697
|
+
enabled: true
|
|
6698
|
+
};
|
|
6699
|
+
const cadence = await installCommitCadence(cadenceContext);
|
|
6700
|
+
const poller = cadence.method === "fallback-poll" ? startFallbackPoller(opts.worktreePath, opts.workerName) : void 0;
|
|
6701
|
+
registerTeamCadence(opts.teamName, cadenceContext, poller);
|
|
6702
|
+
}
|
|
5652
6703
|
const paneConfig = {
|
|
5653
6704
|
teamName: opts.teamName,
|
|
5654
6705
|
workerName: opts.workerName,
|
|
@@ -5752,16 +6803,16 @@ async function rollbackUnpersistedNativeWorktreeStartup(teamName, cwd, cause) {
|
|
|
5752
6803
|
await rm4(teamRoot, { recursive: true, force: true });
|
|
5753
6804
|
return;
|
|
5754
6805
|
}
|
|
5755
|
-
await
|
|
5756
|
-
await
|
|
6806
|
+
await mkdir10(teamRoot, { recursive: true });
|
|
6807
|
+
await writeFile7(join22(teamRoot, "startup-failure.json"), JSON.stringify({
|
|
5757
6808
|
reason: "startup_failed_before_config_persisted",
|
|
5758
6809
|
error: errorMessage,
|
|
5759
6810
|
preserved: cleanup.preserved,
|
|
5760
6811
|
recorded_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
5761
6812
|
}, null, 2), "utf-8");
|
|
5762
6813
|
} catch (rollbackError) {
|
|
5763
|
-
await
|
|
5764
|
-
await
|
|
6814
|
+
await mkdir10(teamRoot, { recursive: true });
|
|
6815
|
+
await writeFile7(join22(teamRoot, "startup-failure.json"), JSON.stringify({
|
|
5765
6816
|
reason: "startup_failed_before_config_persisted",
|
|
5766
6817
|
error: errorMessage,
|
|
5767
6818
|
rollback_error: rollbackError instanceof Error ? rollbackError.message : String(rollbackError),
|
|
@@ -5791,9 +6842,23 @@ async function startTeamV2(config) {
|
|
|
5791
6842
|
validateTeamName(sanitized);
|
|
5792
6843
|
const pluginCfg = config.pluginConfig ?? loadConfig();
|
|
5793
6844
|
const resolvedRouting = buildResolvedRoutingSnapshot(pluginCfg);
|
|
5794
|
-
|
|
6845
|
+
let worktreeMode = normalizeTeamWorktreeMode(
|
|
5795
6846
|
process.env.OMC_TEAM_WORKTREE_MODE ?? pluginCfg.team?.ops?.worktreeMode
|
|
5796
6847
|
);
|
|
6848
|
+
let autoMergeLeaderBranch;
|
|
6849
|
+
if (config.autoMerge) {
|
|
6850
|
+
if (!isRuntimeV2Enabled()) {
|
|
6851
|
+
throw new Error("auto-merge requires OMC_RUNTIME_V2=1 (this feature is v2-only).");
|
|
6852
|
+
}
|
|
6853
|
+
autoMergeLeaderBranch = resolveLeaderBranch(leaderCwd);
|
|
6854
|
+
const stripped = autoMergeLeaderBranch.replace(/^refs\/heads\//i, "").toLowerCase();
|
|
6855
|
+
if (stripped === "main" || stripped === "master") {
|
|
6856
|
+
throw new Error("auto-merge refuses main/master leader branch \u2014 use a feature branch");
|
|
6857
|
+
}
|
|
6858
|
+
if (worktreeMode !== "named") {
|
|
6859
|
+
worktreeMode = "named";
|
|
6860
|
+
}
|
|
6861
|
+
}
|
|
5797
6862
|
const workspaceMode = worktreeMode === "disabled" ? "single" : "worktree";
|
|
5798
6863
|
const agentTypes = config.agentTypes;
|
|
5799
6864
|
const resolvedBinaryPaths = {};
|
|
@@ -5823,9 +6888,9 @@ async function startTeamV2(config) {
|
|
|
5823
6888
|
} catch {
|
|
5824
6889
|
}
|
|
5825
6890
|
}
|
|
5826
|
-
await
|
|
5827
|
-
await
|
|
5828
|
-
await
|
|
6891
|
+
await mkdir10(absPath(leaderCwd, TeamPaths.tasks(sanitized)), { recursive: true });
|
|
6892
|
+
await mkdir10(absPath(leaderCwd, TeamPaths.workers(sanitized)), { recursive: true });
|
|
6893
|
+
await mkdir10(join22(leaderCwd, ".omcp", "state", "team", sanitized, "mailbox"), { recursive: true });
|
|
5829
6894
|
const missingBinaryLogFailure = createSwallowedErrorLogger(
|
|
5830
6895
|
"team.runtime-v2.startTeamV2 cli_binary_missing event failed"
|
|
5831
6896
|
);
|
|
@@ -5843,8 +6908,8 @@ async function startTeamV2(config) {
|
|
|
5843
6908
|
for (let i = 0; i < config.tasks.length; i++) {
|
|
5844
6909
|
const taskId = String(i + 1);
|
|
5845
6910
|
const taskFilePath = absPath(leaderCwd, TeamPaths.taskFile(sanitized, taskId));
|
|
5846
|
-
await
|
|
5847
|
-
await
|
|
6911
|
+
await mkdir10(join22(taskFilePath, ".."), { recursive: true });
|
|
6912
|
+
await writeFile7(taskFilePath, JSON.stringify({
|
|
5848
6913
|
id: taskId,
|
|
5849
6914
|
subject: config.tasks[i].subject,
|
|
5850
6915
|
description: config.tasks[i].description,
|
|
@@ -5916,7 +6981,7 @@ async function startTeamV2(config) {
|
|
|
5916
6981
|
});
|
|
5917
6982
|
const worktree = workerWorktrees.get(wName);
|
|
5918
6983
|
if (worktree) {
|
|
5919
|
-
const overlayContent = await
|
|
6984
|
+
const overlayContent = await readFile10(overlayPath, "utf-8");
|
|
5920
6985
|
installWorktreeRootAgents(sanitized, wName, leaderCwd, worktree.path, overlayContent);
|
|
5921
6986
|
}
|
|
5922
6987
|
}
|
|
@@ -6026,7 +7091,7 @@ async function startTeamV2(config) {
|
|
|
6026
7091
|
next_worker_index: teamConfig.next_worker_index
|
|
6027
7092
|
};
|
|
6028
7093
|
try {
|
|
6029
|
-
await
|
|
7094
|
+
await writeFile7(absPath(leaderCwd, TeamPaths.manifest(sanitized)), JSON.stringify(teamManifest, null, 2), "utf-8");
|
|
6030
7095
|
} catch (error) {
|
|
6031
7096
|
await rollbackStartedNativeWorktreeStartup({
|
|
6032
7097
|
teamName: sanitized,
|
|
@@ -6075,6 +7140,7 @@ async function startTeamV2(config) {
|
|
|
6075
7140
|
cwd: leaderCwd,
|
|
6076
7141
|
workerCwd: workersInfo[workerIndex]?.working_dir ?? leaderCwd,
|
|
6077
7142
|
worktreePath: workersInfo[workerIndex]?.worktree_path,
|
|
7143
|
+
autoMerge: Boolean(config.autoMerge),
|
|
6078
7144
|
resolvedBinaryPaths,
|
|
6079
7145
|
...assignment.model ? { model: assignment.model } : {},
|
|
6080
7146
|
...assignment.role ? { role: assignment.role } : {}
|
|
@@ -6137,6 +7203,51 @@ async function startTeamV2(config) {
|
|
|
6137
7203
|
worker: "leader-fixed",
|
|
6138
7204
|
reason: `start_team_v2: workers=${config.workerCount} tasks=${config.tasks.length} panes=${workerPaneIds.length}`
|
|
6139
7205
|
}, leaderCwd).catch(logEventFailure);
|
|
7206
|
+
if (config.autoMerge && autoMergeLeaderBranch) {
|
|
7207
|
+
try {
|
|
7208
|
+
await ensureLeaderInbox(sanitized, leaderCwd);
|
|
7209
|
+
await appendToLeaderInbox(
|
|
7210
|
+
sanitized,
|
|
7211
|
+
extendLeaderBootstrapPrompt(sanitized),
|
|
7212
|
+
leaderCwd
|
|
7213
|
+
);
|
|
7214
|
+
try {
|
|
7215
|
+
await recoverFromRestart({
|
|
7216
|
+
teamName: sanitized,
|
|
7217
|
+
repoRoot: leaderCwd,
|
|
7218
|
+
leaderBranch: autoMergeLeaderBranch,
|
|
7219
|
+
cwd: leaderCwd
|
|
7220
|
+
});
|
|
7221
|
+
} catch (recErr) {
|
|
7222
|
+
process.stderr.write(`[team/runtime-v2] auto-merge recover-from-restart failed: ${recErr}
|
|
7223
|
+
`);
|
|
7224
|
+
}
|
|
7225
|
+
const orchestrator = await startMergeOrchestrator({
|
|
7226
|
+
teamName: sanitized,
|
|
7227
|
+
repoRoot: leaderCwd,
|
|
7228
|
+
leaderBranch: autoMergeLeaderBranch,
|
|
7229
|
+
cwd: leaderCwd
|
|
7230
|
+
});
|
|
7231
|
+
registerTeamOrchestrator(sanitized, orchestrator);
|
|
7232
|
+
for (const w of workersInfo) {
|
|
7233
|
+
await orchestrator.registerWorker(w.name);
|
|
7234
|
+
}
|
|
7235
|
+
} catch (orchErr) {
|
|
7236
|
+
await stopTeamCadence(sanitized);
|
|
7237
|
+
unregisterTeamOrchestrator(sanitized);
|
|
7238
|
+
await rollbackStartedNativeWorktreeStartup({
|
|
7239
|
+
teamName: sanitized,
|
|
7240
|
+
cwd: leaderCwd,
|
|
7241
|
+
cause: orchErr,
|
|
7242
|
+
sessionName: sessionName2,
|
|
7243
|
+
leaderPaneId,
|
|
7244
|
+
workerPaneIds,
|
|
7245
|
+
sessionMode: session.sessionMode
|
|
7246
|
+
});
|
|
7247
|
+
const reason = orchErr instanceof Error ? orchErr.message : String(orchErr);
|
|
7248
|
+
throw new Error(`auto-merge startup failed: ${reason}`);
|
|
7249
|
+
}
|
|
7250
|
+
}
|
|
6140
7251
|
return {
|
|
6141
7252
|
teamName: sanitized,
|
|
6142
7253
|
sanitizedName: sanitized,
|
|
@@ -6147,16 +7258,16 @@ async function startTeamV2(config) {
|
|
|
6147
7258
|
};
|
|
6148
7259
|
}
|
|
6149
7260
|
async function writeWatchdogFailedMarker(teamName, cwd, reason) {
|
|
6150
|
-
const { writeFile:
|
|
7261
|
+
const { writeFile: writeFile8 } = await import("fs/promises");
|
|
6151
7262
|
const marker = {
|
|
6152
7263
|
failedAt: Date.now(),
|
|
6153
7264
|
reason,
|
|
6154
7265
|
writtenBy: "runtime-v2"
|
|
6155
7266
|
};
|
|
6156
7267
|
const root = absPath(cwd, TeamPaths.root(sanitizeTeamName(teamName)));
|
|
6157
|
-
const markerPath =
|
|
6158
|
-
await
|
|
6159
|
-
await
|
|
7268
|
+
const markerPath = join22(root, "watchdog-failed.json");
|
|
7269
|
+
await mkdir10(root, { recursive: true });
|
|
7270
|
+
await writeFile8(markerPath, JSON.stringify(marker, null, 2), "utf-8");
|
|
6160
7271
|
}
|
|
6161
7272
|
async function requeueDeadWorkerTasks(teamName, deadWorkerNames, cwd) {
|
|
6162
7273
|
const logEventFailure = createSwallowedErrorLogger(
|
|
@@ -6176,9 +7287,9 @@ async function requeueDeadWorkerTasks(teamName, deadWorkerNames, cwd) {
|
|
|
6176
7287
|
retryCount: 0,
|
|
6177
7288
|
lastFailedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
6178
7289
|
};
|
|
6179
|
-
const { writeFile:
|
|
6180
|
-
await
|
|
6181
|
-
await
|
|
7290
|
+
const { writeFile: writeFile8 } = await import("fs/promises");
|
|
7291
|
+
await mkdir10(absPath(cwd, TeamPaths.tasks(sanitized)), { recursive: true });
|
|
7292
|
+
await writeFile8(sidecarPath, JSON.stringify(sidecar, null, 2), "utf-8");
|
|
6182
7293
|
const taskPath2 = absPath(cwd, TeamPaths.taskFile(sanitized, task.id));
|
|
6183
7294
|
try {
|
|
6184
7295
|
const { readFileSync: readFileSync12, writeFileSync: writeFileSync4 } = await import("fs");
|
|
@@ -6227,7 +7338,7 @@ async function processCliWorkerVerdicts(teamName, cwd) {
|
|
|
6227
7338
|
}
|
|
6228
7339
|
let payload;
|
|
6229
7340
|
try {
|
|
6230
|
-
const raw = await
|
|
7341
|
+
const raw = await readFile10(outputFile, "utf-8");
|
|
6231
7342
|
payload = parseCliWorkerVerdict(raw);
|
|
6232
7343
|
} catch (err) {
|
|
6233
7344
|
const reason = err instanceof Error ? err.message : String(err);
|
|
@@ -6510,6 +7621,39 @@ async function shutdownTeamV2(teamName, cwd, options = {}) {
|
|
|
6510
7621
|
const timeoutMs = options.timeoutMs ?? 15e3;
|
|
6511
7622
|
const sanitized = sanitizeTeamName(teamName);
|
|
6512
7623
|
const config = await readTeamConfig(sanitized, cwd);
|
|
7624
|
+
const finalizeAutoMerge = async () => {
|
|
7625
|
+
const orchestrator = getTeamOrchestrator(sanitized);
|
|
7626
|
+
if (orchestrator) {
|
|
7627
|
+
try {
|
|
7628
|
+
const drainResult = await orchestrator.drainAndStop();
|
|
7629
|
+
if (drainResult.unmerged.length > 0) {
|
|
7630
|
+
await appendTeamEvent(sanitized, {
|
|
7631
|
+
type: "team_leader_nudge",
|
|
7632
|
+
worker: "leader-fixed",
|
|
7633
|
+
reason: `auto_merge_drain_unmerged:${drainResult.unmerged.map((u) => `${u.workerName}:${u.reason}`).join(",")}`
|
|
7634
|
+
}, cwd).catch(logEventFailure);
|
|
7635
|
+
}
|
|
7636
|
+
for (const w of config?.workers ?? []) {
|
|
7637
|
+
try {
|
|
7638
|
+
await orchestrator.unregisterWorker(w.name);
|
|
7639
|
+
} catch (err) {
|
|
7640
|
+
process.stderr.write(
|
|
7641
|
+
`[team/runtime-v2] orchestrator.unregisterWorker(${w.name}) failed: ${err}
|
|
7642
|
+
`
|
|
7643
|
+
);
|
|
7644
|
+
}
|
|
7645
|
+
}
|
|
7646
|
+
} catch (err) {
|
|
7647
|
+
process.stderr.write(`[team/runtime-v2] orchestrator drainAndStop: ${err}
|
|
7648
|
+
`);
|
|
7649
|
+
} finally {
|
|
7650
|
+
await stopTeamCadence(sanitized);
|
|
7651
|
+
unregisterTeamOrchestrator(sanitized);
|
|
7652
|
+
}
|
|
7653
|
+
} else {
|
|
7654
|
+
await stopTeamCadence(sanitized);
|
|
7655
|
+
}
|
|
7656
|
+
};
|
|
6513
7657
|
if (!config) {
|
|
6514
7658
|
const cleanupSafety = inspectTeamWorktreeCleanupSafety(sanitized, cwd);
|
|
6515
7659
|
if (cleanupSafety.hasEvidence) {
|
|
@@ -6644,12 +7788,14 @@ Then exit your session.
|
|
|
6644
7788
|
if (aliveWorkers.length > 0) {
|
|
6645
7789
|
process.stderr.write(`[team/runtime-v2] preserving worktrees/state because worker pane(s) are still alive: ${aliveWorkers.join(", ")}
|
|
6646
7790
|
`);
|
|
7791
|
+
await finalizeAutoMerge();
|
|
6647
7792
|
return;
|
|
6648
7793
|
}
|
|
6649
7794
|
const unknownWorkers = liveness.filter(([, state]) => state === "unknown").map(([paneId]) => paneById.get(paneId) ?? paneId);
|
|
6650
7795
|
if (unknownWorkers.length > 0) {
|
|
6651
7796
|
process.stderr.write(`[team/runtime-v2] preserving worktrees/state because worker pane liveness is unknown: ${unknownWorkers.join(", ")}
|
|
6652
7797
|
`);
|
|
7798
|
+
await finalizeAutoMerge();
|
|
6653
7799
|
return;
|
|
6654
7800
|
}
|
|
6655
7801
|
} catch (err) {
|
|
@@ -6657,6 +7803,7 @@ Then exit your session.
|
|
|
6657
7803
|
`);
|
|
6658
7804
|
if (recordedWorkerPaneIds.length > 0) {
|
|
6659
7805
|
process.stderr.write("[team/runtime-v2] preserving worktrees/state because tmux cleanup did not prove worker panes exited\n");
|
|
7806
|
+
await finalizeAutoMerge();
|
|
6660
7807
|
return;
|
|
6661
7808
|
}
|
|
6662
7809
|
}
|
|
@@ -6671,6 +7818,7 @@ Then exit your session.
|
|
|
6671
7818
|
reason: `ralph_cleanup_summary: total=${finalTasks.length} completed=${completed} failed=${failed} pending=${pending} force=${force}`
|
|
6672
7819
|
}, cwd).catch(logEventFailure);
|
|
6673
7820
|
}
|
|
7821
|
+
await finalizeAutoMerge();
|
|
6674
7822
|
let preservedWorktrees = 0;
|
|
6675
7823
|
try {
|
|
6676
7824
|
const worktreeCleanup = cleanupTeamWorktrees(sanitized, cwd);
|
|
@@ -6707,8 +7855,8 @@ async function resumeTeamV2(teamName, cwd) {
|
|
|
6707
7855
|
}
|
|
6708
7856
|
}
|
|
6709
7857
|
async function findActiveTeamsV2(cwd) {
|
|
6710
|
-
const root =
|
|
6711
|
-
if (!
|
|
7858
|
+
const root = join22(cwd, ".omcp", "state", "team");
|
|
7859
|
+
if (!existsSync19(root)) return [];
|
|
6712
7860
|
const entries = await readdir2(root, { withFileTypes: true });
|
|
6713
7861
|
const active = [];
|
|
6714
7862
|
for (const e of entries) {
|
|
@@ -6721,7 +7869,7 @@ async function findActiveTeamsV2(cwd) {
|
|
|
6721
7869
|
}
|
|
6722
7870
|
return active;
|
|
6723
7871
|
}
|
|
6724
|
-
var MONITOR_SIGNAL_STALE_MS, CIRCUIT_BREAKER_THRESHOLD, CircuitBreakerV2;
|
|
7872
|
+
var orchestratorByTeam, cadenceByTeam, MONITOR_SIGNAL_STALE_MS, CIRCUIT_BREAKER_THRESHOLD, CircuitBreakerV2;
|
|
6725
7873
|
var init_runtime_v2 = __esm({
|
|
6726
7874
|
"src/team/runtime-v2.ts"() {
|
|
6727
7875
|
"use strict";
|
|
@@ -6746,6 +7894,13 @@ var init_runtime_v2 = __esm({
|
|
|
6746
7894
|
init_role_router();
|
|
6747
7895
|
init_types2();
|
|
6748
7896
|
init_cli_worker_contract();
|
|
7897
|
+
init_merge_orchestrator();
|
|
7898
|
+
init_leader_inbox();
|
|
7899
|
+
init_runtime_flags();
|
|
7900
|
+
init_worker_commit_cadence();
|
|
7901
|
+
init_runtime_flags();
|
|
7902
|
+
orchestratorByTeam = /* @__PURE__ */ new Map();
|
|
7903
|
+
cadenceByTeam = /* @__PURE__ */ new Map();
|
|
6749
7904
|
MONITOR_SIGNAL_STALE_MS = 3e4;
|
|
6750
7905
|
CIRCUIT_BREAKER_THRESHOLD = 3;
|
|
6751
7906
|
CircuitBreakerV2 = class {
|
|
@@ -6781,16 +7936,16 @@ var init_runtime_v2 = __esm({
|
|
|
6781
7936
|
// src/cli/team.ts
|
|
6782
7937
|
import { randomUUID as randomUUID6 } from "crypto";
|
|
6783
7938
|
import { spawn } from "child_process";
|
|
6784
|
-
import { existsSync as
|
|
6785
|
-
import { readFile as
|
|
7939
|
+
import { existsSync as existsSync21, mkdirSync as mkdirSync3, readFileSync as readFileSync11, writeFileSync as writeFileSync3 } from "fs";
|
|
7940
|
+
import { readFile as readFile11, readdir as readdir3, rm as rm5 } from "fs/promises";
|
|
6786
7941
|
import { homedir as homedir3 } from "os";
|
|
6787
|
-
import { dirname as
|
|
7942
|
+
import { dirname as dirname16, join as join24 } from "path";
|
|
6788
7943
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
6789
7944
|
|
|
6790
7945
|
// src/team/api-interop.ts
|
|
6791
7946
|
init_contracts();
|
|
6792
|
-
import { existsSync as
|
|
6793
|
-
import { dirname as
|
|
7947
|
+
import { existsSync as existsSync20, readFileSync as readFileSync10 } from "node:fs";
|
|
7948
|
+
import { dirname as dirname15, join as join23, resolve as resolvePath } from "node:path";
|
|
6794
7949
|
|
|
6795
7950
|
// src/team/team-ops.ts
|
|
6796
7951
|
init_state_paths();
|
|
@@ -7780,8 +8935,8 @@ function parseValidatedTaskIdArray(value, fieldName) {
|
|
|
7780
8935
|
}
|
|
7781
8936
|
function teamStateExists(teamName, candidateCwd) {
|
|
7782
8937
|
if (!TEAM_NAME_SAFE_PATTERN.test(teamName)) return false;
|
|
7783
|
-
const teamRoot =
|
|
7784
|
-
return
|
|
8938
|
+
const teamRoot = join23(candidateCwd, ".omcp", "state", "team", teamName);
|
|
8939
|
+
return existsSync20(join23(teamRoot, "config.json")) || existsSync20(join23(teamRoot, "tasks")) || existsSync20(teamRoot);
|
|
7785
8940
|
}
|
|
7786
8941
|
function parseTeamWorkerEnv(raw) {
|
|
7787
8942
|
if (typeof raw !== "string" || raw.trim() === "") return null;
|
|
@@ -7835,7 +8990,7 @@ async function executeTeamCleanupViaRuntime(teamName, cwd) {
|
|
|
7835
8990
|
await teamCleanup(teamName, cwd);
|
|
7836
8991
|
}
|
|
7837
8992
|
function readTeamStateRootFromFile(path4) {
|
|
7838
|
-
if (!
|
|
8993
|
+
if (!existsSync20(path4)) return null;
|
|
7839
8994
|
try {
|
|
7840
8995
|
const parsed = JSON.parse(readFileSync10(path4, "utf8"));
|
|
7841
8996
|
return typeof parsed.team_state_root === "string" && parsed.team_state_root.trim() !== "" ? parsed.team_state_root.trim() : null;
|
|
@@ -7851,7 +9006,7 @@ function stateRootToWorkingDirectory(stateRoot2) {
|
|
|
7851
9006
|
if (idx >= 0) {
|
|
7852
9007
|
const workspaceRoot = absolute.slice(0, idx);
|
|
7853
9008
|
if (workspaceRoot && workspaceRoot !== "/") return workspaceRoot;
|
|
7854
|
-
return
|
|
9009
|
+
return dirname15(dirname15(dirname15(dirname15(absolute))));
|
|
7855
9010
|
}
|
|
7856
9011
|
}
|
|
7857
9012
|
for (const marker of ["/.omcp/state", "/.omx/state"]) {
|
|
@@ -7859,22 +9014,22 @@ function stateRootToWorkingDirectory(stateRoot2) {
|
|
|
7859
9014
|
if (idx >= 0) {
|
|
7860
9015
|
const workspaceRoot = absolute.slice(0, idx);
|
|
7861
9016
|
if (workspaceRoot && workspaceRoot !== "/") return workspaceRoot;
|
|
7862
|
-
return
|
|
9017
|
+
return dirname15(dirname15(absolute));
|
|
7863
9018
|
}
|
|
7864
9019
|
}
|
|
7865
|
-
return
|
|
9020
|
+
return dirname15(dirname15(absolute));
|
|
7866
9021
|
}
|
|
7867
9022
|
function resolveTeamWorkingDirectoryFromMetadata(teamName, candidateCwd, workerContext) {
|
|
7868
|
-
const teamRoot =
|
|
7869
|
-
if (!
|
|
9023
|
+
const teamRoot = join23(candidateCwd, ".omcp", "state", "team", teamName);
|
|
9024
|
+
if (!existsSync20(teamRoot)) return null;
|
|
7870
9025
|
if (workerContext?.teamName === teamName) {
|
|
7871
|
-
const workerRoot = readTeamStateRootFromFile(
|
|
9026
|
+
const workerRoot = readTeamStateRootFromFile(join23(teamRoot, "workers", workerContext.workerName, "identity.json"));
|
|
7872
9027
|
if (workerRoot) return stateRootToWorkingDirectory(workerRoot);
|
|
7873
9028
|
}
|
|
7874
|
-
const fromConfig = readTeamStateRootFromFile(
|
|
9029
|
+
const fromConfig = readTeamStateRootFromFile(join23(teamRoot, "config.json"));
|
|
7875
9030
|
if (fromConfig) return stateRootToWorkingDirectory(fromConfig);
|
|
7876
9031
|
for (const manifestName of ["manifest.json", "manifest.v2.json"]) {
|
|
7877
|
-
const fromManifest = readTeamStateRootFromFile(
|
|
9032
|
+
const fromManifest = readTeamStateRootFromFile(join23(teamRoot, manifestName));
|
|
7878
9033
|
if (fromManifest) return stateRootToWorkingDirectory(fromManifest);
|
|
7879
9034
|
}
|
|
7880
9035
|
return null;
|
|
@@ -7901,7 +9056,7 @@ function resolveTeamWorkingDirectory(teamName, preferredCwd) {
|
|
|
7901
9056
|
if (teamStateExists(normalizedTeamName, cursor)) {
|
|
7902
9057
|
return resolveTeamWorkingDirectoryFromMetadata(normalizedTeamName, cursor, workerContext) ?? cursor;
|
|
7903
9058
|
}
|
|
7904
|
-
const parent =
|
|
9059
|
+
const parent = dirname15(cursor);
|
|
7905
9060
|
if (!parent || parent === cursor) break;
|
|
7906
9061
|
cursor = parent;
|
|
7907
9062
|
}
|
|
@@ -8503,31 +9658,31 @@ function assertTeamSpawnAllowed(env = process.env) {
|
|
|
8503
9658
|
);
|
|
8504
9659
|
}
|
|
8505
9660
|
function resolveJobsDir(env = process.env) {
|
|
8506
|
-
return env.OMC_JOBS_DIR ||
|
|
9661
|
+
return env.OMC_JOBS_DIR || join24(homedir3(), ".omcp", "team-jobs");
|
|
8507
9662
|
}
|
|
8508
9663
|
function resolveRuntimeCliPath(env = process.env) {
|
|
8509
9664
|
if (env.OMC_RUNTIME_CLI_PATH) {
|
|
8510
9665
|
return env.OMC_RUNTIME_CLI_PATH;
|
|
8511
9666
|
}
|
|
8512
|
-
const moduleDir =
|
|
8513
|
-
return
|
|
9667
|
+
const moduleDir = dirname16(fileURLToPath3(import.meta.url));
|
|
9668
|
+
return join24(moduleDir, "../../bridge/runtime-cli.cjs");
|
|
8514
9669
|
}
|
|
8515
9670
|
function ensureJobsDir(jobsDir) {
|
|
8516
|
-
if (!
|
|
9671
|
+
if (!existsSync21(jobsDir)) {
|
|
8517
9672
|
mkdirSync3(jobsDir, { recursive: true });
|
|
8518
9673
|
}
|
|
8519
9674
|
}
|
|
8520
9675
|
function jobPath(jobsDir, jobId) {
|
|
8521
|
-
return
|
|
9676
|
+
return join24(jobsDir, `${jobId}.json`);
|
|
8522
9677
|
}
|
|
8523
9678
|
function resultArtifactPath(jobsDir, jobId) {
|
|
8524
|
-
return
|
|
9679
|
+
return join24(jobsDir, `${jobId}-result.json`);
|
|
8525
9680
|
}
|
|
8526
9681
|
function panesArtifactPath(jobsDir, jobId) {
|
|
8527
|
-
return
|
|
9682
|
+
return join24(jobsDir, `${jobId}-panes.json`);
|
|
8528
9683
|
}
|
|
8529
9684
|
function teamStateRoot2(cwd, teamName) {
|
|
8530
|
-
return
|
|
9685
|
+
return join24(cwd, ".omcp", "state", "team", teamName);
|
|
8531
9686
|
}
|
|
8532
9687
|
function validateJobId(jobId) {
|
|
8533
9688
|
if (!JOB_ID_PATTERN.test(jobId)) {
|
|
@@ -8542,7 +9697,7 @@ function parseJsonSafe(content) {
|
|
|
8542
9697
|
}
|
|
8543
9698
|
}
|
|
8544
9699
|
async function resolveCleanupPaneEvidence(job, jobsDir, jobId) {
|
|
8545
|
-
const paneArtifact = await
|
|
9700
|
+
const paneArtifact = await readFile11(panesArtifactPath(jobsDir, jobId), "utf-8").then((content) => parseJsonSafe(content)).catch(() => null);
|
|
8546
9701
|
if (paneArtifact?.paneIds?.length) return { paneArtifact };
|
|
8547
9702
|
const config = await readTeamConfig(job.teamName, job.cwd).catch(() => null);
|
|
8548
9703
|
if (!config) {
|
|
@@ -8686,7 +9841,8 @@ async function startTeamJob(input) {
|
|
|
8686
9841
|
newWindow: input.newWindow,
|
|
8687
9842
|
pollIntervalMs: input.pollIntervalMs,
|
|
8688
9843
|
sentinelGateTimeoutMs: input.sentinelGateTimeoutMs,
|
|
8689
|
-
sentinelGatePollIntervalMs: input.sentinelGatePollIntervalMs
|
|
9844
|
+
sentinelGatePollIntervalMs: input.sentinelGatePollIntervalMs,
|
|
9845
|
+
autoMerge: input.autoMerge
|
|
8690
9846
|
};
|
|
8691
9847
|
child.stdin.write(JSON.stringify(payload));
|
|
8692
9848
|
child.stdin.end();
|
|
@@ -8981,7 +10137,7 @@ async function teamCleanupCommand(jobId, cleanupOptions = {}, options = {}) {
|
|
|
8981
10137
|
}
|
|
8982
10138
|
var TEAM_USAGE = `
|
|
8983
10139
|
Usage:
|
|
8984
|
-
omcp team start --agent <claude|copilot|codex|gemini|cursor>[,<agent>...] --task "<task>" [--count N] [--name TEAM] [--cwd DIR] [--new-window] [--json]
|
|
10140
|
+
omcp team start --agent <claude|copilot|codex|gemini|cursor>[,<agent>...] --task "<task>" [--count N] [--name TEAM] [--cwd DIR] [--new-window] [--auto-merge] [--json]
|
|
8985
10141
|
omcp team status <job_id|team_name> [--json] [--cwd DIR]
|
|
8986
10142
|
omcp team wait <job_id> [--timeout-ms MS] [--json]
|
|
8987
10143
|
omcp team cleanup <job_id> [--grace-ms MS] [--json]
|
|
@@ -8994,6 +10150,13 @@ Worktrees:
|
|
|
8994
10150
|
Native per-worker git worktree mode is opt-in/config-gated with team.ops.worktreeMode or OMC_TEAM_WORKTREE_MODE=detached|named.
|
|
8995
10151
|
Status JSON includes workspace_mode, worktree_mode, team_state_root, and per-worker worktree metadata.
|
|
8996
10152
|
|
|
10153
|
+
Auto-merge (v2-only):
|
|
10154
|
+
--auto-merge Enable per-commit auto-merge to leader and auto-rebase fanout.
|
|
10155
|
+
Each worker runs in a dedicated git worktree on omc-team/{team}/{worker}.
|
|
10156
|
+
Bursts of rapid worker commits coalesce to a single merge of HEAD.
|
|
10157
|
+
Requires OMC_RUNTIME_V2=1. Leader branch must not be 'main' or 'master'.
|
|
10158
|
+
Equivalent to OMC_TEAMS_AUTO_MERGE=1.
|
|
10159
|
+
|
|
8997
10160
|
Examples:
|
|
8998
10161
|
omcp team start --agent codex --count 2 --task "review auth flow" --new-window
|
|
8999
10162
|
omcp team status omc-abc123
|
|
@@ -9015,6 +10178,7 @@ function parseStartArgs(args) {
|
|
|
9015
10178
|
let pollIntervalMs;
|
|
9016
10179
|
let sentinelGateTimeoutMs;
|
|
9017
10180
|
let sentinelGatePollIntervalMs;
|
|
10181
|
+
let autoMerge = process.env.OMC_TEAMS_AUTO_MERGE === "1";
|
|
9018
10182
|
for (let i = 0; i < args.length; i += 1) {
|
|
9019
10183
|
const token = args[i];
|
|
9020
10184
|
const next = args[i + 1];
|
|
@@ -9026,6 +10190,10 @@ function parseStartArgs(args) {
|
|
|
9026
10190
|
newWindow = true;
|
|
9027
10191
|
continue;
|
|
9028
10192
|
}
|
|
10193
|
+
if (token === "--auto-merge") {
|
|
10194
|
+
autoMerge = true;
|
|
10195
|
+
continue;
|
|
10196
|
+
}
|
|
9029
10197
|
if (token === "--agent") {
|
|
9030
10198
|
if (!next) throw new Error("Missing value after --agent");
|
|
9031
10199
|
agentValues.push(...next.split(",").map(normalizeAgentType));
|
|
@@ -9143,7 +10311,8 @@ function parseStartArgs(args) {
|
|
|
9143
10311
|
...newWindow ? { newWindow: true } : {},
|
|
9144
10312
|
...pollIntervalMs != null ? { pollIntervalMs } : {},
|
|
9145
10313
|
...sentinelGateTimeoutMs != null ? { sentinelGateTimeoutMs } : {},
|
|
9146
|
-
...sentinelGatePollIntervalMs != null ? { sentinelGatePollIntervalMs } : {}
|
|
10314
|
+
...sentinelGatePollIntervalMs != null ? { sentinelGatePollIntervalMs } : {},
|
|
10315
|
+
...autoMerge ? { autoMerge: true } : {}
|
|
9147
10316
|
},
|
|
9148
10317
|
json
|
|
9149
10318
|
};
|
|
@@ -9334,6 +10503,7 @@ function parseLegacyStartAlias(args) {
|
|
|
9334
10503
|
let json = false;
|
|
9335
10504
|
let cwd = process.cwd();
|
|
9336
10505
|
let newWindow = false;
|
|
10506
|
+
let autoMerge = process.env.OMC_TEAMS_AUTO_MERGE === "1";
|
|
9337
10507
|
const taskParts = [];
|
|
9338
10508
|
for (let i = index; i < args.length; i += 1) {
|
|
9339
10509
|
const token = args[i];
|
|
@@ -9346,6 +10516,10 @@ function parseLegacyStartAlias(args) {
|
|
|
9346
10516
|
newWindow = true;
|
|
9347
10517
|
continue;
|
|
9348
10518
|
}
|
|
10519
|
+
if (token === "--auto-merge") {
|
|
10520
|
+
autoMerge = true;
|
|
10521
|
+
continue;
|
|
10522
|
+
}
|
|
9349
10523
|
if (token === "--cwd") {
|
|
9350
10524
|
if (!next) throw new Error("Missing value after --cwd");
|
|
9351
10525
|
cwd = next;
|
|
@@ -9369,7 +10543,8 @@ function parseLegacyStartAlias(args) {
|
|
|
9369
10543
|
ralph,
|
|
9370
10544
|
json,
|
|
9371
10545
|
cwd,
|
|
9372
|
-
...newWindow ? { newWindow: true } : {}
|
|
10546
|
+
...newWindow ? { newWindow: true } : {},
|
|
10547
|
+
...autoMerge ? { autoMerge: true } : {}
|
|
9373
10548
|
};
|
|
9374
10549
|
}
|
|
9375
10550
|
async function teamCommand(argv) {
|
|
@@ -9445,7 +10620,8 @@ async function teamCommand(argv) {
|
|
|
9445
10620
|
agentTypes: Array.from({ length: legacy.workerCount }, () => legacy.agentType),
|
|
9446
10621
|
tasks,
|
|
9447
10622
|
cwd: legacy.cwd,
|
|
9448
|
-
...legacy.newWindow ? { newWindow: true } : {}
|
|
10623
|
+
...legacy.newWindow ? { newWindow: true } : {},
|
|
10624
|
+
...legacy.autoMerge ? { autoMerge: true } : {}
|
|
9449
10625
|
});
|
|
9450
10626
|
output(result, legacy.json);
|
|
9451
10627
|
return;
|