@algosuite/vo-mcp 0.2.0-beta.60 → 0.2.0-beta.62

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.
@@ -636,6 +636,49 @@ var init_codex_mcp_config = __esm({
636
636
  }
637
637
  });
638
638
 
639
+ // src/remote-mcp-entry.ts
640
+ function remoteMcpOptIn(env2) {
641
+ return (env2["VO_REMOTE_MCP"] ?? "").trim() === "1";
642
+ }
643
+ function remoteMcpUrl(controlPlaneUrl2) {
644
+ return `${controlPlaneUrl2.trim().replace(/\/+$/, "")}${REMOTE_MCP_ENDPOINT_PATH}`;
645
+ }
646
+ function buildRemoteMcpEntry(controlPlaneUrl2) {
647
+ return {
648
+ type: "http",
649
+ url: remoteMcpUrl(controlPlaneUrl2),
650
+ headers: { Authorization: REMOTE_MCP_AUTH_HEADER }
651
+ };
652
+ }
653
+ function asRecord(value) {
654
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
655
+ }
656
+ function isManagedRemoteMcpEntry(value) {
657
+ const entry = asRecord(value);
658
+ if (!entry) return false;
659
+ if (entry["type"] !== "http") return false;
660
+ const url = entry["url"];
661
+ if (typeof url !== "string" || !url.endsWith(REMOTE_MCP_ENDPOINT_PATH)) return false;
662
+ const headers = asRecord(entry["headers"]);
663
+ return headers?.["Authorization"] === REMOTE_MCP_AUTH_HEADER;
664
+ }
665
+ function remoteMcpEntryIsCurrent(value, desired) {
666
+ const entry = asRecord(value);
667
+ if (!entry) return false;
668
+ const headers = asRecord(entry["headers"]);
669
+ return entry["type"] === desired.type && entry["url"] === desired.url && headers?.["Authorization"] === desired.headers["Authorization"];
670
+ }
671
+ var REMOTE_MCP_SERVER_KEY, REMOTE_MCP_TOKEN_ENV, REMOTE_MCP_ENDPOINT_PATH, REMOTE_MCP_AUTH_HEADER;
672
+ var init_remote_mcp_entry = __esm({
673
+ "src/remote-mcp-entry.ts"() {
674
+ "use strict";
675
+ REMOTE_MCP_SERVER_KEY = "vo-mcp-remote";
676
+ REMOTE_MCP_TOKEN_ENV = "VO_MCP_REMOTE_TOKEN";
677
+ REMOTE_MCP_ENDPOINT_PATH = "/api/v1/mcp";
678
+ REMOTE_MCP_AUTH_HEADER = `Bearer \${${REMOTE_MCP_TOKEN_ENV}}`;
679
+ }
680
+ });
681
+
639
682
  // src/autostart.ts
640
683
  import { homedir as homedir4, platform as platform2 } from "node:os";
641
684
  import { isAbsolute as isAbsolute2, join as join5 } from "node:path";
