@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.
- package/dist/ci/check-local-pr-overlap.js +117 -7
- package/dist/install-cli.js +68 -6
- package/dist/install-cli.js.map +3 -3
- package/dist/runner-cli.js +1127 -519
- package/dist/runner-cli.js.map +4 -4
- package/package.json +1 -1
package/dist/runner-cli.js
CHANGED
|
@@ -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
|
|
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
|
|
931
|
-
|
|
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 {
|
|
7289
|
-
|
|
7290
|
-
|
|
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/
|
|
7936
|
-
function
|
|
7937
|
-
|
|
7938
|
-
|
|
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
|
|
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
|
-
[
|
|
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
|
|
8131
|
-
() => closeSupersededPrAsync(worktreeDir, supersedesPrNumber, replacementUrl, githubToken, {
|
|
8132
|
-
|
|
8079
|
+
const result = await retry(
|
|
8080
|
+
() => closeSupersededPrAsync(worktreeDir, supersedesPrNumber, replacementUrl, githubToken, {
|
|
8081
|
+
runCommand,
|
|
8082
|
+
expectedHeadSha: supersedesHeadSha
|
|
8083
|
+
})
|
|
8133
8084
|
);
|
|
8134
|
-
return {
|
|
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
|
-
|
|
8144
|
-
|
|
8145
|
-
|
|
8146
|
-
|
|
8147
|
-
|
|
8148
|
-
|
|
8149
|
-
|
|
8150
|
-
|
|
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
|
-
|
|
8156
|
-
|
|
8157
|
-
|
|
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
|
-
|
|
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
|
|
8202
|
-
|
|
8203
|
-
|
|
8204
|
-
|
|
8205
|
-
|
|
8206
|
-
|
|
8207
|
-
|
|
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 (
|
|
8212
|
-
throw new Error(`explicit target PR #${
|
|
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
|
-
|
|
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
|
|
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
|
|
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 {
|
|
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
|
-
|
|
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 = {
|
|
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,
|
|
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
|
|
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
|
-
|
|
11579
|
-
|
|
11580
|
-
"in
|
|
11581
|
-
"
|
|
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
|
|
12582
|
-
return
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
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({
|
|
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
|
-
|
|
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}@${
|
|
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
|
-
|
|
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:
|
|
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,
|