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

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,6 +8032,485 @@ var init_process_runner2 = __esm({
7932
8032
  }
7933
8033
  });
7934
8034
 
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" };
8039
+ const env2 = githubToken ? installationTokenEnv(githubToken) : void 0;
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];
8049
+ await runCommand(
8050
+ "gh",
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
+ ],
8058
+ worktreeDir,
8059
+ { env: env2, timeout: 6e4 }
8060
+ );
8061
+ return { closed: true, reason: null };
8062
+ }
8063
+ async function cleanupSupersededPrAsync({
8064
+ worktreeDir,
8065
+ supersedesPrNumber,
8066
+ supersedesHeadSha = null,
8067
+ replacementUrl,
8068
+ replacementNumber,
8069
+ githubToken,
8070
+ runCommand,
8071
+ retry = (fn) => fn(),
8072
+ onCleanupWarning
8073
+ }) {
8074
+ if (!supersedesPrNumber) return {};
8075
+ if (Number(supersedesPrNumber) === Number(replacementNumber)) {
8076
+ return { supersededPrClosed: false };
8077
+ }
8078
+ try {
8079
+ const result = await retry(
8080
+ () => closeSupersededPrAsync(worktreeDir, supersedesPrNumber, replacementUrl, githubToken, {
8081
+ runCommand,
8082
+ expectedHeadSha: supersedesHeadSha
8083
+ })
8084
+ );
8085
+ return {
8086
+ supersededPrClosed: result.closed,
8087
+ ...result.closed ? {} : { supersededPrCloseSkipped: result.reason }
8088
+ };
8089
+ } catch (err) {
8090
+ const message = err && err.message ? err.message : String(err);
8091
+ onCleanupWarning?.(
8092
+ `[publish] replacement PR #${replacementNumber} is open; source PR #${supersedesPrNumber} cleanup failed: ${message}`
8093
+ );
8094
+ return { supersededPrClosed: false, supersededPrCloseError: message };
8095
+ }
8096
+ }
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;
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
+ };
8117
+ }
8118
+ var init_github_git_auth_env = __esm({
8119
+ "../../scripts/virtual-office/code-runner/github-git-auth-env.mjs"() {
8120
+ "use strict";
8121
+ }
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
+
7935
8514
  // ../../scripts/virtual-office/code-runner/partial-pr-continuation.mjs
7936
8515
  function partialPrContinuationResult(run = {}, maxLength = 2e3, reason = null) {
7937
8516
  const summary = String(run.summary || "agent stopped before completing the task").trim();
@@ -7974,10 +8553,10 @@ async function retryTransientAsync(fn, { attempts = 3, baseMs = 5e3, capMs = 3e4
7974
8553
  }
7975
8554
  throw lastErr;
7976
8555
  }
7977
- function defaultRunCommand(cmd, args, cwd, opts = {}) {
8556
+ function defaultRunCommand3(cmd, args, cwd, opts = {}) {
7978
8557
  return runProcess2(cmd, args, { cwd, ...opts });
7979
8558
  }
7980
- async function resolveOrCreateBranchAsync(worktreeDir, branchPrefix, runCommand = defaultRunCommand) {
8559
+ async function resolveOrCreateBranchAsync(worktreeDir, branchPrefix, runCommand = defaultRunCommand3) {
7981
8560
  let branch = "";
7982
8561
  try {
7983
8562
  branch = await runCommand("git", ["branch", "--show-current"], worktreeDir, { timeout: 3e4 });
@@ -8023,7 +8602,7 @@ async function maybeArmAutoMergeAsync({
8023
8602
  githubToken,
8024
8603
  armAutoMerge,
8025
8604
  draft,
8026
- runCommand = defaultRunCommand
8605
+ runCommand = defaultRunCommand3
8027
8606
  }) {
8028
8607
  if (!armAutoMerge || draft) return { autoMergeArmed: false };
8029
8608
  try {
@@ -8039,14 +8618,14 @@ async function maybeArmAutoMergeAsync({
8039
8618
  };
8040
8619
  }
8041
8620
  }
8042
- async function listChangedFilesAsync(cwd, { runCommand = defaultRunCommand } = {}) {
8621
+ async function listChangedFilesAsync(cwd, { runCommand = defaultRunCommand3 } = {}) {
8043
8622
  const out = await runCommand("git", ["-c", "core.quotepath=false", "status", "--porcelain", "-z"], cwd, {
8044
8623
  timeout: 6e4,
8045
8624
  raw: true
8046
8625
  });
8047
8626
  return parsePorcelainZ(out);
8048
8627
  }
8049
- async function listCommittedFilesAsync(cwd, base = "origin/main", { runCommand = defaultRunCommand } = {}) {
8628
+ async function listCommittedFilesAsync(cwd, base = "origin/main", { runCommand = defaultRunCommand3 } = {}) {
8050
8629
  try {
8051
8630
  await runCommand("git", ["fetch", "origin", "main"], cwd, { timeout: 6e4 });
8052
8631
  } catch {
@@ -8068,7 +8647,7 @@ async function commitWorkLocallyAsync(worktreeDir, files, {
8068
8647
  branchPrefix = "vo/code-task",
8069
8648
  botName = "vo-code-runner",
8070
8649
  botEmail = "vo-code-runner@algosuite.ai",
8071
- runCommand = defaultRunCommand
8650
+ runCommand = defaultRunCommand3
8072
8651
  } = {}) {
8073
8652
  const cleaned = (files || []).filter((file) => !isAgentScratch(file));
8074
8653
  if (cleaned.length === 0) throw new Error("commitWorkLocallyAsync: only scratch files, nothing to commit");
@@ -8082,65 +8661,29 @@ async function commitWorkLocallyAsync(worktreeDir, files, {
8082
8661
  await runCommand("git", ["commit", "-m", compactTitle(title, 180)], worktreeDir);
8083
8662
  return { branch, truncated: false, committed: true };
8084
8663
  }
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;
8104
- 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;
8108
- await runCommand(
8109
- "gh",
8110
- ["pr", "close", String(prNumber), "--comment", `Superseded by ${replacementUrl}, rebuilt from current main by AlgoHQ repair.`],
8111
- worktreeDir,
8112
- { env: env2, timeout: 6e4 }
8113
- );
8114
- return true;
8115
- }
8116
- async function cleanupSupersededPrAsync({
8117
- worktreeDir,
8118
- supersedesPrNumber,
8119
- replacementUrl,
8120
- replacementNumber,
8121
- githubToken,
8122
- runCommand,
8123
- onCleanupWarning
8124
- }) {
8125
- if (!supersedesPrNumber) return {};
8126
- if (Number(supersedesPrNumber) === Number(replacementNumber)) {
8127
- return { supersededPrClosed: false };
8128
- }
8664
+ async function existingPrUrlAsync(worktreeDir, branch, githubToken = null, { runCommand = defaultRunCommand3 } = {}) {
8129
8665
  try {
8130
- const closed = await retryTransientAsync(
8131
- () => closeSupersededPrAsync(worktreeDir, supersedesPrNumber, replacementUrl, githubToken, { runCommand }),
8132
- { onRetry: gitRetryLog("gh pr close superseded") }
8133
- );
8134
- return { supersededPrClosed: closed };
8135
- } catch (err) {
8136
- const message = err && err.message ? err.message : String(err);
8137
- onCleanupWarning?.(
8138
- `[publish] replacement PR #${replacementNumber} is open; source PR #${supersedesPrNumber} cleanup failed: ${message}`
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 }
8139
8671
  );
8140
- return { supersededPrClosed: false, supersededPrCloseError: message };
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 {
8141
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
+ });
8142
8685
  }
8143
- async function pushBranchAsync(worktreeDir, branch, githubToken, { runCommand = defaultRunCommand, allowAmbientFallback = false, remoteBranch = branch } = {}) {
8686
+ async function pushBranchAsync(worktreeDir, branch, githubToken, { runCommand = defaultRunCommand3, allowAmbientFallback = false, remoteBranch = branch } = {}) {
8144
8687
  const pushRef = remoteBranch && remoteBranch !== branch ? `HEAD:refs/heads/${remoteBranch}` : branch;
8145
8688
  const { primary, fallback } = pushPlan(pushRef, githubToken, { allowAmbientFallback });
8146
8689
  try {
@@ -8166,9 +8709,13 @@ async function openCodeTaskPrAsync(worktreeDir, files, {
8166
8709
  targetBranch = null,
8167
8710
  targetPrNumber = null,
8168
8711
  supersedesPrNumber = null,
8712
+ supersedesHeadSha = null,
8169
8713
  deferSupersededPrCleanup = false,
8714
+ preserveExistingPr = false,
8715
+ inPlaceNote = "",
8716
+ supersedeFallback = false,
8170
8717
  onCleanupWarning = console.error,
8171
- runCommand = defaultRunCommand,
8718
+ runCommand = defaultRunCommand3,
8172
8719
  runOverlapGate = runLocalPrOverlapGateAsync
8173
8720
  } = {}) {
8174
8721
  if (!Array.isArray(files) || files.length === 0) throw new Error("openCodeTaskPrAsync: no files to commit");
@@ -8189,7 +8736,9 @@ async function openCodeTaskPrAsync(worktreeDir, files, {
8189
8736
  truncated = committed.truncated;
8190
8737
  newCommit = committed.committed;
8191
8738
  }
8192
- const prBranch = String(targetBranch || branch).trim() || branch;
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
  });
@@ -11572,13 +12159,15 @@ function buildCiFixPrompt({ prNumber, repo, branch, headSha, failedChecks, prPat
11572
12159
  `Verified source head SHA: ${headSha || "unknown"}`,
11573
12160
  `Failing checks: ${failedChecks?.length ? failedChecks.join(", ") : "unknown"}`,
11574
12161
  "",
11575
- "Before you start, the runner materializes the complete verified PR head into your fresh current-main worktree.",
12162
+ "Before you start, the runner materializes the complete verified PR head into your worktree.",
11576
12163
  "The bounded patch excerpt and failed-job evidence below are diagnostic context only, never source transfer.",
11577
12164
  "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.",
12165
+ `Fix it. The runner owns publication and by DEFAULT repairs PR #${prNumber} IN PLACE on its own`,
12166
+ "branch \u2014 one PR per lineage, no superseding duplicate; it opens a replacement PR only when",
12167
+ "in-place repair is impossible (branch deleted, fork head, push refused, base hopelessly",
12168
+ "diverged). Do NOT open, close, rebase or supersede any PR yourself, and do not add a",
12169
+ "VO-ALLOW-PR-OVERLAP marker. Your work ships: finish the fix completely, leave UNCOMMITTED",
12170
+ "edits, and the runner publishes for you. A denied git/gh is EXPECTED; do NOT retry it.",
11582
12171
  "",
11583
12172
  HEADLESS_EXECUTION_CONTRACT,
11584
12173
  "",
@@ -12140,112 +12729,6 @@ var init_pr_watcher = __esm({
12140
12729
  }
12141
12730
  });
12142
12731
 
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
12732
  // ../../scripts/virtual-office/code-runner/existing-pr-target.mjs
12250
12733
  function explicitExistingPrNumber(prompt) {
12251
12734
  const match = String(prompt || "").match(
@@ -12253,9 +12736,32 @@ function explicitExistingPrNumber(prompt) {
12253
12736
  );
12254
12737
  return match ? Number(match[1]) : null;
12255
12738
  }
12739
+ function inPlaceRepairNote({ task, repairPlan } = {}) {
12740
+ const head = String(repairPlan?.headSha || "").slice(0, 7) || "unknown";
12741
+ const taskId = String(task?.code_task_id || "unknown").slice(0, 8);
12742
+ 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._`;
12743
+ }
12744
+ function inPlaceTarget({ task, repairPlan }) {
12745
+ return {
12746
+ targetBranch: repairPlan.remoteBranch,
12747
+ targetPrNumber: repairPlan.prNumber,
12748
+ supersedesPrNumber: repairPlan.prNumber,
12749
+ supersedesHeadSha: repairPlan.headSha ?? null,
12750
+ repairStrategy: "in-place",
12751
+ // The predecessor IS this PR: never rewrite its title/body (they carry CI
12752
+ // markers) and never promote its draft flag (the only merge guard it has).
12753
+ preserveExistingPr: true,
12754
+ inPlaceNote: inPlaceRepairNote({ task, repairPlan }),
12755
+ // A remote that REFUSES the in-place push is the enumerated `push_rejected`
12756
+ // escape; publication then falls back to a fresh branch + supersede.
12757
+ supersedeFallback: true,
12758
+ deferSupersededPrCleanup: false
12759
+ };
12760
+ }
12256
12761
  async function resolvePublicationTarget({
12257
12762
  task,
12258
12763
  continuationRestore,
12764
+ repairPlan = null,
12259
12765
  worktreeDir,
12260
12766
  githubToken,
12261
12767
  allowAmbientGithubFallback,
@@ -12266,16 +12772,26 @@ async function resolvePublicationTarget({
12266
12772
  if (!Number.isInteger(repairPrNumber) || repairPrNumber <= 0) {
12267
12773
  throw new Error("structured repair PR target is invalid; refusing publication");
12268
12774
  }
12775
+ if (repairPlan?.strategy === "in-place" && repairPlan.remoteBranch && !continuationRestore) {
12776
+ return inPlaceTarget({ task, repairPlan });
12777
+ }
12269
12778
  return {
12270
12779
  targetBranch: continuationRestore?.remoteBranch || void 0,
12271
- supersedesPrNumber: repairPrNumber
12780
+ supersedesPrNumber: repairPrNumber,
12781
+ // Closing the predecessor at PUBLISH time is what stops a repair lineage
12782
+ // from growing a third open layer; the head-SHA guard in
12783
+ // closeSupersededPrAsync is what keeps that close safe.
12784
+ supersedesHeadSha: repairPlan?.strategy === "supersede" ? repairPlan.headSha ?? null : null,
12785
+ repairStrategy: repairPlan?.strategy ?? "supersede",
12786
+ deferSupersededPrCleanup: !(repairPlan?.strategy === "supersede" && repairPlan.headSha)
12272
12787
  };
12273
12788
  }
12274
12789
  const targetPrNumber = explicitExistingPrNumber(task?.prompt);
12275
12790
  if (!targetPrNumber) {
12276
12791
  return {
12277
12792
  targetBranch: continuationRestore?.remoteBranch || void 0,
12278
- supersedesPrNumber: supersededSourcePrNumber(task?.prompt)
12793
+ supersedesPrNumber: supersededSourcePrNumber(task?.prompt),
12794
+ deferSupersededPrCleanup: true
12279
12795
  };
12280
12796
  }
12281
12797
  const targetBranch = await resolveBranch(worktreeDir, {
@@ -12287,7 +12803,7 @@ async function resolvePublicationTarget({
12287
12803
  if (!targetBranch) {
12288
12804
  throw new Error(`explicit target PR #${targetPrNumber} head branch could not be resolved; refusing duplicate publication`);
12289
12805
  }
12290
- return { targetBranch, targetPrNumber, supersedesPrNumber: targetPrNumber };
12806
+ return { targetBranch, targetPrNumber, supersedesPrNumber: targetPrNumber, deferSupersededPrCleanup: true };
12291
12807
  }
12292
12808
  var init_existing_pr_target = __esm({
12293
12809
  "../../scripts/virtual-office/code-runner/existing-pr-target.mjs"() {
@@ -14722,7 +15238,7 @@ var init_inference_task_runner = __esm({
14722
15238
  import fs12 from "node:fs";
14723
15239
  import fsp11 from "node:fs/promises";
14724
15240
  import path19 from "node:path";
14725
- async function defaultRun(command, args, cwd, options = {}) {
15241
+ async function defaultRun3(command, args, cwd, options = {}) {
14726
15242
  return runProcess2(command, args, { cwd, timeout: 6e4, ...options });
14727
15243
  }
14728
15244
  async function git2(run, cwd, args, options = {}) {
@@ -14758,7 +15274,7 @@ async function isVerifiedRemoteFastForward(baseline, current, run) {
14758
15274
  return false;
14759
15275
  }
14760
15276
  }
14761
- async function captureCanonicalBaseline(worktreeDir, { run = defaultRun } = {}) {
15277
+ async function captureCanonicalBaseline(worktreeDir, { run = defaultRun3 } = {}) {
14762
15278
  const root = await canonicalRootForWorktree(worktreeDir, run);
14763
15279
  if (!root) return { root: null, head: null, status: "", standalone: true };
14764
15280
  const state = await snapshot(root, run);
@@ -14818,7 +15334,7 @@ async function restoreExactCanonicalPaths(baseline, evidence, run) {
14818
15334
  await fsp11.rm(target, { force: true });
14819
15335
  }
14820
15336
  }
14821
- async function assertCanonicalIsolation(baseline, { worktreeDir, taskId, run = defaultRun, now = () => /* @__PURE__ */ new Date() } = {}) {
15337
+ async function assertCanonicalIsolation(baseline, { worktreeDir, taskId, run = defaultRun3, now = () => /* @__PURE__ */ new Date() } = {}) {
14822
15338
  if (baseline.standalone) return { ok: true, standalone: true };
14823
15339
  const current = await snapshot(baseline.root, run);
14824
15340
  if (current.head === baseline.head && !current.status) return { ok: true };
@@ -15058,7 +15574,7 @@ async function closeCancelledReplacementPr({
15058
15574
  githubToken,
15059
15575
  log: log2 = () => {
15060
15576
  },
15061
- runCommand = defaultRunCommand3,
15577
+ runCommand = defaultRunCommand4,
15062
15578
  reason = "the operator cancelled the task before publication was acknowledged"
15063
15579
  }) {
15064
15580
  if (!Number.isInteger(pr?.prNumber) || pr.prNumber <= 0 || pr.resumed) return false;
@@ -15279,7 +15795,7 @@ async function finalizePublishedPr({
15279
15795
  log2(`task ${id} \u2192 PR ${pr.prUrl}`);
15280
15796
  return false;
15281
15797
  }
15282
- var defaultRunCommand3;
15798
+ var defaultRunCommand4;
15283
15799
  var init_publication_outcome = __esm({
15284
15800
  "../../scripts/virtual-office/code-runner/publication-outcome.mjs"() {
15285
15801
  "use strict";
@@ -15292,14 +15808,14 @@ var init_publication_outcome = __esm({
15292
15808
  init_outcome_commit();
15293
15809
  init_terminal_delivery();
15294
15810
  init_rate_limit_resume();
15295
- defaultRunCommand3 = (cmd, args, cwd, opts = {}) => runProcess2(cmd, args, { cwd, ...opts });
15811
+ defaultRunCommand4 = (cmd, args, cwd, opts = {}) => runProcess2(cmd, args, { cwd, ...opts });
15296
15812
  }
15297
15813
  });
15298
15814
 
15299
15815
  // ../../scripts/virtual-office/code-runner/committed-scratch-cleanup.mjs
15300
15816
  import fsp12 from "node:fs/promises";
15301
15817
  import path20 from "node:path";
15302
- function defaultRun2(command, args, cwd, options = {}) {
15818
+ function defaultRun4(command, args, cwd, options = {}) {
15303
15819
  return runProcess2(command, args, { cwd, ...options });
15304
15820
  }
15305
15821
  async function resolveSafeScratchTarget(worktreeDir, file) {
@@ -15323,7 +15839,7 @@ async function resolveSafeScratchTarget(worktreeDir, file) {
15323
15839
  }
15324
15840
  return target;
15325
15841
  }
15326
- async function sanitizePublicationScratch(worktreeDir, scratchFiles, { base = "origin/main", runCommand = defaultRun2 } = {}) {
15842
+ async function sanitizePublicationScratch(worktreeDir, scratchFiles, { base = "origin/main", runCommand = defaultRun4 } = {}) {
15327
15843
  const unique = [...new Set(scratchFiles || [])];
15328
15844
  if (unique.length === 0) return false;
15329
15845
  const targets = /* @__PURE__ */ new Map();
@@ -15610,7 +16126,7 @@ var init_recovery_ledger = __esm({
15610
16126
  });
15611
16127
 
15612
16128
  // ../../scripts/virtual-office/code-runner/no-changes-terminal-status.mjs
15613
- function defaultRunCommand4(cmd, args, cwd, opts = {}) {
16129
+ function defaultRunCommand5(cmd, args, cwd, opts = {}) {
15614
16130
  return runProcess2(cmd, args, { cwd, ...opts });
15615
16131
  }
15616
16132
  function explicitTaskOutcome(summary) {
@@ -15662,10 +16178,15 @@ async function closeSupersededSourceOnNoChanges({
15662
16178
  run,
15663
16179
  worktreeDir,
15664
16180
  githubToken,
16181
+ repairInPlace = false,
15665
16182
  log: log2 = () => {
15666
16183
  },
15667
- runCommand = defaultRunCommand4
16184
+ runCommand = defaultRunCommand5
15668
16185
  } = {}) {
16186
+ if (repairInPlace) {
16187
+ log2("no-changes: repair ran IN PLACE \u2014 leaving the repaired PR open");
16188
+ return false;
16189
+ }
15669
16190
  const structured = task?.repair_pr_number;
15670
16191
  if (structured !== void 0 && structured !== null && (!Number.isInteger(structured) || structured <= 0)) {
15671
16192
  throw new Error("structured repair PR target is invalid; refusing no-change completion");
@@ -15702,10 +16223,11 @@ async function finalizeNoChangesOutcome({
15702
16223
  maxTurns,
15703
16224
  worktreeDir,
15704
16225
  githubToken,
16226
+ repairPlan = null,
15705
16227
  safeProgress: safeProgress2,
15706
16228
  log: log2 = () => {
15707
16229
  },
15708
- runCommand = defaultRunCommand4,
16230
+ runCommand = defaultRunCommand5,
15709
16231
  beginCommit = deliverOutcomeCommit,
15710
16232
  deliverTerminal = deliverTerminalRun
15711
16233
  } = {}) {
@@ -15720,7 +16242,15 @@ async function finalizeNoChangesOutcome({
15720
16242
  message: "committing verified no-change outcome and source PR cleanup"
15721
16243
  });
15722
16244
  if (!committing) return terminal;
15723
- await closeSupersededSourceOnNoChanges({ task, run, worktreeDir, githubToken, log: log2, runCommand });
16245
+ await closeSupersededSourceOnNoChanges({
16246
+ task,
16247
+ run,
16248
+ worktreeDir,
16249
+ githubToken,
16250
+ log: log2,
16251
+ runCommand,
16252
+ repairInPlace: repairPlan?.strategy === "in-place"
16253
+ });
15724
16254
  }
15725
16255
  const persisted = await deliverTerminal({
15726
16256
  client,
@@ -16089,101 +16619,6 @@ var init_daemon_config = __esm({
16089
16619
  }
16090
16620
  });
16091
16621
 
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
16622
  // ../../scripts/virtual-office/code-runner/task-worktree-preparation.mjs
16188
16623
  async function prepareTaskWorktree({ client, task, cfg, safeProgress: safeProgress2, log: log2 }) {
16189
16624
  const id = task.code_task_id;
@@ -16214,6 +16649,7 @@ async function prepareTaskWorktree({ client, task, cfg, safeProgress: safeProgre
16214
16649
  githubToken,
16215
16650
  allowAmbientGithubFallback: cfg.allowAmbientGithub
16216
16651
  });
16652
+ let repairPlan = null;
16217
16653
  if (continuationRestore) {
16218
16654
  log2(`task ${id}: restored continuation branch ${continuationRestore.remoteBranch} into ${continuationRestore.localBranch}`);
16219
16655
  } else if (task.repair_pr_number) {
@@ -16221,22 +16657,23 @@ async function prepareTaskWorktree({ client, task, cfg, safeProgress: safeProgre
16221
16657
  "preparing_worktree",
16222
16658
  `Materializing complete exact source PR #${task.repair_pr_number} before paid repair`
16223
16659
  ));
16224
- const source = await materializeRepairSource(wt.worktreeDir, {
16660
+ repairPlan = await planRepairPublication(wt.worktreeDir, {
16225
16661
  repo: task.repo,
16226
16662
  prNumber: task.repair_pr_number,
16227
16663
  headSha: task.repair_head_sha,
16228
- githubToken
16664
+ githubToken,
16665
+ log: (message) => log2(`task ${id}: ${message}`)
16229
16666
  });
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" : ""}`);
16667
+ 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
16668
  }
16232
- return { wt, githubToken, agentGithubReadToken, continuationRestore };
16669
+ return { wt, githubToken, agentGithubReadToken, continuationRestore, repairPlan };
16233
16670
  }
16234
16671
  var init_task_worktree_preparation = __esm({
16235
16672
  "../../scripts/virtual-office/code-runner/task-worktree-preparation.mjs"() {
16236
16673
  "use strict";
16237
16674
  init_worktree_helper();
16238
16675
  init_resume_branch();
16239
- init_repair_source_materialization();
16676
+ init_repair_publication_strategy();
16240
16677
  init_task_helpers();
16241
16678
  }
16242
16679
  });
@@ -16265,7 +16702,8 @@ async function processOneTask(client, task, cfg, runnerInstanceId, swarmAdmissio
16265
16702
  wt,
16266
16703
  githubToken,
16267
16704
  agentGithubReadToken,
16268
- continuationRestore
16705
+ continuationRestore,
16706
+ repairPlan
16269
16707
  } = await prepareTaskWorktree({ client, task, cfg, safeProgress, log });
16270
16708
  worktreeName = wt.worktreeName;
16271
16709
  const canonicalBaseline = await captureCanonicalBaseline(wt.worktreeDir);
@@ -16400,6 +16838,7 @@ async function processOneTask(client, task, cfg, runnerInstanceId, swarmAdmissio
16400
16838
  partial,
16401
16839
  run,
16402
16840
  maxTurns: effectiveMaxTurns,
16841
+ repairPlan,
16403
16842
  worktreeDir: wt.worktreeDir,
16404
16843
  githubToken,
16405
16844
  safeProgress,
@@ -16410,7 +16849,7 @@ async function processOneTask(client, task, cfg, runnerInstanceId, swarmAdmissio
16410
16849
  if (await taskWasCancelled({ client, id, run, safeProgress, log })) return;
16411
16850
  if (await gateTestGenTaskOrFail({ client, id, task, files, worktreeDir: wt.worktreeDir, log })) return;
16412
16851
  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 });
16852
+ const publicationTarget = await resolvePublicationTarget({ task, continuationRestore, repairPlan, worktreeDir: wt.worktreeDir, githubToken, allowAmbientGithubFallback: cfg.allowAmbientGithub });
16414
16853
  const localBranch = await resolveOrCreateBranchAsync(wt.worktreeDir, "vo/code-task");
16415
16854
  const publicationBranch = publicationTarget.targetBranch || localBranch;
16416
16855
  if (!await recordPublicationIntent({ client, id, branch: publicationBranch, safeProgress, log })) {
@@ -16419,7 +16858,7 @@ async function processOneTask(client, task, cfg, runnerInstanceId, swarmAdmissio
16419
16858
  }
16420
16859
  await safeProgress(client, id, runnerStagePatch("opening_pr", `opening PR for ${files.length} changed file(s)`));
16421
16860
  log(`task ${id}: publishing with merge-owner=${mergeOwner(cfg)}`);
16422
- const closesSource = publicationTarget.supersedesPrNumber ? `
16861
+ const closesSource = publicationTarget.supersedesPrNumber && publicationTarget.supersedesPrNumber !== publicationTarget.targetPrNumber ? `
16423
16862
 
16424
16863
  Closes #${publicationTarget.supersedesPrNumber}` : "";
16425
16864
  const pr = await openCodeTaskPrAsync(wt.worktreeDir, files, {
@@ -16431,8 +16870,8 @@ Closes #${publicationTarget.supersedesPrNumber}` : "";
16431
16870
  draft: partial,
16432
16871
  armAutoMerge: cfg.armGhAutoMerge,
16433
16872
  // default OFF: the watcher waits for green CI, then uses the exact-SHA receipt-gated merge
16434
- ...publicationTarget,
16435
- deferSupersededPrCleanup: true
16873
+ ...publicationTarget
16874
+ // carries deferSupersededPrCleanup: a supersede now closes its predecessor at PUBLISH time (guarded by head SHA), never only at merge
16436
16875
  });
16437
16876
  if (await finalizePublishedPr({
16438
16877
  client,