@@ -913,7 +956,20 @@ function preferredNodeCommand(existing) {
913
956
  function resolveVoMcpCliPath() {
914
957
  return join6(dirname4(fileURLToPath(import.meta.url)), "cli.js");
915
958
  }
916
- function installMcpConfigAt(configPath, cliPath, controlPlaneUrl2, log2, label, launcher = { launcherPath: null, ...INSTALL_LAUNCHER }) {
959
+ function reconcileRemoteEntry(mcpServers, remote) {
960
+ const existing = mcpServers[REMOTE_MCP_SERVER_KEY];
961
+ if (remote) {
962
+ return {
963
+ fragment: { [REMOTE_MCP_SERVER_KEY]: remote },
964
+ current: remoteMcpEntryIsCurrent(existing, remote)
965
+ };
966
+ }
967
+ if (existing !== void 0 && isManagedRemoteMcpEntry(existing)) {
968
+ return { fragment: { [REMOTE_MCP_SERVER_KEY]: void 0 }, current: false };
969
+ }
970
+ return { fragment: {}, current: true };
971
+ }
972
+ function installMcpConfigAt(configPath, cliPath, controlPlaneUrl2, log2, label, launcher = { launcherPath: null, ...INSTALL_LAUNCHER }, remote = null) {
917
973
  if (launcher.onlyExisting && !existsSync7(configPath)) return;
918
974
  const read = readClaudeConfig(configPath);
919
975
  if (read.kind === "invalid" || read.kind === "empty" && launcher.onlyExisting) {
@@ -927,8 +983,9 @@ function installMcpConfigAt(configPath, cliPath, controlPlaneUrl2, log2, label,
927
983
  const voEntry = managedEntry ?? mcpServers["vo"];
928
984
  const { launcherPath } = launcher;
929
985
  const fallbackCli = chooseFallbackCli(voEntry?.env?.[MCP_FALLBACK_CLI_ENV], cliPath, launcher.sticky, launcherPath ? dirname4(launcherPath) : null);
930
- const current = launcherPath ? !isStaleVoMcpEntry(voEntry, launcherPath, fallbackCli) : Boolean(voEntry?.args?.some((a) => a.includes(cliPath)));
931
- if (current) {
986
+ const localCurrent = launcherPath ? !isStaleVoMcpEntry(voEntry, launcherPath, fallbackCli) : Boolean(voEntry?.args?.some((a) => a.includes(cliPath)));
987
+ const remoteState = reconcileRemoteEntry(mcpServers, remote);
988
+ if (localCurrent && remoteState.current) {
932
989
  log2(` ${label} already current: ${configPath}`);
933
990
  return;
934
991
  }
@@ -948,7 +1005,11 @@ function installMcpConfigAt(configPath, cliPath, controlPlaneUrl2, log2, label,
948
1005
  ...preservedEnv,
949
1006
  ...launcherPath && fallbackCli ? { [MCP_FALLBACK_CLI_ENV]: fallbackCli } : {}
950
1007
  }
951
- }
1008
+ },
1009
+ // Additive remote entry (11.3 slice A) — or the removal of one we wrote,
1010
+ // when the operator has opted back out. Spread LAST so it can delete its
1011
+ // own key; it never names 'vo-mcp', so the stdio entry above is safe.
1012
+ ...remoteState.fragment
952
1013
  }
953
1014
  };
954
1015
  if (readMtime !== null && (!existsSync7(configPath) || statSync3(configPath).mtimeMs !== readMtime)) {
@@ -957,6 +1018,12 @@ function installMcpConfigAt(configPath, cliPath, controlPlaneUrl2, log2, label,
957
1018
  }
958
1019
  writeClaudeConfig(configPath, merged);
959
1020
  log2(`\u2713 Wrote vo-mcp to ${label}: ${configPath}`);
1021
+ if (remote) {
1022
+ log2(` + remote MCP entry '${REMOTE_MCP_SERVER_KEY}' \u2192 ${remote.url} (serves vo_skill_list; every other tool stays on the local vo-mcp entry)`);
1023
+ log2(` Set ${REMOTE_MCP_TOKEN_ENV} in your environment to authenticate it \u2014 no token is written to this file.`);
1024
+ } else if (!remoteState.current) {
1025
+ log2(` - removed the remote MCP entry '${REMOTE_MCP_SERVER_KEY}' (VO_REMOTE_MCP is not set to 1)`);
1026
+ }
960
1027
  }
961
1028
  function installMcpConfig(log2, env2, mode = "install") {
962
1029
  const home = env2["HOME"]?.trim() || env2["USERPROFILE"]?.trim() || homedir5();
@@ -964,6 +1031,7 @@ function installMcpConfig(log2, env2, mode = "install") {
964
1031
  const plat = platform3();
965
1032
  const cliPath = resolveVoMcpCliPath();
966
1033
  const controlPlaneUrl2 = env2["VO_CONTROL_PLANE_URL"]?.trim() || DEFAULT_CONTROL_PLANE_URL2;
1034
+ const remote = remoteMcpOptIn(env2) ? buildRemoteMcpEntry(controlPlaneUrl2) : null;
967
1035
  const launcherPath = writeMcpLauncherForEnv(env2, log2, { platform: plat, home });
968
1036
  const launcher = { launcherPath, ...mode === "heal" ? HEAL_LAUNCHER : INSTALL_LAUNCHER };
969
1037
  const leg = (label, run) => {
@@ -974,8 +1042,8 @@ function installMcpConfig(log2, env2, mode = "install") {
974
1042
  log2(` \u26A0 ${label}: could not update the MCP registration \u2014 ${err instanceof Error ? err.message : String(err)}`);
975
1043
  }
976
1044
  };
977
- leg("Claude Code CLI", () => installMcpConfigAt(resolveCodeConfigPath(home), cliPath, controlPlaneUrl2, log2, "Claude Code CLI", launcher));
978
- leg("Claude Desktop", () => installMcpConfigAt(resolveDesktopConfigPath(home, plat, appData), cliPath, controlPlaneUrl2, log2, "Claude Desktop", launcher));
1045
+ leg("Claude Code CLI", () => installMcpConfigAt(resolveCodeConfigPath(home), cliPath, controlPlaneUrl2, log2, "Claude Code CLI", launcher, remote));
1046
+ leg("Claude Desktop", () => installMcpConfigAt(resolveDesktopConfigPath(home, plat, appData), cliPath, controlPlaneUrl2, log2, "Claude Desktop", launcher, remote));
979
1047
  const codexPath = resolveCodexConfigPath(home);
980
1048
  if (launcher.onlyExisting && !existsSync7(codexPath)) return;
981
1049
  leg("Codex", () => {
@@ -1088,6 +1156,7 @@ var init_install = __esm({
1088
1156
  init_codex_mcp_config();
1089
1157
  init_mcp_launcher();
1090
1158
  init_config_backup();
1159
+ init_remote_mcp_entry();
1091
1160
  init_autostart();
1092
1161
  DEFAULT_CONTROL_PLANE_URL2 = "https://vo-control-plane-bzjphrajaq-uc.a.run.app";
1093
1162
  INSTALL_LAUNCHER = { sticky: false, onlyExisting: false };
@@ -7284,18 +7353,49 @@ function publicationTitlePrompt(task = {}) {
7284
7353
  if (!original) return prompt;
7285
7354
  return original.split(/\n\n(?:Operator follow-up instructions|Treat these as task-scoped)/u)[0].trim() || prompt;
7286
7355
  }
7356
+ async function resolveInPlacePrMetadata(worktreeDir, prNumber, { note, env: env2, runFn } = {}) {
7357
+ try {
7358
+ const raw = await runFn("gh", ["pr", "view", String(prNumber), "--json", "title,body"], worktreeDir, { env: env2 });
7359
+ const parsed = JSON.parse(String(raw || "{}"));
7360
+ const title = String(parsed?.title || "").trim();
7361
+ if (!title) return { editMetadata: false };
7362
+ const kept = String(parsed?.body ?? "").split(IN_PLACE_REPAIR_NOTE_MARKER)[0].trimEnd();
7363
+ const trailer = String(note || "").trim();
7364
+ return {
7365
+ editMetadata: true,
7366
+ title,
7367
+ body: trailer ? `${kept}
7368
+
7369
+ ${IN_PLACE_REPAIR_NOTE_MARKER}
7370
+ ${trailer}` : kept
7371
+ };
7372
+ } catch {
7373
+ return { editMetadata: false };
7374
+ }
7375
+ }
7287
7376
  async function syncExistingPrAsync(worktreeDir, existing, options = {}) {
7288
- const { title, body, draft = false, demoteToDraft = false, env: env2, runFn } = options;
7289
- await editExistingPrMetadata(worktreeDir, existing.number, { title, body, env: env2, runFn });
7290
- const markedReady = !draft && existing.isDraft;
7377
+ const {
7378
+ title,
7379
+ body,
7380
+ draft = false,
7381
+ demoteToDraft = false,
7382
+ editMetadata = true,
7383
+ preserveDraftState = false,
7384
+ env: env2,
7385
+ runFn
7386
+ } = options;
7387
+ if (editMetadata) await editExistingPrMetadata(worktreeDir, existing.number, { title, body, env: env2, runFn });
7388
+ const markedReady = !preserveDraftState && !draft && existing.isDraft;
7291
7389
  if (markedReady) await markExistingPrReady(worktreeDir, existing.number, { env: env2, runFn });
7292
7390
  const demoted = draft && demoteToDraft && existing.isDraft === false;
7293
7391
  if (demoted) await demoteExistingPrToDraft(worktreeDir, existing.number, { env: env2, runFn });
7294
7392
  return { markedReady, demoted };
7295
7393
  }
7394
+ var IN_PLACE_REPAIR_NOTE_MARKER;
7296
7395
  var init_existing_pr_publication = __esm({
7297
7396
  "../../scripts/virtual-office/code-runner/existing-pr-publication.mjs"() {
7298
7397
  "use strict";
7398
+ IN_PLACE_REPAIR_NOTE_MARKER = "<!-- vo-repair-in-place -->";
7299
7399
  }
7300
7400
  });
7301
7401
 
@@ -7932,194 +8032,43 @@ var init_process_runner2 = __esm({
7932
8032
  }
7933
8033
  });
7934
8034
 
7935
- // ../../scripts/virtual-office/code-runner/partial-pr-continuation.mjs
7936
- function partialPrContinuationResult(run = {}, maxLength = 2e3, reason = null) {
7937
- const summary = String(run.summary || "agent stopped before completing the task").trim();
7938
- const reasonMarker = reason === "rate_limited" ? `
7939
- ${RATE_LIMITED_CONTINUATION_MARKER}` : "";
7940
- return `${PARTIAL_PR_CONTINUATION_MARKER}${reasonMarker}
7941
- ${summary}`.slice(0, Math.max(0, maxLength));
7942
- }
7943
- var PARTIAL_PR_CONTINUATION_MARKER, RATE_LIMITED_CONTINUATION_MARKER;
7944
- var init_partial_pr_continuation = __esm({
7945
- "../../scripts/virtual-office/code-runner/partial-pr-continuation.mjs"() {
7946
- "use strict";
7947
- PARTIAL_PR_CONTINUATION_MARKER = "ALGOSUITE_TASK_OUTCOME: NEEDS_CONTINUATION";
7948
- RATE_LIMITED_CONTINUATION_MARKER = "ALGOSUITE_CONTINUATION_REASON: RATE_LIMITED";
7949
- }
7950
- });
7951
-
7952
- // ../../scripts/virtual-office/code-runner/publish-async.mjs
7953
- function compactTitle(value, max = 100) {
7954
- return String(value || "").replace(/\s+/g, " ").trim().slice(0, max) || "code-task";
7955
- }
7956
- function gitRetryLog(op) {
7957
- return ({ attempt, delayMs, err }) => {
7958
- const why = String(err && err.message || err).replace(/\s+/g, " ").slice(0, 120);
7959
- console.error(`[publish] transient ${op} failure (attempt ${attempt}): ${why} \u2014 retrying in ${Math.round(delayMs / 1e3)}s`);
7960
- };
7961
- }
7962
- async function retryTransientAsync(fn, { attempts = 3, baseMs = 5e3, capMs = 3e4, rng = secureUnitRandom, onRetry } = {}) {
7963
- let lastErr;
7964
- for (let i = 0; i < attempts; i += 1) {
7965
- try {
7966
- return await fn(i);
7967
- } catch (err) {
7968
- lastErr = err;
7969
- if (i >= attempts - 1 || !isTransientGitError(err)) throw err;
7970
- const delayMs = computeGitBackoffMs(i, { baseMs, capMs, rng });
7971
- if (typeof onRetry === "function") onRetry({ err, attempt: i + 1, delayMs });
7972
- await sleep(delayMs);
7973
- }
7974
- }
7975
- throw lastErr;
7976
- }
7977
- function defaultRunCommand(cmd, args, cwd, opts = {}) {
7978
- return runProcess2(cmd, args, { cwd, ...opts });
7979
- }
7980
- async function resolveOrCreateBranchAsync(worktreeDir, branchPrefix, runCommand = defaultRunCommand) {
7981
- let branch = "";
7982
- try {
7983
- branch = await runCommand("git", ["branch", "--show-current"], worktreeDir, { timeout: 3e4 });
7984
- } catch {
7985
- }
7986
- if (!branch || branch === "main" || branch === "HEAD") {
7987
- const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
7988
- branch = `${branchPrefix}-${stamp}`;
7989
- await runCommand("git", ["checkout", "-b", branch], worktreeDir);
7990
- }
7991
- return branch;
7992
- }
7993
- async function runLocalPrOverlapGateAsync(worktreeDir, files, { branch = "", env: env2 = process.env, excludePrNumber = null, log: log2 = (m) => console.warn(`[pr-overlap-gate] ${m}`) } = {}) {
7994
- const { scriptPath, trusted } = resolveOverlapScript({ worktreeDir });
7995
- const childEnv = trusted ? env2 : stripCredentials(env2);
7996
- if (!trusted) {
7997
- log2(`WARNING: trusted overlap script not found; running worktree copy ${scriptPath} with credentials stripped.`);
7998
- }
7999
- try {
8000
- const output = await runProcess2("node", [
8001
- scriptPath,
8002
- "--stdin",
8003
- ...branch ? ["--branch", String(branch)] : [],
8004
- ...excludePrNumber ? ["--exclude-pr", String(excludePrNumber)] : []
8005
- ], {
8006
- cwd: worktreeDir,
8007
- env: childEnv,
8008
- input: JSON.stringify([...new Set((files || []).map((file) => String(file || "").trim()).filter(Boolean))]),
8009
- timeout: 12e4
8010
- });
8011
- return { ok: true, output };
8012
- } catch (err) {
8013
- return {
8014
- ok: false,
8015
- status: err.status ?? 1,
8016
- output: `${err.stdout || ""}${err.stderr || ""}`.trim() || String(err.message || err)
8017
- };
8018
- }
8019
- }
8020
- async function maybeArmAutoMergeAsync({
8021
- worktreeDir,
8022
- prNumber,
8023
- githubToken,
8024
- armAutoMerge,
8025
- draft,
8026
- runCommand = defaultRunCommand
8027
- }) {
8028
- if (!armAutoMerge || draft) return { autoMergeArmed: false };
8029
- try {
8030
- await runCommand("gh", autoMergeArgs(prNumber), worktreeDir, {
8031
- env: githubToken ? installationTokenEnv(githubToken) : void 0,
8032
- timeout: 6e4
8033
- });
8034
- return { autoMergeArmed: true };
8035
- } catch (err) {
8036
- return {
8037
- autoMergeArmed: false,
8038
- autoMergeError: err && err.message ? err.message : String(err)
8039
- };
8040
- }
8041
- }
8042
- async function listChangedFilesAsync(cwd, { runCommand = defaultRunCommand } = {}) {
8043
- const out = await runCommand("git", ["-c", "core.quotepath=false", "status", "--porcelain", "-z"], cwd, {
8044
- timeout: 6e4,
8045
- raw: true
8046
- });
8047
- return parsePorcelainZ(out);
8048
- }
8049
- async function listCommittedFilesAsync(cwd, base = "origin/main", { runCommand = defaultRunCommand } = {}) {
8050
- try {
8051
- await runCommand("git", ["fetch", "origin", "main"], cwd, { timeout: 6e4 });
8052
- } catch {
8053
- }
8054
- try {
8055
- const out = await runCommand(
8056
- "git",
8057
- ["-c", "core.quotepath=false", "diff", "--name-only", "-z", `${base}...HEAD`],
8058
- cwd,
8059
- { timeout: 6e4, raw: true }
8060
- );
8061
- return String(out).split("\0").map((item) => item.trim()).filter(Boolean);
8062
- } catch {
8063
- return [];
8064
- }
8065
- }
8066
- async function commitWorkLocallyAsync(worktreeDir, files, {
8067
- title,
8068
- branchPrefix = "vo/code-task",
8069
- botName = "vo-code-runner",
8070
- botEmail = "vo-code-runner@algosuite.ai",
8071
- runCommand = defaultRunCommand
8072
- } = {}) {
8073
- const cleaned = (files || []).filter((file) => !isAgentScratch(file));
8074
- if (cleaned.length === 0) throw new Error("commitWorkLocallyAsync: only scratch files, nothing to commit");
8075
- await runCommand("git", ["config", "user.name", botName], worktreeDir);
8076
- await runCommand("git", ["config", "user.email", botEmail], worktreeDir);
8077
- const branch = await resolveOrCreateBranchAsync(worktreeDir, branchPrefix, runCommand);
8078
- for (let i = 0; i < cleaned.length; i += 100) {
8079
- await runCommand("git", ["add", "--", ...cleaned.slice(i, i + 100)], worktreeDir, { timeout: 12e4 });
8080
- }
8081
- if (await nothingStagedAsync(worktreeDir, runCommand)) return { branch, truncated: false, committed: false };
8082
- await runCommand("git", ["commit", "-m", compactTitle(title, 180)], worktreeDir);
8083
- return { branch, truncated: false, committed: true };
8084
- }
8085
- async function existingPrUrlAsync(worktreeDir, branch, githubToken = null, { runCommand = defaultRunCommand } = {}) {
8086
- try {
8087
- const out = await runCommand(
8088
- "gh",
8089
- ["pr", "list", "--head", branch, "--state", "open", "--json", "url,number,isDraft", "--limit", "1"],
8090
- worktreeDir,
8091
- { env: githubToken ? installationTokenEnv(githubToken) : void 0 }
8092
- );
8093
- const list = JSON.parse(out || "[]");
8094
- if (Array.isArray(list) && list[0] && list[0].url) {
8095
- return { url: String(list[0].url), number: Number(list[0].number), isDraft: Boolean(list[0].isDraft) };
8096
- }
8097
- } catch {
8098
- }
8099
- return null;
8100
- }
8101
- async function closeSupersededPrAsync(worktreeDir, prNumber, replacementUrl, githubToken = null, { runCommand = defaultRunCommand } = {}) {
8102
- if (!Number.isInteger(prNumber) || prNumber <= 0) return false;
8103
- if (!/^https:\/\/github\.com\/[^/]+\/[^/]+\/pull\/\d+$/u.test(String(replacementUrl || ""))) return false;
8035
+ // ../../scripts/virtual-office/code-runner/superseded-pr-cleanup.mjs
8036
+ async function closeSupersededPrAsync(worktreeDir, prNumber, replacementUrl, githubToken = null, { runCommand = defaultRunCommand, expectedHeadSha = null } = {}) {
8037
+ if (!Number.isInteger(prNumber) || prNumber <= 0) return { closed: false, reason: "invalid_pr_number" };
8038
+ if (!REPLACEMENT_URL_RE.test(String(replacementUrl || ""))) return { closed: false, reason: "invalid_replacement_url" };
8104
8039
  const env2 = githubToken ? installationTokenEnv(githubToken) : void 0;
8105
- const raw = await runCommand("gh", ["pr", "view", String(prNumber), "--json", "state"], worktreeDir, { env: env2 });
8106
- const state = JSON.parse(raw || "{}")?.state;
8107
- if (state !== "OPEN") return false;
8040
+ const raw = await runCommand("gh", ["pr", "view", String(prNumber), "--json", "state,headRefOid"], worktreeDir, { env: env2 });
8041
+ const view = JSON.parse(raw || "{}");
8042
+ if (view?.state !== "OPEN") return { closed: false, reason: "not_open" };
8043
+ const liveHead = String(view?.headRefOid || "").trim().toLowerCase();
8044
+ const derivedFrom = String(expectedHeadSha || "").trim().toLowerCase();
8045
+ if (derivedFrom && liveHead && liveHead !== derivedFrom) {
8046
+ return { closed: false, reason: "head_moved" };
8047
+ }
8048
+ const replacementNumber = String(replacementUrl).match(REPLACEMENT_URL_RE)[1];
8108
8049
  await runCommand(
8109
8050
  "gh",
8110
- ["pr", "close", String(prNumber), "--comment", `Superseded by ${replacementUrl}, rebuilt from current main by AlgoHQ repair.`],
8051
+ [
8052
+ "pr",
8053
+ "close",
8054
+ String(prNumber),
8055
+ "--comment",
8056
+ `Superseded by ${replacementUrl} (PR #${replacementNumber}), rebuilt from current main by AlgoHQ repair. Closed at publish time so this lineage keeps exactly one open PR.`
8057
+ ],
8111
8058
  worktreeDir,
8112
8059
  { env: env2, timeout: 6e4 }
8113
8060
  );
8114
- return true;
8061
+ return { closed: true, reason: null };
8115
8062
  }
8116
8063
  async function cleanupSupersededPrAsync({
8117
8064
  worktreeDir,
8118
8065
  supersedesPrNumber,
8066
+ supersedesHeadSha = null,
8119
8067
  replacementUrl,
8120
8068
  replacementNumber,
8121
8069
  githubToken,
8122
8070
  runCommand,
8071
+ retry = (fn) => fn(),
8123
8072
  onCleanupWarning
8124
8073
  }) {
8125
8074
  if (!supersedesPrNumber) return {};
@@ -8127,11 +8076,16 @@ async function cleanupSupersededPrAsync({
8127
8076
  return { supersededPrClosed: false };
8128
8077
  }
8129
8078
  try {
8130
- const closed = await retryTransientAsync(
8131
- () => closeSupersededPrAsync(worktreeDir, supersedesPrNumber, replacementUrl, githubToken, { runCommand }),
8132
- { onRetry: gitRetryLog("gh pr close superseded") }
8079
+ const result = await retry(
8080
+ () => closeSupersededPrAsync(worktreeDir, supersedesPrNumber, replacementUrl, githubToken, {
8081
+ runCommand,
8082
+ expectedHeadSha: supersedesHeadSha
8083
+ })
8133
8084
  );
8134
- return { supersededPrClosed: closed };
8085
+ return {
8086
+ supersededPrClosed: result.closed,
8087
+ ...result.closed ? {} : { supersededPrCloseSkipped: result.reason }
8088
+ };
8135
8089
  } catch (err) {
8136
8090
  const message = err && err.message ? err.message : String(err);
8137
8091
  onCleanupWarning?.(
@@ -8140,56 +8094,651 @@ async function cleanupSupersededPrAsync({
8140
8094
  return { supersededPrClosed: false, supersededPrCloseError: message };
8141
8095
  }
8142
8096
  }
8143
- async function pushBranchAsync(worktreeDir, branch, githubToken, { runCommand = defaultRunCommand, allowAmbientFallback = false, remoteBranch = branch } = {}) {
8144
- const pushRef = remoteBranch && remoteBranch !== branch ? `HEAD:refs/heads/${remoteBranch}` : branch;
8145
- const { primary, fallback } = pushPlan(pushRef, githubToken, { allowAmbientFallback });
8146
- try {
8147
- await runCommand("git", primary.args, worktreeDir, { env: primary.env });
8148
- return primary.tokenUsed;
8149
- } catch (err) {
8150
- if (!fallback) throw err;
8151
- await runCommand("git", fallback.args, worktreeDir, { env: fallback.env });
8152
- return fallback.tokenUsed;
8097
+ var defaultRunCommand, REPLACEMENT_URL_RE;
8098
+ var init_superseded_pr_cleanup = __esm({
8099
+ "../../scripts/virtual-office/code-runner/superseded-pr-cleanup.mjs"() {
8100
+ "use strict";
8101
+ init_publish();
8102
+ init_process_runner2();
8103
+ defaultRunCommand = (cmd, args, cwd, opts = {}) => runProcess2(cmd, args, { cwd, ...opts });
8104
+ REPLACEMENT_URL_RE = /^https:\/\/github\.com\/[^/]+\/[^/]+\/pull\/(\d+)$/u;
8153
8105
  }
8106
+ });
8107
+
8108
+ // ../../scripts/virtual-office/code-runner/github-git-auth-env.mjs
8109
+ function githubGitAuthEnv2(githubToken) {
8110
+ if (!githubToken) return void 0;
8111
+ const credentials = Buffer.from(`x-access-token:${githubToken}`).toString("base64");
8112
+ return {
8113
+ GIT_CONFIG_COUNT: "1",
8114
+ GIT_CONFIG_KEY_0: "http.https://github.com/.extraheader",
8115
+ GIT_CONFIG_VALUE_0: `AUTHORIZATION: basic ${credentials}`
8116
+ };
8154
8117
  }
8155
- async function openCodeTaskPrAsync(worktreeDir, files, {
8156
- title,
8157
- body,
8158
- branchPrefix = "vo/code-task",
8159
- botName = "vo-code-runner",
8160
- botEmail = "vo-code-runner@algosuite.ai",
8161
- alreadyCommitted = false,
8162
- githubToken = null,
8163
- allowAmbientGithubFallback = false,
8164
- draft = false,
8165
- armAutoMerge = false,
8166
- targetBranch = null,
8167
- targetPrNumber = null,
8168
- supersedesPrNumber = null,
8169
- deferSupersededPrCleanup = false,
8170
- onCleanupWarning = console.error,
8171
- runCommand = defaultRunCommand,
8172
- runOverlapGate = runLocalPrOverlapGateAsync
8173
- } = {}) {
8174
- if (!Array.isArray(files) || files.length === 0) throw new Error("openCodeTaskPrAsync: no files to commit");
8175
- let branch;
8176
- let truncated = false;
8177
- let newCommit = null;
8178
- if (alreadyCommitted) {
8179
- branch = await resolveOrCreateBranchAsync(worktreeDir, branchPrefix, runCommand);
8180
- } else {
8181
- const committed = await commitWorkLocallyAsync(worktreeDir, files, {
8182
- title,
8183
- branchPrefix,
8184
- botName,
8185
- botEmail,
8186
- runCommand
8187
- });
8188
- branch = committed.branch;
8189
- truncated = committed.truncated;
8190
- newCommit = committed.committed;
8118
+ var init_github_git_auth_env = __esm({
8119
+ "../../scripts/virtual-office/code-runner/github-git-auth-env.mjs"() {
8120
+ "use strict";
8191
8121
  }
8192
- const prBranch = String(targetBranch || branch).trim() || branch;
8122
+ });
8123
+
8124
+ // ../../scripts/virtual-office/code-runner/resume-branch.mjs
8125
+ function compactBranchSegment(value) {
8126
+ return String(value || "").trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48) || "resume";
8127
+ }
8128
+ function defaultRunCommand2(cmd, args, cwd, opts = {}) {
8129
+ return runProcess2(cmd, args, { cwd, ...opts });
8130
+ }
8131
+ function buildResumeLocalBranchName(remoteBranch, {
8132
+ now = () => /* @__PURE__ */ new Date(),
8133
+ pid = process.pid,
8134
+ random = secureUnitRandom
8135
+ } = {}) {
8136
+ const stamp = now().toISOString().replace(/[:.]/g, "-");
8137
+ const unique = `${pid}-${random().toString(36).slice(2, 8)}`;
8138
+ return `vo/resume-${compactBranchSegment(remoteBranch)}-${stamp}-${unique}`;
8139
+ }
8140
+ async function assertBranchName(worktreeDir, branch, { env: env2, runCommand = defaultRunCommand2 } = {}) {
8141
+ const candidate = String(branch || "").trim();
8142
+ if (!candidate) throw new Error("resume branch is required");
8143
+ await runCommand("git", ["check-ref-format", "--branch", candidate], worktreeDir, {
8144
+ env: env2,
8145
+ timeout: 3e4
8146
+ });
8147
+ return candidate;
8148
+ }
8149
+ async function restoreContinuationBranch(worktreeDir, remoteBranch, {
8150
+ env: env2,
8151
+ runCommand = defaultRunCommand2,
8152
+ localBranchName
8153
+ } = {}) {
8154
+ const branch = await assertBranchName(worktreeDir, remoteBranch, { env: env2, runCommand });
8155
+ const localBranch = await assertBranchName(
8156
+ worktreeDir,
8157
+ localBranchName || buildResumeLocalBranchName(branch),
8158
+ { env: env2, runCommand }
8159
+ );
8160
+ await runCommand(
8161
+ "git",
8162
+ ["fetch", "origin", `${branch}:refs/remotes/origin/${branch}`],
8163
+ worktreeDir,
8164
+ { env: env2, timeout: 12e4 }
8165
+ );
8166
+ await runCommand("git", ["checkout", "-b", localBranch, `origin/${branch}`], worktreeDir, {
8167
+ env: env2,
8168
+ timeout: 6e4
8169
+ });
8170
+ return { localBranch, remoteBranch: branch };
8171
+ }
8172
+ function parseHeadRefName(output) {
8173
+ const parsed = JSON.parse(String(output || "{}"));
8174
+ const branch = String(parsed?.headRefName || "").trim();
8175
+ return branch || null;
8176
+ }
8177
+ async function resolvePrHeadBranch(worktreeDir, {
8178
+ repo,
8179
+ prNumber,
8180
+ githubToken = null,
8181
+ allowAmbientGithubFallback = false,
8182
+ runCommand = defaultRunCommand2
8183
+ } = {}) {
8184
+ if (!repo || !Number.isInteger(prNumber) || prNumber <= 0) return null;
8185
+ const args = ["pr", "view", String(prNumber), "-R", String(repo), "--json", "headRefName"];
8186
+ try {
8187
+ const output = await runCommand("gh", args, worktreeDir, {
8188
+ env: githubToken ? installationTokenEnv(githubToken) : void 0,
8189
+ timeout: 6e4
8190
+ });
8191
+ return parseHeadRefName(output);
8192
+ } catch (error) {
8193
+ if (!githubToken || !allowAmbientGithubFallback) throw error;
8194
+ }
8195
+ const fallback = await runCommand("gh", args, worktreeDir, { timeout: 6e4 });
8196
+ return parseHeadRefName(fallback);
8197
+ }
8198
+ async function prepareContinuationBranch(worktreeDir, {
8199
+ task,
8200
+ parentTask,
8201
+ githubToken = null,
8202
+ allowAmbientGithubFallback = false,
8203
+ runCommand = defaultRunCommand2
8204
+ } = {}) {
8205
+ const continuationBranch = task?.pr_branch || parentTask?.pr_branch || (parentTask?.pr_number ? await resolvePrHeadBranch(worktreeDir, {
8206
+ repo: task?.repo,
8207
+ prNumber: parentTask.pr_number,
8208
+ githubToken,
8209
+ allowAmbientGithubFallback,
8210
+ runCommand
8211
+ }) : null);
8212
+ if (!continuationBranch && (task?.pr_branch || parentTask?.pr_url || parentTask?.pr_number)) {
8213
+ throw new Error(`continuation PR branch could not be resolved for task ${task?.code_task_id || "unknown"}`);
8214
+ }
8215
+ if (!continuationBranch) return null;
8216
+ return restoreContinuationBranch(worktreeDir, continuationBranch, {
8217
+ env: githubToken ? installationTokenEnv(githubToken) : void 0,
8218
+ runCommand
8219
+ });
8220
+ }
8221
+ var init_resume_branch = __esm({
8222
+ "../../scripts/virtual-office/code-runner/resume-branch.mjs"() {
8223
+ "use strict";
8224
+ init_publish();
8225
+ init_process_runner2();
8226
+ init_secure_random();
8227
+ }
8228
+ });
8229
+
8230
+ // ../../scripts/virtual-office/code-runner/repair-source-materialization.mjs
8231
+ function assertRepairSource({ repo, prNumber, headSha }) {
8232
+ if (!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/u.test(String(repo || ""))) {
8233
+ throw new Error("repair source repository is invalid");
8234
+ }
8235
+ if (!Number.isInteger(prNumber) || prNumber < 1) {
8236
+ throw new Error("repair source PR number is invalid");
8237
+ }
8238
+ if (!/^[0-9a-f]{40}$/u.test(String(headSha || "").toLowerCase())) {
8239
+ throw new Error("repair source head SHA is missing or invalid");
8240
+ }
8241
+ }
8242
+ function defaultRun(command, args, cwd, options = {}) {
8243
+ const env2 = options.env ? { ...process.env, ...options.env } : process.env;
8244
+ return runProcess2(command, args, { cwd, ...options, env: env2 });
8245
+ }
8246
+ async function materializeRepairSource(worktreeDir, {
8247
+ repo,
8248
+ prNumber,
8249
+ headSha,
8250
+ githubToken = null,
8251
+ runCommand = defaultRun
8252
+ } = {}) {
8253
+ assertRepairSource({ repo, prNumber, headSha });
8254
+ const expectedHead = headSha.toLowerCase();
8255
+ const sourceRef = `refs/vo/repairs/${expectedHead}`;
8256
+ const env2 = githubToken ? githubGitAuthEnv2(githubToken) : void 0;
8257
+ await runCommand(
8258
+ "git",
8259
+ ["fetch", "origin", `+refs/pull/${prNumber}/head:${sourceRef}`],
8260
+ worktreeDir,
8261
+ { env: env2, timeout: 12e4 }
8262
+ );
8263
+ const fetchedHead = String(await runCommand(
8264
+ "git",
8265
+ ["rev-parse", sourceRef],
8266
+ worktreeDir,
8267
+ { env: env2, timeout: 3e4 }
8268
+ )).trim().toLowerCase();
8269
+ if (fetchedHead !== expectedHead) {
8270
+ throw new Error(`repair source head changed: admitted ${expectedHead}, fetched ${fetchedHead || "none"}`);
8271
+ }
8272
+ let conflicted = false;
8273
+ try {
8274
+ await runCommand(
8275
+ "git",
8276
+ ["merge", "--squash", "--no-commit", sourceRef],
8277
+ worktreeDir,
8278
+ { env: env2, timeout: 12e4 }
8279
+ );
8280
+ } catch (error) {
8281
+ const conflicts = String(await runCommand(
8282
+ "git",
8283
+ ["diff", "--name-only", "--diff-filter=U"],
8284
+ worktreeDir,
8285
+ { env: env2, timeout: 3e4 }
8286
+ )).trim();
8287
+ if (!conflicts) throw error;
8288
+ conflicted = true;
8289
+ }
8290
+ const status = String(await runCommand(
8291
+ "git",
8292
+ ["status", "--porcelain"],
8293
+ worktreeDir,
8294
+ { env: env2, timeout: 3e4 }
8295
+ )).trim();
8296
+ if (!status) {
8297
+ throw new Error(`repair source ${repo}#${prNumber}@${expectedHead} produced no changes on current main`);
8298
+ }
8299
+ return { sourceRef, headSha: expectedHead, conflicted };
8300
+ }
8301
+ var init_repair_source_materialization = __esm({
8302
+ "../../scripts/virtual-office/code-runner/repair-source-materialization.mjs"() {
8303
+ "use strict";
8304
+ init_process_runner2();
8305
+ init_github_git_auth_env();
8306
+ }
8307
+ });
8308
+
8309
+ // ../../scripts/virtual-office/code-runner/repair-publication-strategy.mjs
8310
+ function isPushRejection(error) {
8311
+ if (!error) return false;
8312
+ const text = `${error.message || ""} ${error.stderr || ""} ${error.stdout || ""}`;
8313
+ return PUSH_REJECTION_RE.test(text);
8314
+ }
8315
+ function defaultRun2(command, args, cwd, options = {}) {
8316
+ return runProcess2(command, args, { cwd, ...options });
8317
+ }
8318
+ function decideRepairStrategy({
8319
+ headBranch,
8320
+ prState,
8321
+ isCrossRepository = false,
8322
+ admittedHeadSha = null,
8323
+ liveHeadSha = null,
8324
+ branchFetched = true,
8325
+ behindCount = null,
8326
+ maxBehindCommits = DEFAULT_MAX_BEHIND_COMMITS,
8327
+ inPlaceEnabled = true
8328
+ } = {}) {
8329
+ const supersede = (reason) => ({ strategy: "supersede", reason });
8330
+ if (!inPlaceEnabled) return supersede(SUPERSEDE_REASONS.disabled);
8331
+ if (!String(headBranch || "").trim()) return supersede(SUPERSEDE_REASONS.branchUnresolved);
8332
+ if (prState && String(prState).toUpperCase() !== "OPEN") return supersede(SUPERSEDE_REASONS.predecessorNotOpen);
8333
+ if (isCrossRepository) return supersede(SUPERSEDE_REASONS.crossForkHead);
8334
+ if (admittedHeadSha && liveHeadSha && String(liveHeadSha).toLowerCase() !== String(admittedHeadSha).toLowerCase()) {
8335
+ return supersede(SUPERSEDE_REASONS.headMoved);
8336
+ }
8337
+ if (branchFetched === false) return supersede(SUPERSEDE_REASONS.branchDeleted);
8338
+ if (Number.isInteger(behindCount) && Number.isInteger(maxBehindCommits) && behindCount > maxBehindCommits) {
8339
+ return supersede(SUPERSEDE_REASONS.baseDiverged);
8340
+ }
8341
+ return { strategy: "in-place", reason: null };
8342
+ }
8343
+ async function viewRepairPr(worktreeDir, { repo, prNumber, env: env2, runCommand }) {
8344
+ try {
8345
+ const raw = await runCommand("gh", [
8346
+ "pr",
8347
+ "view",
8348
+ String(prNumber),
8349
+ "-R",
8350
+ String(repo),
8351
+ "--json",
8352
+ "headRefName,headRefOid,isCrossRepository,state"
8353
+ ], worktreeDir, { env: env2, timeout: 6e4 });
8354
+ const parsed = JSON.parse(String(raw || "{}"));
8355
+ return {
8356
+ headBranch: String(parsed.headRefName || "").trim(),
8357
+ liveHeadSha: String(parsed.headRefOid || "").trim().toLowerCase() || null,
8358
+ isCrossRepository: Boolean(parsed.isCrossRepository),
8359
+ prState: String(parsed.state || "").trim() || null
8360
+ };
8361
+ } catch {
8362
+ return { headBranch: "", liveHeadSha: null, isCrossRepository: false, prState: null };
8363
+ }
8364
+ }
8365
+ async function probeRepairBranch(worktreeDir, { headBranch, env: env2, runCommand }) {
8366
+ try {
8367
+ await runCommand("git", ["check-ref-format", "--branch", headBranch], worktreeDir, { env: env2, timeout: 3e4 });
8368
+ await runCommand(
8369
+ "git",
8370
+ ["fetch", "origin", `+refs/heads/${headBranch}:refs/remotes/origin/${headBranch}`],
8371
+ worktreeDir,
8372
+ { env: env2, timeout: 12e4 }
8373
+ );
8374
+ } catch {
8375
+ return { branchFetched: false, fetchedHeadSha: null, behindCount: null };
8376
+ }
8377
+ const fetchedHeadSha = String(await runCommand(
8378
+ "git",
8379
+ ["rev-parse", `refs/remotes/origin/${headBranch}`],
8380
+ worktreeDir,
8381
+ { env: env2, timeout: 3e4 }
8382
+ ) || "").trim().toLowerCase() || null;
8383
+ let behindCount = null;
8384
+ try {
8385
+ const raw = await runCommand(
8386
+ "git",
8387
+ ["rev-list", "--count", `refs/remotes/origin/${headBranch}..origin/main`],
8388
+ worktreeDir,
8389
+ { env: env2, timeout: 6e4 }
8390
+ );
8391
+ const parsed = Number.parseInt(String(raw || "").trim(), 10);
8392
+ if (Number.isInteger(parsed)) behindCount = parsed;
8393
+ } catch {
8394
+ }
8395
+ return { branchFetched: true, fetchedHeadSha, behindCount };
8396
+ }
8397
+ async function planRepairPublication(worktreeDir, {
8398
+ repo,
8399
+ prNumber,
8400
+ headSha,
8401
+ githubToken = null,
8402
+ env: env2 = process.env,
8403
+ maxBehindCommits = DEFAULT_MAX_BEHIND_COMMITS,
8404
+ runCommand = defaultRun2,
8405
+ materializeSource = materializeRepairSource,
8406
+ log: log2 = () => {
8407
+ }
8408
+ } = {}) {
8409
+ if (!Number.isInteger(prNumber) || prNumber <= 0) {
8410
+ throw new Error("structured repair PR target is invalid; refusing publication");
8411
+ }
8412
+ const ghEnv = githubToken ? installationTokenEnv(githubToken) : void 0;
8413
+ const gitEnv = githubToken ? githubGitAuthEnv2(githubToken) : void 0;
8414
+ const admittedHeadSha = String(headSha || "").trim().toLowerCase() || null;
8415
+ const inPlaceEnabled = String(env2?.[REPAIR_IN_PLACE_ENV] ?? "1") !== "0";
8416
+ const view = await viewRepairPr(worktreeDir, { repo, prNumber, env: ghEnv, runCommand });
8417
+ let decision = decideRepairStrategy({ ...view, admittedHeadSha, inPlaceEnabled, maxBehindCommits });
8418
+ let probe = { branchFetched: true, fetchedHeadSha: null, behindCount: null };
8419
+ if (decision.strategy === "in-place") {
8420
+ probe = await probeRepairBranch(worktreeDir, { headBranch: view.headBranch, env: gitEnv, runCommand });
8421
+ decision = decideRepairStrategy({
8422
+ ...view,
8423
+ ...probe,
8424
+ admittedHeadSha,
8425
+ inPlaceEnabled,
8426
+ maxBehindCommits,
8427
+ // The fetched tip is the authoritative head; `gh` can lag a force-push.
8428
+ liveHeadSha: probe.fetchedHeadSha || view.liveHeadSha
8429
+ });
8430
+ }
8431
+ if (decision.strategy === "supersede") {
8432
+ log2(`repair PR #${prNumber}: in-place impossible (${decision.reason}) \u2014 superseding from current main`);
8433
+ const source = await materializeSource(worktreeDir, { repo, prNumber, headSha, githubToken, runCommand });
8434
+ return { strategy: "supersede", reason: decision.reason, prNumber, headSha: source.headSha, conflicted: source.conflicted };
8435
+ }
8436
+ const localBranch = buildResumeLocalBranchName(view.headBranch);
8437
+ await runCommand("git", ["check-ref-format", "--branch", localBranch], worktreeDir, { env: gitEnv, timeout: 3e4 });
8438
+ await runCommand(
8439
+ "git",
8440
+ ["checkout", "-b", localBranch, `refs/remotes/origin/${view.headBranch}`],
8441
+ worktreeDir,
8442
+ { env: gitEnv, timeout: 6e4 }
8443
+ );
8444
+ log2(`repair PR #${prNumber}: repairing IN PLACE on its own branch ${view.headBranch} (local ${localBranch})`);
8445
+ return {
8446
+ strategy: "in-place",
8447
+ reason: null,
8448
+ prNumber,
8449
+ remoteBranch: view.headBranch,
8450
+ localBranch,
8451
+ headSha: probe.fetchedHeadSha || admittedHeadSha
8452
+ };
8453
+ }
8454
+ async function pushWithInPlaceFallback({
8455
+ worktreeDir,
8456
+ branch,
8457
+ remoteBranch,
8458
+ githubToken,
8459
+ allowAmbientFallback = false,
8460
+ supersedeFallback = false,
8461
+ push,
8462
+ retry,
8463
+ onFallback = () => {
8464
+ }
8465
+ }) {
8466
+ const target = String(remoteBranch || branch);
8467
+ try {
8468
+ const tokenUsed = await retry(() => push(worktreeDir, branch, githubToken, { allowAmbientFallback, remoteBranch: target }));
8469
+ return { tokenUsed, remoteBranch: target, fallbackReason: null };
8470
+ } catch (error) {
8471
+ if (!supersedeFallback || target === branch || !isPushRejection(error)) throw error;
8472
+ onFallback(error);
8473
+ const tokenUsed = await retry(() => push(worktreeDir, branch, githubToken, { allowAmbientFallback, remoteBranch: branch }));
8474
+ return { tokenUsed, remoteBranch: branch, fallbackReason: SUPERSEDE_REASONS.pushRejected };
8475
+ }
8476
+ }
8477
+ var REPAIR_IN_PLACE_ENV, DEFAULT_MAX_BEHIND_COMMITS, SUPERSEDE_REASONS, PUSH_REJECTION_RE;
8478
+ var init_repair_publication_strategy = __esm({
8479
+ "../../scripts/virtual-office/code-runner/repair-publication-strategy.mjs"() {
8480
+ "use strict";
8481
+ init_process_runner2();
8482
+ init_publish();
8483
+ init_github_git_auth_env();
8484
+ init_resume_branch();
8485
+ init_repair_source_materialization();
8486
+ REPAIR_IN_PLACE_ENV = "VO_CODE_RUNNER_REPAIR_IN_PLACE";
8487
+ DEFAULT_MAX_BEHIND_COMMITS = 20;
8488
+ SUPERSEDE_REASONS = Object.freeze({
8489
+ branchUnresolved: "branch_unresolved",
8490
+ predecessorNotOpen: "predecessor_not_open",
8491
+ crossForkHead: "cross_fork_head",
8492
+ headMoved: "head_moved",
8493
+ branchDeleted: "branch_deleted",
8494
+ baseDiverged: "base_diverged",
8495
+ pushRejected: "push_rejected",
8496
+ disabled: "in_place_disabled"
8497
+ });
8498
+ PUSH_REJECTION_RE = new RegExp([
8499
+ "\\[rejected\\]",
8500
+ "non-fast-forward",
8501
+ "fetch first",
8502
+ "updates were rejected",
8503
+ "protected branch",
8504
+ "pre-receive hook declined",
8505
+ "remote rejected",
8506
+ "permission to [^\\s]+ denied",
8507
+ "write access to repository not granted",
8508
+ "refusing to allow",
8509
+ "the requested url returned error: 40[13]"
8510
+ ].join("|"), "iu");
8511
+ }
8512
+ });
8513
+
8514
+ // ../../scripts/virtual-office/code-runner/partial-pr-continuation.mjs
8515
+ function partialPrContinuationResult(run = {}, maxLength = 2e3, reason = null) {
8516
+ const summary = String(run.summary || "agent stopped before completing the task").trim();
8517
+ const reasonMarker = reason === "rate_limited" ? `
8518
+ ${RATE_LIMITED_CONTINUATION_MARKER}` : "";
8519
+ return `${PARTIAL_PR_CONTINUATION_MARKER}${reasonMarker}
8520
+ ${summary}`.slice(0, Math.max(0, maxLength));
8521
+ }
8522
+ var PARTIAL_PR_CONTINUATION_MARKER, RATE_LIMITED_CONTINUATION_MARKER;
8523
+ var init_partial_pr_continuation = __esm({
8524
+ "../../scripts/virtual-office/code-runner/partial-pr-continuation.mjs"() {
8525
+ "use strict";
8526
+ PARTIAL_PR_CONTINUATION_MARKER = "ALGOSUITE_TASK_OUTCOME: NEEDS_CONTINUATION";
8527
+ RATE_LIMITED_CONTINUATION_MARKER = "ALGOSUITE_CONTINUATION_REASON: RATE_LIMITED";
8528
+ }
8529
+ });
8530
+
8531
+ // ../../scripts/virtual-office/code-runner/publish-async.mjs
8532
+ function compactTitle(value, max = 100) {
8533
+ return String(value || "").replace(/\s+/g, " ").trim().slice(0, max) || "code-task";
8534
+ }
8535
+ function gitRetryLog(op) {
8536
+ return ({ attempt, delayMs, err }) => {
8537
+ const why = String(err && err.message || err).replace(/\s+/g, " ").slice(0, 120);
8538
+ console.error(`[publish] transient ${op} failure (attempt ${attempt}): ${why} \u2014 retrying in ${Math.round(delayMs / 1e3)}s`);
8539
+ };
8540
+ }
8541
+ async function retryTransientAsync(fn, { attempts = 3, baseMs = 5e3, capMs = 3e4, rng = secureUnitRandom, onRetry } = {}) {
8542
+ let lastErr;
8543
+ for (let i = 0; i < attempts; i += 1) {
8544
+ try {
8545
+ return await fn(i);
8546
+ } catch (err) {
8547
+ lastErr = err;
8548
+ if (i >= attempts - 1 || !isTransientGitError(err)) throw err;
8549
+ const delayMs = computeGitBackoffMs(i, { baseMs, capMs, rng });
8550
+ if (typeof onRetry === "function") onRetry({ err, attempt: i + 1, delayMs });
8551
+ await sleep(delayMs);
8552
+ }
8553
+ }
8554
+ throw lastErr;
8555
+ }
8556
+ function defaultRunCommand3(cmd, args, cwd, opts = {}) {
8557
+ return runProcess2(cmd, args, { cwd, ...opts });
8558
+ }
8559
+ async function resolveOrCreateBranchAsync(worktreeDir, branchPrefix, runCommand = defaultRunCommand3) {
8560
+ let branch = "";
8561
+ try {
8562
+ branch = await runCommand("git", ["branch", "--show-current"], worktreeDir, { timeout: 3e4 });
8563
+ } catch {
8564
+ }
8565
+ if (!branch || branch === "main" || branch === "HEAD") {
8566
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
8567
+ branch = `${branchPrefix}-${stamp}`;
8568
+ await runCommand("git", ["checkout", "-b", branch], worktreeDir);
8569
+ }
8570
+ return branch;
8571
+ }
8572
+ async function runLocalPrOverlapGateAsync(worktreeDir, files, { branch = "", env: env2 = process.env, excludePrNumber = null, log: log2 = (m) => console.warn(`[pr-overlap-gate] ${m}`) } = {}) {
8573
+ const { scriptPath, trusted } = resolveOverlapScript({ worktreeDir });
8574
+ const childEnv = trusted ? env2 : stripCredentials(env2);
8575
+ if (!trusted) {
8576
+ log2(`WARNING: trusted overlap script not found; running worktree copy ${scriptPath} with credentials stripped.`);
8577
+ }
8578
+ try {
8579
+ const output = await runProcess2("node", [
8580
+ scriptPath,
8581
+ "--stdin",
8582
+ ...branch ? ["--branch", String(branch)] : [],
8583
+ ...excludePrNumber ? ["--exclude-pr", String(excludePrNumber)] : []
8584
+ ], {
8585
+ cwd: worktreeDir,
8586
+ env: childEnv,
8587
+ input: JSON.stringify([...new Set((files || []).map((file) => String(file || "").trim()).filter(Boolean))]),
8588
+ timeout: 12e4
8589
+ });
8590
+ return { ok: true, output };
8591
+ } catch (err) {
8592
+ return {
8593
+ ok: false,
8594
+ status: err.status ?? 1,
8595
+ output: `${err.stdout || ""}${err.stderr || ""}`.trim() || String(err.message || err)
8596
+ };
8597
+ }
8598
+ }
8599
+ async function maybeArmAutoMergeAsync({
8600
+ worktreeDir,
8601
+ prNumber,
8602
+ githubToken,
8603
+ armAutoMerge,
8604
+ draft,
8605
+ runCommand = defaultRunCommand3
8606
+ }) {
8607
+ if (!armAutoMerge || draft) return { autoMergeArmed: false };
8608
+ try {
8609
+ await runCommand("gh", autoMergeArgs(prNumber), worktreeDir, {
8610
+ env: githubToken ? installationTokenEnv(githubToken) : void 0,
8611
+ timeout: 6e4
8612
+ });
8613
+ return { autoMergeArmed: true };
8614
+ } catch (err) {
8615
+ return {
8616
+ autoMergeArmed: false,
8617
+ autoMergeError: err && err.message ? err.message : String(err)
8618
+ };
8619
+ }
8620
+ }
8621
+ async function listChangedFilesAsync(cwd, { runCommand = defaultRunCommand3 } = {}) {
8622
+ const out = await runCommand("git", ["-c", "core.quotepath=false", "status", "--porcelain", "-z"], cwd, {
8623
+ timeout: 6e4,
8624
+ raw: true
8625
+ });
8626
+ return parsePorcelainZ(out);
8627
+ }
8628
+ async function listCommittedFilesAsync(cwd, base = "origin/main", { runCommand = defaultRunCommand3 } = {}) {
8629
+ try {
8630
+ await runCommand("git", ["fetch", "origin", "main"], cwd, { timeout: 6e4 });
8631
+ } catch {
8632
+ }
8633
+ try {
8634
+ const out = await runCommand(
8635
+ "git",
8636
+ ["-c", "core.quotepath=false", "diff", "--name-only", "-z", `${base}...HEAD`],
8637
+ cwd,
8638
+ { timeout: 6e4, raw: true }
8639
+ );
8640
+ return String(out).split("\0").map((item) => item.trim()).filter(Boolean);
8641
+ } catch {
8642
+ return [];
8643
+ }
8644
+ }
8645
+ async function commitWorkLocallyAsync(worktreeDir, files, {
8646
+ title,
8647
+ branchPrefix = "vo/code-task",
8648
+ botName = "vo-code-runner",
8649
+ botEmail = "vo-code-runner@algosuite.ai",
8650
+ runCommand = defaultRunCommand3
8651
+ } = {}) {
8652
+ const cleaned = (files || []).filter((file) => !isAgentScratch(file));
8653
+ if (cleaned.length === 0) throw new Error("commitWorkLocallyAsync: only scratch files, nothing to commit");
8654
+ await runCommand("git", ["config", "user.name", botName], worktreeDir);
8655
+ await runCommand("git", ["config", "user.email", botEmail], worktreeDir);
8656
+ const branch = await resolveOrCreateBranchAsync(worktreeDir, branchPrefix, runCommand);
8657
+ for (let i = 0; i < cleaned.length; i += 100) {
8658
+ await runCommand("git", ["add", "--", ...cleaned.slice(i, i + 100)], worktreeDir, { timeout: 12e4 });
8659
+ }
8660
+ if (await nothingStagedAsync(worktreeDir, runCommand)) return { branch, truncated: false, committed: false };
8661
+ await runCommand("git", ["commit", "-m", compactTitle(title, 180)], worktreeDir);
8662
+ return { branch, truncated: false, committed: true };
8663
+ }
8664
+ async function existingPrUrlAsync(worktreeDir, branch, githubToken = null, { runCommand = defaultRunCommand3 } = {}) {
8665
+ try {
8666
+ const out = await runCommand(
8667
+ "gh",
8668
+ ["pr", "list", "--head", branch, "--state", "open", "--json", "url,number,isDraft", "--limit", "1"],
8669
+ worktreeDir,
8670
+ { env: githubToken ? installationTokenEnv(githubToken) : void 0 }
8671
+ );
8672
+ const list = JSON.parse(out || "[]");
8673
+ if (Array.isArray(list) && list[0] && list[0].url) {
8674
+ return { url: String(list[0].url), number: Number(list[0].number), isDraft: Boolean(list[0].isDraft) };
8675
+ }
8676
+ } catch {
8677
+ }
8678
+ return null;
8679
+ }
8680
+ function cleanupSuperseded(options) {
8681
+ return cleanupSupersededPrAsync({
8682
+ ...options,
8683
+ retry: (fn) => retryTransientAsync(fn, { onRetry: gitRetryLog("gh pr close superseded") })
8684
+ });
8685
+ }
8686
+ async function pushBranchAsync(worktreeDir, branch, githubToken, { runCommand = defaultRunCommand3, allowAmbientFallback = false, remoteBranch = branch } = {}) {
8687
+ const pushRef = remoteBranch && remoteBranch !== branch ? `HEAD:refs/heads/${remoteBranch}` : branch;
8688
+ const { primary, fallback } = pushPlan(pushRef, githubToken, { allowAmbientFallback });
8689
+ try {
8690
+ await runCommand("git", primary.args, worktreeDir, { env: primary.env });
8691
+ return primary.tokenUsed;
8692
+ } catch (err) {
8693
+ if (!fallback) throw err;
8694
+ await runCommand("git", fallback.args, worktreeDir, { env: fallback.env });
8695
+ return fallback.tokenUsed;
8696
+ }
8697
+ }
8698
+ async function openCodeTaskPrAsync(worktreeDir, files, {
8699
+ title,
8700
+ body,
8701
+ branchPrefix = "vo/code-task",
8702
+ botName = "vo-code-runner",
8703
+ botEmail = "vo-code-runner@algosuite.ai",
8704
+ alreadyCommitted = false,
8705
+ githubToken = null,
8706
+ allowAmbientGithubFallback = false,
8707
+ draft = false,
8708
+ armAutoMerge = false,
8709
+ targetBranch = null,
8710
+ targetPrNumber = null,
8711
+ supersedesPrNumber = null,
8712
+ supersedesHeadSha = null,
8713
+ deferSupersededPrCleanup = false,
8714
+ preserveExistingPr = false,
8715
+ inPlaceNote = "",
8716
+ supersedeFallback = false,
8717
+ onCleanupWarning = console.error,
8718
+ runCommand = defaultRunCommand3,
8719
+ runOverlapGate = runLocalPrOverlapGateAsync
8720
+ } = {}) {
8721
+ if (!Array.isArray(files) || files.length === 0) throw new Error("openCodeTaskPrAsync: no files to commit");
8722
+ let branch;
8723
+ let truncated = false;
8724
+ let newCommit = null;
8725
+ if (alreadyCommitted) {
8726
+ branch = await resolveOrCreateBranchAsync(worktreeDir, branchPrefix, runCommand);
8727
+ } else {
8728
+ const committed = await commitWorkLocallyAsync(worktreeDir, files, {
8729
+ title,
8730
+ branchPrefix,
8731
+ botName,
8732
+ botEmail,
8733
+ runCommand
8734
+ });
8735
+ branch = committed.branch;
8736
+ truncated = committed.truncated;
8737
+ newCommit = committed.committed;
8738
+ }
8739
+ let prBranch = String(targetBranch || branch).trim() || branch;
8740
+ let inPlacePrNumber = targetPrNumber;
8741
+ let inPlace = preserveExistingPr;
8193
8742
  const overlap = await runOverlapGate(worktreeDir, files.filter((file) => !isAgentScratch(file)), {
8194
8743
  branch: prBranch,
8195
8744
  env: githubToken ? installationTokenEnv(githubToken) : process.env,
@@ -8198,26 +8747,47 @@ async function openCodeTaskPrAsync(worktreeDir, files, {
8198
8747
  const policy = applyOverlapPublishPolicy({ overlap, draft, body });
8199
8748
  ({ draft, body } = policy);
8200
8749
  const { overlapDraft, overlapBlockedBy } = policy;
8201
- const tokenUsed = await retryTransientAsync(
8202
- () => pushBranchAsync(worktreeDir, branch, githubToken, {
8203
- runCommand,
8204
- allowAmbientFallback: allowAmbientGithubFallback,
8205
- remoteBranch: prBranch
8206
- }),
8207
- { onRetry: gitRetryLog("git push") }
8208
- );
8750
+ const pushed = await pushWithInPlaceFallback({
8751
+ worktreeDir,
8752
+ branch,
8753
+ remoteBranch: prBranch,
8754
+ githubToken,
8755
+ allowAmbientFallback: allowAmbientGithubFallback,
8756
+ supersedeFallback,
8757
+ push: (dir, localBranch, token2, options) => pushBranchAsync(dir, localBranch, token2, { ...options, runCommand }),
8758
+ retry: (fn) => retryTransientAsync(fn, { onRetry: gitRetryLog("git push") }),
8759
+ onFallback: (err) => onCleanupWarning?.(
8760
+ `[publish] in-place push to ${prBranch} was refused (${String(err?.message || err).replace(/\s+/g, " ").slice(0, 160)}); superseding on ${branch} instead`
8761
+ )
8762
+ });
8763
+ const tokenUsed = pushed.tokenUsed;
8764
+ if (pushed.fallbackReason) {
8765
+ prBranch = pushed.remoteBranch;
8766
+ inPlacePrNumber = null;
8767
+ inPlace = false;
8768
+ }
8209
8769
  const authToken = tokenUsed ? githubToken : null;
8210
8770
  const existing = await existingPrUrlAsync(worktreeDir, prBranch, authToken, { runCommand });
8211
- if (targetPrNumber && existing?.number !== targetPrNumber) {
8212
- throw new Error(`explicit target PR #${targetPrNumber} was not found on ${prBranch}; refusing duplicate publication`);
8771
+ if (inPlacePrNumber && existing?.number !== inPlacePrNumber) {
8772
+ throw new Error(`explicit target PR #${inPlacePrNumber} was not found on ${prBranch}; refusing duplicate publication`);
8213
8773
  }
8214
8774
  if (existing) {
8775
+ const env2 = authToken ? installationTokenEnv(authToken) : void 0;
8776
+ const meta = inPlace ? await resolveInPlacePrMetadata(worktreeDir, existing.number, {
8777
+ note: overlapDraft ? `${body}` : inPlaceNote,
8778
+ env: env2,
8779
+ runFn: runCommand
8780
+ }) : { editMetadata: true, title: compactTitle(title), body };
8215
8781
  const { markedReady } = await retryTransientAsync(() => syncExistingPrAsync(worktreeDir, existing, {
8216
- title: compactTitle(title),
8217
- body,
8782
+ title: meta.title ?? compactTitle(title),
8783
+ body: meta.body ?? body,
8218
8784
  draft,
8219
8785
  demoteToDraft: overlapDraft,
8220
- env: authToken ? installationTokenEnv(authToken) : void 0,
8786
+ editMetadata: meta.editMetadata !== false,
8787
+ // Promoting a draft would remove the only merge guard it carries, and the
8788
+ // repair task did not author that readiness decision. Demotion still runs.
8789
+ preserveDraftState: inPlace,
8790
+ env: env2,
8221
8791
  runFn: runCommand
8222
8792
  }), { onRetry: gitRetryLog("gh pr sync") });
8223
8793
  const autoMerge2 = await maybeArmAutoMergeAsync({
@@ -8228,9 +8798,10 @@ async function openCodeTaskPrAsync(worktreeDir, files, {
8228
8798
  draft,
8229
8799
  runCommand
8230
8800
  });
8231
- const superseded2 = deferSupersededPrCleanup ? {} : await cleanupSupersededPrAsync({
8801
+ const superseded2 = deferSupersededPrCleanup ? {} : await cleanupSuperseded({
8232
8802
  worktreeDir,
8233
8803
  supersedesPrNumber,
8804
+ supersedesHeadSha,
8234
8805
  replacementUrl: existing.url,
8235
8806
  replacementNumber: existing.number,
8236
8807
  githubToken: authToken,
@@ -8245,6 +8816,7 @@ async function openCodeTaskPrAsync(worktreeDir, files, {
8245
8816
  newCommit,
8246
8817
  resumed: true,
8247
8818
  markedReady,
8819
+ repairedInPlace: inPlace,
8248
8820
  overlapDraft,
8249
8821
  overlapBlockedBy,
8250
8822
  ...autoMerge2,
@@ -8272,16 +8844,28 @@ async function openCodeTaskPrAsync(worktreeDir, files, {
8272
8844
  draft,
8273
8845
  runCommand
8274
8846
  });
8275
- const superseded = deferSupersededPrCleanup ? {} : await cleanupSupersededPrAsync({
8847
+ const superseded = deferSupersededPrCleanup ? {} : await cleanupSuperseded({
8276
8848
  worktreeDir,
8277
8849
  supersedesPrNumber,
8850
+ supersedesHeadSha,
8278
8851
  replacementUrl: prUrl,
8279
8852
  replacementNumber: prNumber,
8280
8853
  githubToken: authToken,
8281
8854
  runCommand,
8282
8855
  onCleanupWarning
8283
8856
  });
8284
- return { prUrl, prNumber, branch: prBranch, truncated, newCommit, overlapDraft, overlapBlockedBy, ...autoMerge, ...superseded };
8857
+ return {
8858
+ prUrl,
8859
+ prNumber,
8860
+ branch: prBranch,
8861
+ truncated,
8862
+ newCommit,
8863
+ overlapDraft,
8864
+ overlapBlockedBy,
8865
+ ...pushed.fallbackReason ? { supersedeFallbackReason: pushed.fallbackReason } : {},
8866
+ ...autoMerge,
8867
+ ...superseded
8868
+ };
8285
8869
  }
8286
8870
  var sleep;
8287
8871
  var init_publish_async = __esm({
@@ -8295,7 +8879,10 @@ var init_publish_async = __esm({
8295
8879
  init_secure_random();
8296
8880
  init_pr_overlap_gate();
8297
8881
  init_existing_pr_publication();
8882
+ init_superseded_pr_cleanup();
8883
+ init_repair_publication_strategy();
8298
8884
  init_partial_pr_continuation();
8885
+ init_superseded_pr_cleanup();
8299
8886
  sleep = (ms) => new Promise((resolve3) => setTimeout(resolve3, ms));
8300
8887
  }
8301
8888
  });
@@ -8350,15 +8937,25 @@ function parseFrontmatterNameDescription(raw) {
8350
8937
  }
8351
8938
  return name && description ? { name, description } : null;
8352
8939
  }
8940
+ function isRepoCheckout(dir) {
8941
+ try {
8942
+ if (!statSync4(join10(dir, ".claude", "skills")).isDirectory()) return false;
8943
+ } catch {
8944
+ return false;
8945
+ }
8946
+ try {
8947
+ statSync4(join10(dir, ".git"));
8948
+ return true;
8949
+ } catch {
8950
+ return false;
8951
+ }
8952
+ }
8353
8953
  function resolveDefaultRepoRoot() {
8354
8954
  const starts = [dirname7(fileURLToPath4(import.meta.url)), process.cwd()];
8355
8955
  for (const start of starts) {
8356
8956
  let dir = start;
8357
8957
  for (let i = 0; i < 8; i += 1) {
8358
- try {
8359
- if (statSync4(join10(dir, ".claude", "skills")).isDirectory()) return dir;
8360
- } catch {
8361
- }
8958
+ if (isRepoCheckout(dir)) return dir;
8362
8959
  const parent = dirname7(dir);
8363
8960
  if (parent === dir) break;
8364
8961
  dir = parent;
@@ -8719,11 +9316,12 @@ function methodologyLedgerFields(methodology) {
8719
9316
  return { ...shape ? { methodology_shape: shape } : {}, ...stakes ? { governed_stakes: stakes } : {} };
8720
9317
  }
8721
9318
  async function composeCodeTaskPrompt(client, task, { log: log2 = () => {
8722
- }, allowMissingKnowledgeContext = false, attachmentManifestMarkdown = "", onMethodology } = {}) {
9319
+ }, allowMissingKnowledgeContext = false, attachmentManifestMarkdown = "", onMethodology, repoRoot: repoRoot2 } = {}) {
8723
9320
  const taskId = task?.code_task_id;
8724
9321
  if (!taskId) {
8725
9322
  return composeDispatchPrompt(withComposedMethodology(withAttachmentManifest(task?.prompt, attachmentManifestMarkdown), task, log2, taskId, onMethodology), {
8726
9323
  repo: task?.repo,
9324
+ repoRoot: repoRoot2,
8727
9325
  knowledgeContextMarkdown: handleMissingKnowledgeContext(taskId, "missing code_task_id on the claimed task", {
8728
9326
  allowMissingKnowledgeContext,
8729
9327
  log: log2
@@ -8733,6 +9331,7 @@ async function composeCodeTaskPrompt(client, task, { log: log2 = () => {
8733
9331
  if (typeof client?.getTaskKnowledgeContext !== "function") {
8734
9332
  return composeDispatchPrompt(withComposedMethodology(withAttachmentManifest(task?.prompt, attachmentManifestMarkdown), task, log2, taskId, onMethodology), {
8735
9333
  repo: task?.repo,
9334
+ repoRoot: repoRoot2,
8736
9335
  knowledgeContextMarkdown: handleMissingKnowledgeContext(taskId, "control-plane client cannot fetch knowledge context", {
8737
9336
  allowMissingKnowledgeContext,
8738
9337
  log: log2
@@ -8762,6 +9361,7 @@ async function composeCodeTaskPrompt(client, task, { log: log2 = () => {
8762
9361
  ${operatorInstructions}` : task?.prompt;
8763
9362
  return composeDispatchPrompt(withComposedMethodology(withAttachmentManifest(prompt, attachmentManifestMarkdown), task, log2, taskId, onMethodology), {
8764
9363
  repo: task?.repo,
9364
+ repoRoot: repoRoot2,
8765
9365
  knowledgeContextMarkdown
8766
9366
  });
8767
9367
  }
@@ -10021,6 +10621,167 @@ var init_local_model_remote_config = __esm({
10021
10621
  }
10022
10622
  });
10023
10623
 
10624
+ // ../../scripts/virtual-office/code-runner/prepared-job-divergence.mjs
10625
+ function context7LocalOnlyEnvPresent(env2 = {}) {
10626
+ return CONTEXT7_LOCAL_ONLY_ENV_KEYS.some((k) => String(env2?.[k] ?? "").trim().length > 0);
10627
+ }
10628
+ function argvDivergesOnlyOnModel(runnerArgv, planeArgv, { runnerModel, planeModel } = {}) {
10629
+ if (!Array.isArray(runnerArgv) || !Array.isArray(planeArgv)) return false;
10630
+ if (runnerArgv.length !== planeArgv.length) return false;
10631
+ if (runnerModel === void 0 || runnerModel === null) return false;
10632
+ if (planeModel === void 0 || planeModel === null) return false;
10633
+ let modelValueDiffs = 0;
10634
+ for (let i = 0; i < runnerArgv.length; i += 1) {
10635
+ if (runnerArgv[i] === planeArgv[i]) continue;
10636
+ if (i === 0) return false;
10637
+ if (runnerArgv[i - 1] !== MODEL_FLAG || planeArgv[i - 1] !== MODEL_FLAG) return false;
10638
+ if (runnerArgv[i] !== String(runnerModel) || planeArgv[i] !== String(planeModel)) return false;
10639
+ modelValueDiffs += 1;
10640
+ }
10641
+ return modelValueDiffs > 0;
10642
+ }
10643
+ function splitPromptSections(prompt) {
10644
+ const text = String(prompt ?? "");
10645
+ const hits = [];
10646
+ let cursor = 0;
10647
+ for (const [name, marker] of PROMPT_SECTION_MARKERS) {
10648
+ const at = text.indexOf(marker, cursor);
10649
+ if (at === -1) continue;
10650
+ hits.push({ name, at });
10651
+ cursor = at + marker.length;
10652
+ }
10653
+ const sections = { preamble: text.slice(0, hits.length ? hits[0].at : text.length) };
10654
+ for (let i = 0; i < hits.length; i += 1) {
10655
+ sections[hits[i].name] = text.slice(hits[i].at, i + 1 < hits.length ? hits[i + 1].at : text.length);
10656
+ }
10657
+ return sections;
10658
+ }
10659
+ function promptSectionDivergence(runnerPrompt, planePrompt) {
10660
+ const a = splitPromptSections(runnerPrompt);
10661
+ const b = splitPromptSections(planePrompt);
10662
+ const names = PROMPT_SECTION_ORDER.filter((n) => n in a || n in b);
10663
+ if (names.some((n) => !(n in a) || !(n in b))) return null;
10664
+ return names.filter((n) => a[n] !== b[n]);
10665
+ }
10666
+ function promptConfinedTo(sections, section) {
10667
+ return Array.isArray(sections) && sections.length === 1 && sections[0] === section;
10668
+ }
10669
+ function context7Config(raw) {
10670
+ let parsed;
10671
+ try {
10672
+ parsed = JSON.parse(String(raw));
10673
+ } catch {
10674
+ return null;
10675
+ }
10676
+ const server = parsed?.mcpServers?.context7;
10677
+ return server && typeof server === "object" && !Array.isArray(server) ? parsed : null;
10678
+ }
10679
+ function mcpConfigDiffersOnlyOnHostLocalContext7(runnerRaw, planeRaw) {
10680
+ const runner = context7Config(runnerRaw);
10681
+ const plane = context7Config(planeRaw);
10682
+ if (!runner || !plane) return false;
10683
+ if (plane.mcpServers.context7.headers !== void 0) return false;
10684
+ const strip = (cfg) => {
10685
+ const copy = JSON.parse(JSON.stringify(cfg));
10686
+ for (const field of CONTEXT7_HOST_LOCAL_FIELDS) delete copy.mcpServers.context7[field];
10687
+ return JSON.stringify(copy);
10688
+ };
10689
+ return strip(runner) === strip(plane);
10690
+ }
10691
+ function argvDivergesOnlyOnLocalMcpConfig(runnerArgv, planeArgv) {
10692
+ if (!Array.isArray(runnerArgv) || !Array.isArray(planeArgv)) return false;
10693
+ if (runnerArgv.length !== planeArgv.length) return false;
10694
+ let diffs = 0;
10695
+ for (let i = 0; i < runnerArgv.length; i += 1) {
10696
+ if (runnerArgv[i] === planeArgv[i]) continue;
10697
+ if (i === 0) return false;
10698
+ if (runnerArgv[i - 1] !== MCP_CONFIG_FLAG || planeArgv[i - 1] !== MCP_CONFIG_FLAG) return false;
10699
+ if (!mcpConfigDiffersOnlyOnHostLocalContext7(runnerArgv[i], planeArgv[i])) return false;
10700
+ diffs += 1;
10701
+ }
10702
+ return diffs > 0;
10703
+ }
10704
+ function safeEndpoint(url) {
10705
+ try {
10706
+ const u = new URL(String(url));
10707
+ return `${u.protocol}//${u.host}${u.pathname}`;
10708
+ } catch {
10709
+ return REDACTED;
10710
+ }
10711
+ }
10712
+ function redactArgvForRecord(argv) {
10713
+ if (!Array.isArray(argv)) return argv ?? null;
10714
+ return argv.map((value, i) => {
10715
+ if (i === 0 || argv[i - 1] !== MCP_CONFIG_FLAG) return value;
10716
+ let parsed;
10717
+ try {
10718
+ parsed = JSON.parse(String(value));
10719
+ } catch {
10720
+ return REDACTED;
10721
+ }
10722
+ for (const server of Object.values(parsed?.mcpServers ?? {})) {
10723
+ if (!server || typeof server !== "object") continue;
10724
+ if (typeof server.url === "string") server.url = safeEndpoint(server.url);
10725
+ if (server.headers && typeof server.headers === "object") {
10726
+ for (const key of Object.keys(server.headers)) server.headers[key] = REDACTED;
10727
+ }
10728
+ }
10729
+ return JSON.stringify(parsed);
10730
+ });
10731
+ }
10732
+ function classifyDivergence(field, ctx = {}) {
10733
+ const known = (reason) => ({ class: "known_remainder", reason });
10734
+ if (Array.isArray(ctx.envDropped) && ctx.envDropped.length > 0) {
10735
+ return known(`env_value_too_long:${ctx.envDropped.join("+")}`);
10736
+ }
10737
+ if ((field === "model" || field === "tier") && ctx.planeModelRegistryUnavailable) {
10738
+ return known("no_plane_model_registry");
10739
+ }
10740
+ if (field === "argv" && ctx.planeModelRegistryUnavailable && ctx.argvDivergesOnlyOnModel) {
10741
+ return known("downstream_of_model_remainder");
10742
+ }
10743
+ if (field === "argv" && ctx.context7LocalOnlyEnvPresent && ctx.argvDivergesOnlyOnLocalMcpConfig) {
10744
+ return known("host_local_context7_mcp_config");
10745
+ }
10746
+ if (field === "dispatch_mode" && ctx.taskDispatchModeAbsent) return known("plane_dispatch_mode_default");
10747
+ if (ctx.dispatchModeDiverged && DISPATCH_MODE_DEPENDENT.has(field)) {
10748
+ return known("downstream_of_dispatch_mode");
10749
+ }
10750
+ if (field === "prompt" && ctx.attachmentManifestPresent && promptConfinedTo(ctx.promptSectionsDiverged, "task")) {
10751
+ return known("attachment_manifest_not_sent");
10752
+ }
10753
+ if (field === "prompt" && ctx.planeSkillCatalogUnavailable && (ctx.promptSectionsDiverged === null || promptConfinedTo(ctx.promptSectionsDiverged, "skill_catalog"))) {
10754
+ return known("skill_catalog_unavailable");
10755
+ }
10756
+ return { class: "unexplained", reason: null };
10757
+ }
10758
+ var DISPATCH_MODE_DEPENDENT, MODEL_FLAG, MCP_CONFIG_FLAG, CONTEXT7_HOST_LOCAL_FIELDS, CONTEXT7_LOCAL_ONLY_ENV_KEYS, PROMPT_SECTION_MARKERS, PROMPT_SECTION_ORDER, REDACTED;
10759
+ var init_prepared_job_divergence = __esm({
10760
+ "../../scripts/virtual-office/code-runner/prepared-job-divergence.mjs"() {
10761
+ "use strict";
10762
+ DISPATCH_MODE_DEPENDENT = /* @__PURE__ */ new Set([
10763
+ "prompt",
10764
+ "argv",
10765
+ "permission_mode",
10766
+ "tier",
10767
+ "model",
10768
+ "effort",
10769
+ "max_turns"
10770
+ ]);
10771
+ MODEL_FLAG = "--model";
10772
+ MCP_CONFIG_FLAG = "--mcp-config";
10773
+ CONTEXT7_HOST_LOCAL_FIELDS = ["url", "headers"];
10774
+ CONTEXT7_LOCAL_ONLY_ENV_KEYS = ["CONTEXT7_API_KEY", "VO_CONTEXT7_URL"];
10775
+ PROMPT_SECTION_MARKERS = [
10776
+ ["skill_catalog", "\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 ALGOSUITE SKILL CATALOG \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"],
10777
+ ["knowledge_context", "\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 ALGOHQ KNOWLEDGE CONTEXT \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"],
10778
+ ["task", "\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 YOUR TASK \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"]
10779
+ ];
10780
+ PROMPT_SECTION_ORDER = ["preamble", "skill_catalog", "knowledge_context", "task"];
10781
+ REDACTED = "[redacted]";
10782
+ }
10783
+ });
10784
+
10024
10785
  // ../../scripts/virtual-office/code-runner/prepared-job-shadow.mjs
10025
10786
  import { appendFileSync, mkdirSync as mkdirSync8 } from "node:fs";
10026
10787
  import { createHash as createHash6 } from "node:crypto";
@@ -10085,33 +10846,29 @@ function planeJobAsComparable(job = {}) {
10085
10846
  methodology_stakes: job.methodology?.stakes ?? null
10086
10847
  };
10087
10848
  }
10088
- function classifyDivergence(field, ctx = {}) {
10089
- const known = (reason) => ({ class: "known_remainder", reason });
10090
- if (Array.isArray(ctx.envDropped) && ctx.envDropped.length > 0) {
10091
- return known(`env_value_too_long:${ctx.envDropped.join("+")}`);
10092
- }
10093
- if ((field === "model" || field === "tier") && ctx.planeModelRegistryUnavailable) {
10094
- return known("no_plane_model_registry");
10095
- }
10096
- if (field === "dispatch_mode" && ctx.taskDispatchModeAbsent) return known("plane_dispatch_mode_default");
10097
- if (ctx.dispatchModeDiverged && DISPATCH_MODE_DEPENDENT.has(field)) {
10098
- return known("downstream_of_dispatch_mode");
10099
- }
10100
- if (field === "prompt" && ctx.attachmentManifestPresent) return known("attachment_manifest_not_sent");
10101
- if (field === "prompt" && ctx.planeSkillCatalogUnavailable) return known("skill_catalog_unavailable");
10102
- return { class: "unexplained", reason: null };
10103
- }
10104
10849
  function comparePreparedJob({ local, plane, context = {} }) {
10105
10850
  const L = local || {};
10106
10851
  const P = plane || {};
10107
10852
  const raw = COMPARED_FIELDS.filter((f) => !sameValue(L[f], P[f]));
10108
- const ctx = { ...context, dispatchModeDiverged: raw.includes("dispatch_mode") };
10853
+ const ctx = {
10854
+ ...context,
10855
+ dispatchModeDiverged: raw.includes("dispatch_mode"),
10856
+ // ADR-004 § 11.1c: computed from the argv arrays themselves, never asserted
10857
+ // by the caller — the classifier may only explain argv when the bytes say so.
10858
+ argvDivergesOnlyOnModel: argvDivergesOnlyOnModel(L.argv, P.argv, {
10859
+ runnerModel: L.model,
10860
+ planeModel: P.model
10861
+ }),
10862
+ argvDivergesOnlyOnLocalMcpConfig: argvDivergesOnlyOnLocalMcpConfig(L.argv, P.argv),
10863
+ // Section NAMES, never text; null when the banners differ (fail-closed).
10864
+ promptSectionsDiverged: promptSectionDivergence(L.prompt, P.prompt)
10865
+ };
10109
10866
  const divergences = raw.map((field) => {
10110
10867
  const { class: cls, reason } = classifyDivergence(field, ctx);
10111
10868
  const entry = { field, class: cls, reason };
10112
10869
  if (field !== "prompt") {
10113
- entry.runner = L[field] ?? null;
10114
- entry.plane = P[field] ?? null;
10870
+ entry.runner = (field === "argv" ? redactArgvForRecord(L.argv) : L[field]) ?? null;
10871
+ entry.plane = (field === "argv" ? redactArgvForRecord(P.argv) : P[field]) ?? null;
10115
10872
  }
10116
10873
  return entry;
10117
10874
  });
@@ -10126,7 +10883,9 @@ function comparePreparedJob({ local, plane, context = {} }) {
10126
10883
  plane_len: String(P.prompt ?? "").length,
10127
10884
  runner_sha256: sha256(L.prompt),
10128
10885
  plane_sha256: sha256(P.prompt),
10129
- first_diff_index: firstDivergenceIndex(L.prompt, P.prompt)
10886
+ first_diff_index: firstDivergenceIndex(L.prompt, P.prompt),
10887
+ // null ⇒ the two prompts do not carry the same banners (not attributable).
10888
+ divergent_sections: ctx.promptSectionsDiverged
10130
10889
  }
10131
10890
  };
10132
10891
  }
@@ -10194,6 +10953,7 @@ function formatShadowLogLine(record) {
10194
10953
  if (known.length) parts.push(`known=${known.map((d) => `${d.field}(${d.reason})`).join(",")}`);
10195
10954
  if (record.verdict === "parity_diverged" && typeof record.prompt?.first_diff_index === "number" && record.prompt.first_diff_index >= 0 && record.unexplained_fields?.includes("prompt")) {
10196
10955
  parts.push(`prompt_first_diff_index=${record.prompt.first_diff_index}`);
10956
+ parts.push(`prompt_sections=${record.prompt.divergent_sections?.join(",") || "not_attributable"}`);
10197
10957
  }
10198
10958
  const loud = record.unexplained_fields?.length ? "!! " : "";
10199
10959
  return `${loud}[prepared-job-shadow] ${parts.join(" ")}`;
@@ -10242,6 +11002,7 @@ async function preparedJobShadowPass({
10242
11002
  planeSkillCatalogUnavailable: fetched.composition?.skill_catalog_available === false,
10243
11003
  taskDispatchModeAbsent: !task?.dispatch_mode,
10244
11004
  attachmentManifestPresent: Boolean(local?.attachmentManifestMarkdown),
11005
+ context7LocalOnlyEnvPresent: context7LocalOnlyEnvPresent(env2),
10245
11006
  envDropped: fetched.envDropped
10246
11007
  }
10247
11008
  }) : null;
@@ -10286,11 +11047,13 @@ async function runPreparedJobShadow(options = {}) {
10286
11047
  }
10287
11048
  return result;
10288
11049
  }
10289
- var PREPARED_JOB_MODES, PREPARED_JOB_MODE_ENV, PREPARED_JOB_SHADOW_TIMEOUT_ENV, DEFAULT_SHADOW_TIMEOUT_MS, PREPARED_JOB_SHADOW_SINK, remotePreparedJobMode, COMPARED_FIELDS, DISPATCH_MODE_DEPENDENT, sha256, sameValue, verdictObserver;
11050
+ var PREPARED_JOB_MODES, PREPARED_JOB_MODE_ENV, PREPARED_JOB_SHADOW_TIMEOUT_ENV, DEFAULT_SHADOW_TIMEOUT_MS, PREPARED_JOB_SHADOW_SINK, remotePreparedJobMode, COMPARED_FIELDS, sha256, sameValue, verdictObserver;
10290
11051
  var init_prepared_job_shadow = __esm({
10291
11052
  "../../scripts/virtual-office/code-runner/prepared-job-shadow.mjs"() {
10292
11053
  "use strict";
10293
11054
  init_claude_args();
11055
+ init_prepared_job_divergence();
11056
+ init_prepared_job_divergence();
10294
11057
  PREPARED_JOB_MODES = ["off", "shadow", "prepared"];
10295
11058
  PREPARED_JOB_MODE_ENV = "VO_CODE_RUNNER_PREPARED_JOB";
10296
11059
  PREPARED_JOB_SHADOW_TIMEOUT_ENV = "VO_PREPARED_JOB_SHADOW_TIMEOUT_MS";
@@ -10310,15 +11073,6 @@ var init_prepared_job_shadow = __esm({
10310
11073
  "methodology_shape",
10311
11074
  "methodology_stakes"
10312
11075
  ];
10313
- DISPATCH_MODE_DEPENDENT = /* @__PURE__ */ new Set([
10314
- "prompt",
10315
- "argv",
10316
- "permission_mode",
10317
- "tier",
10318
- "model",
10319
- "effort",
10320
- "max_turns"
10321
- ]);
10322
11076
  sha256 = (s) => createHash6("sha256").update(String(s ?? ""), "utf8").digest("hex");
10323
11077
  sameValue = (a, b) => Array.isArray(a) || Array.isArray(b) ? JSON.stringify(a) === JSON.stringify(b) : (a ?? null) === (b ?? null);
10324
11078
  verdictObserver = null;
@@ -11572,13 +12326,15 @@ function buildCiFixPrompt({ prNumber, repo, branch, headSha, failedChecks, prPat
11572
12326
  `Verified source head SHA: ${headSha || "unknown"}`,
11573
12327
  `Failing checks: ${failedChecks?.length ? failedChecks.join(", ") : "unknown"}`,
11574
12328
  "",
11575
- "Before you start, the runner materializes the complete verified PR head into your fresh current-main worktree.",
12329
+ "Before you start, the runner materializes the complete verified PR head into your worktree.",
11576
12330
  "The bounded patch excerpt and failed-job evidence below are diagnostic context only, never source transfer.",
11577
12331
  "Diagnose the actual failure from this evidence and the worktree; do not substitute unrelated current-main failures.",
11578
- "Fix it. Follow the PR Freshness / safe-rebuild protocol: open a FRESH fix on a new",
11579
- `branch off current main that SUPERSEDES PR #${prNumber} (note "Supersedes #${prNumber}"`,
11580
- "in your summary). Your work ships: finish the fix completely, leave UNCOMMITTED edits,",
11581
- "and the runner opens the PR for you. A denied git/gh is EXPECTED; do NOT retry it.",
12332
+ `Fix it. The runner owns publication and by DEFAULT repairs PR #${prNumber} IN PLACE on its own`,
12333
+ "branch \u2014 one PR per lineage, no superseding duplicate; it opens a replacement PR only when",
12334
+ "in-place repair is impossible (branch deleted, fork head, push refused, base hopelessly",
12335
+ "diverged). Do NOT open, close, rebase or supersede any PR yourself, and do not add a",
12336
+ "VO-ALLOW-PR-OVERLAP marker. Your work ships: finish the fix completely, leave UNCOMMITTED",
12337
+ "edits, and the runner publishes for you. A denied git/gh is EXPECTED; do NOT retry it.",
11582
12338
  "",
11583
12339
  HEADLESS_EXECUTION_CONTRACT,
11584
12340
  "",
@@ -12140,112 +12896,6 @@ var init_pr_watcher = __esm({
12140
12896
  }
12141
12897
  });
12142
12898
 
12143
- // ../../scripts/virtual-office/code-runner/resume-branch.mjs
12144
- function compactBranchSegment(value) {
12145
- return String(value || "").trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48) || "resume";
12146
- }
12147
- function defaultRunCommand2(cmd, args, cwd, opts = {}) {
12148
- return runProcess2(cmd, args, { cwd, ...opts });
12149
- }
12150
- function buildResumeLocalBranchName(remoteBranch, {
12151
- now = () => /* @__PURE__ */ new Date(),
12152
- pid = process.pid,
12153
- random = secureUnitRandom
12154
- } = {}) {
12155
- const stamp = now().toISOString().replace(/[:.]/g, "-");
12156
- const unique = `${pid}-${random().toString(36).slice(2, 8)}`;
12157
- return `vo/resume-${compactBranchSegment(remoteBranch)}-${stamp}-${unique}`;
12158
- }
12159
- async function assertBranchName(worktreeDir, branch, { env: env2, runCommand = defaultRunCommand2 } = {}) {
12160
- const candidate = String(branch || "").trim();
12161
- if (!candidate) throw new Error("resume branch is required");
12162
- await runCommand("git", ["check-ref-format", "--branch", candidate], worktreeDir, {
12163
- env: env2,
12164
- timeout: 3e4
12165
- });
12166
- return candidate;
12167
- }
12168
- async function restoreContinuationBranch(worktreeDir, remoteBranch, {
12169
- env: env2,
12170
- runCommand = defaultRunCommand2,
12171
- localBranchName
12172
- } = {}) {
12173
- const branch = await assertBranchName(worktreeDir, remoteBranch, { env: env2, runCommand });
12174
- const localBranch = await assertBranchName(
12175
- worktreeDir,
12176
- localBranchName || buildResumeLocalBranchName(branch),
12177
- { env: env2, runCommand }
12178
- );
12179
- await runCommand(
12180
- "git",
12181
- ["fetch", "origin", `${branch}:refs/remotes/origin/${branch}`],
12182
- worktreeDir,
12183
- { env: env2, timeout: 12e4 }
12184
- );
12185
- await runCommand("git", ["checkout", "-b", localBranch, `origin/${branch}`], worktreeDir, {
12186
- env: env2,
12187
- timeout: 6e4
12188
- });
12189
- return { localBranch, remoteBranch: branch };
12190
- }
12191
- function parseHeadRefName(output) {
12192
- const parsed = JSON.parse(String(output || "{}"));
12193
- const branch = String(parsed?.headRefName || "").trim();
12194
- return branch || null;
12195
- }
12196
- async function resolvePrHeadBranch(worktreeDir, {
12197
- repo,
12198
- prNumber,
12199
- githubToken = null,
12200
- allowAmbientGithubFallback = false,
12201
- runCommand = defaultRunCommand2
12202
- } = {}) {
12203
- if (!repo || !Number.isInteger(prNumber) || prNumber <= 0) return null;
12204
- const args = ["pr", "view", String(prNumber), "-R", String(repo), "--json", "headRefName"];
12205
- try {
12206
- const output = await runCommand("gh", args, worktreeDir, {
12207
- env: githubToken ? installationTokenEnv(githubToken) : void 0,
12208
- timeout: 6e4
12209
- });
12210
- return parseHeadRefName(output);
12211
- } catch (error) {
12212
- if (!githubToken || !allowAmbientGithubFallback) throw error;
12213
- }
12214
- const fallback = await runCommand("gh", args, worktreeDir, { timeout: 6e4 });
12215
- return parseHeadRefName(fallback);
12216
- }
12217
- async function prepareContinuationBranch(worktreeDir, {
12218
- task,
12219
- parentTask,
12220
- githubToken = null,
12221
- allowAmbientGithubFallback = false,
12222
- runCommand = defaultRunCommand2
12223
- } = {}) {
12224
- const continuationBranch = task?.pr_branch || parentTask?.pr_branch || (parentTask?.pr_number ? await resolvePrHeadBranch(worktreeDir, {
12225
- repo: task?.repo,
12226
- prNumber: parentTask.pr_number,
12227
- githubToken,
12228
- allowAmbientGithubFallback,
12229
- runCommand
12230
- }) : null);
12231
- if (!continuationBranch && (task?.pr_branch || parentTask?.pr_url || parentTask?.pr_number)) {
12232
- throw new Error(`continuation PR branch could not be resolved for task ${task?.code_task_id || "unknown"}`);
12233
- }
12234
- if (!continuationBranch) return null;
12235
- return restoreContinuationBranch(worktreeDir, continuationBranch, {
12236
- env: githubToken ? installationTokenEnv(githubToken) : void 0,
12237
- runCommand
12238
- });
12239
- }
12240
- var init_resume_branch = __esm({
12241
- "../../scripts/virtual-office/code-runner/resume-branch.mjs"() {
12242
- "use strict";
12243
- init_publish();
12244
- init_process_runner2();
12245
- init_secure_random();
12246
- }
12247
- });
12248
-
12249
12899
  // ../../scripts/virtual-office/code-runner/existing-pr-target.mjs
12250
12900
  function explicitExistingPrNumber(prompt) {
12251
12901
  const match = String(prompt || "").match(
@@ -12253,9 +12903,32 @@ function explicitExistingPrNumber(prompt) {
12253
12903
  );
12254
12904
  return match ? Number(match[1]) : null;
12255
12905
  }
12906
+ function inPlaceRepairNote({ task, repairPlan } = {}) {
12907
+ const head = String(repairPlan?.headSha || "").slice(0, 7) || "unknown";
12908
+ const taskId = String(task?.code_task_id || "unknown").slice(0, 8);
12909
+ return `_AlgoHQ autonomous repair updated this PR **in place** on its own branch \`${repairPlan?.remoteBranch}\` (code task \`${taskId}\`, from head \`${head}\`). No superseding PR was opened._`;
12910
+ }
12911
+ function inPlaceTarget({ task, repairPlan }) {
12912
+ return {
12913
+ targetBranch: repairPlan.remoteBranch,
12914
+ targetPrNumber: repairPlan.prNumber,
12915
+ supersedesPrNumber: repairPlan.prNumber,
12916
+ supersedesHeadSha: repairPlan.headSha ?? null,
12917
+ repairStrategy: "in-place",
12918
+ // The predecessor IS this PR: never rewrite its title/body (they carry CI
12919
+ // markers) and never promote its draft flag (the only merge guard it has).
12920
+ preserveExistingPr: true,
12921
+ inPlaceNote: inPlaceRepairNote({ task, repairPlan }),
12922
+ // A remote that REFUSES the in-place push is the enumerated `push_rejected`
12923
+ // escape; publication then falls back to a fresh branch + supersede.
12924
+ supersedeFallback: true,
12925
+ deferSupersededPrCleanup: false
12926
+ };
12927
+ }
12256
12928
  async function resolvePublicationTarget({
12257
12929
  task,
12258
12930
  continuationRestore,
12931
+ repairPlan = null,
12259
12932
  worktreeDir,
12260
12933
  githubToken,
12261
12934
  allowAmbientGithubFallback,
@@ -12266,16 +12939,26 @@ async function resolvePublicationTarget({
12266
12939
  if (!Number.isInteger(repairPrNumber) || repairPrNumber <= 0) {
12267
12940
  throw new Error("structured repair PR target is invalid; refusing publication");
12268
12941
  }
12942
+ if (repairPlan?.strategy === "in-place" && repairPlan.remoteBranch && !continuationRestore) {
12943
+ return inPlaceTarget({ task, repairPlan });
12944
+ }
12269
12945
  return {
12270
12946
  targetBranch: continuationRestore?.remoteBranch || void 0,
12271
- supersedesPrNumber: repairPrNumber
12947
+ supersedesPrNumber: repairPrNumber,
12948
+ // Closing the predecessor at PUBLISH time is what stops a repair lineage
12949
+ // from growing a third open layer; the head-SHA guard in
12950
+ // closeSupersededPrAsync is what keeps that close safe.
12951
+ supersedesHeadSha: repairPlan?.strategy === "supersede" ? repairPlan.headSha ?? null : null,
12952
+ repairStrategy: repairPlan?.strategy ?? "supersede",
12953
+ deferSupersededPrCleanup: !(repairPlan?.strategy === "supersede" && repairPlan.headSha)
12272
12954
  };
12273
12955
  }
12274
12956
  const targetPrNumber = explicitExistingPrNumber(task?.prompt);
12275
12957
  if (!targetPrNumber) {
12276
12958
  return {
12277
12959
  targetBranch: continuationRestore?.remoteBranch || void 0,
12278
- supersedesPrNumber: supersededSourcePrNumber(task?.prompt)
12960
+ supersedesPrNumber: supersededSourcePrNumber(task?.prompt),
12961
+ deferSupersededPrCleanup: true
12279
12962
  };
12280
12963
  }
12281
12964
  const targetBranch = await resolveBranch(worktreeDir, {
@@ -12287,7 +12970,7 @@ async function resolvePublicationTarget({
12287
12970
  if (!targetBranch) {
12288
12971
  throw new Error(`explicit target PR #${targetPrNumber} head branch could not be resolved; refusing duplicate publication`);
12289
12972
  }
12290
- return { targetBranch, targetPrNumber, supersedesPrNumber: targetPrNumber };
12973
+ return { targetBranch, targetPrNumber, supersedesPrNumber: targetPrNumber, deferSupersededPrCleanup: true };
12291
12974
  }
12292
12975
  var init_existing_pr_target = __esm({
12293
12976
  "../../scripts/virtual-office/code-runner/existing-pr-target.mjs"() {
@@ -12578,8 +13261,8 @@ function resolveCacheBaseDir(env2 = process.env, moduleDir = __dirname) {
12578
13261
  if (env2.VO_MODEL_REGISTRY_CACHE_DIR) return env2.VO_MODEL_REGISTRY_CACHE_DIR;
12579
13262
  if (env2.VO_RUNNER_RUNTIME_ROOT) return env2.VO_RUNNER_RUNTIME_ROOT;
12580
13263
  const segments = moduleDir.split(path18.sep);
12581
- const isRepoCheckout = segments.at(-1) === "virtual-office" && segments.at(-2) === "scripts";
12582
- return isRepoCheckout ? path18.resolve(moduleDir, "..", "..") : userCacheRoot();
13264
+ const isRepoCheckout2 = segments.at(-1) === "virtual-office" && segments.at(-2) === "scripts";
13265
+ return isRepoCheckout2 ? path18.resolve(moduleDir, "..", "..") : userCacheRoot();
12583
13266
  }
12584
13267
  function uniqueModels(models = []) {
12585
13268
  return [...new Set(models.map((model) => String(model || "").trim()).filter(Boolean))];
@@ -14722,7 +15405,7 @@ var init_inference_task_runner = __esm({
14722
15405
  import fs12 from "node:fs";
14723
15406
  import fsp11 from "node:fs/promises";
14724
15407
  import path19 from "node:path";
14725
- async function defaultRun(command, args, cwd, options = {}) {
15408
+ async function defaultRun3(command, args, cwd, options = {}) {
14726
15409
  return runProcess2(command, args, { cwd, timeout: 6e4, ...options });
14727
15410
  }
14728
15411
  async function git2(run, cwd, args, options = {}) {
@@ -14758,7 +15441,7 @@ async function isVerifiedRemoteFastForward(baseline, current, run) {
14758
15441
  return false;
14759
15442
  }
14760
15443
  }
14761
- async function captureCanonicalBaseline(worktreeDir, { run = defaultRun } = {}) {
15444
+ async function captureCanonicalBaseline(worktreeDir, { run = defaultRun3 } = {}) {
14762
15445
  const root = await canonicalRootForWorktree(worktreeDir, run);
14763
15446
  if (!root) return { root: null, head: null, status: "", standalone: true };
14764
15447
  const state = await snapshot(root, run);
@@ -14818,7 +15501,7 @@ async function restoreExactCanonicalPaths(baseline, evidence, run) {
14818
15501
  await fsp11.rm(target, { force: true });
14819
15502
  }
14820
15503
  }
14821
- async function assertCanonicalIsolation(baseline, { worktreeDir, taskId, run = defaultRun, now = () => /* @__PURE__ */ new Date() } = {}) {
15504
+ async function assertCanonicalIsolation(baseline, { worktreeDir, taskId, run = defaultRun3, now = () => /* @__PURE__ */ new Date() } = {}) {
14822
15505
  if (baseline.standalone) return { ok: true, standalone: true };
14823
15506
  const current = await snapshot(baseline.root, run);
14824
15507
  if (current.head === baseline.head && !current.status) return { ok: true };
@@ -15058,7 +15741,7 @@ async function closeCancelledReplacementPr({
15058
15741
  githubToken,
15059
15742
  log: log2 = () => {
15060
15743
  },
15061
- runCommand = defaultRunCommand3,
15744
+ runCommand = defaultRunCommand4,
15062
15745
  reason = "the operator cancelled the task before publication was acknowledged"
15063
15746
  }) {
15064
15747
  if (!Number.isInteger(pr?.prNumber) || pr.prNumber <= 0 || pr.resumed) return false;
@@ -15279,7 +15962,7 @@ async function finalizePublishedPr({
15279
15962
  log2(`task ${id} \u2192 PR ${pr.prUrl}`);
15280
15963
  return false;
15281
15964
  }
15282
- var defaultRunCommand3;
15965
+ var defaultRunCommand4;
15283
15966
  var init_publication_outcome = __esm({
15284
15967
  "../../scripts/virtual-office/code-runner/publication-outcome.mjs"() {
15285
15968
  "use strict";
@@ -15292,14 +15975,14 @@ var init_publication_outcome = __esm({
15292
15975
  init_outcome_commit();
15293
15976
  init_terminal_delivery();
15294
15977
  init_rate_limit_resume();
15295
- defaultRunCommand3 = (cmd, args, cwd, opts = {}) => runProcess2(cmd, args, { cwd, ...opts });
15978
+ defaultRunCommand4 = (cmd, args, cwd, opts = {}) => runProcess2(cmd, args, { cwd, ...opts });
15296
15979
  }
15297
15980
  });
15298
15981
 
15299
15982
  // ../../scripts/virtual-office/code-runner/committed-scratch-cleanup.mjs
15300
15983
  import fsp12 from "node:fs/promises";
15301
15984
  import path20 from "node:path";
15302
- function defaultRun2(command, args, cwd, options = {}) {
15985
+ function defaultRun4(command, args, cwd, options = {}) {
15303
15986
  return runProcess2(command, args, { cwd, ...options });
15304
15987
  }
15305
15988
  async function resolveSafeScratchTarget(worktreeDir, file) {
@@ -15323,7 +16006,7 @@ async function resolveSafeScratchTarget(worktreeDir, file) {
15323
16006
  }
15324
16007
  return target;
15325
16008
  }
15326
- async function sanitizePublicationScratch(worktreeDir, scratchFiles, { base = "origin/main", runCommand = defaultRun2 } = {}) {
16009
+ async function sanitizePublicationScratch(worktreeDir, scratchFiles, { base = "origin/main", runCommand = defaultRun4 } = {}) {
15327
16010
  const unique = [...new Set(scratchFiles || [])];
15328
16011
  if (unique.length === 0) return false;
15329
16012
  const targets = /* @__PURE__ */ new Map();
@@ -15610,7 +16293,7 @@ var init_recovery_ledger = __esm({
15610
16293
  });
15611
16294
 
15612
16295
  // ../../scripts/virtual-office/code-runner/no-changes-terminal-status.mjs
15613
- function defaultRunCommand4(cmd, args, cwd, opts = {}) {
16296
+ function defaultRunCommand5(cmd, args, cwd, opts = {}) {
15614
16297
  return runProcess2(cmd, args, { cwd, ...opts });
15615
16298
  }
15616
16299
  function explicitTaskOutcome(summary) {
@@ -15662,10 +16345,15 @@ async function closeSupersededSourceOnNoChanges({
15662
16345
  run,
15663
16346
  worktreeDir,
15664
16347
  githubToken,
16348
+ repairInPlace = false,
15665
16349
  log: log2 = () => {
15666
16350
  },
15667
- runCommand = defaultRunCommand4
16351
+ runCommand = defaultRunCommand5
15668
16352
  } = {}) {
16353
+ if (repairInPlace) {
16354
+ log2("no-changes: repair ran IN PLACE \u2014 leaving the repaired PR open");
16355
+ return false;
16356
+ }
15669
16357
  const structured = task?.repair_pr_number;
15670
16358
  if (structured !== void 0 && structured !== null && (!Number.isInteger(structured) || structured <= 0)) {
15671
16359
  throw new Error("structured repair PR target is invalid; refusing no-change completion");
@@ -15702,10 +16390,11 @@ async function finalizeNoChangesOutcome({
15702
16390
  maxTurns,
15703
16391
  worktreeDir,
15704
16392
  githubToken,
16393
+ repairPlan = null,
15705
16394
  safeProgress: safeProgress2,
15706
16395
  log: log2 = () => {
15707
16396
  },
15708
- runCommand = defaultRunCommand4,
16397
+ runCommand = defaultRunCommand5,
15709
16398
  beginCommit = deliverOutcomeCommit,
15710
16399
  deliverTerminal = deliverTerminalRun
15711
16400
  } = {}) {
@@ -15720,7 +16409,15 @@ async function finalizeNoChangesOutcome({
15720
16409
  message: "committing verified no-change outcome and source PR cleanup"
15721
16410
  });
15722
16411
  if (!committing) return terminal;
15723
- await closeSupersededSourceOnNoChanges({ task, run, worktreeDir, githubToken, log: log2, runCommand });
16412
+ await closeSupersededSourceOnNoChanges({
16413
+ task,
16414
+ run,
16415
+ worktreeDir,
16416
+ githubToken,
16417
+ log: log2,
16418
+ runCommand,
16419
+ repairInPlace: repairPlan?.strategy === "in-place"
16420
+ });
15724
16421
  }
15725
16422
  const persisted = await deliverTerminal({
15726
16423
  client,
@@ -16089,101 +16786,6 @@ var init_daemon_config = __esm({
16089
16786
  }
16090
16787
  });
16091
16788
 
16092
- // ../../scripts/virtual-office/code-runner/github-git-auth-env.mjs
16093
- function githubGitAuthEnv2(githubToken) {
16094
- if (!githubToken) return void 0;
16095
- const credentials = Buffer.from(`x-access-token:${githubToken}`).toString("base64");
16096
- return {
16097
- GIT_CONFIG_COUNT: "1",
16098
- GIT_CONFIG_KEY_0: "http.https://github.com/.extraheader",
16099
- GIT_CONFIG_VALUE_0: `AUTHORIZATION: basic ${credentials}`
16100
- };
16101
- }
16102
- var init_github_git_auth_env = __esm({
16103
- "../../scripts/virtual-office/code-runner/github-git-auth-env.mjs"() {
16104
- "use strict";
16105
- }
16106
- });
16107
-
16108
- // ../../scripts/virtual-office/code-runner/repair-source-materialization.mjs
16109
- function assertRepairSource({ repo, prNumber, headSha }) {
16110
- if (!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/u.test(String(repo || ""))) {
16111
- throw new Error("repair source repository is invalid");
16112
- }
16113
- if (!Number.isInteger(prNumber) || prNumber < 1) {
16114
- throw new Error("repair source PR number is invalid");
16115
- }
16116
- if (!/^[0-9a-f]{40}$/u.test(String(headSha || "").toLowerCase())) {
16117
- throw new Error("repair source head SHA is missing or invalid");
16118
- }
16119
- }
16120
- function defaultRun3(command, args, cwd, options = {}) {
16121
- const env2 = options.env ? { ...process.env, ...options.env } : process.env;
16122
- return runProcess2(command, args, { cwd, ...options, env: env2 });
16123
- }
16124
- async function materializeRepairSource(worktreeDir, {
16125
- repo,
16126
- prNumber,
16127
- headSha,
16128
- githubToken = null,
16129
- runCommand = defaultRun3
16130
- } = {}) {
16131
- assertRepairSource({ repo, prNumber, headSha });
16132
- const expectedHead = headSha.toLowerCase();
16133
- const sourceRef = `refs/vo/repairs/${expectedHead}`;
16134
- const env2 = githubToken ? githubGitAuthEnv2(githubToken) : void 0;
16135
- await runCommand(
16136
- "git",
16137
- ["fetch", "origin", `+refs/pull/${prNumber}/head:${sourceRef}`],
16138
- worktreeDir,
16139
- { env: env2, timeout: 12e4 }
16140
- );
16141
- const fetchedHead = String(await runCommand(
16142
- "git",
16143
- ["rev-parse", sourceRef],
16144
- worktreeDir,
16145
- { env: env2, timeout: 3e4 }
16146
- )).trim().toLowerCase();
16147
- if (fetchedHead !== expectedHead) {
16148
- throw new Error(`repair source head changed: admitted ${expectedHead}, fetched ${fetchedHead || "none"}`);
16149
- }
16150
- let conflicted = false;
16151
- try {
16152
- await runCommand(
16153
- "git",
16154
- ["merge", "--squash", "--no-commit", sourceRef],
16155
- worktreeDir,
16156
- { env: env2, timeout: 12e4 }
16157
- );
16158
- } catch (error) {
16159
- const conflicts = String(await runCommand(
16160
- "git",
16161
- ["diff", "--name-only", "--diff-filter=U"],
16162
- worktreeDir,
16163
- { env: env2, timeout: 3e4 }
16164
- )).trim();
16165
- if (!conflicts) throw error;
16166
- conflicted = true;
16167
- }
16168
- const status = String(await runCommand(
16169
- "git",
16170
- ["status", "--porcelain"],
16171
- worktreeDir,
16172
- { env: env2, timeout: 3e4 }
16173
- )).trim();
16174
- if (!status) {
16175
- throw new Error(`repair source ${repo}#${prNumber}@${expectedHead} produced no changes on current main`);
16176
- }
16177
- return { sourceRef, headSha: expectedHead, conflicted };
16178
- }
16179
- var init_repair_source_materialization = __esm({
16180
- "../../scripts/virtual-office/code-runner/repair-source-materialization.mjs"() {
16181
- "use strict";
16182
- init_process_runner2();
16183
- init_github_git_auth_env();
16184
- }
16185
- });
16186
-
16187
16789
  // ../../scripts/virtual-office/code-runner/task-worktree-preparation.mjs
16188
16790
  async function prepareTaskWorktree({ client, task, cfg, safeProgress: safeProgress2, log: log2 }) {
16189
16791
  const id = task.code_task_id;
@@ -16214,6 +16816,7 @@ async function prepareTaskWorktree({ client, task, cfg, safeProgress: safeProgre
16214
16816
  githubToken,
16215
16817
  allowAmbientGithubFallback: cfg.allowAmbientGithub
16216
16818
  });
16819
+ let repairPlan = null;
16217
16820
  if (continuationRestore) {
16218
16821
  log2(`task ${id}: restored continuation branch ${continuationRestore.remoteBranch} into ${continuationRestore.localBranch}`);
16219
16822
  } else if (task.repair_pr_number) {
@@ -16221,22 +16824,23 @@ async function prepareTaskWorktree({ client, task, cfg, safeProgress: safeProgre
16221
16824
  "preparing_worktree",
16222
16825
  `Materializing complete exact source PR #${task.repair_pr_number} before paid repair`
16223
16826
  ));
16224
- const source = await materializeRepairSource(wt.worktreeDir, {
16827
+ repairPlan = await planRepairPublication(wt.worktreeDir, {
16225
16828
  repo: task.repo,
16226
16829
  prNumber: task.repair_pr_number,
16227
16830
  headSha: task.repair_head_sha,
16228
- githubToken
16831
+ githubToken,
16832
+ log: (message) => log2(`task ${id}: ${message}`)
16229
16833
  });
16230
- log2(`task ${id}: materialized full repair source ${task.repo}#${task.repair_pr_number}@${source.headSha}${source.conflicted ? " with conflicts for the agent to resolve" : ""}`);
16834
+ log2(repairPlan.strategy === "in-place" ? `task ${id}: repairing ${task.repo}#${task.repair_pr_number}@${repairPlan.headSha} IN PLACE on ${repairPlan.remoteBranch}` : `task ${id}: materialized full repair source ${task.repo}#${task.repair_pr_number}@${repairPlan.headSha} for supersede (${repairPlan.reason})${repairPlan.conflicted ? " with conflicts for the agent to resolve" : ""}`);
16231
16835
  }
16232
- return { wt, githubToken, agentGithubReadToken, continuationRestore };
16836
+ return { wt, githubToken, agentGithubReadToken, continuationRestore, repairPlan };
16233
16837
  }
16234
16838
  var init_task_worktree_preparation = __esm({
16235
16839
  "../../scripts/virtual-office/code-runner/task-worktree-preparation.mjs"() {
16236
16840
  "use strict";
16237
16841
  init_worktree_helper();
16238
16842
  init_resume_branch();
16239
- init_repair_source_materialization();
16843
+ init_repair_publication_strategy();
16240
16844
  init_task_helpers();
16241
16845
  }
16242
16846
  });
@@ -16265,7 +16869,8 @@ async function processOneTask(client, task, cfg, runnerInstanceId, swarmAdmissio
16265
16869
  wt,
16266
16870
  githubToken,
16267
16871
  agentGithubReadToken,
16268
- continuationRestore
16872
+ continuationRestore,
16873
+ repairPlan
16269
16874
  } = await prepareTaskWorktree({ client, task, cfg, safeProgress, log });
16270
16875
  worktreeName = wt.worktreeName;
16271
16876
  const canonicalBaseline = await captureCanonicalBaseline(wt.worktreeDir);
@@ -16279,7 +16884,9 @@ async function processOneTask(client, task, cfg, runnerInstanceId, swarmAdmissio
16279
16884
  methodology = m;
16280
16885
  },
16281
16886
  allowMissingKnowledgeContext: process.env.VO_CODE_RUNNER_ALLOW_MISSING_KNOWLEDGE_CONTEXT === "1",
16282
- attachmentManifestMarkdown: attachmentBundle.manifestMarkdown
16887
+ attachmentManifestMarkdown: attachmentBundle.manifestMarkdown,
16888
+ repoRoot: wt.worktreeDir
16889
+ // ADR-004 § 11.1c: the skill catalog must be the one THIS agent can read
16283
16890
  }) });
16284
16891
  await runPreparedJobShadow({ client, task, agent: sel.agent, env: process.env, log, local: {
16285
16892
  // ADR-004 § 11.1b SHADOW: default OFF; return value deliberately unread — slice C owns consumption
@@ -16400,6 +17007,7 @@ async function processOneTask(client, task, cfg, runnerInstanceId, swarmAdmissio
16400
17007
  partial,
16401
17008
  run,
16402
17009
  maxTurns: effectiveMaxTurns,
17010
+ repairPlan,
16403
17011
  worktreeDir: wt.worktreeDir,
16404
17012
  githubToken,
16405
17013
  safeProgress,
@@ -16410,7 +17018,7 @@ async function processOneTask(client, task, cfg, runnerInstanceId, swarmAdmissio
16410
17018
  if (await taskWasCancelled({ client, id, run, safeProgress, log })) return;
16411
17019
  if (await gateTestGenTaskOrFail({ client, id, task, files, worktreeDir: wt.worktreeDir, log })) return;
16412
17020
  if (await enforceCompletionGateOrFail({ client, id, task, worktreeDir: wt.worktreeDir, run, truncated: partial, log })) return;
16413
- const publicationTarget = await resolvePublicationTarget({ task, continuationRestore, worktreeDir: wt.worktreeDir, githubToken, allowAmbientGithubFallback: cfg.allowAmbientGithub });
17021
+ const publicationTarget = await resolvePublicationTarget({ task, continuationRestore, repairPlan, worktreeDir: wt.worktreeDir, githubToken, allowAmbientGithubFallback: cfg.allowAmbientGithub });
16414
17022
  const localBranch = await resolveOrCreateBranchAsync(wt.worktreeDir, "vo/code-task");
16415
17023
  const publicationBranch = publicationTarget.targetBranch || localBranch;
16416
17024
  if (!await recordPublicationIntent({ client, id, branch: publicationBranch, safeProgress, log })) {
@@ -16419,7 +17027,7 @@ async function processOneTask(client, task, cfg, runnerInstanceId, swarmAdmissio
16419
17027
  }
16420
17028
  await safeProgress(client, id, runnerStagePatch("opening_pr", `opening PR for ${files.length} changed file(s)`));
16421
17029
  log(`task ${id}: publishing with merge-owner=${mergeOwner(cfg)}`);
16422
- const closesSource = publicationTarget.supersedesPrNumber ? `
17030
+ const closesSource = publicationTarget.supersedesPrNumber && publicationTarget.supersedesPrNumber !== publicationTarget.targetPrNumber ? `
16423
17031
 
16424
17032
  Closes #${publicationTarget.supersedesPrNumber}` : "";
16425
17033
  const pr = await openCodeTaskPrAsync(wt.worktreeDir, files, {
@@ -16431,8 +17039,8 @@ Closes #${publicationTarget.supersedesPrNumber}` : "";
16431
17039
  draft: partial,
16432
17040
  armAutoMerge: cfg.armGhAutoMerge,
16433
17041
  // default OFF: the watcher waits for green CI, then uses the exact-SHA receipt-gated merge
16434
- ...publicationTarget,
16435
- deferSupersededPrCleanup: true
17042
+ ...publicationTarget
17043
+ // carries deferSupersededPrCleanup: a supersede now closes its predecessor at PUBLISH time (guarded by head SHA), never only at merge
16436
17044
  });
16437
17045
  if (await finalizePublishedPr({
16438
17046
  client,