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/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");
|
|
@@ -2819,8 +2820,8 @@ function getPromptModeArgs(agentType, instruction) {
|
|
|
2819
2820
|
init_team_name();
|
|
2820
2821
|
|
|
2821
2822
|
// src/team/worker-bootstrap.ts
|
|
2822
|
-
var
|
|
2823
|
-
var
|
|
2823
|
+
var import_promises2 = require("fs/promises");
|
|
2824
|
+
var import_path12 = require("path");
|
|
2824
2825
|
|
|
2825
2826
|
// src/agents/prompt-helpers.ts
|
|
2826
2827
|
var import_fs6 = require("fs");
|
|
@@ -2923,10 +2924,74 @@ function formatOmcCliInvocation(commandSuffix, options = {}) {
|
|
|
2923
2924
|
return `${resolveInvocationPrefix(suffix, options)} ${suffix}`.trim();
|
|
2924
2925
|
}
|
|
2925
2926
|
|
|
2927
|
+
// src/team/worker-bootstrap.ts
|
|
2928
|
+
init_tmux_session();
|
|
2929
|
+
|
|
2930
|
+
// src/team/fs-utils.ts
|
|
2931
|
+
var import_fs8 = require("fs");
|
|
2932
|
+
var import_path11 = require("path");
|
|
2933
|
+
function atomicWriteJson(filePath, data, mode = 384) {
|
|
2934
|
+
const dir = (0, import_path11.dirname)(filePath);
|
|
2935
|
+
if (!(0, import_fs8.existsSync)(dir)) (0, import_fs8.mkdirSync)(dir, { recursive: true, mode: 448 });
|
|
2936
|
+
const tmpPath = `${filePath}.tmp.${process.pid}.${Date.now()}`;
|
|
2937
|
+
const serialized = JSON.stringify(data, null, 2) + "\n";
|
|
2938
|
+
(0, import_fs8.writeFileSync)(tmpPath, serialized, { encoding: "utf-8", mode });
|
|
2939
|
+
try {
|
|
2940
|
+
(0, import_fs8.renameSync)(tmpPath, filePath);
|
|
2941
|
+
} catch (err) {
|
|
2942
|
+
if (process.platform === "win32" && err && typeof err === "object" && "code" in err && err.code === "EPERM") {
|
|
2943
|
+
try {
|
|
2944
|
+
(0, import_fs8.writeFileSync)(filePath, serialized, { encoding: "utf-8", mode });
|
|
2945
|
+
} finally {
|
|
2946
|
+
try {
|
|
2947
|
+
(0, import_fs8.unlinkSync)(tmpPath);
|
|
2948
|
+
} catch {
|
|
2949
|
+
}
|
|
2950
|
+
}
|
|
2951
|
+
} else {
|
|
2952
|
+
try {
|
|
2953
|
+
(0, import_fs8.unlinkSync)(tmpPath);
|
|
2954
|
+
} catch {
|
|
2955
|
+
}
|
|
2956
|
+
throw err;
|
|
2957
|
+
}
|
|
2958
|
+
}
|
|
2959
|
+
}
|
|
2960
|
+
function ensureDirWithMode(dirPath, mode = 448) {
|
|
2961
|
+
if (!(0, import_fs8.existsSync)(dirPath)) (0, import_fs8.mkdirSync)(dirPath, { recursive: true, mode });
|
|
2962
|
+
}
|
|
2963
|
+
function safeRealpath(p) {
|
|
2964
|
+
try {
|
|
2965
|
+
return (0, import_fs8.realpathSync)(p);
|
|
2966
|
+
} catch {
|
|
2967
|
+
const segments = [];
|
|
2968
|
+
let current = (0, import_path11.resolve)(p);
|
|
2969
|
+
while (!(0, import_fs8.existsSync)(current)) {
|
|
2970
|
+
segments.unshift((0, import_path11.basename)(current));
|
|
2971
|
+
const parent = (0, import_path11.dirname)(current);
|
|
2972
|
+
if (parent === current) break;
|
|
2973
|
+
current = parent;
|
|
2974
|
+
}
|
|
2975
|
+
try {
|
|
2976
|
+
return (0, import_path11.join)((0, import_fs8.realpathSync)(current), ...segments);
|
|
2977
|
+
} catch {
|
|
2978
|
+
return (0, import_path11.resolve)(p);
|
|
2979
|
+
}
|
|
2980
|
+
}
|
|
2981
|
+
}
|
|
2982
|
+
function validateResolvedPath(resolvedPath, expectedBase) {
|
|
2983
|
+
const absResolved = safeRealpath(resolvedPath);
|
|
2984
|
+
const absBase = safeRealpath(expectedBase);
|
|
2985
|
+
const rel = (0, import_path11.relative)(absBase, absResolved);
|
|
2986
|
+
if (rel.startsWith("..") || (0, import_path11.resolve)(absBase, rel) !== absResolved) {
|
|
2987
|
+
throw new Error(`Path traversal detected: "${resolvedPath}" escapes base "${expectedBase}"`);
|
|
2988
|
+
}
|
|
2989
|
+
}
|
|
2990
|
+
|
|
2926
2991
|
// src/team/worker-bootstrap.ts
|
|
2927
2992
|
var DEFAULT_INSTRUCTION_STATE_ROOT = ".omcp/state";
|
|
2928
2993
|
function buildInstructionPath(...parts) {
|
|
2929
|
-
return (0,
|
|
2994
|
+
return (0, import_path12.join)(...parts).replaceAll("\\", "/");
|
|
2930
2995
|
}
|
|
2931
2996
|
function buildTeamStateInstructionPath(teamName, instructionStateRoot, ...teamRelativeParts) {
|
|
2932
2997
|
const baseParts = instructionStateRoot === DEFAULT_INSTRUCTION_STATE_ROOT ? [instructionStateRoot, "team", teamName] : [instructionStateRoot];
|
|
@@ -3105,26 +3170,37 @@ ${bootstrapInstructions}
|
|
|
3105
3170
|
` : ""}`;
|
|
3106
3171
|
}
|
|
3107
3172
|
async function composeInitialInbox(teamName, workerName2, content, cwd, cliOutputContract) {
|
|
3108
|
-
const inboxPath = (0,
|
|
3109
|
-
await (0,
|
|
3173
|
+
const inboxPath = (0, import_path12.join)(cwd, `.omcp/state/team/${teamName}/workers/${workerName2}/inbox.md`);
|
|
3174
|
+
await (0, import_promises2.mkdir)((0, import_path12.dirname)(inboxPath), { recursive: true });
|
|
3110
3175
|
const finalContent = cliOutputContract && !content.includes(cliOutputContract) ? `${content}
|
|
3111
3176
|
${cliOutputContract}` : content;
|
|
3112
|
-
await (0,
|
|
3177
|
+
await (0, import_promises2.writeFile)(inboxPath, finalContent, "utf-8");
|
|
3178
|
+
}
|
|
3179
|
+
async function appendToInbox(teamName, workerName2, message, cwd) {
|
|
3180
|
+
const safeTeam = sanitizeName(teamName);
|
|
3181
|
+
const safeWorker = sanitizeName(workerName2);
|
|
3182
|
+
const inboxPath = (0, import_path12.join)(cwd, `.omcp/state/team/${safeTeam}/workers/${safeWorker}/inbox.md`);
|
|
3183
|
+
validateResolvedPath(inboxPath, cwd);
|
|
3184
|
+
await (0, import_promises2.mkdir)((0, import_path12.dirname)(inboxPath), { recursive: true });
|
|
3185
|
+
await (0, import_promises2.appendFile)(inboxPath, `
|
|
3186
|
+
|
|
3187
|
+
---
|
|
3188
|
+
${message}`, "utf-8");
|
|
3113
3189
|
}
|
|
3114
3190
|
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,
|
|
3191
|
+
const workerDir = (0, import_path12.join)(cwd, `.omcp/state/team/${teamName}/workers/${workerName2}`);
|
|
3192
|
+
await (0, import_promises2.mkdir)(workerDir, { recursive: true });
|
|
3193
|
+
const mailboxDir = (0, import_path12.join)(cwd, `.omcp/state/team/${teamName}/mailbox`);
|
|
3194
|
+
await (0, import_promises2.mkdir)(mailboxDir, { recursive: true });
|
|
3195
|
+
const tasksDir = (0, import_path12.join)(cwd, `.omcp/state/team/${teamName}/tasks`);
|
|
3196
|
+
await (0, import_promises2.mkdir)(tasksDir, { recursive: true });
|
|
3121
3197
|
}
|
|
3122
3198
|
async function writeWorkerOverlay(params) {
|
|
3123
3199
|
const { teamName, workerName: workerName2, cwd } = params;
|
|
3124
3200
|
const overlay = generateWorkerOverlay(params);
|
|
3125
|
-
const overlayPath = (0,
|
|
3126
|
-
await (0,
|
|
3127
|
-
await (0,
|
|
3201
|
+
const overlayPath = (0, import_path12.join)(cwd, `.omcp/state/team/${teamName}/workers/${workerName2}/AGENTS.md`);
|
|
3202
|
+
await (0, import_promises2.mkdir)((0, import_path12.dirname)(overlayPath), { recursive: true });
|
|
3203
|
+
await (0, import_promises2.writeFile)(overlayPath, overlay, "utf-8");
|
|
3128
3204
|
return overlayPath;
|
|
3129
3205
|
}
|
|
3130
3206
|
|
|
@@ -3141,69 +3217,6 @@ init_tmux_session();
|
|
|
3141
3217
|
var import_node_fs = require("node:fs");
|
|
3142
3218
|
var import_node_path = require("node:path");
|
|
3143
3219
|
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
3220
|
init_tmux_session();
|
|
3208
3221
|
init_file_lock();
|
|
3209
3222
|
function getWorktreePath(repoRoot, teamName, workerName2) {
|
|
@@ -3587,6 +3600,9 @@ function removeWorkerWorktree(teamName, workerName2, repoRoot) {
|
|
|
3587
3600
|
forgetMetadataUnlocked(repoRoot, teamName, workerName2);
|
|
3588
3601
|
});
|
|
3589
3602
|
}
|
|
3603
|
+
function listTeamWorktrees(teamName, repoRoot) {
|
|
3604
|
+
return readMetadata(repoRoot, teamName);
|
|
3605
|
+
}
|
|
3590
3606
|
function inspectTeamWorktreeCleanupSafety(teamName, repoRoot) {
|
|
3591
3607
|
const metadata = readMetadataResult(repoRoot, teamName);
|
|
3592
3608
|
const entries = metadata.entries;
|
|
@@ -4157,8 +4173,8 @@ function watchdogCliWorkers(runtime, intervalMs) {
|
|
|
4157
4173
|
unresponsiveCounts.delete(wName);
|
|
4158
4174
|
await markTaskFromDone(root, runtime.teamName, runtime.cwd, signal.taskId || active.taskId, signal.status, signal.summary);
|
|
4159
4175
|
try {
|
|
4160
|
-
const { unlink:
|
|
4161
|
-
await
|
|
4176
|
+
const { unlink: unlink4 } = await import("fs/promises");
|
|
4177
|
+
await unlink4(donePath);
|
|
4162
4178
|
} catch {
|
|
4163
4179
|
}
|
|
4164
4180
|
await killWorkerPane(runtime, wName, active.paneId);
|
|
@@ -5056,9 +5072,9 @@ async function waitForSentinelReadiness(options = {}) {
|
|
|
5056
5072
|
|
|
5057
5073
|
// src/team/runtime-v2.ts
|
|
5058
5074
|
init_tmux_utils();
|
|
5059
|
-
var
|
|
5060
|
-
var
|
|
5061
|
-
var
|
|
5075
|
+
var import_path22 = require("path");
|
|
5076
|
+
var import_fs20 = require("fs");
|
|
5077
|
+
var import_promises10 = require("fs/promises");
|
|
5062
5078
|
var import_perf_hooks = require("perf_hooks");
|
|
5063
5079
|
|
|
5064
5080
|
// src/team/allocation-policy.ts
|
|
@@ -5271,10 +5287,10 @@ async function readJsonSafe2(filePath) {
|
|
|
5271
5287
|
}
|
|
5272
5288
|
}
|
|
5273
5289
|
async function writeAtomic(filePath, data) {
|
|
5274
|
-
const { writeFile:
|
|
5290
|
+
const { writeFile: writeFile8 } = await import("fs/promises");
|
|
5275
5291
|
await (0, import_promises5.mkdir)((0, import_path18.dirname)(filePath), { recursive: true });
|
|
5276
5292
|
const tmpPath = `${filePath}.tmp.${process.pid}.${Date.now()}`;
|
|
5277
|
-
await
|
|
5293
|
+
await writeFile8(tmpPath, data, "utf-8");
|
|
5278
5294
|
const { rename: rename4 } = await import("fs/promises");
|
|
5279
5295
|
await rename4(tmpPath, filePath);
|
|
5280
5296
|
}
|
|
@@ -6198,13 +6214,996 @@ function cliWorkerOutputFilePath(teamStateRootAbs, workerName2) {
|
|
|
6198
6214
|
return `${teamStateRootAbs.replaceAll("\\", "/")}/workers/${workerName2}/verdict.json`;
|
|
6199
6215
|
}
|
|
6200
6216
|
|
|
6201
|
-
// src/team/
|
|
6217
|
+
// src/team/merge-orchestrator.ts
|
|
6218
|
+
var import_node_child_process3 = require("node:child_process");
|
|
6219
|
+
var import_node_fs2 = require("node:fs");
|
|
6220
|
+
var import_promises9 = require("node:fs/promises");
|
|
6221
|
+
var import_node_path2 = require("node:path");
|
|
6222
|
+
|
|
6223
|
+
// src/team/runtime-flags.ts
|
|
6202
6224
|
function isRuntimeV2Enabled(env = process.env) {
|
|
6203
6225
|
const raw = env.OMC_RUNTIME_V2;
|
|
6204
6226
|
if (!raw) return true;
|
|
6205
6227
|
const normalized = raw.trim().toLowerCase();
|
|
6206
6228
|
return !["0", "false", "no", "off"].includes(normalized);
|
|
6207
6229
|
}
|
|
6230
|
+
|
|
6231
|
+
// src/team/merge-orchestrator.ts
|
|
6232
|
+
init_tmux_session();
|
|
6233
|
+
|
|
6234
|
+
// src/team/merge-coordinator.ts
|
|
6235
|
+
var import_node_child_process2 = require("node:child_process");
|
|
6236
|
+
var BRANCH_NAME_RE = /^[a-zA-Z0-9][a-zA-Z0-9/_.-]*$/;
|
|
6237
|
+
function validateBranchName(branch) {
|
|
6238
|
+
if (!BRANCH_NAME_RE.test(branch)) {
|
|
6239
|
+
throw new Error(`Invalid branch name: "${branch}" \u2014 must match ${BRANCH_NAME_RE}`);
|
|
6240
|
+
}
|
|
6241
|
+
}
|
|
6242
|
+
function checkMergeConflicts(workerBranch, baseBranch, repoRoot) {
|
|
6243
|
+
validateBranchName(workerBranch);
|
|
6244
|
+
validateBranchName(baseBranch);
|
|
6245
|
+
try {
|
|
6246
|
+
(0, import_node_child_process2.execFileSync)(
|
|
6247
|
+
"git",
|
|
6248
|
+
["merge-tree", "--write-tree", baseBranch, workerBranch],
|
|
6249
|
+
{ cwd: repoRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
6250
|
+
);
|
|
6251
|
+
return [];
|
|
6252
|
+
} catch (err) {
|
|
6253
|
+
const error = err;
|
|
6254
|
+
if (error.status === 1 && typeof error.stdout === "string") {
|
|
6255
|
+
const lines = error.stdout.split("\n");
|
|
6256
|
+
const conflicts = [];
|
|
6257
|
+
for (const line of lines) {
|
|
6258
|
+
const match = line.match(/^CONFLICT\s.*?:\s+.*?\s+in\s+(.+)$/);
|
|
6259
|
+
if (match) {
|
|
6260
|
+
conflicts.push(match[1].trim());
|
|
6261
|
+
}
|
|
6262
|
+
}
|
|
6263
|
+
return conflicts.length > 0 ? conflicts : ["(merge-tree reported conflicts)"];
|
|
6264
|
+
}
|
|
6265
|
+
}
|
|
6266
|
+
const mergeBase = (0, import_node_child_process2.execFileSync)(
|
|
6267
|
+
"git",
|
|
6268
|
+
["merge-base", baseBranch, workerBranch],
|
|
6269
|
+
{ cwd: repoRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
6270
|
+
).trim();
|
|
6271
|
+
const baseDiff = (0, import_node_child_process2.execFileSync)(
|
|
6272
|
+
"git",
|
|
6273
|
+
["diff", "--name-only", mergeBase, baseBranch],
|
|
6274
|
+
{ cwd: repoRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
6275
|
+
).trim();
|
|
6276
|
+
const workerDiff = (0, import_node_child_process2.execFileSync)(
|
|
6277
|
+
"git",
|
|
6278
|
+
["diff", "--name-only", mergeBase, workerBranch],
|
|
6279
|
+
{ cwd: repoRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
6280
|
+
).trim();
|
|
6281
|
+
if (!baseDiff || !workerDiff) {
|
|
6282
|
+
return [];
|
|
6283
|
+
}
|
|
6284
|
+
const baseFiles = new Set(baseDiff.split("\n").filter((f) => f));
|
|
6285
|
+
const workerFiles = workerDiff.split("\n").filter((f) => f);
|
|
6286
|
+
return workerFiles.filter((f) => baseFiles.has(f));
|
|
6287
|
+
}
|
|
6288
|
+
function mergeWorkerBranch(workerBranch, baseBranch, repoRoot) {
|
|
6289
|
+
validateBranchName(workerBranch);
|
|
6290
|
+
validateBranchName(baseBranch);
|
|
6291
|
+
const workerName2 = workerBranch.split("/").pop() || workerBranch;
|
|
6292
|
+
try {
|
|
6293
|
+
try {
|
|
6294
|
+
(0, import_node_child_process2.execFileSync)("git", ["diff-index", "--quiet", "HEAD", "--"], {
|
|
6295
|
+
cwd: repoRoot,
|
|
6296
|
+
stdio: "pipe"
|
|
6297
|
+
});
|
|
6298
|
+
} catch {
|
|
6299
|
+
throw new Error("Working tree has uncommitted changes \u2014 commit or stash before merging");
|
|
6300
|
+
}
|
|
6301
|
+
(0, import_node_child_process2.execFileSync)("git", ["checkout", baseBranch], {
|
|
6302
|
+
cwd: repoRoot,
|
|
6303
|
+
stdio: "pipe"
|
|
6304
|
+
});
|
|
6305
|
+
(0, import_node_child_process2.execFileSync)("git", ["merge", "--no-ff", "-m", `Merge ${workerBranch} into ${baseBranch}`, workerBranch], {
|
|
6306
|
+
cwd: repoRoot,
|
|
6307
|
+
stdio: "pipe"
|
|
6308
|
+
});
|
|
6309
|
+
const mergeCommit = (0, import_node_child_process2.execFileSync)("git", ["rev-parse", "HEAD"], {
|
|
6310
|
+
cwd: repoRoot,
|
|
6311
|
+
encoding: "utf-8",
|
|
6312
|
+
stdio: "pipe"
|
|
6313
|
+
}).trim();
|
|
6314
|
+
return {
|
|
6315
|
+
workerName: workerName2,
|
|
6316
|
+
branch: workerBranch,
|
|
6317
|
+
success: true,
|
|
6318
|
+
conflicts: [],
|
|
6319
|
+
mergeCommit
|
|
6320
|
+
};
|
|
6321
|
+
} catch (_err) {
|
|
6322
|
+
try {
|
|
6323
|
+
(0, import_node_child_process2.execFileSync)("git", ["merge", "--abort"], { cwd: repoRoot, stdio: "pipe" });
|
|
6324
|
+
} catch {
|
|
6325
|
+
}
|
|
6326
|
+
const conflicts = checkMergeConflicts(workerBranch, baseBranch, repoRoot);
|
|
6327
|
+
return {
|
|
6328
|
+
workerName: workerName2,
|
|
6329
|
+
branch: workerBranch,
|
|
6330
|
+
success: false,
|
|
6331
|
+
conflicts
|
|
6332
|
+
};
|
|
6333
|
+
}
|
|
6334
|
+
}
|
|
6335
|
+
|
|
6336
|
+
// src/team/leader-inbox.ts
|
|
6337
|
+
var import_promises7 = require("fs/promises");
|
|
6338
|
+
var import_fs18 = require("fs");
|
|
6339
|
+
var import_path20 = require("path");
|
|
6340
|
+
init_tmux_session();
|
|
6341
|
+
var LEADER_INBOX_HEADER = `# Leader Inbox
|
|
6342
|
+
|
|
6343
|
+
Runtime notifications (merge conflicts, rebase events, etc.) appear here.
|
|
6344
|
+
Check this file periodically and after long-running operations.
|
|
6345
|
+
|
|
6346
|
+
---
|
|
6347
|
+
`;
|
|
6348
|
+
function leaderInboxPath(teamName, cwd) {
|
|
6349
|
+
const safe = sanitizeName(teamName);
|
|
6350
|
+
return (0, import_path20.join)(cwd, `.omcp/state/team/${safe}/leader/inbox.md`);
|
|
6351
|
+
}
|
|
6352
|
+
async function ensureLeaderInbox(teamName, cwd) {
|
|
6353
|
+
const inboxPath = leaderInboxPath(teamName, cwd);
|
|
6354
|
+
validateResolvedPath(inboxPath, cwd);
|
|
6355
|
+
await (0, import_promises7.mkdir)((0, import_path20.dirname)(inboxPath), { recursive: true });
|
|
6356
|
+
if (!(0, import_fs18.existsSync)(inboxPath)) {
|
|
6357
|
+
await (0, import_promises7.writeFile)(inboxPath, LEADER_INBOX_HEADER, "utf-8");
|
|
6358
|
+
}
|
|
6359
|
+
return inboxPath;
|
|
6360
|
+
}
|
|
6361
|
+
async function appendToLeaderInbox(teamName, message, cwd) {
|
|
6362
|
+
const inboxPath = leaderInboxPath(teamName, cwd);
|
|
6363
|
+
validateResolvedPath(inboxPath, cwd);
|
|
6364
|
+
await (0, import_promises7.mkdir)((0, import_path20.dirname)(inboxPath), { recursive: true });
|
|
6365
|
+
await (0, import_promises7.appendFile)(inboxPath, `
|
|
6366
|
+
|
|
6367
|
+
---
|
|
6368
|
+
${message}`, "utf-8");
|
|
6369
|
+
}
|
|
6370
|
+
function extendLeaderBootstrapPrompt(teamName) {
|
|
6371
|
+
const safe = sanitizeName(teamName);
|
|
6372
|
+
const path4 = `.omcp/state/team/${safe}/leader/inbox.md`;
|
|
6373
|
+
return `Runtime notifications appear at ${path4} \u2014 check this file periodically and after long-running operations.`;
|
|
6374
|
+
}
|
|
6375
|
+
|
|
6376
|
+
// src/team/conflict-mailbox.ts
|
|
6377
|
+
function sanitizeConflictPath(path4) {
|
|
6378
|
+
return path4.replace(/[`\r\n]/g, "?");
|
|
6379
|
+
}
|
|
6380
|
+
function formatMergeConflictForLeader(args) {
|
|
6381
|
+
const { workerName: workerName2, workerBranch, leaderBranch, conflictingFiles, mergeBaseSha, observedAt } = args;
|
|
6382
|
+
const ts = new Date(observedAt).toISOString();
|
|
6383
|
+
const safeFiles = conflictingFiles.map(sanitizeConflictPath);
|
|
6384
|
+
const fileList = safeFiles.map((f) => `- \`${f}\``).join("\n");
|
|
6385
|
+
return `### Merge conflict: ${workerName2} \u2192 ${leaderBranch}
|
|
6386
|
+
|
|
6387
|
+
**Worker branch:** \`${workerBranch}\`
|
|
6388
|
+
**Leader branch:** \`${leaderBranch}\`
|
|
6389
|
+
**Merge base:** \`${mergeBaseSha}\`
|
|
6390
|
+
**Observed at:** ${ts}
|
|
6391
|
+
|
|
6392
|
+
**Conflicting files:**
|
|
6393
|
+
${fileList}
|
|
6394
|
+
|
|
6395
|
+
**Leader: choose strategy.** To resolve, run:
|
|
6396
|
+
|
|
6397
|
+
\`\`\`sh
|
|
6398
|
+
git checkout ${leaderBranch} && git merge --no-ff ${workerBranch}
|
|
6399
|
+
# resolve conflicts in the files listed above
|
|
6400
|
+
git add ${safeFiles.join(" ")}
|
|
6401
|
+
git commit
|
|
6402
|
+
\`\`\`
|
|
6403
|
+
|
|
6404
|
+
Or abort with \`git merge --abort\` to defer resolution.`;
|
|
6405
|
+
}
|
|
6406
|
+
function formatRebaseConflictForWorker(args) {
|
|
6407
|
+
const { workerName: workerName2, workerBranch, leaderBranch, conflictingFiles, baseSha, worktreePath, observedAt } = args;
|
|
6408
|
+
const ts = new Date(observedAt).toISOString();
|
|
6409
|
+
const safeFiles = conflictingFiles.map(sanitizeConflictPath);
|
|
6410
|
+
const fileList = safeFiles.map((f) => `- \`${f}\``).join("\n");
|
|
6411
|
+
return `### Rebase conflict: ${workerName2} onto ${leaderBranch}
|
|
6412
|
+
|
|
6413
|
+
**Worker branch:** \`${workerBranch}\`
|
|
6414
|
+
**Base branch:** \`${leaderBranch}\`
|
|
6415
|
+
**Base SHA:** \`${baseSha}\`
|
|
6416
|
+
**Worktree:** \`${worktreePath}\`
|
|
6417
|
+
**Observed at:** ${ts}
|
|
6418
|
+
|
|
6419
|
+
**Conflicting files:**
|
|
6420
|
+
${fileList}
|
|
6421
|
+
|
|
6422
|
+
Resolve conflicts in your own pane, then \`git add <files>\` and \`git rebase --continue\`.
|
|
6423
|
+
Cadence stays paused until \`.git/rebase-merge\` is gone.
|
|
6424
|
+
|
|
6425
|
+
Or run \`git rebase --abort\` to bail and return to the pre-rebase state.`;
|
|
6426
|
+
}
|
|
6427
|
+
|
|
6428
|
+
// src/team/worker-commit-cadence.ts
|
|
6429
|
+
var import_fs19 = require("fs");
|
|
6430
|
+
var import_promises8 = require("fs/promises");
|
|
6431
|
+
var import_path21 = require("path");
|
|
6432
|
+
var import_child_process5 = require("child_process");
|
|
6433
|
+
var SENTINEL_FILENAME = ".hook-paused";
|
|
6434
|
+
var HOOK_MATCHER = "Write|Edit|MultiEdit";
|
|
6435
|
+
var DEFAULT_POLL_DEBOUNCE_MS = 3e3;
|
|
6436
|
+
var WORKER_NAME_RE = /^[A-Za-z0-9_-]{1,50}$/;
|
|
6437
|
+
function assertSafeWorkerName(workerName2) {
|
|
6438
|
+
if (!WORKER_NAME_RE.test(workerName2)) {
|
|
6439
|
+
throw new Error(
|
|
6440
|
+
`Invalid worker name for shell hook: "${workerName2}" \u2014 must match ${WORKER_NAME_RE}`
|
|
6441
|
+
);
|
|
6442
|
+
}
|
|
6443
|
+
}
|
|
6444
|
+
function buildHookCommand(workerName2) {
|
|
6445
|
+
assertSafeWorkerName(workerName2);
|
|
6446
|
+
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)")'`;
|
|
6447
|
+
}
|
|
6448
|
+
async function mergeSettingsWithHook(settingsPath, hookCommand) {
|
|
6449
|
+
let existing = { hooks: { PostToolUse: [] } };
|
|
6450
|
+
try {
|
|
6451
|
+
const raw = await (0, import_promises8.readFile)(settingsPath, "utf-8");
|
|
6452
|
+
const parsed = JSON.parse(raw);
|
|
6453
|
+
existing = {
|
|
6454
|
+
...parsed,
|
|
6455
|
+
hooks: {
|
|
6456
|
+
PostToolUse: [],
|
|
6457
|
+
...parsed.hooks ?? {}
|
|
6458
|
+
}
|
|
6459
|
+
};
|
|
6460
|
+
} catch {
|
|
6461
|
+
}
|
|
6462
|
+
const filteredHooks = (existing.hooks.PostToolUse ?? []).filter(
|
|
6463
|
+
(h) => h.matcher !== HOOK_MATCHER
|
|
6464
|
+
);
|
|
6465
|
+
const newEntry = {
|
|
6466
|
+
matcher: HOOK_MATCHER,
|
|
6467
|
+
hooks: [{ type: "command", command: hookCommand }]
|
|
6468
|
+
};
|
|
6469
|
+
return {
|
|
6470
|
+
...existing,
|
|
6471
|
+
hooks: {
|
|
6472
|
+
...existing.hooks,
|
|
6473
|
+
PostToolUse: [...filteredHooks, newEntry]
|
|
6474
|
+
}
|
|
6475
|
+
};
|
|
6476
|
+
}
|
|
6477
|
+
async function installPostToolUseHook(worktreePath, workerName2) {
|
|
6478
|
+
assertSafeWorkerName(workerName2);
|
|
6479
|
+
if (isHookPaused(worktreePath)) {
|
|
6480
|
+
return;
|
|
6481
|
+
}
|
|
6482
|
+
const claudeDir = (0, import_path21.join)(worktreePath, ".claude");
|
|
6483
|
+
await (0, import_promises8.mkdir)(claudeDir, { recursive: true });
|
|
6484
|
+
const settingsPath = (0, import_path21.join)(claudeDir, "settings.json");
|
|
6485
|
+
const hookCommand = buildHookCommand(workerName2);
|
|
6486
|
+
const merged = await mergeSettingsWithHook(settingsPath, hookCommand);
|
|
6487
|
+
await (0, import_promises8.writeFile)(settingsPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
|
|
6488
|
+
}
|
|
6489
|
+
async function pauseHookViaSentinel(worktreePath) {
|
|
6490
|
+
const sentinelPath = (0, import_path21.join)(worktreePath, SENTINEL_FILENAME);
|
|
6491
|
+
await (0, import_promises8.mkdir)((0, import_path21.dirname)(sentinelPath), { recursive: true });
|
|
6492
|
+
await (0, import_promises8.writeFile)(sentinelPath, "", "utf-8");
|
|
6493
|
+
}
|
|
6494
|
+
async function resumeHookViaSentinel(worktreePath) {
|
|
6495
|
+
const sentinelPath = (0, import_path21.join)(worktreePath, SENTINEL_FILENAME);
|
|
6496
|
+
try {
|
|
6497
|
+
await (0, import_promises8.unlink)(sentinelPath);
|
|
6498
|
+
} catch {
|
|
6499
|
+
}
|
|
6500
|
+
}
|
|
6501
|
+
function isHookPaused(worktreePath) {
|
|
6502
|
+
return (0, import_fs19.existsSync)((0, import_path21.join)(worktreePath, SENTINEL_FILENAME));
|
|
6503
|
+
}
|
|
6504
|
+
function startFallbackPoller(worktreePath, workerName2, opts) {
|
|
6505
|
+
assertSafeWorkerName(workerName2);
|
|
6506
|
+
const debounceMs = opts?.intervalMs ?? DEFAULT_POLL_DEBOUNCE_MS;
|
|
6507
|
+
let debounceTimer = null;
|
|
6508
|
+
let stopped = false;
|
|
6509
|
+
const runAutoCommit = () => {
|
|
6510
|
+
if (stopped) return;
|
|
6511
|
+
if (isHookPaused(worktreePath)) return;
|
|
6512
|
+
const cmd = buildHookCommand(workerName2);
|
|
6513
|
+
(0, import_child_process5.exec)(cmd, { cwd: worktreePath }, (_err) => {
|
|
6514
|
+
});
|
|
6515
|
+
};
|
|
6516
|
+
const scheduleDebounce = () => {
|
|
6517
|
+
if (stopped) return;
|
|
6518
|
+
if (debounceTimer !== null) {
|
|
6519
|
+
clearTimeout(debounceTimer);
|
|
6520
|
+
}
|
|
6521
|
+
debounceTimer = setTimeout(() => {
|
|
6522
|
+
debounceTimer = null;
|
|
6523
|
+
runAutoCommit();
|
|
6524
|
+
}, debounceMs);
|
|
6525
|
+
};
|
|
6526
|
+
const watcher = (0, import_fs19.watch)(worktreePath, { recursive: true }, (eventType, filename) => {
|
|
6527
|
+
if (stopped) return;
|
|
6528
|
+
if (filename && (filename.startsWith(".git") || filename.startsWith(".git/"))) return;
|
|
6529
|
+
scheduleDebounce();
|
|
6530
|
+
});
|
|
6531
|
+
return {
|
|
6532
|
+
stop() {
|
|
6533
|
+
stopped = true;
|
|
6534
|
+
if (debounceTimer !== null) {
|
|
6535
|
+
clearTimeout(debounceTimer);
|
|
6536
|
+
debounceTimer = null;
|
|
6537
|
+
}
|
|
6538
|
+
watcher.close();
|
|
6539
|
+
}
|
|
6540
|
+
};
|
|
6541
|
+
}
|
|
6542
|
+
async function installCommitCadence(ctx) {
|
|
6543
|
+
if (!ctx.enabled) {
|
|
6544
|
+
return { method: "none" };
|
|
6545
|
+
}
|
|
6546
|
+
if (ctx.agentType === "claude") {
|
|
6547
|
+
await installPostToolUseHook(ctx.worktreePath, ctx.workerName);
|
|
6548
|
+
return { method: "hook" };
|
|
6549
|
+
}
|
|
6550
|
+
return { method: "fallback-poll" };
|
|
6551
|
+
}
|
|
6552
|
+
async function uninstallCommitCadence(ctx) {
|
|
6553
|
+
if (ctx.agentType !== "claude") return;
|
|
6554
|
+
const settingsPath = (0, import_path21.join)(ctx.worktreePath, ".claude", "settings.json");
|
|
6555
|
+
try {
|
|
6556
|
+
const raw = await (0, import_promises8.readFile)(settingsPath, "utf-8");
|
|
6557
|
+
const parsed = JSON.parse(raw);
|
|
6558
|
+
const filtered = (parsed.hooks?.PostToolUse ?? []).filter(
|
|
6559
|
+
(h) => h.matcher !== HOOK_MATCHER
|
|
6560
|
+
);
|
|
6561
|
+
const updated = {
|
|
6562
|
+
...parsed,
|
|
6563
|
+
hooks: {
|
|
6564
|
+
...parsed.hooks,
|
|
6565
|
+
PostToolUse: filtered
|
|
6566
|
+
}
|
|
6567
|
+
};
|
|
6568
|
+
await (0, import_promises8.writeFile)(settingsPath, JSON.stringify(updated, null, 2) + "\n", "utf-8");
|
|
6569
|
+
} catch {
|
|
6570
|
+
}
|
|
6571
|
+
}
|
|
6572
|
+
|
|
6573
|
+
// src/team/merge-orchestrator.ts
|
|
6574
|
+
var DEFAULT_POLL_INTERVAL_MS = 1e3;
|
|
6575
|
+
var DEFAULT_DRAIN_TIMEOUT_MS = 1e4;
|
|
6576
|
+
function mergerWorktreePathFor(repoRoot, teamName) {
|
|
6577
|
+
return (0, import_node_path2.join)(repoRoot, ".omcp", "team", sanitizeName(teamName), "merger");
|
|
6578
|
+
}
|
|
6579
|
+
function persistedStatePath(repoRoot, teamName) {
|
|
6580
|
+
return (0, import_node_path2.join)(
|
|
6581
|
+
repoRoot,
|
|
6582
|
+
".omcp",
|
|
6583
|
+
"state",
|
|
6584
|
+
"team",
|
|
6585
|
+
sanitizeName(teamName),
|
|
6586
|
+
"auto-merge-state.json"
|
|
6587
|
+
);
|
|
6588
|
+
}
|
|
6589
|
+
function teardownAuditPath(repoRoot, teamName) {
|
|
6590
|
+
return (0, import_node_path2.join)(
|
|
6591
|
+
repoRoot,
|
|
6592
|
+
".omcp",
|
|
6593
|
+
"state",
|
|
6594
|
+
"team",
|
|
6595
|
+
sanitizeName(teamName),
|
|
6596
|
+
"teardown-audit.jsonl"
|
|
6597
|
+
);
|
|
6598
|
+
}
|
|
6599
|
+
function orchestratorEventLogPath(repoRoot, teamName) {
|
|
6600
|
+
return (0, import_node_path2.join)(
|
|
6601
|
+
repoRoot,
|
|
6602
|
+
".omcp",
|
|
6603
|
+
"state",
|
|
6604
|
+
"team",
|
|
6605
|
+
sanitizeName(teamName),
|
|
6606
|
+
"orchestrator-events.jsonl"
|
|
6607
|
+
);
|
|
6608
|
+
}
|
|
6609
|
+
function assertLeaderBranchAllowed(leaderBranch) {
|
|
6610
|
+
const stripped = leaderBranch.replace(/^refs\/heads\//i, "").toLowerCase();
|
|
6611
|
+
if (stripped === "main" || stripped === "master") {
|
|
6612
|
+
throw new Error("auto-merge refuses main/master leader branch \u2014 use a feature branch");
|
|
6613
|
+
}
|
|
6614
|
+
}
|
|
6615
|
+
function assertRuntimeV2Gate() {
|
|
6616
|
+
if (!isRuntimeV2Enabled()) {
|
|
6617
|
+
throw new Error("auto-merge requires runtime v2 (OMC_RUNTIME_V2 is explicitly disabled).");
|
|
6618
|
+
}
|
|
6619
|
+
}
|
|
6620
|
+
async function appendEvent(repoRoot, teamName, event) {
|
|
6621
|
+
const path4 = orchestratorEventLogPath(repoRoot, teamName);
|
|
6622
|
+
await (0, import_promises9.mkdir)((0, import_node_path2.dirname)(path4), { recursive: true });
|
|
6623
|
+
const full = {
|
|
6624
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6625
|
+
team: teamName,
|
|
6626
|
+
...event
|
|
6627
|
+
};
|
|
6628
|
+
await (0, import_promises9.appendFile)(path4, `${JSON.stringify(full)}
|
|
6629
|
+
`, "utf-8");
|
|
6630
|
+
}
|
|
6631
|
+
function createMutex() {
|
|
6632
|
+
let lock = Promise.resolve();
|
|
6633
|
+
return (fn) => {
|
|
6634
|
+
const next = lock.then(fn, fn);
|
|
6635
|
+
lock = next.catch(() => void 0);
|
|
6636
|
+
return next;
|
|
6637
|
+
};
|
|
6638
|
+
}
|
|
6639
|
+
function gitRevParseHead(repoRoot, branch) {
|
|
6640
|
+
return (0, import_node_child_process3.execFileSync)("git", ["rev-parse", `refs/heads/${branch}`], {
|
|
6641
|
+
cwd: repoRoot,
|
|
6642
|
+
encoding: "utf-8",
|
|
6643
|
+
stdio: "pipe"
|
|
6644
|
+
}).trim();
|
|
6645
|
+
}
|
|
6646
|
+
function gitPath(worktreePath, gitPathName) {
|
|
6647
|
+
try {
|
|
6648
|
+
const resolved = (0, import_node_child_process3.execFileSync)("git", ["rev-parse", "--git-path", gitPathName], {
|
|
6649
|
+
cwd: worktreePath,
|
|
6650
|
+
encoding: "utf-8",
|
|
6651
|
+
stdio: "pipe"
|
|
6652
|
+
}).trim();
|
|
6653
|
+
if (resolved) return resolved;
|
|
6654
|
+
} catch {
|
|
6655
|
+
}
|
|
6656
|
+
return (0, import_node_path2.join)(worktreePath, ".git", gitPathName);
|
|
6657
|
+
}
|
|
6658
|
+
function isRebaseInProgress(worktreePath) {
|
|
6659
|
+
return (0, import_node_fs2.existsSync)(gitPath(worktreePath, "rebase-merge"));
|
|
6660
|
+
}
|
|
6661
|
+
function isWorktreeRegistered(repoRoot, wtPath) {
|
|
6662
|
+
try {
|
|
6663
|
+
const out = (0, import_node_child_process3.execFileSync)("git", ["worktree", "list", "--porcelain"], {
|
|
6664
|
+
cwd: repoRoot,
|
|
6665
|
+
encoding: "utf-8",
|
|
6666
|
+
stdio: "pipe"
|
|
6667
|
+
});
|
|
6668
|
+
for (const line of out.split("\n")) {
|
|
6669
|
+
if (line.startsWith("worktree ")) {
|
|
6670
|
+
if (line.slice("worktree ".length).trim() === wtPath) return true;
|
|
6671
|
+
}
|
|
6672
|
+
}
|
|
6673
|
+
} catch {
|
|
6674
|
+
}
|
|
6675
|
+
return false;
|
|
6676
|
+
}
|
|
6677
|
+
function ensureMergerWorktree(repoRoot, mergerPath, leaderBranch) {
|
|
6678
|
+
ensureDirWithMode((0, import_node_path2.dirname)(mergerPath));
|
|
6679
|
+
if ((0, import_node_fs2.existsSync)(mergerPath) && isWorktreeRegistered(repoRoot, mergerPath)) {
|
|
6680
|
+
return;
|
|
6681
|
+
}
|
|
6682
|
+
(0, import_node_child_process3.execFileSync)("git", ["worktree", "add", "--force", mergerPath, leaderBranch], {
|
|
6683
|
+
cwd: repoRoot,
|
|
6684
|
+
stdio: "pipe"
|
|
6685
|
+
});
|
|
6686
|
+
}
|
|
6687
|
+
function preflightMergerWorktree(mergerPath, leaderBranch) {
|
|
6688
|
+
try {
|
|
6689
|
+
(0, import_node_child_process3.execFileSync)("git", ["fetch", "--no-tags", "origin", leaderBranch], {
|
|
6690
|
+
cwd: mergerPath,
|
|
6691
|
+
stdio: "pipe"
|
|
6692
|
+
});
|
|
6693
|
+
} catch {
|
|
6694
|
+
}
|
|
6695
|
+
(0, import_node_child_process3.execFileSync)("git", ["reset", "--hard", leaderBranch], {
|
|
6696
|
+
cwd: mergerPath,
|
|
6697
|
+
stdio: "pipe"
|
|
6698
|
+
});
|
|
6699
|
+
}
|
|
6700
|
+
function parseUUFiles(porcelainOutput) {
|
|
6701
|
+
const files = [];
|
|
6702
|
+
for (const line of porcelainOutput.split("\n")) {
|
|
6703
|
+
if (line.startsWith("UU ")) {
|
|
6704
|
+
files.push(line.slice(3).trim());
|
|
6705
|
+
} else if (line.startsWith("AA ") || line.startsWith("DD ")) {
|
|
6706
|
+
files.push(line.slice(3).trim());
|
|
6707
|
+
}
|
|
6708
|
+
}
|
|
6709
|
+
return files;
|
|
6710
|
+
}
|
|
6711
|
+
async function startMergeOrchestrator(config) {
|
|
6712
|
+
assertRuntimeV2Gate();
|
|
6713
|
+
assertLeaderBranchAllowed(config.leaderBranch);
|
|
6714
|
+
validateBranchName(config.leaderBranch);
|
|
6715
|
+
const pollIntervalMs = config.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
6716
|
+
const drainTimeoutMs = config.drainTimeoutMs ?? DEFAULT_DRAIN_TIMEOUT_MS;
|
|
6717
|
+
const mergerPath = mergerWorktreePathFor(config.repoRoot, config.teamName);
|
|
6718
|
+
validateResolvedPath(mergerPath, config.repoRoot);
|
|
6719
|
+
ensureMergerWorktree(config.repoRoot, mergerPath, config.leaderBranch);
|
|
6720
|
+
await ensureLeaderInbox(config.teamName, config.cwd);
|
|
6721
|
+
const persistedPath = persistedStatePath(config.repoRoot, config.teamName);
|
|
6722
|
+
let persisted = { lastShas: {} };
|
|
6723
|
+
if ((0, import_node_fs2.existsSync)(persistedPath)) {
|
|
6724
|
+
try {
|
|
6725
|
+
const { readFileSync: readFileSync12 } = await import("node:fs");
|
|
6726
|
+
persisted = JSON.parse(readFileSync12(persistedPath, "utf-8"));
|
|
6727
|
+
} catch {
|
|
6728
|
+
persisted = { lastShas: {} };
|
|
6729
|
+
}
|
|
6730
|
+
}
|
|
6731
|
+
const workers = /* @__PURE__ */ new Map();
|
|
6732
|
+
const pausedWorkers = /* @__PURE__ */ new Set();
|
|
6733
|
+
const mutex = createMutex();
|
|
6734
|
+
let stopped = false;
|
|
6735
|
+
function persistState() {
|
|
6736
|
+
const payload = {
|
|
6737
|
+
lastShas: Object.fromEntries(
|
|
6738
|
+
Array.from(workers.values()).map((w) => [w.workerName, w.lastObservedSha])
|
|
6739
|
+
)
|
|
6740
|
+
};
|
|
6741
|
+
atomicWriteJson(persistedPath, payload);
|
|
6742
|
+
}
|
|
6743
|
+
async function fanOutRebase(triggeringWorker) {
|
|
6744
|
+
for (const other of workers.values()) {
|
|
6745
|
+
if (other.workerName === triggeringWorker) continue;
|
|
6746
|
+
const wtPath = other.workerWorktreePath;
|
|
6747
|
+
if (isRebaseInProgress(wtPath)) {
|
|
6748
|
+
await appendEvent(config.repoRoot, config.teamName, {
|
|
6749
|
+
type: "rebase_skipped_in_progress",
|
|
6750
|
+
worker: other.workerName,
|
|
6751
|
+
reason: "rebase-already-in-progress"
|
|
6752
|
+
});
|
|
6753
|
+
continue;
|
|
6754
|
+
}
|
|
6755
|
+
await appendEvent(config.repoRoot, config.teamName, {
|
|
6756
|
+
type: "rebase_triggered",
|
|
6757
|
+
worker: other.workerName
|
|
6758
|
+
});
|
|
6759
|
+
await pauseHookViaSentinel(wtPath);
|
|
6760
|
+
pausedWorkers.add(other.workerName);
|
|
6761
|
+
try {
|
|
6762
|
+
(0, import_node_child_process3.execFileSync)("git", ["fetch", "--no-tags", "origin", config.leaderBranch], {
|
|
6763
|
+
cwd: wtPath,
|
|
6764
|
+
stdio: "pipe"
|
|
6765
|
+
});
|
|
6766
|
+
} catch {
|
|
6767
|
+
}
|
|
6768
|
+
try {
|
|
6769
|
+
(0, import_node_child_process3.execFileSync)("git", ["rebase", config.leaderBranch], {
|
|
6770
|
+
cwd: wtPath,
|
|
6771
|
+
stdio: "pipe"
|
|
6772
|
+
});
|
|
6773
|
+
await resumeHookViaSentinel(wtPath);
|
|
6774
|
+
pausedWorkers.delete(other.workerName);
|
|
6775
|
+
await appendEvent(config.repoRoot, config.teamName, {
|
|
6776
|
+
type: "rebase_succeeded",
|
|
6777
|
+
worker: other.workerName
|
|
6778
|
+
});
|
|
6779
|
+
} catch {
|
|
6780
|
+
let conflictingFiles = [];
|
|
6781
|
+
try {
|
|
6782
|
+
const status = (0, import_node_child_process3.execFileSync)("git", ["status", "--porcelain"], {
|
|
6783
|
+
cwd: wtPath,
|
|
6784
|
+
encoding: "utf-8",
|
|
6785
|
+
stdio: "pipe"
|
|
6786
|
+
});
|
|
6787
|
+
conflictingFiles = parseUUFiles(status);
|
|
6788
|
+
} catch {
|
|
6789
|
+
conflictingFiles = ["(rebase status unavailable)"];
|
|
6790
|
+
}
|
|
6791
|
+
const baseSha = (() => {
|
|
6792
|
+
try {
|
|
6793
|
+
return (0, import_node_child_process3.execFileSync)("git", ["rev-parse", `refs/heads/${config.leaderBranch}`], {
|
|
6794
|
+
cwd: config.repoRoot,
|
|
6795
|
+
encoding: "utf-8",
|
|
6796
|
+
stdio: "pipe"
|
|
6797
|
+
}).trim();
|
|
6798
|
+
} catch {
|
|
6799
|
+
return "unknown";
|
|
6800
|
+
}
|
|
6801
|
+
})();
|
|
6802
|
+
const message = formatRebaseConflictForWorker({
|
|
6803
|
+
workerName: other.workerName,
|
|
6804
|
+
workerBranch: other.workerBranch,
|
|
6805
|
+
leaderBranch: config.leaderBranch,
|
|
6806
|
+
conflictingFiles,
|
|
6807
|
+
baseSha,
|
|
6808
|
+
worktreePath: wtPath,
|
|
6809
|
+
observedAt: Date.now()
|
|
6810
|
+
});
|
|
6811
|
+
try {
|
|
6812
|
+
await appendToInbox(config.teamName, other.workerName, message, config.cwd);
|
|
6813
|
+
} catch {
|
|
6814
|
+
}
|
|
6815
|
+
await appendEvent(config.repoRoot, config.teamName, {
|
|
6816
|
+
type: "rebase_conflict",
|
|
6817
|
+
worker: other.workerName,
|
|
6818
|
+
data: { conflictingFiles }
|
|
6819
|
+
});
|
|
6820
|
+
}
|
|
6821
|
+
}
|
|
6822
|
+
}
|
|
6823
|
+
async function attemptMergeForWorker(entry) {
|
|
6824
|
+
await mutex(async () => {
|
|
6825
|
+
const targetSha = entry.lastObservedSha;
|
|
6826
|
+
await appendEvent(config.repoRoot, config.teamName, {
|
|
6827
|
+
type: "merge_attempted",
|
|
6828
|
+
worker: entry.workerName,
|
|
6829
|
+
data: { targetSha }
|
|
6830
|
+
});
|
|
6831
|
+
try {
|
|
6832
|
+
preflightMergerWorktree(mergerPath, config.leaderBranch);
|
|
6833
|
+
} catch (err) {
|
|
6834
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
6835
|
+
entry.consecutiveFailures += 1;
|
|
6836
|
+
await appendEvent(config.repoRoot, config.teamName, {
|
|
6837
|
+
type: "merge_conflict",
|
|
6838
|
+
worker: entry.workerName,
|
|
6839
|
+
reason: `preflight_failed:${reason}`
|
|
6840
|
+
});
|
|
6841
|
+
return;
|
|
6842
|
+
}
|
|
6843
|
+
const conflicts = checkMergeConflicts(
|
|
6844
|
+
entry.workerBranch,
|
|
6845
|
+
config.leaderBranch,
|
|
6846
|
+
mergerPath
|
|
6847
|
+
);
|
|
6848
|
+
if (conflicts.length > 0) {
|
|
6849
|
+
let mergeBaseSha = "unknown";
|
|
6850
|
+
try {
|
|
6851
|
+
mergeBaseSha = (0, import_node_child_process3.execFileSync)(
|
|
6852
|
+
"git",
|
|
6853
|
+
["merge-base", config.leaderBranch, entry.workerBranch],
|
|
6854
|
+
{ cwd: mergerPath, encoding: "utf-8", stdio: "pipe" }
|
|
6855
|
+
).trim();
|
|
6856
|
+
} catch {
|
|
6857
|
+
}
|
|
6858
|
+
const message = formatMergeConflictForLeader({
|
|
6859
|
+
workerName: entry.workerName,
|
|
6860
|
+
workerBranch: entry.workerBranch,
|
|
6861
|
+
leaderBranch: config.leaderBranch,
|
|
6862
|
+
conflictingFiles: conflicts,
|
|
6863
|
+
mergeBaseSha,
|
|
6864
|
+
observedAt: Date.now()
|
|
6865
|
+
});
|
|
6866
|
+
try {
|
|
6867
|
+
await appendToLeaderInbox(config.teamName, message, config.cwd);
|
|
6868
|
+
} catch {
|
|
6869
|
+
}
|
|
6870
|
+
await appendEvent(config.repoRoot, config.teamName, {
|
|
6871
|
+
type: "merge_conflict",
|
|
6872
|
+
worker: entry.workerName,
|
|
6873
|
+
data: { conflictingFiles: conflicts, mergeBaseSha }
|
|
6874
|
+
});
|
|
6875
|
+
entry.consecutiveFailures += 1;
|
|
6876
|
+
return;
|
|
6877
|
+
}
|
|
6878
|
+
const result = mergeWorkerBranch(
|
|
6879
|
+
entry.workerBranch,
|
|
6880
|
+
config.leaderBranch,
|
|
6881
|
+
mergerPath
|
|
6882
|
+
);
|
|
6883
|
+
if (!result.success) {
|
|
6884
|
+
const message = formatMergeConflictForLeader({
|
|
6885
|
+
workerName: entry.workerName,
|
|
6886
|
+
workerBranch: entry.workerBranch,
|
|
6887
|
+
leaderBranch: config.leaderBranch,
|
|
6888
|
+
conflictingFiles: result.conflicts.length > 0 ? result.conflicts : ["(merge failed after clean check)"],
|
|
6889
|
+
mergeBaseSha: "unknown",
|
|
6890
|
+
observedAt: Date.now()
|
|
6891
|
+
});
|
|
6892
|
+
try {
|
|
6893
|
+
await appendToLeaderInbox(config.teamName, message, config.cwd);
|
|
6894
|
+
} catch {
|
|
6895
|
+
}
|
|
6896
|
+
await appendEvent(config.repoRoot, config.teamName, {
|
|
6897
|
+
type: "merge_conflict",
|
|
6898
|
+
worker: entry.workerName,
|
|
6899
|
+
data: { conflictingFiles: result.conflicts }
|
|
6900
|
+
});
|
|
6901
|
+
entry.consecutiveFailures += 1;
|
|
6902
|
+
return;
|
|
6903
|
+
}
|
|
6904
|
+
entry.lastMergedSha = targetSha;
|
|
6905
|
+
entry.consecutiveFailures = 0;
|
|
6906
|
+
await appendEvent(config.repoRoot, config.teamName, {
|
|
6907
|
+
type: "merge_succeeded",
|
|
6908
|
+
worker: entry.workerName,
|
|
6909
|
+
data: { mergeCommit: result.mergeCommit, targetSha }
|
|
6910
|
+
});
|
|
6911
|
+
if (stopped) return;
|
|
6912
|
+
await fanOutRebase(entry.workerName);
|
|
6913
|
+
});
|
|
6914
|
+
}
|
|
6915
|
+
async function runPollOnce() {
|
|
6916
|
+
if (stopped) return;
|
|
6917
|
+
for (const entry of workers.values()) {
|
|
6918
|
+
const skipModulo = Math.min(30, Math.pow(2, entry.consecutiveFailures));
|
|
6919
|
+
if (skipModulo > 1 && pollTickCount % skipModulo !== 0) {
|
|
6920
|
+
continue;
|
|
6921
|
+
}
|
|
6922
|
+
if (pausedWorkers.has(entry.workerName)) {
|
|
6923
|
+
if (!isRebaseInProgress(entry.workerWorktreePath)) {
|
|
6924
|
+
await handleRebaseResolution(entry);
|
|
6925
|
+
} else {
|
|
6926
|
+
continue;
|
|
6927
|
+
}
|
|
6928
|
+
}
|
|
6929
|
+
let currentSha;
|
|
6930
|
+
try {
|
|
6931
|
+
currentSha = gitRevParseHead(config.repoRoot, entry.workerBranch);
|
|
6932
|
+
} catch (err) {
|
|
6933
|
+
entry.consecutiveFailures += 1;
|
|
6934
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
6935
|
+
await appendEvent(config.repoRoot, config.teamName, {
|
|
6936
|
+
type: "commit_observed",
|
|
6937
|
+
worker: entry.workerName,
|
|
6938
|
+
reason: `rev_parse_failed:${reason}`
|
|
6939
|
+
});
|
|
6940
|
+
continue;
|
|
6941
|
+
}
|
|
6942
|
+
if (currentSha && currentSha !== entry.lastObservedSha) {
|
|
6943
|
+
entry.lastObservedSha = currentSha;
|
|
6944
|
+
try {
|
|
6945
|
+
persistState();
|
|
6946
|
+
} catch {
|
|
6947
|
+
}
|
|
6948
|
+
await appendEvent(config.repoRoot, config.teamName, {
|
|
6949
|
+
type: "commit_observed",
|
|
6950
|
+
worker: entry.workerName,
|
|
6951
|
+
data: { sha: currentSha }
|
|
6952
|
+
});
|
|
6953
|
+
try {
|
|
6954
|
+
await attemptMergeForWorker(entry);
|
|
6955
|
+
} catch (err) {
|
|
6956
|
+
entry.consecutiveFailures += 1;
|
|
6957
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
6958
|
+
await appendEvent(config.repoRoot, config.teamName, {
|
|
6959
|
+
type: "merge_conflict",
|
|
6960
|
+
worker: entry.workerName,
|
|
6961
|
+
reason: `merge_threw:${reason}`
|
|
6962
|
+
});
|
|
6963
|
+
}
|
|
6964
|
+
}
|
|
6965
|
+
}
|
|
6966
|
+
}
|
|
6967
|
+
async function handleRebaseResolution(entry) {
|
|
6968
|
+
pausedWorkers.delete(entry.workerName);
|
|
6969
|
+
try {
|
|
6970
|
+
const status = (0, import_node_child_process3.execFileSync)("git", ["status", "--porcelain"], {
|
|
6971
|
+
cwd: entry.workerWorktreePath,
|
|
6972
|
+
encoding: "utf-8",
|
|
6973
|
+
stdio: "pipe"
|
|
6974
|
+
}).trim();
|
|
6975
|
+
if (status.length > 0) {
|
|
6976
|
+
const dirtyFiles = status.split("\n").map((l) => l.trim().replace(/^\S+\s+/, "")).filter((s) => s.length > 0);
|
|
6977
|
+
const audit = `## Auto-commit audit: the following files were modified during rebase pause and will be folded into the next auto-commit:
|
|
6978
|
+
${dirtyFiles.map((f) => `- \`${f}\``).join("\n")}`;
|
|
6979
|
+
try {
|
|
6980
|
+
await appendToInbox(config.teamName, entry.workerName, audit, config.cwd);
|
|
6981
|
+
} catch {
|
|
6982
|
+
}
|
|
6983
|
+
}
|
|
6984
|
+
} catch {
|
|
6985
|
+
}
|
|
6986
|
+
await resumeHookViaSentinel(entry.workerWorktreePath);
|
|
6987
|
+
await appendEvent(config.repoRoot, config.teamName, {
|
|
6988
|
+
type: "rebase_resolved",
|
|
6989
|
+
worker: entry.workerName
|
|
6990
|
+
});
|
|
6991
|
+
}
|
|
6992
|
+
let pollTickCount = 0;
|
|
6993
|
+
const interval = setInterval(() => {
|
|
6994
|
+
pollTickCount += 1;
|
|
6995
|
+
void runPollOnce().catch(() => {
|
|
6996
|
+
});
|
|
6997
|
+
}, pollIntervalMs);
|
|
6998
|
+
if (typeof interval.unref === "function") interval.unref();
|
|
6999
|
+
return {
|
|
7000
|
+
async registerWorker(workerName2) {
|
|
7001
|
+
if (workers.has(workerName2)) return;
|
|
7002
|
+
const workerBranch = getBranchName(config.teamName, workerName2);
|
|
7003
|
+
validateBranchName(workerBranch);
|
|
7004
|
+
const wtPath = getWorktreePath(config.repoRoot, config.teamName, workerName2);
|
|
7005
|
+
let seedSha = persisted.lastShas[workerName2] ?? "";
|
|
7006
|
+
if (!seedSha) {
|
|
7007
|
+
try {
|
|
7008
|
+
seedSha = gitRevParseHead(config.repoRoot, workerBranch);
|
|
7009
|
+
} catch {
|
|
7010
|
+
seedSha = "";
|
|
7011
|
+
}
|
|
7012
|
+
}
|
|
7013
|
+
workers.set(workerName2, {
|
|
7014
|
+
workerName: workerName2,
|
|
7015
|
+
workerBranch,
|
|
7016
|
+
workerWorktreePath: wtPath,
|
|
7017
|
+
lastObservedSha: seedSha,
|
|
7018
|
+
lastMergedSha: seedSha,
|
|
7019
|
+
consecutiveFailures: 0
|
|
7020
|
+
});
|
|
7021
|
+
try {
|
|
7022
|
+
persistState();
|
|
7023
|
+
} catch {
|
|
7024
|
+
}
|
|
7025
|
+
},
|
|
7026
|
+
async unregisterWorker(workerName2) {
|
|
7027
|
+
workers.delete(workerName2);
|
|
7028
|
+
pausedWorkers.delete(workerName2);
|
|
7029
|
+
try {
|
|
7030
|
+
persistState();
|
|
7031
|
+
} catch {
|
|
7032
|
+
}
|
|
7033
|
+
},
|
|
7034
|
+
async pollOnce() {
|
|
7035
|
+
await runPollOnce();
|
|
7036
|
+
},
|
|
7037
|
+
async drainAndStop() {
|
|
7038
|
+
stopped = true;
|
|
7039
|
+
clearInterval(interval);
|
|
7040
|
+
const start = Date.now();
|
|
7041
|
+
const unmerged = [];
|
|
7042
|
+
const candidates = Array.from(workers.values()).filter(
|
|
7043
|
+
(w) => w.lastObservedSha && w.lastObservedSha !== w.lastMergedSha
|
|
7044
|
+
);
|
|
7045
|
+
for (const entry of candidates) {
|
|
7046
|
+
const remaining = drainTimeoutMs - (Date.now() - start);
|
|
7047
|
+
if (remaining <= 0) {
|
|
7048
|
+
unmerged.push({ workerName: entry.workerName, reason: "drain-timeout" });
|
|
7049
|
+
continue;
|
|
7050
|
+
}
|
|
7051
|
+
const merged = await Promise.race([
|
|
7052
|
+
(async () => {
|
|
7053
|
+
try {
|
|
7054
|
+
await attemptMergeForWorker(entry);
|
|
7055
|
+
return true;
|
|
7056
|
+
} catch {
|
|
7057
|
+
return false;
|
|
7058
|
+
}
|
|
7059
|
+
})(),
|
|
7060
|
+
new Promise((resolve6) => {
|
|
7061
|
+
const t = setTimeout(() => resolve6(false), remaining);
|
|
7062
|
+
if (typeof t.unref === "function") t.unref();
|
|
7063
|
+
})
|
|
7064
|
+
]);
|
|
7065
|
+
if (!merged || entry.lastMergedSha !== entry.lastObservedSha) {
|
|
7066
|
+
unmerged.push({
|
|
7067
|
+
workerName: entry.workerName,
|
|
7068
|
+
reason: merged ? "merge-conflict" : "drain-timeout"
|
|
7069
|
+
});
|
|
7070
|
+
}
|
|
7071
|
+
}
|
|
7072
|
+
if (unmerged.length > 0) {
|
|
7073
|
+
const auditPath = teardownAuditPath(config.repoRoot, config.teamName);
|
|
7074
|
+
await (0, import_promises9.mkdir)((0, import_node_path2.dirname)(auditPath), { recursive: true });
|
|
7075
|
+
for (const u of unmerged) {
|
|
7076
|
+
const row = JSON.stringify({
|
|
7077
|
+
type: "unmerged_at_shutdown",
|
|
7078
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7079
|
+
team: config.teamName,
|
|
7080
|
+
worker: u.workerName,
|
|
7081
|
+
reason: u.reason
|
|
7082
|
+
});
|
|
7083
|
+
try {
|
|
7084
|
+
await (0, import_promises9.appendFile)(auditPath, `${row}
|
|
7085
|
+
`, "utf-8");
|
|
7086
|
+
} catch {
|
|
7087
|
+
}
|
|
7088
|
+
}
|
|
7089
|
+
const message = `## Teardown audit: unmerged worker branches at shutdown
|
|
7090
|
+
|
|
7091
|
+
${unmerged.map((u) => `- ${u.workerName}: ${u.reason}`).join("\n")}`;
|
|
7092
|
+
try {
|
|
7093
|
+
await appendToLeaderInbox(config.teamName, message, config.cwd);
|
|
7094
|
+
} catch {
|
|
7095
|
+
}
|
|
7096
|
+
}
|
|
7097
|
+
return { unmerged };
|
|
7098
|
+
},
|
|
7099
|
+
getState() {
|
|
7100
|
+
return {
|
|
7101
|
+
workers: Array.from(workers.keys()),
|
|
7102
|
+
lastShas: Object.fromEntries(
|
|
7103
|
+
Array.from(workers.values()).map((w) => [w.workerName, w.lastObservedSha])
|
|
7104
|
+
),
|
|
7105
|
+
mergerWorktreePath: mergerPath
|
|
7106
|
+
};
|
|
7107
|
+
}
|
|
7108
|
+
};
|
|
7109
|
+
}
|
|
7110
|
+
async function recoverFromRestart(config) {
|
|
7111
|
+
const persistedPath = persistedStatePath(config.repoRoot, config.teamName);
|
|
7112
|
+
let persistedShasLoaded = 0;
|
|
7113
|
+
if ((0, import_node_fs2.existsSync)(persistedPath)) {
|
|
7114
|
+
try {
|
|
7115
|
+
const { readFileSync: readFileSync12 } = await import("node:fs");
|
|
7116
|
+
const persisted = JSON.parse(readFileSync12(persistedPath, "utf-8"));
|
|
7117
|
+
persistedShasLoaded = Object.keys(persisted.lastShas ?? {}).length;
|
|
7118
|
+
} catch {
|
|
7119
|
+
persistedShasLoaded = 0;
|
|
7120
|
+
}
|
|
7121
|
+
}
|
|
7122
|
+
const orphanedRebases = [];
|
|
7123
|
+
let entries = [];
|
|
7124
|
+
try {
|
|
7125
|
+
entries = listTeamWorktrees(config.teamName, config.repoRoot).map((w) => ({
|
|
7126
|
+
workerName: w.workerName,
|
|
7127
|
+
path: w.path
|
|
7128
|
+
}));
|
|
7129
|
+
} catch {
|
|
7130
|
+
entries = [];
|
|
7131
|
+
}
|
|
7132
|
+
for (const entry of entries) {
|
|
7133
|
+
if (!isRebaseInProgress(entry.path)) continue;
|
|
7134
|
+
orphanedRebases.push(entry.workerName);
|
|
7135
|
+
const message = `### Runtime restart recovery \u2014 your branch is mid-rebase
|
|
7136
|
+
|
|
7137
|
+
Runtime restarted while your branch was mid-rebase onto \`${config.leaderBranch}\`.
|
|
7138
|
+
|
|
7139
|
+
**Worktree:** \`${entry.path}\`
|
|
7140
|
+
|
|
7141
|
+
Cadence remains paused. Resolve and \`git rebase --continue\`, or \`git rebase --abort\` to bail.
|
|
7142
|
+
Cadence resumes once the git rebase state is gone.`;
|
|
7143
|
+
try {
|
|
7144
|
+
await appendToInbox(config.teamName, entry.workerName, message, config.cwd);
|
|
7145
|
+
} catch {
|
|
7146
|
+
}
|
|
7147
|
+
}
|
|
7148
|
+
if (orphanedRebases.length > 0 || persistedShasLoaded > 0) {
|
|
7149
|
+
try {
|
|
7150
|
+
await appendEvent(config.repoRoot, config.teamName, {
|
|
7151
|
+
type: "restart_recovery",
|
|
7152
|
+
data: { orphanedRebases, persistedShasLoaded }
|
|
7153
|
+
});
|
|
7154
|
+
} catch {
|
|
7155
|
+
}
|
|
7156
|
+
}
|
|
7157
|
+
return { orphanedRebases, persistedShasLoaded };
|
|
7158
|
+
}
|
|
7159
|
+
|
|
7160
|
+
// src/team/runtime-v2.ts
|
|
7161
|
+
var import_node_child_process4 = require("node:child_process");
|
|
7162
|
+
var orchestratorByTeam = /* @__PURE__ */ new Map();
|
|
7163
|
+
var cadenceByTeam = /* @__PURE__ */ new Map();
|
|
7164
|
+
function registerTeamOrchestrator(teamName, handle) {
|
|
7165
|
+
orchestratorByTeam.set(teamName, handle);
|
|
7166
|
+
}
|
|
7167
|
+
function getTeamOrchestrator(teamName) {
|
|
7168
|
+
return orchestratorByTeam.get(teamName);
|
|
7169
|
+
}
|
|
7170
|
+
function unregisterTeamOrchestrator(teamName) {
|
|
7171
|
+
orchestratorByTeam.delete(teamName);
|
|
7172
|
+
}
|
|
7173
|
+
function registerTeamCadence(teamName, context, poller) {
|
|
7174
|
+
const entry = cadenceByTeam.get(teamName) ?? { pollers: [], contexts: [] };
|
|
7175
|
+
entry.contexts.push(context);
|
|
7176
|
+
if (poller) entry.pollers.push(poller);
|
|
7177
|
+
cadenceByTeam.set(teamName, entry);
|
|
7178
|
+
}
|
|
7179
|
+
async function stopTeamCadence(teamName) {
|
|
7180
|
+
const entry = cadenceByTeam.get(teamName);
|
|
7181
|
+
if (!entry) return;
|
|
7182
|
+
cadenceByTeam.delete(teamName);
|
|
7183
|
+
for (const poller of entry.pollers) {
|
|
7184
|
+
try {
|
|
7185
|
+
poller.stop();
|
|
7186
|
+
} catch {
|
|
7187
|
+
}
|
|
7188
|
+
}
|
|
7189
|
+
for (const context of entry.contexts) {
|
|
7190
|
+
try {
|
|
7191
|
+
await uninstallCommitCadence(context);
|
|
7192
|
+
} catch {
|
|
7193
|
+
}
|
|
7194
|
+
}
|
|
7195
|
+
}
|
|
7196
|
+
function resolveLeaderBranch(cwd) {
|
|
7197
|
+
const out = (0, import_node_child_process4.execFileSync)("git", ["branch", "--show-current"], {
|
|
7198
|
+
cwd,
|
|
7199
|
+
encoding: "utf-8",
|
|
7200
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
7201
|
+
}).trim();
|
|
7202
|
+
if (!out) {
|
|
7203
|
+
throw new Error("auto-merge requires a non-detached leader branch (git branch --show-current returned empty)");
|
|
7204
|
+
}
|
|
7205
|
+
return out;
|
|
7206
|
+
}
|
|
6208
7207
|
var MONITOR_SIGNAL_STALE_MS = 3e4;
|
|
6209
7208
|
function resolveTaskAssignment(task, resolvedRouting, roleRoutingConfig, resolvedBinaryPaths, fallbackAgent) {
|
|
6210
7209
|
const canonicalRoles = new Set(CANONICAL_TEAM_ROLES);
|
|
@@ -6347,7 +7346,7 @@ function hasWorkerStatusProgress(status, taskId) {
|
|
|
6347
7346
|
}
|
|
6348
7347
|
async function hasWorkerTaskClaimEvidence(teamName, workerName2, cwd, taskId) {
|
|
6349
7348
|
try {
|
|
6350
|
-
const raw = await (0,
|
|
7349
|
+
const raw = await (0, import_promises10.readFile)(absPath(cwd, TeamPaths.taskFile(teamName, taskId)), "utf-8");
|
|
6351
7350
|
const task = JSON.parse(raw);
|
|
6352
7351
|
return task.owner === workerName2 && ["in_progress", "completed", "failed"].includes(task.status);
|
|
6353
7352
|
} catch {
|
|
@@ -6446,6 +7445,18 @@ async function spawnV2Worker(opts) {
|
|
|
6446
7445
|
if (usePromptMode) {
|
|
6447
7446
|
launchArgs.push(...getPromptModeArgs(opts.agentType, promptModeStartupPrompt));
|
|
6448
7447
|
}
|
|
7448
|
+
if (opts.autoMerge && opts.worktreePath) {
|
|
7449
|
+
const cadenceContext = {
|
|
7450
|
+
teamName: opts.teamName,
|
|
7451
|
+
workerName: opts.workerName,
|
|
7452
|
+
worktreePath: opts.worktreePath,
|
|
7453
|
+
agentType: opts.agentType,
|
|
7454
|
+
enabled: true
|
|
7455
|
+
};
|
|
7456
|
+
const cadence = await installCommitCadence(cadenceContext);
|
|
7457
|
+
const poller = cadence.method === "fallback-poll" ? startFallbackPoller(opts.worktreePath, opts.workerName) : void 0;
|
|
7458
|
+
registerTeamCadence(opts.teamName, cadenceContext, poller);
|
|
7459
|
+
}
|
|
6449
7460
|
const paneConfig = {
|
|
6450
7461
|
teamName: opts.teamName,
|
|
6451
7462
|
workerName: opts.workerName,
|
|
@@ -6546,19 +7557,19 @@ async function rollbackUnpersistedNativeWorktreeStartup(teamName, cwd, cause) {
|
|
|
6546
7557
|
try {
|
|
6547
7558
|
const cleanup = cleanupTeamWorktrees(teamName, cwd);
|
|
6548
7559
|
if (cleanup.preserved.length === 0) {
|
|
6549
|
-
await (0,
|
|
7560
|
+
await (0, import_promises10.rm)(teamRoot, { recursive: true, force: true });
|
|
6550
7561
|
return;
|
|
6551
7562
|
}
|
|
6552
|
-
await (0,
|
|
6553
|
-
await (0,
|
|
7563
|
+
await (0, import_promises10.mkdir)(teamRoot, { recursive: true });
|
|
7564
|
+
await (0, import_promises10.writeFile)((0, import_path22.join)(teamRoot, "startup-failure.json"), JSON.stringify({
|
|
6554
7565
|
reason: "startup_failed_before_config_persisted",
|
|
6555
7566
|
error: errorMessage,
|
|
6556
7567
|
preserved: cleanup.preserved,
|
|
6557
7568
|
recorded_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
6558
7569
|
}, null, 2), "utf-8");
|
|
6559
7570
|
} catch (rollbackError) {
|
|
6560
|
-
await (0,
|
|
6561
|
-
await (0,
|
|
7571
|
+
await (0, import_promises10.mkdir)(teamRoot, { recursive: true });
|
|
7572
|
+
await (0, import_promises10.writeFile)((0, import_path22.join)(teamRoot, "startup-failure.json"), JSON.stringify({
|
|
6562
7573
|
reason: "startup_failed_before_config_persisted",
|
|
6563
7574
|
error: errorMessage,
|
|
6564
7575
|
rollback_error: rollbackError instanceof Error ? rollbackError.message : String(rollbackError),
|
|
@@ -6584,13 +7595,27 @@ async function rollbackStartedNativeWorktreeStartup(args) {
|
|
|
6584
7595
|
}
|
|
6585
7596
|
async function startTeamV2(config) {
|
|
6586
7597
|
const sanitized = sanitizeTeamName(config.teamName);
|
|
6587
|
-
const leaderCwd = (0,
|
|
7598
|
+
const leaderCwd = (0, import_path22.resolve)(config.cwd);
|
|
6588
7599
|
validateTeamName(sanitized);
|
|
6589
7600
|
const pluginCfg = config.pluginConfig ?? loadConfig();
|
|
6590
7601
|
const resolvedRouting = buildResolvedRoutingSnapshot(pluginCfg);
|
|
6591
|
-
|
|
7602
|
+
let worktreeMode = normalizeTeamWorktreeMode(
|
|
6592
7603
|
process.env.OMC_TEAM_WORKTREE_MODE ?? pluginCfg.team?.ops?.worktreeMode
|
|
6593
7604
|
);
|
|
7605
|
+
let autoMergeLeaderBranch;
|
|
7606
|
+
if (config.autoMerge) {
|
|
7607
|
+
if (!isRuntimeV2Enabled()) {
|
|
7608
|
+
throw new Error("auto-merge requires OMC_RUNTIME_V2=1 (this feature is v2-only).");
|
|
7609
|
+
}
|
|
7610
|
+
autoMergeLeaderBranch = resolveLeaderBranch(leaderCwd);
|
|
7611
|
+
const stripped = autoMergeLeaderBranch.replace(/^refs\/heads\//i, "").toLowerCase();
|
|
7612
|
+
if (stripped === "main" || stripped === "master") {
|
|
7613
|
+
throw new Error("auto-merge refuses main/master leader branch \u2014 use a feature branch");
|
|
7614
|
+
}
|
|
7615
|
+
if (worktreeMode !== "named") {
|
|
7616
|
+
worktreeMode = "named";
|
|
7617
|
+
}
|
|
7618
|
+
}
|
|
6594
7619
|
const workspaceMode = worktreeMode === "disabled" ? "single" : "worktree";
|
|
6595
7620
|
const agentTypes = config.agentTypes;
|
|
6596
7621
|
const resolvedBinaryPaths = {};
|
|
@@ -6620,9 +7645,9 @@ async function startTeamV2(config) {
|
|
|
6620
7645
|
} catch {
|
|
6621
7646
|
}
|
|
6622
7647
|
}
|
|
6623
|
-
await (0,
|
|
6624
|
-
await (0,
|
|
6625
|
-
await (0,
|
|
7648
|
+
await (0, import_promises10.mkdir)(absPath(leaderCwd, TeamPaths.tasks(sanitized)), { recursive: true });
|
|
7649
|
+
await (0, import_promises10.mkdir)(absPath(leaderCwd, TeamPaths.workers(sanitized)), { recursive: true });
|
|
7650
|
+
await (0, import_promises10.mkdir)((0, import_path22.join)(leaderCwd, ".omcp", "state", "team", sanitized, "mailbox"), { recursive: true });
|
|
6626
7651
|
const missingBinaryLogFailure = createSwallowedErrorLogger(
|
|
6627
7652
|
"team.runtime-v2.startTeamV2 cli_binary_missing event failed"
|
|
6628
7653
|
);
|
|
@@ -6640,8 +7665,8 @@ async function startTeamV2(config) {
|
|
|
6640
7665
|
for (let i = 0; i < config.tasks.length; i++) {
|
|
6641
7666
|
const taskId = String(i + 1);
|
|
6642
7667
|
const taskFilePath = absPath(leaderCwd, TeamPaths.taskFile(sanitized, taskId));
|
|
6643
|
-
await (0,
|
|
6644
|
-
await (0,
|
|
7668
|
+
await (0, import_promises10.mkdir)((0, import_path22.join)(taskFilePath, ".."), { recursive: true });
|
|
7669
|
+
await (0, import_promises10.writeFile)(taskFilePath, JSON.stringify({
|
|
6645
7670
|
id: taskId,
|
|
6646
7671
|
subject: config.tasks[i].subject,
|
|
6647
7672
|
description: config.tasks[i].description,
|
|
@@ -6713,7 +7738,7 @@ async function startTeamV2(config) {
|
|
|
6713
7738
|
});
|
|
6714
7739
|
const worktree = workerWorktrees.get(wName);
|
|
6715
7740
|
if (worktree) {
|
|
6716
|
-
const overlayContent = await (0,
|
|
7741
|
+
const overlayContent = await (0, import_promises10.readFile)(overlayPath, "utf-8");
|
|
6717
7742
|
installWorktreeRootAgents(sanitized, wName, leaderCwd, worktree.path, overlayContent);
|
|
6718
7743
|
}
|
|
6719
7744
|
}
|
|
@@ -6823,7 +7848,7 @@ async function startTeamV2(config) {
|
|
|
6823
7848
|
next_worker_index: teamConfig.next_worker_index
|
|
6824
7849
|
};
|
|
6825
7850
|
try {
|
|
6826
|
-
await (0,
|
|
7851
|
+
await (0, import_promises10.writeFile)(absPath(leaderCwd, TeamPaths.manifest(sanitized)), JSON.stringify(teamManifest, null, 2), "utf-8");
|
|
6827
7852
|
} catch (error) {
|
|
6828
7853
|
await rollbackStartedNativeWorktreeStartup({
|
|
6829
7854
|
teamName: sanitized,
|
|
@@ -6872,6 +7897,7 @@ async function startTeamV2(config) {
|
|
|
6872
7897
|
cwd: leaderCwd,
|
|
6873
7898
|
workerCwd: workersInfo[workerIndex]?.working_dir ?? leaderCwd,
|
|
6874
7899
|
worktreePath: workersInfo[workerIndex]?.worktree_path,
|
|
7900
|
+
autoMerge: Boolean(config.autoMerge),
|
|
6875
7901
|
resolvedBinaryPaths,
|
|
6876
7902
|
...assignment.model ? { model: assignment.model } : {},
|
|
6877
7903
|
...assignment.role ? { role: assignment.role } : {}
|
|
@@ -6934,6 +7960,51 @@ async function startTeamV2(config) {
|
|
|
6934
7960
|
worker: "leader-fixed",
|
|
6935
7961
|
reason: `start_team_v2: workers=${config.workerCount} tasks=${config.tasks.length} panes=${workerPaneIds.length}`
|
|
6936
7962
|
}, leaderCwd).catch(logEventFailure);
|
|
7963
|
+
if (config.autoMerge && autoMergeLeaderBranch) {
|
|
7964
|
+
try {
|
|
7965
|
+
await ensureLeaderInbox(sanitized, leaderCwd);
|
|
7966
|
+
await appendToLeaderInbox(
|
|
7967
|
+
sanitized,
|
|
7968
|
+
extendLeaderBootstrapPrompt(sanitized),
|
|
7969
|
+
leaderCwd
|
|
7970
|
+
);
|
|
7971
|
+
try {
|
|
7972
|
+
await recoverFromRestart({
|
|
7973
|
+
teamName: sanitized,
|
|
7974
|
+
repoRoot: leaderCwd,
|
|
7975
|
+
leaderBranch: autoMergeLeaderBranch,
|
|
7976
|
+
cwd: leaderCwd
|
|
7977
|
+
});
|
|
7978
|
+
} catch (recErr) {
|
|
7979
|
+
process.stderr.write(`[team/runtime-v2] auto-merge recover-from-restart failed: ${recErr}
|
|
7980
|
+
`);
|
|
7981
|
+
}
|
|
7982
|
+
const orchestrator = await startMergeOrchestrator({
|
|
7983
|
+
teamName: sanitized,
|
|
7984
|
+
repoRoot: leaderCwd,
|
|
7985
|
+
leaderBranch: autoMergeLeaderBranch,
|
|
7986
|
+
cwd: leaderCwd
|
|
7987
|
+
});
|
|
7988
|
+
registerTeamOrchestrator(sanitized, orchestrator);
|
|
7989
|
+
for (const w of workersInfo) {
|
|
7990
|
+
await orchestrator.registerWorker(w.name);
|
|
7991
|
+
}
|
|
7992
|
+
} catch (orchErr) {
|
|
7993
|
+
await stopTeamCadence(sanitized);
|
|
7994
|
+
unregisterTeamOrchestrator(sanitized);
|
|
7995
|
+
await rollbackStartedNativeWorktreeStartup({
|
|
7996
|
+
teamName: sanitized,
|
|
7997
|
+
cwd: leaderCwd,
|
|
7998
|
+
cause: orchErr,
|
|
7999
|
+
sessionName: sessionName2,
|
|
8000
|
+
leaderPaneId,
|
|
8001
|
+
workerPaneIds,
|
|
8002
|
+
sessionMode: session.sessionMode
|
|
8003
|
+
});
|
|
8004
|
+
const reason = orchErr instanceof Error ? orchErr.message : String(orchErr);
|
|
8005
|
+
throw new Error(`auto-merge startup failed: ${reason}`);
|
|
8006
|
+
}
|
|
8007
|
+
}
|
|
6937
8008
|
return {
|
|
6938
8009
|
teamName: sanitized,
|
|
6939
8010
|
sanitizedName: sanitized,
|
|
@@ -6965,7 +8036,7 @@ async function processCliWorkerVerdicts(teamName, cwd) {
|
|
|
6965
8036
|
}
|
|
6966
8037
|
let payload;
|
|
6967
8038
|
try {
|
|
6968
|
-
const raw = await (0,
|
|
8039
|
+
const raw = await (0, import_promises10.readFile)(outputFile, "utf-8");
|
|
6969
8040
|
payload = parseCliWorkerVerdict(raw);
|
|
6970
8041
|
} catch (err) {
|
|
6971
8042
|
const reason = err instanceof Error ? err.message : String(err);
|
|
@@ -7248,6 +8319,39 @@ async function shutdownTeamV2(teamName, cwd, options = {}) {
|
|
|
7248
8319
|
const timeoutMs = options.timeoutMs ?? 15e3;
|
|
7249
8320
|
const sanitized = sanitizeTeamName(teamName);
|
|
7250
8321
|
const config = await readTeamConfig(sanitized, cwd);
|
|
8322
|
+
const finalizeAutoMerge = async () => {
|
|
8323
|
+
const orchestrator = getTeamOrchestrator(sanitized);
|
|
8324
|
+
if (orchestrator) {
|
|
8325
|
+
try {
|
|
8326
|
+
const drainResult = await orchestrator.drainAndStop();
|
|
8327
|
+
if (drainResult.unmerged.length > 0) {
|
|
8328
|
+
await appendTeamEvent(sanitized, {
|
|
8329
|
+
type: "team_leader_nudge",
|
|
8330
|
+
worker: "leader-fixed",
|
|
8331
|
+
reason: `auto_merge_drain_unmerged:${drainResult.unmerged.map((u) => `${u.workerName}:${u.reason}`).join(",")}`
|
|
8332
|
+
}, cwd).catch(logEventFailure);
|
|
8333
|
+
}
|
|
8334
|
+
for (const w of config?.workers ?? []) {
|
|
8335
|
+
try {
|
|
8336
|
+
await orchestrator.unregisterWorker(w.name);
|
|
8337
|
+
} catch (err) {
|
|
8338
|
+
process.stderr.write(
|
|
8339
|
+
`[team/runtime-v2] orchestrator.unregisterWorker(${w.name}) failed: ${err}
|
|
8340
|
+
`
|
|
8341
|
+
);
|
|
8342
|
+
}
|
|
8343
|
+
}
|
|
8344
|
+
} catch (err) {
|
|
8345
|
+
process.stderr.write(`[team/runtime-v2] orchestrator drainAndStop: ${err}
|
|
8346
|
+
`);
|
|
8347
|
+
} finally {
|
|
8348
|
+
await stopTeamCadence(sanitized);
|
|
8349
|
+
unregisterTeamOrchestrator(sanitized);
|
|
8350
|
+
}
|
|
8351
|
+
} else {
|
|
8352
|
+
await stopTeamCadence(sanitized);
|
|
8353
|
+
}
|
|
8354
|
+
};
|
|
7251
8355
|
if (!config) {
|
|
7252
8356
|
const cleanupSafety = inspectTeamWorktreeCleanupSafety(sanitized, cwd);
|
|
7253
8357
|
if (cleanupSafety.hasEvidence) {
|
|
@@ -7382,12 +8486,14 @@ Then exit your session.
|
|
|
7382
8486
|
if (aliveWorkers.length > 0) {
|
|
7383
8487
|
process.stderr.write(`[team/runtime-v2] preserving worktrees/state because worker pane(s) are still alive: ${aliveWorkers.join(", ")}
|
|
7384
8488
|
`);
|
|
8489
|
+
await finalizeAutoMerge();
|
|
7385
8490
|
return;
|
|
7386
8491
|
}
|
|
7387
8492
|
const unknownWorkers = liveness.filter(([, state]) => state === "unknown").map(([paneId]) => paneById.get(paneId) ?? paneId);
|
|
7388
8493
|
if (unknownWorkers.length > 0) {
|
|
7389
8494
|
process.stderr.write(`[team/runtime-v2] preserving worktrees/state because worker pane liveness is unknown: ${unknownWorkers.join(", ")}
|
|
7390
8495
|
`);
|
|
8496
|
+
await finalizeAutoMerge();
|
|
7391
8497
|
return;
|
|
7392
8498
|
}
|
|
7393
8499
|
} catch (err) {
|
|
@@ -7395,6 +8501,7 @@ Then exit your session.
|
|
|
7395
8501
|
`);
|
|
7396
8502
|
if (recordedWorkerPaneIds.length > 0) {
|
|
7397
8503
|
process.stderr.write("[team/runtime-v2] preserving worktrees/state because tmux cleanup did not prove worker panes exited\n");
|
|
8504
|
+
await finalizeAutoMerge();
|
|
7398
8505
|
return;
|
|
7399
8506
|
}
|
|
7400
8507
|
}
|
|
@@ -7409,6 +8516,7 @@ Then exit your session.
|
|
|
7409
8516
|
reason: `ralph_cleanup_summary: total=${finalTasks.length} completed=${completed} failed=${failed} pending=${pending} force=${force}`
|
|
7410
8517
|
}, cwd).catch(logEventFailure);
|
|
7411
8518
|
}
|
|
8519
|
+
await finalizeAutoMerge();
|
|
7412
8520
|
let preservedWorktrees = 0;
|
|
7413
8521
|
try {
|
|
7414
8522
|
const worktreeCleanup = cleanupTeamWorktrees(sanitized, cwd);
|
|
@@ -7427,6 +8535,11 @@ Then exit your session.
|
|
|
7427
8535
|
}
|
|
7428
8536
|
|
|
7429
8537
|
// src/team/runtime-cli.ts
|
|
8538
|
+
function assertAutoMergeRuntimeSupported(useV2, autoMerge) {
|
|
8539
|
+
if (autoMerge && !useV2) {
|
|
8540
|
+
throw new Error("--auto-merge requires runtime v2; unset OMC_RUNTIME_V2=0 or disable --auto-merge");
|
|
8541
|
+
}
|
|
8542
|
+
}
|
|
7430
8543
|
function getTerminalStatus(taskCounts, expectedTaskCount) {
|
|
7431
8544
|
const active = taskCounts.pending + taskCounts.inProgress;
|
|
7432
8545
|
const terminal = taskCounts.completed + taskCounts.failed;
|
|
@@ -7444,10 +8557,10 @@ function parseWatchdogFailedAt(marker) {
|
|
|
7444
8557
|
throw new Error("watchdog marker missing valid failedAt");
|
|
7445
8558
|
}
|
|
7446
8559
|
async function checkWatchdogFailedMarker(stateRoot2, startTime) {
|
|
7447
|
-
const markerPath = (0,
|
|
8560
|
+
const markerPath = (0, import_path23.join)(stateRoot2, "watchdog-failed.json");
|
|
7448
8561
|
let raw;
|
|
7449
8562
|
try {
|
|
7450
|
-
raw = await (0,
|
|
8563
|
+
raw = await (0, import_promises11.readFile)(markerPath, "utf-8");
|
|
7451
8564
|
} catch (err) {
|
|
7452
8565
|
const code = err.code;
|
|
7453
8566
|
if (code === "ENOENT") return { failed: false };
|
|
@@ -7469,21 +8582,21 @@ async function checkWatchdogFailedMarker(stateRoot2, startTime) {
|
|
|
7469
8582
|
return { failed: true, reason: `Watchdog marked team failed at ${new Date(failedAt).toISOString()}` };
|
|
7470
8583
|
}
|
|
7471
8584
|
try {
|
|
7472
|
-
await (0,
|
|
8585
|
+
await (0, import_promises11.unlink)(markerPath);
|
|
7473
8586
|
} catch {
|
|
7474
8587
|
}
|
|
7475
8588
|
return { failed: false };
|
|
7476
8589
|
}
|
|
7477
8590
|
async function writeResultArtifact(output, finishedAt, jobId = process.env.OMC_JOB_ID, omcJobsDir = process.env.OMC_JOBS_DIR) {
|
|
7478
8591
|
if (!jobId || !omcJobsDir) return;
|
|
7479
|
-
const resultPath = (0,
|
|
8592
|
+
const resultPath = (0, import_path23.join)(omcJobsDir, `${jobId}-result.json`);
|
|
7480
8593
|
const tmpPath = `${resultPath}.tmp`;
|
|
7481
|
-
await (0,
|
|
8594
|
+
await (0, import_promises11.writeFile)(
|
|
7482
8595
|
tmpPath,
|
|
7483
8596
|
JSON.stringify({ ...output, finishedAt }),
|
|
7484
8597
|
"utf-8"
|
|
7485
8598
|
);
|
|
7486
|
-
await (0,
|
|
8599
|
+
await (0, import_promises11.rename)(tmpPath, resultPath);
|
|
7487
8600
|
}
|
|
7488
8601
|
function buildCliOutput(stateRoot2, teamName, status, workerCount, startTimeMs) {
|
|
7489
8602
|
const taskResults = collectTaskResults(stateRoot2);
|
|
@@ -7508,20 +8621,20 @@ function buildTerminalCliResult(stateRoot2, teamName, phase, workerCount, startT
|
|
|
7508
8621
|
async function writePanesFile(jobId, paneIds, leaderPaneId, sessionName2, ownsWindow) {
|
|
7509
8622
|
const omcJobsDir = process.env.OMC_JOBS_DIR;
|
|
7510
8623
|
if (!jobId || !omcJobsDir) return;
|
|
7511
|
-
const panesPath = (0,
|
|
7512
|
-
await (0,
|
|
8624
|
+
const panesPath = (0, import_path23.join)(omcJobsDir, `${jobId}-panes.json`);
|
|
8625
|
+
await (0, import_promises11.writeFile)(
|
|
7513
8626
|
panesPath + ".tmp",
|
|
7514
8627
|
JSON.stringify({ paneIds: [...paneIds], leaderPaneId, sessionName: sessionName2, ownsWindow })
|
|
7515
8628
|
);
|
|
7516
|
-
await (0,
|
|
8629
|
+
await (0, import_promises11.rename)(panesPath + ".tmp", panesPath);
|
|
7517
8630
|
}
|
|
7518
8631
|
function collectTaskResults(stateRoot2) {
|
|
7519
|
-
const tasksDir = (0,
|
|
8632
|
+
const tasksDir = (0, import_path23.join)(stateRoot2, "tasks");
|
|
7520
8633
|
try {
|
|
7521
|
-
const files = (0,
|
|
8634
|
+
const files = (0, import_fs21.readdirSync)(tasksDir).filter((f) => f.endsWith(".json"));
|
|
7522
8635
|
return files.map((f) => {
|
|
7523
8636
|
try {
|
|
7524
|
-
const raw = (0,
|
|
8637
|
+
const raw = (0, import_fs21.readFileSync)((0, import_path23.join)(tasksDir, f), "utf-8");
|
|
7525
8638
|
const task = JSON.parse(raw);
|
|
7526
8639
|
return {
|
|
7527
8640
|
taskId: task.id ?? f.replace(".json", ""),
|
|
@@ -7572,10 +8685,11 @@ async function main() {
|
|
|
7572
8685
|
newWindow = false,
|
|
7573
8686
|
pollIntervalMs = 5e3,
|
|
7574
8687
|
sentinelGateTimeoutMs = 3e4,
|
|
7575
|
-
sentinelGatePollIntervalMs = 250
|
|
8688
|
+
sentinelGatePollIntervalMs = 250,
|
|
8689
|
+
autoMerge = false
|
|
7576
8690
|
} = input;
|
|
7577
8691
|
const workerCount = input.workerCount ?? agentTypes.length;
|
|
7578
|
-
const stateRoot2 = (0,
|
|
8692
|
+
const stateRoot2 = (0, import_path23.join)(cwd, `.omcp/state/team/${teamName}`);
|
|
7579
8693
|
const config = {
|
|
7580
8694
|
teamName,
|
|
7581
8695
|
workerCount,
|
|
@@ -7585,6 +8699,13 @@ async function main() {
|
|
|
7585
8699
|
newWindow
|
|
7586
8700
|
};
|
|
7587
8701
|
const useV2 = isRuntimeV2Enabled();
|
|
8702
|
+
try {
|
|
8703
|
+
assertAutoMergeRuntimeSupported(useV2, autoMerge);
|
|
8704
|
+
} catch (err) {
|
|
8705
|
+
process.stderr.write(`[runtime-cli] ${err instanceof Error ? err.message : String(err)}
|
|
8706
|
+
`);
|
|
8707
|
+
process.exit(1);
|
|
8708
|
+
}
|
|
7588
8709
|
let runtime = null;
|
|
7589
8710
|
let finalStatus = "failed";
|
|
7590
8711
|
let pollActive = true;
|
|
@@ -7649,7 +8770,8 @@ async function main() {
|
|
|
7649
8770
|
agentTypes,
|
|
7650
8771
|
tasks,
|
|
7651
8772
|
cwd,
|
|
7652
|
-
newWindow
|
|
8773
|
+
newWindow,
|
|
8774
|
+
autoMerge
|
|
7653
8775
|
});
|
|
7654
8776
|
const v2PaneIds = v2Runtime.config.workers.map((w) => w.pane_id).filter((p) => typeof p === "string");
|
|
7655
8777
|
runtime = {
|
|
@@ -7769,7 +8891,7 @@ async function main() {
|
|
|
7769
8891
|
if (snap.allTasksTerminal) {
|
|
7770
8892
|
const hasFailures = snap.tasks.failed > 0;
|
|
7771
8893
|
if (!hasFailures) {
|
|
7772
|
-
const sentinelLogPath = (0,
|
|
8894
|
+
const sentinelLogPath = (0, import_path23.join)(cwd, "sentinel_stop.jsonl");
|
|
7773
8895
|
const gateResult = await waitForSentinelReadiness({
|
|
7774
8896
|
workspace: cwd,
|
|
7775
8897
|
logPath: sentinelLogPath,
|
|
@@ -7846,7 +8968,7 @@ async function main() {
|
|
|
7846
8968
|
mismatchStreak = 0;
|
|
7847
8969
|
const terminalStatus = getTerminalStatus(snap.taskCounts, expectedTaskCount);
|
|
7848
8970
|
if (terminalStatus === "completed") {
|
|
7849
|
-
const sentinelLogPath = (0,
|
|
8971
|
+
const sentinelLogPath = (0, import_path23.join)(cwd, "sentinel_stop.jsonl");
|
|
7850
8972
|
const gateResult = await waitForSentinelReadiness({
|
|
7851
8973
|
workspace: cwd,
|
|
7852
8974
|
logPath: sentinelLogPath,
|
|
@@ -7890,6 +9012,7 @@ if (require.main === module) {
|
|
|
7890
9012
|
}
|
|
7891
9013
|
// Annotate the CommonJS export names for ESM import in node:
|
|
7892
9014
|
0 && (module.exports = {
|
|
9015
|
+
assertAutoMergeRuntimeSupported,
|
|
7893
9016
|
buildCliOutput,
|
|
7894
9017
|
buildTerminalCliResult,
|
|
7895
9018
|
checkWatchdogFailedMarker,
|