@algosuite/vo-mcp 0.2.0-beta.67 → 0.2.0-beta.69
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/agent-auth-probe-cli.mjs +42 -29
- package/dist/autostart-cli.js.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/install-cli.js.map +0 -1
- package/dist/login-cli.js.map +0 -1
- package/dist/pair-cli.js.map +0 -1
- package/dist/runner-cli.js +643 -343
- package/dist/runner-cli.js.map +3 -4
- package/dist/runner-supervisor.js +5 -1
- package/dist/runner-supervisor.js.map +1 -2
- package/dist/set-key-cli.js.map +0 -1
- package/dist/supervisor-credential-helper.js.map +0 -1
- package/dist/update-cli.js.map +0 -1
- package/package.json +1 -1
package/dist/runner-cli.js
CHANGED
|
@@ -314,21 +314,21 @@ function backupConfigOnce(configPath) {
|
|
|
314
314
|
copyFileSync(configPath, backupPath);
|
|
315
315
|
return backupPath;
|
|
316
316
|
}
|
|
317
|
-
function writeFileAtomic(
|
|
318
|
-
sweepStaleTempFiles(
|
|
319
|
-
const temp = `${
|
|
317
|
+
function writeFileAtomic(path23, content) {
|
|
318
|
+
sweepStaleTempFiles(path23);
|
|
319
|
+
const temp = `${path23}.vo-mcp-tmp-${process.pid}-${Date.now()}`;
|
|
320
320
|
try {
|
|
321
321
|
writeFileSync2(temp, content, { encoding: "utf8", mode: 384 });
|
|
322
|
-
if (existsSync3(
|
|
322
|
+
if (existsSync3(path23)) {
|
|
323
323
|
try {
|
|
324
|
-
chmodSync2(temp, statSync(
|
|
324
|
+
chmodSync2(temp, statSync(path23).mode & 511);
|
|
325
325
|
} catch {
|
|
326
326
|
}
|
|
327
327
|
}
|
|
328
328
|
let lastErr = null;
|
|
329
329
|
for (let attempt = 0; attempt < RENAME_RETRIES; attempt += 1) {
|
|
330
330
|
try {
|
|
331
|
-
renameSync(temp,
|
|
331
|
+
renameSync(temp, path23);
|
|
332
332
|
return;
|
|
333
333
|
} catch (err) {
|
|
334
334
|
lastErr = err;
|
|
@@ -337,7 +337,7 @@ function writeFileAtomic(path22, content) {
|
|
|
337
337
|
sleepSync(RENAME_RETRY_MS);
|
|
338
338
|
}
|
|
339
339
|
}
|
|
340
|
-
writeFileSync2(
|
|
340
|
+
writeFileSync2(path23, content, "utf8");
|
|
341
341
|
try {
|
|
342
342
|
unlinkSync2(temp);
|
|
343
343
|
} catch {
|
|
@@ -503,8 +503,8 @@ function tablePath(line) {
|
|
|
503
503
|
function tableSections(lines) {
|
|
504
504
|
const starts = [];
|
|
505
505
|
for (let index = 0; index < lines.length; index += 1) {
|
|
506
|
-
const
|
|
507
|
-
if (
|
|
506
|
+
const path23 = tablePath(lines[index] ?? "");
|
|
507
|
+
if (path23) starts.push({ path: path23, start: index });
|
|
508
508
|
}
|
|
509
509
|
return starts.map((section, index) => ({
|
|
510
510
|
...section,
|
|
@@ -696,11 +696,11 @@ function resolveLinuxConfigHome(home, env2) {
|
|
|
696
696
|
const configured = env2["XDG_CONFIG_HOME"]?.trim();
|
|
697
697
|
return configured && isAbsolute2(configured) ? configured : join5(home, ".config");
|
|
698
698
|
}
|
|
699
|
-
function launcherIsCurrent(
|
|
700
|
-
if (!existsSync6(
|
|
701
|
-
if (readFileSync5(
|
|
702
|
-
const backupPath = `${
|
|
703
|
-
copyFileSync2(
|
|
699
|
+
function launcherIsCurrent(path23, desiredContent, label, log2) {
|
|
700
|
+
if (!existsSync6(path23)) return false;
|
|
701
|
+
if (readFileSync5(path23, "utf8") === desiredContent) return true;
|
|
702
|
+
const backupPath = `${path23}.backup-${Date.now()}`;
|
|
703
|
+
copyFileSync2(path23, backupPath);
|
|
704
704
|
log2(` Backed up existing ${label} to: ${backupPath}`);
|
|
705
705
|
return false;
|
|
706
706
|
}
|
|
@@ -916,17 +916,17 @@ function resolveDesktopConfigPath(home, plat, appData) {
|
|
|
916
916
|
}
|
|
917
917
|
return join6(home, ".config", "Claude", "claude_desktop_config.json");
|
|
918
918
|
}
|
|
919
|
-
function readClaudeConfig(
|
|
920
|
-
if (!existsSync7(
|
|
919
|
+
function readClaudeConfig(path23) {
|
|
920
|
+
if (!existsSync7(path23)) return { kind: "absent", config: {}, mtimeMs: null };
|
|
921
921
|
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
922
|
-
const before = statSync3(
|
|
922
|
+
const before = statSync3(path23).mtimeMs;
|
|
923
923
|
let raw;
|
|
924
924
|
try {
|
|
925
|
-
raw = readFileSync6(
|
|
925
|
+
raw = readFileSync6(path23, "utf8");
|
|
926
926
|
} catch {
|
|
927
927
|
return { kind: "invalid", config: {}, mtimeMs: before };
|
|
928
928
|
}
|
|
929
|
-
if (!existsSync7(
|
|
929
|
+
if (!existsSync7(path23) || statSync3(path23).mtimeMs !== before) continue;
|
|
930
930
|
const text = raw.replace(/^\uFEFF/u, "");
|
|
931
931
|
if (!text.trim()) return { kind: "empty", config: {}, mtimeMs: before };
|
|
932
932
|
try {
|
|
@@ -938,9 +938,9 @@ function readClaudeConfig(path22) {
|
|
|
938
938
|
}
|
|
939
939
|
return { kind: "invalid", config: {}, mtimeMs: null };
|
|
940
940
|
}
|
|
941
|
-
function writeClaudeConfig(
|
|
942
|
-
mkdirSync6(dirname4(
|
|
943
|
-
writeFileAtomic(
|
|
941
|
+
function writeClaudeConfig(path23, config) {
|
|
942
|
+
mkdirSync6(dirname4(path23), { recursive: true });
|
|
943
|
+
writeFileAtomic(path23, `${JSON.stringify(config, null, 2)}
|
|
944
944
|
`);
|
|
945
945
|
}
|
|
946
946
|
function carriedEntryKeys(entry) {
|
|
@@ -1344,6 +1344,7 @@ var init_pnpm_link_detach = __esm({
|
|
|
1344
1344
|
|
|
1345
1345
|
// src/runner/process-runner.mjs
|
|
1346
1346
|
import { spawn } from "node:child_process";
|
|
1347
|
+
import path2 from "node:path";
|
|
1347
1348
|
function sleepMs(ms) {
|
|
1348
1349
|
if (ms <= 0) return Promise.resolve();
|
|
1349
1350
|
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
@@ -1351,15 +1352,25 @@ function sleepMs(ms) {
|
|
|
1351
1352
|
async function spawnUtility(command, args, timeoutMs) {
|
|
1352
1353
|
return await new Promise((resolve3) => {
|
|
1353
1354
|
let settled = false;
|
|
1355
|
+
let stdout = "";
|
|
1356
|
+
let stderr = "";
|
|
1354
1357
|
const finish = (result) => {
|
|
1355
1358
|
if (settled) return;
|
|
1356
1359
|
settled = true;
|
|
1357
|
-
resolve3(result);
|
|
1360
|
+
resolve3({ stdout, stderr, ...result });
|
|
1358
1361
|
};
|
|
1359
1362
|
const child = spawn(command, args, {
|
|
1360
|
-
stdio: "ignore",
|
|
1363
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1361
1364
|
windowsHide: true
|
|
1362
1365
|
});
|
|
1366
|
+
child.stdout?.setEncoding("utf8");
|
|
1367
|
+
child.stderr?.setEncoding("utf8");
|
|
1368
|
+
child.stdout?.on("data", (chunk) => {
|
|
1369
|
+
stdout = `${stdout}${chunk}`.slice(-MAX_UTILITY_OUTPUT_CHARS);
|
|
1370
|
+
});
|
|
1371
|
+
child.stderr?.on("data", (chunk) => {
|
|
1372
|
+
stderr = `${stderr}${chunk}`.slice(-MAX_UTILITY_OUTPUT_CHARS);
|
|
1373
|
+
});
|
|
1363
1374
|
let timer = null;
|
|
1364
1375
|
if (timeoutMs > 0) {
|
|
1365
1376
|
timer = setTimeout(() => {
|
|
@@ -1381,27 +1392,48 @@ async function spawnUtility(command, args, timeoutMs) {
|
|
|
1381
1392
|
});
|
|
1382
1393
|
});
|
|
1383
1394
|
}
|
|
1395
|
+
function windowsTaskkillExe(env2 = process.env) {
|
|
1396
|
+
const systemRoot = env2.SystemRoot || env2.WINDIR || "C:\\Windows";
|
|
1397
|
+
return path2.win32.join(systemRoot, "System32", "taskkill.exe");
|
|
1398
|
+
}
|
|
1384
1399
|
async function killProcessTreeDefault(pid, options = {}) {
|
|
1385
1400
|
if (!Number.isInteger(pid) || pid <= 0) return;
|
|
1386
|
-
if (process.platform === "win32") {
|
|
1387
|
-
|
|
1388
|
-
|
|
1401
|
+
if ((options.platform || process.platform) === "win32") {
|
|
1402
|
+
const utilityRunner = options.spawnUtility || spawnUtility;
|
|
1403
|
+
return await utilityRunner(
|
|
1404
|
+
windowsTaskkillExe(options.env || process.env),
|
|
1389
1405
|
["/pid", String(pid), "/t", "/f"],
|
|
1390
1406
|
options.timeoutMs ?? DEFAULT_FORCE_KILL_TIMEOUT_MS
|
|
1391
1407
|
);
|
|
1392
|
-
return;
|
|
1393
1408
|
}
|
|
1394
1409
|
try {
|
|
1395
1410
|
process.kill(pid, "SIGKILL");
|
|
1396
1411
|
} catch {
|
|
1397
1412
|
}
|
|
1398
1413
|
}
|
|
1414
|
+
function cleanupResultError(result) {
|
|
1415
|
+
if (!result || typeof result !== "object") return null;
|
|
1416
|
+
if (result.error) {
|
|
1417
|
+
return result.error instanceof Error ? result.error : new Error(String(result.error));
|
|
1418
|
+
}
|
|
1419
|
+
const detail = String(result.stderr || result.stdout || "").trim().replace(/\s+/g, " ").slice(-400);
|
|
1420
|
+
if (result.timedOut) {
|
|
1421
|
+
return new Error(`process-tree cleanup utility timed out${detail ? `: ${detail}` : ""}`);
|
|
1422
|
+
}
|
|
1423
|
+
if ("status" in result && result.status !== 0) {
|
|
1424
|
+
return new Error(
|
|
1425
|
+
`process-tree cleanup utility exited with status ${String(result.status)}${detail ? `: ${detail}` : ""}`
|
|
1426
|
+
);
|
|
1427
|
+
}
|
|
1428
|
+
return null;
|
|
1429
|
+
}
|
|
1399
1430
|
async function runProcess(command, args = [], options = {}) {
|
|
1400
1431
|
const timeoutMs = options.timeoutMs ?? DEFAULT_PROCESS_TIMEOUT_MS;
|
|
1401
1432
|
const killGraceMs = options.killGraceMs ?? DEFAULT_KILL_GRACE_MS;
|
|
1402
1433
|
const forceKillTimeoutMs = options.forceKillTimeoutMs ?? DEFAULT_FORCE_KILL_TIMEOUT_MS;
|
|
1434
|
+
const platform4 = options.platform || process.platform;
|
|
1403
1435
|
const spawnImpl = options.spawnImpl || spawn;
|
|
1404
|
-
const killProcessTree2 = options.killProcessTree || killProcessTreeDefault;
|
|
1436
|
+
const killProcessTree2 = options.killProcessTree || ((pid, killOptions) => killProcessTreeDefault(pid, { ...killOptions, platform: platform4 }));
|
|
1405
1437
|
const spawnOptions = {
|
|
1406
1438
|
cwd: options.cwd,
|
|
1407
1439
|
env: options.env,
|
|
@@ -1417,6 +1449,9 @@ async function runProcess(command, args = [], options = {}) {
|
|
|
1417
1449
|
let graceTimeout = null;
|
|
1418
1450
|
let forceKillTimeout = null;
|
|
1419
1451
|
let child = null;
|
|
1452
|
+
let parentExit = null;
|
|
1453
|
+
let cleanupInFlight = false;
|
|
1454
|
+
let pendingTerminalResult = null;
|
|
1420
1455
|
const clearTimers = () => {
|
|
1421
1456
|
if (timeout) clearTimeout(timeout);
|
|
1422
1457
|
if (graceTimeout) clearTimeout(graceTimeout);
|
|
@@ -1428,8 +1463,33 @@ async function runProcess(command, args = [], options = {}) {
|
|
|
1428
1463
|
clearTimers();
|
|
1429
1464
|
resolve3({ stdout, stderr, timedOut, ...result });
|
|
1430
1465
|
};
|
|
1466
|
+
const handleTerminalResult = (result) => {
|
|
1467
|
+
const normalizedResult = timedOut && typeof result.status === "number" ? { ...result, exitStatus: result.status, status: null } : result;
|
|
1468
|
+
if (timedOut && cleanupInFlight) {
|
|
1469
|
+
pendingTerminalResult = normalizedResult;
|
|
1470
|
+
return;
|
|
1471
|
+
}
|
|
1472
|
+
finish(normalizedResult);
|
|
1473
|
+
};
|
|
1474
|
+
const exitedParentOwnershipError = () => {
|
|
1475
|
+
return new Error(
|
|
1476
|
+
`process parent exited before timeout cleanup, but inherited stdio remained open; PID ${String(child?.pid ?? "unknown")} may no longer belong to the spawned process`
|
|
1477
|
+
);
|
|
1478
|
+
};
|
|
1431
1479
|
const beginForceKill = () => {
|
|
1432
|
-
if (settled
|
|
1480
|
+
if (settled) return;
|
|
1481
|
+
if (parentExit) {
|
|
1482
|
+
finish({
|
|
1483
|
+
status: null,
|
|
1484
|
+
error: exitedParentOwnershipError()
|
|
1485
|
+
});
|
|
1486
|
+
return;
|
|
1487
|
+
}
|
|
1488
|
+
if (!Number.isInteger(child?.pid) || child.pid <= 0) {
|
|
1489
|
+
finish({ status: null, error: new Error("process timed out before a process-tree cleanup PID was available") });
|
|
1490
|
+
return;
|
|
1491
|
+
}
|
|
1492
|
+
cleanupInFlight = true;
|
|
1433
1493
|
forceKillTimeout = setTimeout(() => {
|
|
1434
1494
|
finish({
|
|
1435
1495
|
status: null,
|
|
@@ -1437,17 +1497,36 @@ async function runProcess(command, args = [], options = {}) {
|
|
|
1437
1497
|
});
|
|
1438
1498
|
}, forceKillTimeoutMs);
|
|
1439
1499
|
forceKillTimeout.unref?.();
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1500
|
+
const cleanupPromise = Promise.resolve().then(
|
|
1501
|
+
() => {
|
|
1502
|
+
if (settled) return void 0;
|
|
1503
|
+
if (parentExit) throw exitedParentOwnershipError();
|
|
1504
|
+
return killProcessTree2(child.pid, { timeoutMs: forceKillTimeoutMs });
|
|
1505
|
+
}
|
|
1506
|
+
);
|
|
1507
|
+
void cleanupPromise.then(
|
|
1508
|
+
(result) => {
|
|
1509
|
+
const error = cleanupResultError(result);
|
|
1510
|
+
cleanupInFlight = false;
|
|
1511
|
+
if (error) {
|
|
1512
|
+
finish({ status: null, error });
|
|
1513
|
+
return;
|
|
1514
|
+
}
|
|
1515
|
+
if (pendingTerminalResult) finish(pendingTerminalResult);
|
|
1516
|
+
},
|
|
1517
|
+
(error) => {
|
|
1518
|
+
cleanupInFlight = false;
|
|
1444
1519
|
finish({ status: null, error });
|
|
1445
1520
|
}
|
|
1446
|
-
|
|
1521
|
+
);
|
|
1447
1522
|
};
|
|
1448
1523
|
const beginTimeout = () => {
|
|
1449
1524
|
if (settled || !child) return;
|
|
1450
1525
|
timedOut = true;
|
|
1526
|
+
if (platform4 === "win32") {
|
|
1527
|
+
beginForceKill();
|
|
1528
|
+
return;
|
|
1529
|
+
}
|
|
1451
1530
|
try {
|
|
1452
1531
|
child.kill("SIGTERM");
|
|
1453
1532
|
} catch (error) {
|
|
@@ -1473,8 +1552,11 @@ async function runProcess(command, args = [], options = {}) {
|
|
|
1473
1552
|
stderr += chunk;
|
|
1474
1553
|
if (typeof options.onStderr === "function") options.onStderr(chunk);
|
|
1475
1554
|
});
|
|
1476
|
-
child.on("error", (error) =>
|
|
1477
|
-
child.on("
|
|
1555
|
+
child.on("error", (error) => handleTerminalResult({ status: null, error }));
|
|
1556
|
+
child.on("exit", (code, signal) => {
|
|
1557
|
+
parentExit = { code, signal };
|
|
1558
|
+
});
|
|
1559
|
+
child.on("close", (code, signal) => handleTerminalResult({ status: code, signal }));
|
|
1478
1560
|
if (timeoutMs > 0) {
|
|
1479
1561
|
timeout = setTimeout(beginTimeout, timeoutMs);
|
|
1480
1562
|
timeout.unref?.();
|
|
@@ -1499,13 +1581,14 @@ function summarizeProcessFailure(result, options = {}) {
|
|
|
1499
1581
|
const timedOutText = result?.timedOut ? "timed out" : "";
|
|
1500
1582
|
return [errorText, timedOutText, tail].filter(Boolean).join("; ").slice(0, limit) || `command exited with status ${String(result?.status ?? "unknown")}`;
|
|
1501
1583
|
}
|
|
1502
|
-
var DEFAULT_PROCESS_TIMEOUT_MS, DEFAULT_KILL_GRACE_MS, DEFAULT_FORCE_KILL_TIMEOUT_MS;
|
|
1584
|
+
var DEFAULT_PROCESS_TIMEOUT_MS, DEFAULT_KILL_GRACE_MS, DEFAULT_FORCE_KILL_TIMEOUT_MS, MAX_UTILITY_OUTPUT_CHARS;
|
|
1503
1585
|
var init_process_runner = __esm({
|
|
1504
1586
|
"src/runner/process-runner.mjs"() {
|
|
1505
1587
|
"use strict";
|
|
1506
1588
|
DEFAULT_PROCESS_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
1507
1589
|
DEFAULT_KILL_GRACE_MS = 5e3;
|
|
1508
1590
|
DEFAULT_FORCE_KILL_TIMEOUT_MS = 15e3;
|
|
1591
|
+
MAX_UTILITY_OUTPUT_CHARS = 4096;
|
|
1509
1592
|
}
|
|
1510
1593
|
});
|
|
1511
1594
|
|
|
@@ -1616,14 +1699,18 @@ async function addWorktreeWithRetry({
|
|
|
1616
1699
|
sleep: sleep3 = sleepMs,
|
|
1617
1700
|
removeDir = async (target) => {
|
|
1618
1701
|
await fsp2.rm(target, { recursive: true, force: true });
|
|
1619
|
-
}
|
|
1702
|
+
},
|
|
1703
|
+
baseRef = "origin/main"
|
|
1620
1704
|
}) {
|
|
1705
|
+
if (baseRef !== "origin/main" && !/^[0-9a-f]{40}$/u.test(baseRef)) {
|
|
1706
|
+
throw new Error("worktree base ref must be origin/main or an exact lowercase SHA");
|
|
1707
|
+
}
|
|
1621
1708
|
const maxAttempts = Math.max(1, Math.floor(attempts));
|
|
1622
1709
|
let result = null;
|
|
1623
1710
|
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
1624
1711
|
result = await runner(
|
|
1625
1712
|
"git",
|
|
1626
|
-
["worktree", "add", "-b", branchName, worktreeDir,
|
|
1713
|
+
["worktree", "add", "-b", branchName, worktreeDir, baseRef],
|
|
1627
1714
|
{ cwd: root, timeoutMs }
|
|
1628
1715
|
);
|
|
1629
1716
|
if (result.status === 0 && await pathExistsFn(worktreeDir)) {
|
|
@@ -1648,7 +1735,7 @@ var init_worktree_add = __esm({
|
|
|
1648
1735
|
// src/runner/pnpm-canonical-health.mjs
|
|
1649
1736
|
import fs from "node:fs";
|
|
1650
1737
|
import fsp3 from "node:fs/promises";
|
|
1651
|
-
import
|
|
1738
|
+
import path3 from "node:path";
|
|
1652
1739
|
function readJsonFile(file) {
|
|
1653
1740
|
if (!fs.existsSync(file)) return null;
|
|
1654
1741
|
try {
|
|
@@ -1658,7 +1745,7 @@ function readJsonFile(file) {
|
|
|
1658
1745
|
}
|
|
1659
1746
|
}
|
|
1660
1747
|
function packageJsonAt(root) {
|
|
1661
|
-
return readJsonFile(
|
|
1748
|
+
return readJsonFile(path3.join(root, "package.json"));
|
|
1662
1749
|
}
|
|
1663
1750
|
function dependencyNamesForPackage(root) {
|
|
1664
1751
|
const manifest = packageJsonAt(root);
|
|
@@ -1670,7 +1757,7 @@ function dependencyNamesForPackage(root) {
|
|
|
1670
1757
|
])].sort();
|
|
1671
1758
|
}
|
|
1672
1759
|
function packageEntryPath(nodeModulesDir, packageName) {
|
|
1673
|
-
return
|
|
1760
|
+
return path3.join(nodeModulesDir, ...String(packageName || "").split("/"));
|
|
1674
1761
|
}
|
|
1675
1762
|
function rootFsApi() {
|
|
1676
1763
|
return {
|
|
@@ -1699,7 +1786,7 @@ async function inspectDependencyEntry(nodeModulesDir, packageName, fsApi) {
|
|
|
1699
1786
|
} catch {
|
|
1700
1787
|
return { issue: `missing or dangling dependency link: ${entry}` };
|
|
1701
1788
|
}
|
|
1702
|
-
const packageJsonPath =
|
|
1789
|
+
const packageJsonPath = path3.join(resolved, "package.json");
|
|
1703
1790
|
if (!await pathExists3(packageJsonPath, fsApi)) {
|
|
1704
1791
|
return { issue: `dependency target is missing package.json: ${entry} -> ${resolved}` };
|
|
1705
1792
|
}
|
|
@@ -1711,11 +1798,11 @@ async function inspectDependencyEntry(nodeModulesDir, packageName, fsApi) {
|
|
|
1711
1798
|
return { issue: null };
|
|
1712
1799
|
}
|
|
1713
1800
|
function representativePackages(root, linkedWorkspaceDirs, sampleLimit) {
|
|
1714
|
-
const packages = [{ nodeModulesDir:
|
|
1801
|
+
const packages = [{ nodeModulesDir: path3.join(root, "node_modules"), packageRoot: root }];
|
|
1715
1802
|
for (const relativeDir of linkedWorkspaceDirs) {
|
|
1716
1803
|
packages.push({
|
|
1717
|
-
nodeModulesDir:
|
|
1718
|
-
packageRoot:
|
|
1804
|
+
nodeModulesDir: path3.join(root, relativeDir, "node_modules"),
|
|
1805
|
+
packageRoot: path3.join(root, relativeDir)
|
|
1719
1806
|
});
|
|
1720
1807
|
}
|
|
1721
1808
|
const representatives = [];
|
|
@@ -1727,7 +1814,7 @@ function representativePackages(root, linkedWorkspaceDirs, sampleLimit) {
|
|
|
1727
1814
|
return representatives;
|
|
1728
1815
|
}
|
|
1729
1816
|
function canonicalNodeModulesQuarantineRoot(root) {
|
|
1730
|
-
return
|
|
1817
|
+
return path3.join(root, ".agent-worktrees", QUARANTINE_DIRNAME);
|
|
1731
1818
|
}
|
|
1732
1819
|
async function inspectCanonicalNodeModulesHealth({
|
|
1733
1820
|
root,
|
|
@@ -1738,8 +1825,8 @@ async function inspectCanonicalNodeModulesHealth({
|
|
|
1738
1825
|
sampleLimit = DEFAULT_SAMPLE_LIMIT
|
|
1739
1826
|
}) {
|
|
1740
1827
|
const issues = [];
|
|
1741
|
-
const nodeModulesDir =
|
|
1742
|
-
const markerPath =
|
|
1828
|
+
const nodeModulesDir = path3.join(root, "node_modules");
|
|
1829
|
+
const markerPath = path3.join(nodeModulesDir, ".vo-deps-state.json");
|
|
1743
1830
|
if (!await pathExists3(nodeModulesDir, fsApi)) {
|
|
1744
1831
|
issues.push(`missing root node_modules: ${nodeModulesDir}`);
|
|
1745
1832
|
} else {
|
|
@@ -1748,8 +1835,8 @@ async function inspectCanonicalNodeModulesHealth({
|
|
|
1748
1835
|
issues.push(`root node_modules must be a real directory: ${nodeModulesDir}`);
|
|
1749
1836
|
}
|
|
1750
1837
|
}
|
|
1751
|
-
if (!await pathExists3(
|
|
1752
|
-
issues.push(`missing root .pnpm store view: ${
|
|
1838
|
+
if (!await pathExists3(path3.join(nodeModulesDir, ".pnpm"), fsApi)) {
|
|
1839
|
+
issues.push(`missing root .pnpm store view: ${path3.join(nodeModulesDir, ".pnpm")}`);
|
|
1753
1840
|
}
|
|
1754
1841
|
if (requireMarker) {
|
|
1755
1842
|
try {
|
|
@@ -1762,7 +1849,7 @@ async function inspectCanonicalNodeModulesHealth({
|
|
|
1762
1849
|
}
|
|
1763
1850
|
}
|
|
1764
1851
|
for (const relativeDir of linkedWorkspaceDirs) {
|
|
1765
|
-
const workspaceNodeModules =
|
|
1852
|
+
const workspaceNodeModules = path3.join(root, relativeDir, "node_modules");
|
|
1766
1853
|
if (!await pathExists3(workspaceNodeModules, fsApi)) {
|
|
1767
1854
|
issues.push(`missing workspace node_modules: ${workspaceNodeModules}`);
|
|
1768
1855
|
continue;
|
|
@@ -1785,17 +1872,17 @@ async function inspectCanonicalNodeModulesHealth({
|
|
|
1785
1872
|
async function quarantineCanonicalNodeModules(root, issues, options = {}) {
|
|
1786
1873
|
const fsApi = options.fsApi || rootFsApi();
|
|
1787
1874
|
const logger = options.logger || console.error;
|
|
1788
|
-
const nodeModulesDir =
|
|
1875
|
+
const nodeModulesDir = path3.join(root, "node_modules");
|
|
1789
1876
|
const quarantineRoot = canonicalNodeModulesQuarantineRoot(root);
|
|
1790
1877
|
if (!await pathExists3(nodeModulesDir, fsApi)) return null;
|
|
1791
1878
|
await fsApi.mkdir(quarantineRoot, { recursive: true });
|
|
1792
1879
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
1793
1880
|
let attempt = 0;
|
|
1794
1881
|
for (; ; ) {
|
|
1795
|
-
const quarantinePath =
|
|
1882
|
+
const quarantinePath = path3.join(quarantineRoot, `node_modules-${stamp}-${process.pid}-${attempt}`);
|
|
1796
1883
|
try {
|
|
1797
1884
|
await fsApi.rename(nodeModulesDir, quarantinePath);
|
|
1798
|
-
const metadataPath =
|
|
1885
|
+
const metadataPath = path3.join(quarantinePath, ".vo-runner-quarantine.json");
|
|
1799
1886
|
await fsApi.writeFile(metadataPath, `${JSON.stringify({
|
|
1800
1887
|
issues,
|
|
1801
1888
|
originalPath: nodeModulesDir,
|
|
@@ -1821,22 +1908,22 @@ var init_pnpm_canonical_health = __esm({
|
|
|
1821
1908
|
|
|
1822
1909
|
// src/runner/pnpm-command.mjs
|
|
1823
1910
|
import fs2 from "node:fs";
|
|
1824
|
-
import
|
|
1911
|
+
import path4 from "node:path";
|
|
1825
1912
|
function isWindowsDriveOrUnc(value) {
|
|
1826
1913
|
return WINDOWS_DRIVE_OR_UNC_RE.test(String(value || ""));
|
|
1827
1914
|
}
|
|
1828
1915
|
function portableDirname(value) {
|
|
1829
|
-
if (isWindowsDriveOrUnc(value)) return
|
|
1830
|
-
if (
|
|
1831
|
-
return
|
|
1916
|
+
if (isWindowsDriveOrUnc(value)) return path4.win32.dirname(value);
|
|
1917
|
+
if (path4.posix.isAbsolute(value)) return path4.posix.dirname(value);
|
|
1918
|
+
return path4.dirname(value);
|
|
1832
1919
|
}
|
|
1833
1920
|
function portableJoin(root, ...segments) {
|
|
1834
|
-
if (isWindowsDriveOrUnc(root)) return
|
|
1835
|
-
if (
|
|
1836
|
-
return
|
|
1921
|
+
if (isWindowsDriveOrUnc(root)) return path4.win32.join(root, ...segments);
|
|
1922
|
+
if (path4.posix.isAbsolute(root)) return path4.posix.join(root, ...segments);
|
|
1923
|
+
return path4.join(root, ...segments);
|
|
1837
1924
|
}
|
|
1838
1925
|
function readPackageManager(root) {
|
|
1839
|
-
const packagePath =
|
|
1926
|
+
const packagePath = path4.join(root, "package.json");
|
|
1840
1927
|
if (!fs2.existsSync(packagePath)) return "";
|
|
1841
1928
|
try {
|
|
1842
1929
|
return String(JSON.parse(fs2.readFileSync(packagePath, "utf8"))?.packageManager || "").trim();
|
|
@@ -1856,13 +1943,13 @@ function pnpmSelector(root) {
|
|
|
1856
1943
|
const packageManager = readPackageManager(root);
|
|
1857
1944
|
const version = validatedPnpmVersionToken(root);
|
|
1858
1945
|
if (packageManager && !version) {
|
|
1859
|
-
throw new Error(`[vo-mcp runner] invalid pnpm packageManager token in ${
|
|
1946
|
+
throw new Error(`[vo-mcp runner] invalid pnpm packageManager token in ${path4.join(root, "package.json")}`);
|
|
1860
1947
|
}
|
|
1861
1948
|
return version ? `pnpm@${version}` : "pnpm";
|
|
1862
1949
|
}
|
|
1863
1950
|
function whereResults(result) {
|
|
1864
1951
|
if (result?.status !== 0) return [];
|
|
1865
|
-
return String(result.stdout || "").split(/\r?\n/u).map((line) => line.trim()).filter((line) =>
|
|
1952
|
+
return String(result.stdout || "").split(/\r?\n/u).map((line) => line.trim()).filter((line) => path4.win32.isAbsolute(line));
|
|
1866
1953
|
}
|
|
1867
1954
|
async function resolveWindowsNativeCommand(command, runner) {
|
|
1868
1955
|
const result = await runner("where", [command], { timeoutMs: 1e4 });
|
|
@@ -1874,7 +1961,7 @@ function trustedCorepackCandidates(options) {
|
|
|
1874
1961
|
const roots = [portableDirname(execPath)];
|
|
1875
1962
|
for (const key of ["ProgramW6432", "ProgramFiles", "ProgramFiles(x86)"]) {
|
|
1876
1963
|
const programFiles = String(env2[key] || "").trim();
|
|
1877
|
-
if (isWindowsDriveOrUnc(programFiles) ||
|
|
1964
|
+
if (isWindowsDriveOrUnc(programFiles) || path4.posix.isAbsolute(programFiles)) {
|
|
1878
1965
|
roots.push(portableJoin(programFiles, "nodejs"));
|
|
1879
1966
|
}
|
|
1880
1967
|
}
|
|
@@ -1953,7 +2040,7 @@ var init_pnpm_command = __esm({
|
|
|
1953
2040
|
|
|
1954
2041
|
// src/runner/pnpm-materialize.mjs
|
|
1955
2042
|
import fsp4 from "node:fs/promises";
|
|
1956
|
-
import
|
|
2043
|
+
import path5 from "node:path";
|
|
1957
2044
|
function linkType() {
|
|
1958
2045
|
return process.platform === "win32" ? "junction" : "dir";
|
|
1959
2046
|
}
|
|
@@ -1965,13 +2052,13 @@ async function realpathOrThrow(target, fsApi) {
|
|
|
1965
2052
|
}
|
|
1966
2053
|
}
|
|
1967
2054
|
function shouldMapToWorktree(resolvedTarget, canonicalRoot) {
|
|
1968
|
-
const relative =
|
|
2055
|
+
const relative = path5.relative(canonicalRoot, resolvedTarget).replace(/\\/g, "/");
|
|
1969
2056
|
return Boolean(relative && !relative.startsWith("..") && !relative.split("/").includes("node_modules"));
|
|
1970
2057
|
}
|
|
1971
2058
|
async function resolveEntryTarget(sourceEntry, canonicalRoot, worktreeRoot, fsApi) {
|
|
1972
2059
|
const resolved = await realpathOrThrow(sourceEntry, fsApi);
|
|
1973
2060
|
if (!shouldMapToWorktree(resolved, canonicalRoot)) return resolved;
|
|
1974
|
-
const mapped =
|
|
2061
|
+
const mapped = path5.join(worktreeRoot, path5.relative(canonicalRoot, resolved));
|
|
1975
2062
|
if (!await fsApi.pathExists(mapped)) {
|
|
1976
2063
|
throw new Error(`[vo-mcp runner] task-local workspace target is missing for dependency link: ${mapped}`);
|
|
1977
2064
|
}
|
|
@@ -1982,7 +2069,7 @@ async function ensureLinkedDirectory(source, target, fsApi) {
|
|
|
1982
2069
|
if (await fsApi.realpath(target) === await fsApi.realpath(source)) return;
|
|
1983
2070
|
throw new Error(`[vo-mcp runner] refusing to overwrite existing dependency path: ${target}`);
|
|
1984
2071
|
}
|
|
1985
|
-
await fsApi.mkdir(
|
|
2072
|
+
await fsApi.mkdir(path5.dirname(target), { recursive: true });
|
|
1986
2073
|
await fsApi.symlink(source, target, linkType());
|
|
1987
2074
|
if (await fsApi.realpath(target) !== await fsApi.realpath(source)) {
|
|
1988
2075
|
throw new Error(`[vo-mcp runner] dependency link validation failed for ${target}`);
|
|
@@ -1996,8 +2083,8 @@ async function maybeYield2(state) {
|
|
|
1996
2083
|
async function copyDirRecursive(sourceDir, targetDir, fsApi, yieldState) {
|
|
1997
2084
|
await fsApi.mkdir(targetDir, { recursive: true });
|
|
1998
2085
|
for (const entry of await fsApi.readdir(sourceDir, { withFileTypes: true })) {
|
|
1999
|
-
const source =
|
|
2000
|
-
const target =
|
|
2086
|
+
const source = path5.join(sourceDir, entry.name);
|
|
2087
|
+
const target = path5.join(targetDir, entry.name);
|
|
2001
2088
|
await maybeYield2(yieldState);
|
|
2002
2089
|
if (entry.isDirectory()) {
|
|
2003
2090
|
await copyDirRecursive(source, target, fsApi, yieldState);
|
|
@@ -2009,17 +2096,17 @@ async function copyDirRecursive(sourceDir, targetDir, fsApi, yieldState) {
|
|
|
2009
2096
|
async function materializeEntry(sourceEntry, targetEntry, canonicalRoot, worktreeRoot, options) {
|
|
2010
2097
|
await options.beforeEntry?.(sourceEntry, targetEntry);
|
|
2011
2098
|
const stat3 = await options.fsApi.lstat(sourceEntry);
|
|
2012
|
-
if (stat3.isDirectory() && !stat3.isSymbolicLink() &&
|
|
2099
|
+
if (stat3.isDirectory() && !stat3.isSymbolicLink() && path5.basename(sourceEntry) === ".bin") {
|
|
2013
2100
|
await copyDirRecursive(sourceEntry, targetEntry, options.fsApi, options.yieldState);
|
|
2014
2101
|
return;
|
|
2015
2102
|
}
|
|
2016
|
-
if (stat3.isDirectory() && !stat3.isSymbolicLink() &&
|
|
2103
|
+
if (stat3.isDirectory() && !stat3.isSymbolicLink() && path5.basename(sourceEntry).startsWith("@")) {
|
|
2017
2104
|
await options.fsApi.mkdir(targetEntry, { recursive: true });
|
|
2018
2105
|
for (const nested of await options.fsApi.readdir(sourceEntry, { withFileTypes: true })) {
|
|
2019
2106
|
await maybeYield2(options.yieldState);
|
|
2020
2107
|
await materializeEntry(
|
|
2021
|
-
|
|
2022
|
-
|
|
2108
|
+
path5.join(sourceEntry, nested.name),
|
|
2109
|
+
path5.join(targetEntry, nested.name),
|
|
2023
2110
|
canonicalRoot,
|
|
2024
2111
|
worktreeRoot,
|
|
2025
2112
|
options
|
|
@@ -2032,7 +2119,7 @@ async function materializeEntry(sourceEntry, targetEntry, canonicalRoot, worktre
|
|
|
2032
2119
|
await ensureLinkedDirectory(resolvedTarget, targetEntry, options.fsApi);
|
|
2033
2120
|
return;
|
|
2034
2121
|
}
|
|
2035
|
-
await options.fsApi.mkdir(
|
|
2122
|
+
await options.fsApi.mkdir(path5.dirname(targetEntry), { recursive: true });
|
|
2036
2123
|
await options.fsApi.copyFile(sourceEntry, targetEntry);
|
|
2037
2124
|
}
|
|
2038
2125
|
function createPnpmFsApi(pathExists6) {
|
|
@@ -2056,8 +2143,8 @@ async function materializeNodeModulesForest(sourceNodeModules, targetNodeModules
|
|
|
2056
2143
|
for (const entry of await options.fsApi.readdir(sourceNodeModules, { withFileTypes: true })) {
|
|
2057
2144
|
await maybeYield2(options.yieldState);
|
|
2058
2145
|
await materializeEntry(
|
|
2059
|
-
|
|
2060
|
-
|
|
2146
|
+
path5.join(sourceNodeModules, entry.name),
|
|
2147
|
+
path5.join(targetNodeModules, entry.name),
|
|
2061
2148
|
canonicalRoot,
|
|
2062
2149
|
worktreeRoot,
|
|
2063
2150
|
options
|
|
@@ -2074,15 +2161,15 @@ var init_pnpm_materialize = __esm({
|
|
|
2074
2161
|
import { createHash as createHash2 } from "node:crypto";
|
|
2075
2162
|
import fs3 from "node:fs";
|
|
2076
2163
|
import fsp5 from "node:fs/promises";
|
|
2077
|
-
import
|
|
2164
|
+
import path6 from "node:path";
|
|
2078
2165
|
function hashText(text) {
|
|
2079
2166
|
return createHash2("sha256").update(String(text)).digest("hex");
|
|
2080
2167
|
}
|
|
2081
2168
|
function statePath(root) {
|
|
2082
|
-
return
|
|
2169
|
+
return path6.join(root, ".agent-worktrees", "runner-pnpm-hydration.json");
|
|
2083
2170
|
}
|
|
2084
2171
|
function packageJson(root) {
|
|
2085
|
-
const packagePath =
|
|
2172
|
+
const packagePath = path6.join(root, "package.json");
|
|
2086
2173
|
if (!fs3.existsSync(packagePath)) return null;
|
|
2087
2174
|
try {
|
|
2088
2175
|
return JSON.parse(fs3.readFileSync(packagePath, "utf8"));
|
|
@@ -2099,7 +2186,7 @@ async function pathExists4(target) {
|
|
|
2099
2186
|
}
|
|
2100
2187
|
}
|
|
2101
2188
|
function lockfileHash(root) {
|
|
2102
|
-
const lockPath =
|
|
2189
|
+
const lockPath = path6.join(root, "pnpm-lock.yaml");
|
|
2103
2190
|
if (!fs3.existsSync(lockPath)) return "";
|
|
2104
2191
|
return hashText(fs3.readFileSync(lockPath, "utf8"));
|
|
2105
2192
|
}
|
|
@@ -2114,7 +2201,7 @@ function readHydrationState(root) {
|
|
|
2114
2201
|
}
|
|
2115
2202
|
function writeHydrationState(root, state) {
|
|
2116
2203
|
const file = statePath(root);
|
|
2117
|
-
fs3.mkdirSync(
|
|
2204
|
+
fs3.mkdirSync(path6.dirname(file), { recursive: true });
|
|
2118
2205
|
fs3.writeFileSync(file, `${JSON.stringify({
|
|
2119
2206
|
...state,
|
|
2120
2207
|
stateVersion: 2,
|
|
@@ -2123,7 +2210,7 @@ function writeHydrationState(root, state) {
|
|
|
2123
2210
|
`, "utf8");
|
|
2124
2211
|
}
|
|
2125
2212
|
function voDepsStatePath(root) {
|
|
2126
|
-
return
|
|
2213
|
+
return path6.join(root, "node_modules", ".vo-deps-state.json");
|
|
2127
2214
|
}
|
|
2128
2215
|
async function ensureVoDepsState(root, expectedHash, fsApi = fsp5) {
|
|
2129
2216
|
const marker = voDepsStatePath(root);
|
|
@@ -2133,7 +2220,7 @@ async function ensureVoDepsState(root, expectedHash, fsApi = fsp5) {
|
|
|
2133
2220
|
} catch {
|
|
2134
2221
|
}
|
|
2135
2222
|
const temp = `${marker}.tmp-${process.pid}-${Date.now()}`;
|
|
2136
|
-
await fsApi.mkdir(
|
|
2223
|
+
await fsApi.mkdir(path6.dirname(marker), { recursive: true });
|
|
2137
2224
|
await fsApi.writeFile(temp, `${JSON.stringify({ lockfileHash: expectedHash, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2)}
|
|
2138
2225
|
`, "utf8");
|
|
2139
2226
|
await fsApi.rename(temp, marker);
|
|
@@ -2160,7 +2247,7 @@ function workspacePatternsFromPackageJson(root) {
|
|
|
2160
2247
|
return [];
|
|
2161
2248
|
}
|
|
2162
2249
|
function workspacePatternsFromPnpmWorkspace(root) {
|
|
2163
|
-
const workspacePath =
|
|
2250
|
+
const workspacePath = path6.join(root, "pnpm-workspace.yaml");
|
|
2164
2251
|
if (!fs3.existsSync(workspacePath)) return [];
|
|
2165
2252
|
const lines = fs3.readFileSync(workspacePath, "utf8").split(/\r?\n/u);
|
|
2166
2253
|
const patterns = [];
|
|
@@ -2219,8 +2306,8 @@ function collectPackageDirs(root, options = {}) {
|
|
|
2219
2306
|
while (stack.length > 0) {
|
|
2220
2307
|
const current = stack.pop();
|
|
2221
2308
|
if (!current) continue;
|
|
2222
|
-
const relativeDir =
|
|
2223
|
-
if (relativeDir && fs3.existsSync(
|
|
2309
|
+
const relativeDir = path6.relative(root, current.dir).replace(/\\/g, "/");
|
|
2310
|
+
if (relativeDir && fs3.existsSync(path6.join(current.dir, "package.json"))) {
|
|
2224
2311
|
found.push(relativeDir);
|
|
2225
2312
|
if (found.length >= maxDirs) break;
|
|
2226
2313
|
}
|
|
@@ -2228,7 +2315,7 @@ function collectPackageDirs(root, options = {}) {
|
|
|
2228
2315
|
for (const entry of fs3.readdirSync(current.dir, { withFileTypes: true })) {
|
|
2229
2316
|
if (!entry.isDirectory()) continue;
|
|
2230
2317
|
if (IGNORED_SCAN_DIRS.has(entry.name)) continue;
|
|
2231
|
-
stack.push({ dir:
|
|
2318
|
+
stack.push({ dir: path6.join(current.dir, entry.name), depth: current.depth + 1 });
|
|
2232
2319
|
}
|
|
2233
2320
|
}
|
|
2234
2321
|
return found.sort();
|
|
@@ -2252,8 +2339,8 @@ async function runInstall(root, options = {}) {
|
|
|
2252
2339
|
}
|
|
2253
2340
|
}
|
|
2254
2341
|
async function hasReadyNodeModules(root) {
|
|
2255
|
-
const nodeModules =
|
|
2256
|
-
return await pathExists4(
|
|
2342
|
+
const nodeModules = path6.join(root, "node_modules");
|
|
2343
|
+
return await pathExists4(path6.join(nodeModules, ".modules.yaml")) || await pathExists4(path6.join(nodeModules, ".pnpm"));
|
|
2257
2344
|
}
|
|
2258
2345
|
function hydrationFsApi(overrides = {}) {
|
|
2259
2346
|
return { ...createPnpmFsApi(pathExists4), ...overrides };
|
|
@@ -2290,7 +2377,7 @@ async function ensurePnpmHydration(root, options = {}) {
|
|
|
2290
2377
|
expectedHash: hash,
|
|
2291
2378
|
requireMarker: false
|
|
2292
2379
|
});
|
|
2293
|
-
if (!health.healthy && await fsApi.pathExists(
|
|
2380
|
+
if (!health.healthy && await fsApi.pathExists(path6.join(root, "node_modules"))) {
|
|
2294
2381
|
quarantine = await quarantineCanonicalNodeModules(root, health.issues, {
|
|
2295
2382
|
fsApi,
|
|
2296
2383
|
logger: options.logger
|
|
@@ -2302,7 +2389,7 @@ async function ensurePnpmHydration(root, options = {}) {
|
|
|
2302
2389
|
}
|
|
2303
2390
|
const linkedWorkspaceDirs = [];
|
|
2304
2391
|
for (const relativeDir of workspaceDirs) {
|
|
2305
|
-
if (await pathExists4(
|
|
2392
|
+
if (await pathExists4(path6.join(root, relativeDir, "node_modules"))) {
|
|
2306
2393
|
linkedWorkspaceDirs.push(relativeDir);
|
|
2307
2394
|
}
|
|
2308
2395
|
}
|
|
@@ -2338,18 +2425,18 @@ async function linkHydratedNodeModules({ root, worktreeDir, hydration, options =
|
|
|
2338
2425
|
yieldEvery: Math.max(1, options.yieldEvery ?? DEFAULT_YIELD_EVERY2)
|
|
2339
2426
|
}
|
|
2340
2427
|
};
|
|
2341
|
-
recordOwnedNodeModulesRoot(dependencyOwnership,
|
|
2428
|
+
recordOwnedNodeModulesRoot(dependencyOwnership, path6.join(worktreeDir, "node_modules"));
|
|
2342
2429
|
await materializeNodeModulesForest(
|
|
2343
|
-
|
|
2344
|
-
|
|
2430
|
+
path6.join(root, "node_modules"),
|
|
2431
|
+
path6.join(worktreeDir, "node_modules"),
|
|
2345
2432
|
root,
|
|
2346
2433
|
worktreeDir,
|
|
2347
2434
|
materializeOptions
|
|
2348
2435
|
);
|
|
2349
2436
|
for (const relativeDir of hydration.linkedWorkspaceDirs) {
|
|
2350
|
-
const sourceNodeModules =
|
|
2351
|
-
const targetNodeModules =
|
|
2352
|
-
if (!await fsApi.pathExists(
|
|
2437
|
+
const sourceNodeModules = path6.join(root, relativeDir, "node_modules");
|
|
2438
|
+
const targetNodeModules = path6.join(worktreeDir, relativeDir, "node_modules");
|
|
2439
|
+
if (!await fsApi.pathExists(path6.join(worktreeDir, relativeDir))) continue;
|
|
2353
2440
|
recordOwnedNodeModulesRoot(dependencyOwnership, targetNodeModules);
|
|
2354
2441
|
await materializeNodeModulesForest(sourceNodeModules, targetNodeModules, root, worktreeDir, materializeOptions);
|
|
2355
2442
|
}
|
|
@@ -2387,28 +2474,28 @@ var init_pnpm_hydration = __esm({
|
|
|
2387
2474
|
|
|
2388
2475
|
// src/runner/worktree-paths.mjs
|
|
2389
2476
|
import { createHash as createHash3 } from "node:crypto";
|
|
2390
|
-
import
|
|
2477
|
+
import path7 from "node:path";
|
|
2391
2478
|
function samePath2(left, right) {
|
|
2392
|
-
const a =
|
|
2393
|
-
const b =
|
|
2479
|
+
const a = path7.resolve(left);
|
|
2480
|
+
const b = path7.resolve(right);
|
|
2394
2481
|
return process.platform === "win32" ? a.toLowerCase() === b.toLowerCase() : a === b;
|
|
2395
2482
|
}
|
|
2396
2483
|
function worktreePoolForRoot(root, { clonesRootDir = process.env.VO_CODE_RUNNER_CLONES_ROOT || "" } = {}) {
|
|
2397
|
-
const canonicalRoot =
|
|
2484
|
+
const canonicalRoot = path7.resolve(root);
|
|
2398
2485
|
if (clonesRootDir) {
|
|
2399
|
-
const clonePool =
|
|
2400
|
-
if (samePath2(
|
|
2401
|
-
return
|
|
2486
|
+
const clonePool = path7.resolve(clonesRootDir);
|
|
2487
|
+
if (samePath2(path7.dirname(canonicalRoot), clonePool)) {
|
|
2488
|
+
return path7.join(clonePool, ".agent-worktrees", path7.basename(canonicalRoot));
|
|
2402
2489
|
}
|
|
2403
2490
|
}
|
|
2404
|
-
return
|
|
2491
|
+
return path7.join(canonicalRoot, ".agent-worktrees");
|
|
2405
2492
|
}
|
|
2406
2493
|
function worktreeDirForName(root, worktreeName, options = {}) {
|
|
2407
2494
|
const leaf = createHash3("sha256").update(String(worktreeName)).digest("hex").slice(0, 16);
|
|
2408
|
-
return
|
|
2495
|
+
return path7.join(worktreePoolForRoot(root, options), leaf);
|
|
2409
2496
|
}
|
|
2410
2497
|
function recoveryLedgerPathForRoot(root, options = {}) {
|
|
2411
|
-
return
|
|
2498
|
+
return path7.join(worktreePoolForRoot(root, options), "recovery-ledger.jsonl");
|
|
2412
2499
|
}
|
|
2413
2500
|
var init_worktree_paths = __esm({
|
|
2414
2501
|
"src/runner/worktree-paths.mjs"() {
|
|
@@ -2419,7 +2506,7 @@ var init_worktree_paths = __esm({
|
|
|
2419
2506
|
// src/runner/worktree-cleanup.mjs
|
|
2420
2507
|
import fs4 from "node:fs";
|
|
2421
2508
|
import fsp6 from "node:fs/promises";
|
|
2422
|
-
import
|
|
2509
|
+
import path8 from "node:path";
|
|
2423
2510
|
function stateFromEntry(entry) {
|
|
2424
2511
|
return {
|
|
2425
2512
|
root: entry.root,
|
|
@@ -2460,14 +2547,14 @@ function pruneSuccessfulStates(nowMs = Date.now()) {
|
|
|
2460
2547
|
function assertTrackedCleanupPath(entry) {
|
|
2461
2548
|
const poolRoot = worktreePoolForRoot(entry.root);
|
|
2462
2549
|
const expected = worktreeDirForName(entry.root, entry.worktreeName);
|
|
2463
|
-
const resolvedPool =
|
|
2464
|
-
const resolvedTarget =
|
|
2465
|
-
if (!resolvedTarget.startsWith(`${resolvedPool}${
|
|
2550
|
+
const resolvedPool = path8.resolve(poolRoot);
|
|
2551
|
+
const resolvedTarget = path8.resolve(entry.worktreeDir);
|
|
2552
|
+
if (!resolvedTarget.startsWith(`${resolvedPool}${path8.sep}`)) {
|
|
2466
2553
|
const error = new Error(`cleanup refused outside managed pool: ${entry.worktreeDir}`);
|
|
2467
2554
|
error.cleanupFatal = true;
|
|
2468
2555
|
throw error;
|
|
2469
2556
|
}
|
|
2470
|
-
if (resolvedTarget !==
|
|
2557
|
+
if (resolvedTarget !== path8.resolve(expected)) {
|
|
2471
2558
|
const error = new Error(`cleanup refused for unexpected tracked path: ${entry.worktreeDir}`);
|
|
2472
2559
|
error.cleanupFatal = true;
|
|
2473
2560
|
throw error;
|
|
@@ -2481,8 +2568,8 @@ async function worktreeStillRegistered2(root, worktreeDir, gitRunner) {
|
|
|
2481
2568
|
if (result.status !== 0) {
|
|
2482
2569
|
throw new Error(`git worktree list --porcelain failed during cleanup verification: ${summarizeProcessFailure(result)}`);
|
|
2483
2570
|
}
|
|
2484
|
-
const registered = String(result.stdout || "").split(/\r?\n/u).filter((line) => line.startsWith("worktree ")).map((line) =>
|
|
2485
|
-
return registered.includes(
|
|
2571
|
+
const registered = String(result.stdout || "").split(/\r?\n/u).filter((line) => line.startsWith("worktree ")).map((line) => path8.resolve(line.slice("worktree ".length).trim()));
|
|
2572
|
+
return registered.includes(path8.resolve(worktreeDir));
|
|
2486
2573
|
}
|
|
2487
2574
|
function cleanupBackoff(attempt) {
|
|
2488
2575
|
return 250 * attempt;
|
|
@@ -2592,7 +2679,7 @@ function scheduleTrackedCleanup(entry, options = {}) {
|
|
|
2592
2679
|
}
|
|
2593
2680
|
function pendingCleanupDirs() {
|
|
2594
2681
|
return new Set(
|
|
2595
|
-
[...CLEANUP_STATES.values()].filter((state) => state.status === "pending").map((state) =>
|
|
2682
|
+
[...CLEANUP_STATES.values()].filter((state) => state.status === "pending").map((state) => path8.resolve(state.worktreeDir))
|
|
2596
2683
|
);
|
|
2597
2684
|
}
|
|
2598
2685
|
var CLEANUP_STATES, CLEANUP_PROMISES, SUCCESS_HISTORY_LIMIT, SUCCESS_HISTORY_TTL_MS, DEFAULT_CLEANUP_ATTEMPTS;
|
|
@@ -2613,13 +2700,13 @@ var init_worktree_cleanup = __esm({
|
|
|
2613
2700
|
// src/runner/task-root-prepare.mjs
|
|
2614
2701
|
import fs5 from "node:fs";
|
|
2615
2702
|
import fsp7 from "node:fs/promises";
|
|
2616
|
-
import
|
|
2703
|
+
import path9 from "node:path";
|
|
2617
2704
|
function prepLockDir(root) {
|
|
2618
|
-
return
|
|
2705
|
+
return path9.join(root, ".agent-worktrees", "runner-root-prep.lock");
|
|
2619
2706
|
}
|
|
2620
2707
|
function readLockMeta(lockDir) {
|
|
2621
2708
|
try {
|
|
2622
|
-
return JSON.parse(fs5.readFileSync(
|
|
2709
|
+
return JSON.parse(fs5.readFileSync(path9.join(lockDir, "owner.json"), "utf8"));
|
|
2623
2710
|
} catch {
|
|
2624
2711
|
return null;
|
|
2625
2712
|
}
|
|
@@ -2639,9 +2726,9 @@ async function acquirePrepLock(root, options = {}) {
|
|
|
2639
2726
|
const staleMs = options.lockStaleMs ?? PREP_LOCK_STALE_MS;
|
|
2640
2727
|
const sleep3 = options.sleep || sleepMs;
|
|
2641
2728
|
const lockDir = prepLockDir(root);
|
|
2642
|
-
const ownerPath =
|
|
2729
|
+
const ownerPath = path9.join(lockDir, "owner.json");
|
|
2643
2730
|
const deadline = nowMs() + waitMs;
|
|
2644
|
-
fs5.mkdirSync(
|
|
2731
|
+
fs5.mkdirSync(path9.dirname(lockDir), { recursive: true });
|
|
2645
2732
|
for (; ; ) {
|
|
2646
2733
|
try {
|
|
2647
2734
|
fs5.mkdirSync(lockDir);
|
|
@@ -2691,16 +2778,16 @@ async function gitText(root, args, options = {}) {
|
|
|
2691
2778
|
}
|
|
2692
2779
|
function canonicalRecoveryDir(root, options = {}) {
|
|
2693
2780
|
const now = options.now || (() => /* @__PURE__ */ new Date());
|
|
2694
|
-
return
|
|
2695
|
-
options.managedPool ||
|
|
2781
|
+
return path9.join(
|
|
2782
|
+
options.managedPool || path9.join(root, ".agent-worktrees"),
|
|
2696
2783
|
".canonical-recovery",
|
|
2697
2784
|
`preexisting-${now().toISOString().replace(/[:.]/gu, "-")}`
|
|
2698
2785
|
);
|
|
2699
2786
|
}
|
|
2700
2787
|
function canonicalPath(root, relative) {
|
|
2701
|
-
const resolvedRoot =
|
|
2702
|
-
const target =
|
|
2703
|
-
const prefix = `${resolvedRoot}${
|
|
2788
|
+
const resolvedRoot = path9.resolve(root);
|
|
2789
|
+
const target = path9.resolve(root, relative);
|
|
2790
|
+
const prefix = `${resolvedRoot}${path9.sep}`;
|
|
2704
2791
|
if (!target.startsWith(prefix)) {
|
|
2705
2792
|
throw new Error(`canonical recovery path escaped the runner clone: ${relative}`);
|
|
2706
2793
|
}
|
|
@@ -2728,7 +2815,7 @@ async function recoverManagedCanonicalResidue(root, options = {}) {
|
|
|
2728
2815
|
if (patchResult.status !== 0) {
|
|
2729
2816
|
throw new Error(`could not preserve canonical tracked changes: ${summarizeProcessFailure(patchResult)}`);
|
|
2730
2817
|
}
|
|
2731
|
-
await fsp7.writeFile(
|
|
2818
|
+
await fsp7.writeFile(path9.join(quarantineDir, "tracked.patch"), String(patchResult.stdout || ""), "utf8");
|
|
2732
2819
|
const symlinks = [];
|
|
2733
2820
|
for (const relative of paths.untracked) {
|
|
2734
2821
|
const source = canonicalPath(root, relative);
|
|
@@ -2740,13 +2827,13 @@ async function recoverManagedCanonicalResidue(root, options = {}) {
|
|
|
2740
2827
|
if (!stat3.isFile()) {
|
|
2741
2828
|
throw new Error(`canonical recovery refuses unsupported untracked entry: ${relative}`);
|
|
2742
2829
|
}
|
|
2743
|
-
const target = canonicalPath(
|
|
2744
|
-
await fsp7.mkdir(
|
|
2830
|
+
const target = canonicalPath(path9.join(quarantineDir, "untracked"), relative);
|
|
2831
|
+
await fsp7.mkdir(path9.dirname(target), { recursive: true });
|
|
2745
2832
|
await fsp7.copyFile(source, target);
|
|
2746
2833
|
}
|
|
2747
|
-
await fsp7.writeFile(
|
|
2834
|
+
await fsp7.writeFile(path9.join(quarantineDir, "manifest.json"), `${JSON.stringify({
|
|
2748
2835
|
recoveredAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2749
|
-
canonicalRoot:
|
|
2836
|
+
canonicalRoot: path9.resolve(root),
|
|
2750
2837
|
canonicalHead: headSha,
|
|
2751
2838
|
tracked: paths.tracked,
|
|
2752
2839
|
untracked: paths.untracked,
|
|
@@ -2809,6 +2896,18 @@ async function alignCanonicalClone(root, options = {}) {
|
|
|
2809
2896
|
}
|
|
2810
2897
|
return { updated: true, headSha, originSha };
|
|
2811
2898
|
}
|
|
2899
|
+
async function fetchExactTaskBase(root, baseSha, options = {}) {
|
|
2900
|
+
if (!/^[0-9a-f]{40}$/u.test(baseSha)) {
|
|
2901
|
+
throw new Error("comparative base SHA must be exact lowercase hex");
|
|
2902
|
+
}
|
|
2903
|
+
const fetched = await git(root, ["fetch", "origin", baseSha], options);
|
|
2904
|
+
if (fetched.status !== 0) {
|
|
2905
|
+
throw new Error(`exact comparative source is unavailable: ${summarizeProcessFailure(fetched)}`);
|
|
2906
|
+
}
|
|
2907
|
+
const resolved = await gitText(root, ["rev-parse", `${baseSha}^{commit}`], options);
|
|
2908
|
+
if (resolved !== baseSha) throw new Error("exact comparative source did not resolve to the requested commit");
|
|
2909
|
+
return resolved;
|
|
2910
|
+
}
|
|
2812
2911
|
function shouldIgnoreManagedEntry(entryName) {
|
|
2813
2912
|
return IGNORED_MANAGED_ENTRIES.has(entryName) || entryName.endsWith(".lock") || entryName.startsWith("runner-");
|
|
2814
2913
|
}
|
|
@@ -2818,11 +2917,11 @@ async function registeredWorktreeDirs(root, options = {}) {
|
|
|
2818
2917
|
throw new Error(`git worktree list --porcelain failed while checking managed residue: ${summarizeProcessFailure(listed)}`);
|
|
2819
2918
|
}
|
|
2820
2919
|
return new Set(
|
|
2821
|
-
String(listed.stdout || "").split(/\r?\n/u).filter((line) => line.startsWith("worktree ")).map((line) =>
|
|
2920
|
+
String(listed.stdout || "").split(/\r?\n/u).filter((line) => line.startsWith("worktree ")).map((line) => path9.resolve(line.slice("worktree ".length).trim()))
|
|
2822
2921
|
);
|
|
2823
2922
|
}
|
|
2824
2923
|
async function reportLegacyResiduals(root, options = {}) {
|
|
2825
|
-
const managedRoot =
|
|
2924
|
+
const managedRoot = path9.join(root, ".agent-worktrees");
|
|
2826
2925
|
if (!fs5.existsSync(managedRoot)) return [];
|
|
2827
2926
|
const registered = await registeredWorktreeDirs(root, options);
|
|
2828
2927
|
const pending = pendingCleanupDirs();
|
|
@@ -2830,7 +2929,7 @@ async function reportLegacyResiduals(root, options = {}) {
|
|
|
2830
2929
|
for (const entry of fs5.readdirSync(managedRoot, { withFileTypes: true })) {
|
|
2831
2930
|
if (!entry.isDirectory()) continue;
|
|
2832
2931
|
if (shouldIgnoreManagedEntry(entry.name)) continue;
|
|
2833
|
-
const absolute =
|
|
2932
|
+
const absolute = path9.resolve(path9.join(managedRoot, entry.name));
|
|
2834
2933
|
if (registered.has(absolute)) continue;
|
|
2835
2934
|
if (pending.has(absolute)) continue;
|
|
2836
2935
|
found.push(absolute);
|
|
@@ -2849,6 +2948,9 @@ async function prepareTaskRoot(root, options = {}) {
|
|
|
2849
2948
|
try {
|
|
2850
2949
|
await reportLegacyResiduals(root, options);
|
|
2851
2950
|
const alignment = await alignCanonicalClone(root, options);
|
|
2951
|
+
if (options.baseSha) {
|
|
2952
|
+
await fetchExactTaskBase(root, options.baseSha, options);
|
|
2953
|
+
}
|
|
2852
2954
|
const hydration = await ensurePnpmHydration(root, {
|
|
2853
2955
|
logger: options.logger,
|
|
2854
2956
|
runner: options.runner || runProcess
|
|
@@ -2899,7 +3001,7 @@ var init_worktree_github_auth = __esm({
|
|
|
2899
3001
|
|
|
2900
3002
|
// src/runner/worktree-recovery-start.mjs
|
|
2901
3003
|
import fsp8 from "node:fs/promises";
|
|
2902
|
-
import
|
|
3004
|
+
import path10 from "node:path";
|
|
2903
3005
|
async function recordWorktreeStarted(worktreeTarget, meta = {}) {
|
|
2904
3006
|
if (!worktreeTarget?.worktreeName || !meta.taskId) return null;
|
|
2905
3007
|
const entry = {
|
|
@@ -2914,7 +3016,7 @@ async function recordWorktreeStarted(worktreeTarget, meta = {}) {
|
|
|
2914
3016
|
reason: "worktree allocated before execution"
|
|
2915
3017
|
};
|
|
2916
3018
|
const ledger = recoveryLedgerPathForRoot(worktreeTarget.root || process.cwd());
|
|
2917
|
-
await fsp8.mkdir(
|
|
3019
|
+
await fsp8.mkdir(path10.dirname(ledger), { recursive: true });
|
|
2918
3020
|
await fsp8.appendFile(ledger, `${JSON.stringify(entry)}
|
|
2919
3021
|
`, "utf8");
|
|
2920
3022
|
return { entry, ledger };
|
|
@@ -2929,7 +3031,7 @@ var init_worktree_recovery_start = __esm({
|
|
|
2929
3031
|
// src/runner/worktree-helper.mjs
|
|
2930
3032
|
import fs6 from "node:fs";
|
|
2931
3033
|
import fsp9 from "node:fs/promises";
|
|
2932
|
-
import
|
|
3034
|
+
import path11 from "node:path";
|
|
2933
3035
|
function repoRoot() {
|
|
2934
3036
|
return process.env.VO_CODE_RUNNER_REPO || process.cwd();
|
|
2935
3037
|
}
|
|
@@ -2945,7 +3047,7 @@ function cloneDirForSlug(repoSlug, clonesRootDir) {
|
|
|
2945
3047
|
const [owner, name] = String(repoSlug).split("/");
|
|
2946
3048
|
if (owner === "." || owner === ".." || name === "." || name === "..") return null;
|
|
2947
3049
|
if (owner.startsWith("-") || name.startsWith("-")) return null;
|
|
2948
|
-
return
|
|
3050
|
+
return path11.join(clonesRootDir, `${sanitize(owner, "owner")}__${sanitize(name, "repo")}`);
|
|
2949
3051
|
}
|
|
2950
3052
|
function cloneLockDir(dir) {
|
|
2951
3053
|
return `${dir}.clone-lock`;
|
|
@@ -2960,7 +3062,7 @@ async function pathExists5(target) {
|
|
|
2960
3062
|
}
|
|
2961
3063
|
async function readLockMeta2(lockDir) {
|
|
2962
3064
|
try {
|
|
2963
|
-
return JSON.parse(await fsp9.readFile(
|
|
3065
|
+
return JSON.parse(await fsp9.readFile(path11.join(lockDir, "owner.json"), "utf8"));
|
|
2964
3066
|
} catch {
|
|
2965
3067
|
return null;
|
|
2966
3068
|
}
|
|
@@ -2981,11 +3083,11 @@ async function acquireCloneLock(dir, options = {}) {
|
|
|
2981
3083
|
const sleep3 = options.sleep || sleepMs;
|
|
2982
3084
|
const lockDir = cloneLockDir(dir);
|
|
2983
3085
|
const deadline = nowMs() + waitMs;
|
|
2984
|
-
await fsp9.mkdir(
|
|
3086
|
+
await fsp9.mkdir(path11.dirname(lockDir), { recursive: true });
|
|
2985
3087
|
for (; ; ) {
|
|
2986
3088
|
try {
|
|
2987
3089
|
await fsp9.mkdir(lockDir);
|
|
2988
|
-
await fsp9.writeFile(
|
|
3090
|
+
await fsp9.writeFile(path11.join(lockDir, "owner.json"), `${JSON.stringify({
|
|
2989
3091
|
pid: process.pid,
|
|
2990
3092
|
createdAt: new Date(nowMs()).toISOString(),
|
|
2991
3093
|
dir
|
|
@@ -3014,7 +3116,7 @@ async function acquireCloneLock(dir, options = {}) {
|
|
|
3014
3116
|
}
|
|
3015
3117
|
}
|
|
3016
3118
|
async function isUsableGitClone(dir, runner = runProcess) {
|
|
3017
|
-
if (!await pathExists5(
|
|
3119
|
+
if (!await pathExists5(path11.join(dir, ".git"))) return false;
|
|
3018
3120
|
const result = await runner("git", ["-C", dir, "rev-parse", "HEAD"], { timeoutMs: 1e4 });
|
|
3019
3121
|
return result.status === 0 && Boolean(String(result.stdout || "").trim());
|
|
3020
3122
|
}
|
|
@@ -3035,7 +3137,7 @@ async function ensureUsableClone(repoSlug, dir, options = {}) {
|
|
|
3035
3137
|
const maxAttempts = options.maxAttempts || 5;
|
|
3036
3138
|
const raceWaitMs = options.raceWaitMs ?? 1e4;
|
|
3037
3139
|
let lastError = null;
|
|
3038
|
-
await fsp9.mkdir(
|
|
3140
|
+
await fsp9.mkdir(path11.dirname(dir), { recursive: true });
|
|
3039
3141
|
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
3040
3142
|
if (await pathExists5(dir)) {
|
|
3041
3143
|
if (await waitForUsableClone(dir, runner, sleep3, raceWaitMs)) return dir;
|
|
@@ -3047,7 +3149,7 @@ async function ensureUsableClone(repoSlug, dir, options = {}) {
|
|
|
3047
3149
|
["clone", "--no-tags", `https://github.com/${owner}/${name}.git`, tmpDir],
|
|
3048
3150
|
{ timeoutMs: 6e5, env: githubGitAuthEnv(options.githubToken) }
|
|
3049
3151
|
);
|
|
3050
|
-
if (clone.status !== 0 || !await pathExists5(
|
|
3152
|
+
if (clone.status !== 0 || !await pathExists5(path11.join(tmpDir, ".git"))) {
|
|
3051
3153
|
await fsp9.rm(tmpDir, { recursive: true, force: true });
|
|
3052
3154
|
lastError = new Error(`[vo-mcp runner] clone failed for ${repoSlug}: ${describeGitFailure(clone)}`);
|
|
3053
3155
|
continue;
|
|
@@ -3074,7 +3176,7 @@ async function ensureUsableClone(repoSlug, dir, options = {}) {
|
|
|
3074
3176
|
}
|
|
3075
3177
|
async function resolveTaskRoot(repoSlug, options = {}) {
|
|
3076
3178
|
const root = clonesRoot();
|
|
3077
|
-
if (root && !
|
|
3179
|
+
if (root && !path11.isAbsolute(root)) {
|
|
3078
3180
|
throw new Error(`[vo-mcp runner] VO_CODE_RUNNER_CLONES_ROOT must be an absolute path (got '${root}')`);
|
|
3079
3181
|
}
|
|
3080
3182
|
const dir = cloneDirForSlug(repoSlug, root);
|
|
@@ -3123,10 +3225,16 @@ async function createFixWorktree(kind, error = {}, options = {}) {
|
|
|
3123
3225
|
const dependencyOwnership = createDependencyOwnershipTracker(worktreeDir);
|
|
3124
3226
|
const prep = await prepare(root, {
|
|
3125
3227
|
recoverDirtyCanonical: multiRepo,
|
|
3126
|
-
managedPool:
|
|
3228
|
+
managedPool: path11.dirname(worktreeDir),
|
|
3229
|
+
baseSha: options.baseSha || null
|
|
3127
3230
|
});
|
|
3128
3231
|
await processRunner("git", ["config", "core.longpaths", "true"], { cwd: root, timeoutMs: 3e4 });
|
|
3129
|
-
const add = await addWorktree({
|
|
3232
|
+
const add = await addWorktree({
|
|
3233
|
+
root,
|
|
3234
|
+
branchName,
|
|
3235
|
+
worktreeDir,
|
|
3236
|
+
baseRef: options.baseSha || "origin/main"
|
|
3237
|
+
});
|
|
3130
3238
|
if (!add.ok) {
|
|
3131
3239
|
const detail = describeGitFailure(add.result);
|
|
3132
3240
|
if (multiRepo) {
|
|
@@ -3189,7 +3297,7 @@ function preserveFailedWorktree(worktreeName, meta = {}) {
|
|
|
3189
3297
|
};
|
|
3190
3298
|
try {
|
|
3191
3299
|
const ledger = recoveryLedgerPathForRoot(root);
|
|
3192
|
-
fs6.mkdirSync(
|
|
3300
|
+
fs6.mkdirSync(path11.dirname(ledger), { recursive: true });
|
|
3193
3301
|
fs6.appendFileSync(ledger, `${JSON.stringify(entry)}
|
|
3194
3302
|
`, "utf8");
|
|
3195
3303
|
console.error(`[vo-mcp runner] Preserved worktree ${worktreeName} (reason: ${entry.reason})`);
|
|
@@ -3235,9 +3343,9 @@ function partitionRecoveryLedger(lines, { nowMs, ttlMs = PRESERVED_WORKTREE_TTL_
|
|
|
3235
3343
|
return decisions;
|
|
3236
3344
|
}
|
|
3237
3345
|
function isManagedPreservedDir(dir, root = repoRoot()) {
|
|
3238
|
-
const normalized =
|
|
3239
|
-
const pool =
|
|
3240
|
-
return normalized.startsWith(pool +
|
|
3346
|
+
const normalized = path11.resolve(String(dir || ""));
|
|
3347
|
+
const pool = path11.resolve(worktreePoolForRoot(root));
|
|
3348
|
+
return normalized.startsWith(pool + path11.sep);
|
|
3241
3349
|
}
|
|
3242
3350
|
function mergeAppendedSinceRead(originalRaw, currentRaw, keptLines) {
|
|
3243
3351
|
const originalSet = new Set(String(originalRaw || "").split(/\r?\n/u).map((l) => l.trim()).filter(Boolean));
|
|
@@ -3762,13 +3870,13 @@ async function getTaskKnowledgeContextRequest(req, taskId, { query } = {}, {
|
|
|
3762
3870
|
} = {}) {
|
|
3763
3871
|
const body = {};
|
|
3764
3872
|
if (typeof query === "string" && query.trim()) body.query = query;
|
|
3765
|
-
const
|
|
3873
|
+
const path23 = `/api/v1/code-task/${encodeURIComponent(taskId)}/knowledge-context`;
|
|
3766
3874
|
const timeoutMs = Math.max(Number(taskRequestTimeoutMs) || 0, MIN_KNOWLEDGE_CONTEXT_TIMEOUT_MS);
|
|
3767
3875
|
for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt += 1) {
|
|
3768
3876
|
let res;
|
|
3769
3877
|
let cause;
|
|
3770
3878
|
try {
|
|
3771
|
-
res = await req("POST",
|
|
3879
|
+
res = await req("POST", path23, body, { timeoutMs });
|
|
3772
3880
|
} catch (err) {
|
|
3773
3881
|
cause = err;
|
|
3774
3882
|
}
|
|
@@ -3835,10 +3943,10 @@ async function getPreparedJobRequest(req, taskId, options = {}, invalidateToken
|
|
|
3835
3943
|
if (typeof taskId !== "string" || taskId.length === 0) {
|
|
3836
3944
|
return { ok: false, reason: "missing_task_id", status: 0, ...envMeta };
|
|
3837
3945
|
}
|
|
3838
|
-
const
|
|
3946
|
+
const path23 = `/api/v1/code-task/${encodeURIComponent(taskId)}/prepared-job?${query}`;
|
|
3839
3947
|
let res;
|
|
3840
3948
|
try {
|
|
3841
|
-
res = await req("GET",
|
|
3949
|
+
res = await req("GET", path23, void 0, { timeoutMs });
|
|
3842
3950
|
} catch (err) {
|
|
3843
3951
|
return { ok: false, reason: `transport: ${err?.message || String(err)}`, status: 0, ...envMeta };
|
|
3844
3952
|
}
|
|
@@ -3937,11 +4045,11 @@ function createControlPlaneClient({
|
|
|
3937
4045
|
const resolvedBaseUrl = baseUrl ?? env2.VO_CONTROL_PLANE_URL ?? "";
|
|
3938
4046
|
if (!resolvedBaseUrl) throw new Error("VO_CONTROL_PLANE_URL is required for the code-runner daemon");
|
|
3939
4047
|
const root = resolvedBaseUrl.replace(/\/+$/, "");
|
|
3940
|
-
async function req(method,
|
|
4048
|
+
async function req(method, path23, body, { timeoutMs } = {}) {
|
|
3941
4049
|
const bearer = await resolveBearer(env2);
|
|
3942
4050
|
const controller = timeoutMs ? new AbortController() : null;
|
|
3943
4051
|
let timeoutId;
|
|
3944
|
-
const request = Promise.resolve(fetchImpl(`${root}${
|
|
4052
|
+
const request = Promise.resolve(fetchImpl(`${root}${path23}`, {
|
|
3945
4053
|
method,
|
|
3946
4054
|
headers: {
|
|
3947
4055
|
"content-type": "application/json",
|
|
@@ -3954,7 +4062,7 @@ function createControlPlaneClient({
|
|
|
3954
4062
|
const timeout = new Promise((_, reject) => {
|
|
3955
4063
|
timeoutId = setTimeout(() => {
|
|
3956
4064
|
controller.abort();
|
|
3957
|
-
reject(new Error(`control-plane ${
|
|
4065
|
+
reject(new Error(`control-plane ${path23} timed out after ${timeoutMs}ms`));
|
|
3958
4066
|
}, timeoutMs);
|
|
3959
4067
|
});
|
|
3960
4068
|
try {
|
|
@@ -3963,7 +4071,7 @@ function createControlPlaneClient({
|
|
|
3963
4071
|
clearTimeout(timeoutId);
|
|
3964
4072
|
}
|
|
3965
4073
|
}
|
|
3966
|
-
const taskReq = (method,
|
|
4074
|
+
const taskReq = (method, path23, body, options = {}) => req(method, path23, body, { timeoutMs: taskRequestTimeoutMs, ...options });
|
|
3967
4075
|
const claimGate = makeClaimGateNotice({ log: (m) => console.warn(`[code-runner ${(/* @__PURE__ */ new Date()).toISOString()}] ${m}`) });
|
|
3968
4076
|
return {
|
|
3969
4077
|
getClaimGate: () => claimGate.current(),
|
|
@@ -4107,8 +4215,8 @@ function createControlPlaneClient({
|
|
|
4107
4215
|
return listAllPrOpenedTasks(taskReq);
|
|
4108
4216
|
},
|
|
4109
4217
|
async downloadTaskAttachment(taskId, attachmentId) {
|
|
4110
|
-
const
|
|
4111
|
-
const res = await taskReq("GET",
|
|
4218
|
+
const path23 = `/api/v1/code-task/${encodeURIComponent(taskId)}/attachment/${encodeURIComponent(attachmentId)}`;
|
|
4219
|
+
const res = await taskReq("GET", path23);
|
|
4112
4220
|
if (res.status === 401) cachedFirebaseToken = null;
|
|
4113
4221
|
if (!res.ok) throw new Error(`attachment download failed: HTTP ${res.status}`);
|
|
4114
4222
|
return Buffer.from(await res.arrayBuffer());
|
|
@@ -4266,7 +4374,7 @@ var init_control_plane_client = __esm({
|
|
|
4266
4374
|
|
|
4267
4375
|
// ../../scripts/virtual-office/code-runner/windows-claude-launch.mjs
|
|
4268
4376
|
import { existsSync as existsSync8, realpathSync } from "node:fs";
|
|
4269
|
-
import { win32 as
|
|
4377
|
+
import { win32 as path12 } from "node:path";
|
|
4270
4378
|
import { spawnSync } from "node:child_process";
|
|
4271
4379
|
function pathValue(env2) {
|
|
4272
4380
|
for (const key of ["Path", "PATH", "path"]) {
|
|
@@ -4287,38 +4395,38 @@ function envValue(env2, name) {
|
|
|
4287
4395
|
function userClaudeCandidates(bin, env2) {
|
|
4288
4396
|
if (!/^claude(?:\.(?:exe|cmd|ps1))?$/iu.test(bin)) return [];
|
|
4289
4397
|
const userProfile = envValue(env2, "USERPROFILE");
|
|
4290
|
-
const appData = envValue(env2, "APPDATA") || (userProfile ?
|
|
4291
|
-
const localAppData = envValue(env2, "LOCALAPPDATA") || (userProfile ?
|
|
4398
|
+
const appData = envValue(env2, "APPDATA") || (userProfile ? path12.join(userProfile, "AppData", "Roaming") : "");
|
|
4399
|
+
const localAppData = envValue(env2, "LOCALAPPDATA") || (userProfile ? path12.join(userProfile, "AppData", "Local") : "");
|
|
4292
4400
|
const candidates = [];
|
|
4293
4401
|
if (appData) {
|
|
4294
|
-
const npmBin =
|
|
4402
|
+
const npmBin = path12.join(appData, "npm");
|
|
4295
4403
|
candidates.push(
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
|
|
4299
|
-
|
|
4300
|
-
|
|
4404
|
+
path12.join(npmBin, "claude.exe"),
|
|
4405
|
+
path12.join(npmBin, "claude.cmd"),
|
|
4406
|
+
path12.join(npmBin, "claude.ps1"),
|
|
4407
|
+
path12.join(npmBin, "claude"),
|
|
4408
|
+
path12.join(npmBin, ...NATIVE_CLAUDE_PARTS)
|
|
4301
4409
|
);
|
|
4302
4410
|
}
|
|
4303
|
-
if (userProfile) candidates.push(
|
|
4411
|
+
if (userProfile) candidates.push(path12.join(userProfile, ".local", "bin", "claude.exe"));
|
|
4304
4412
|
if (localAppData) {
|
|
4305
4413
|
candidates.push(
|
|
4306
|
-
|
|
4307
|
-
|
|
4414
|
+
path12.join(localAppData, "Microsoft", "WinGet", "Links", "claude.exe"),
|
|
4415
|
+
path12.join(localAppData, "Microsoft", "WindowsApps", "claude.exe")
|
|
4308
4416
|
);
|
|
4309
4417
|
}
|
|
4310
4418
|
return candidates;
|
|
4311
4419
|
}
|
|
4312
4420
|
function pathCandidates(bin, env2) {
|
|
4313
|
-
if (
|
|
4314
|
-
return [
|
|
4315
|
-
}
|
|
4316
|
-
const extension =
|
|
4317
|
-
const fromPath = pathValue(env2).split(";").map(cleanPathSegment).filter(Boolean).flatMap((directory) => extension ? [
|
|
4318
|
-
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
|
|
4421
|
+
if (path12.isAbsolute(bin) || /[\\/]/u.test(bin)) {
|
|
4422
|
+
return [path12.resolve(bin)];
|
|
4423
|
+
}
|
|
4424
|
+
const extension = path12.extname(bin);
|
|
4425
|
+
const fromPath = pathValue(env2).split(";").map(cleanPathSegment).filter(Boolean).flatMap((directory) => extension ? [path12.join(directory, bin)] : [
|
|
4426
|
+
path12.join(directory, `${bin}.exe`),
|
|
4427
|
+
path12.join(directory, `${bin}.cmd`),
|
|
4428
|
+
path12.join(directory, `${bin}.ps1`),
|
|
4429
|
+
path12.join(directory, bin)
|
|
4322
4430
|
]);
|
|
4323
4431
|
const seen = /* @__PURE__ */ new Set();
|
|
4324
4432
|
return [...fromPath, ...userClaudeCandidates(bin, env2)].filter((candidate) => {
|
|
@@ -4349,8 +4457,8 @@ function resolveWindowsClaudeExecutable({
|
|
|
4349
4457
|
for (const candidate of pathCandidates(requested, env2)) {
|
|
4350
4458
|
const found = canonicalExistingPath(candidate, exists, canonicalize);
|
|
4351
4459
|
if (!found) continue;
|
|
4352
|
-
if (
|
|
4353
|
-
const native =
|
|
4460
|
+
if (path12.extname(found).toLowerCase() === ".exe") return found;
|
|
4461
|
+
const native = path12.join(path12.dirname(found), ...NATIVE_CLAUDE_PARTS);
|
|
4354
4462
|
const resolvedNative = canonicalExistingPath(native, exists, canonicalize);
|
|
4355
4463
|
if (resolvedNative) return resolvedNative;
|
|
4356
4464
|
}
|
|
@@ -4611,6 +4719,7 @@ function finalizeAgentTaskResult({
|
|
|
4611
4719
|
result,
|
|
4612
4720
|
bin,
|
|
4613
4721
|
stderrTail,
|
|
4722
|
+
classifyStderr = null,
|
|
4614
4723
|
timedOut,
|
|
4615
4724
|
killed,
|
|
4616
4725
|
cancelReason,
|
|
@@ -4641,8 +4750,16 @@ function finalizeAgentTaskResult({
|
|
|
4641
4750
|
if (code !== 0 || signal) summary = fallback || `${bin} exited ${signal || code}`;
|
|
4642
4751
|
else summary = fallback || `${bin} exited without terminal result`;
|
|
4643
4752
|
}
|
|
4753
|
+
let failure = result.failure ?? null;
|
|
4754
|
+
if (!failure && !sawTerminalResult && (code !== 0 || signal) && typeof classifyStderr === "function") {
|
|
4755
|
+
try {
|
|
4756
|
+
failure = classifyStderr(stderrTail);
|
|
4757
|
+
} catch {
|
|
4758
|
+
}
|
|
4759
|
+
}
|
|
4644
4760
|
return {
|
|
4645
4761
|
...result,
|
|
4762
|
+
...failure ? { failure } : {},
|
|
4646
4763
|
ok: sawTerminalResult && result.ok && (code === 0 || forcedAfterResult),
|
|
4647
4764
|
summary: augmentSummary(summary)
|
|
4648
4765
|
};
|
|
@@ -4847,12 +4964,12 @@ var init_terminal_process_cleanup = __esm({
|
|
|
4847
4964
|
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
4848
4965
|
import { existsSync as existsSync9, mkdirSync as mkdirSync7, readdirSync as readdirSync2, readFileSync as readFileSync7, rmSync as rmSync2, writeFileSync as writeFileSync5 } from "node:fs";
|
|
4849
4966
|
import os from "node:os";
|
|
4850
|
-
import
|
|
4967
|
+
import path13 from "node:path";
|
|
4851
4968
|
function registryRoot(tmp = os.tmpdir()) {
|
|
4852
|
-
return
|
|
4969
|
+
return path13.join(tmp, REGISTRY_ROOT_NAME);
|
|
4853
4970
|
}
|
|
4854
4971
|
function instanceDir(root, instanceId) {
|
|
4855
|
-
return
|
|
4972
|
+
return path13.join(root, String(instanceId).replace(/[^A-Za-z0-9_-]/g, ""));
|
|
4856
4973
|
}
|
|
4857
4974
|
function registerDaemonInstance({
|
|
4858
4975
|
root = registryRoot(),
|
|
@@ -4863,7 +4980,7 @@ function registerDaemonInstance({
|
|
|
4863
4980
|
if (!instanceId) return null;
|
|
4864
4981
|
const dir = instanceDir(root, instanceId);
|
|
4865
4982
|
mkdirSync7(dir, { recursive: true });
|
|
4866
|
-
const file =
|
|
4983
|
+
const file = path13.join(dir, DAEMON_RECORD);
|
|
4867
4984
|
writeFileSync5(file, JSON.stringify({ daemonPid, daemonStartedAtMs, instanceId }), {
|
|
4868
4985
|
encoding: "utf8",
|
|
4869
4986
|
mode: 384
|
|
@@ -4882,7 +4999,7 @@ function recordAgentPid({
|
|
|
4882
4999
|
const dir = instanceDir(root, instanceId);
|
|
4883
5000
|
mkdirSync7(dir, { recursive: true });
|
|
4884
5001
|
writeFileSync5(
|
|
4885
|
-
|
|
5002
|
+
path13.join(dir, `${pid}.json`),
|
|
4886
5003
|
JSON.stringify({ pid, agentId, startedAtMs, instanceId }),
|
|
4887
5004
|
{ encoding: "utf8", mode: 384 }
|
|
4888
5005
|
);
|
|
@@ -4898,7 +5015,7 @@ function unrecordAgentPid({
|
|
|
4898
5015
|
} = {}) {
|
|
4899
5016
|
if (!instanceId || !Number.isInteger(pid)) return false;
|
|
4900
5017
|
try {
|
|
4901
|
-
rmSync2(
|
|
5018
|
+
rmSync2(path13.join(instanceDir(root, instanceId), `${pid}.json`), { force: true });
|
|
4902
5019
|
return true;
|
|
4903
5020
|
} catch {
|
|
4904
5021
|
return false;
|
|
@@ -4926,7 +5043,7 @@ function readRegistry({ root = registryRoot(), currentInstanceId } = {}) {
|
|
|
4926
5043
|
for (const dirent of dirents) {
|
|
4927
5044
|
if (!dirent.isDirectory()) continue;
|
|
4928
5045
|
if (currentInstanceId && dirent.name === instanceDirName(currentInstanceId)) continue;
|
|
4929
|
-
const dir =
|
|
5046
|
+
const dir = path13.join(root, dirent.name);
|
|
4930
5047
|
let daemon = null;
|
|
4931
5048
|
const agents = [];
|
|
4932
5049
|
let files;
|
|
@@ -4938,7 +5055,7 @@ function readRegistry({ root = registryRoot(), currentInstanceId } = {}) {
|
|
|
4938
5055
|
for (const name of files) {
|
|
4939
5056
|
let parsed;
|
|
4940
5057
|
try {
|
|
4941
|
-
parsed = JSON.parse(readFileSync7(
|
|
5058
|
+
parsed = JSON.parse(readFileSync7(path13.join(dir, name), "utf8"));
|
|
4942
5059
|
} catch {
|
|
4943
5060
|
continue;
|
|
4944
5061
|
}
|
|
@@ -4986,7 +5103,7 @@ function windowsSystemRoot(env2 = process.env) {
|
|
|
4986
5103
|
return env2.SystemRoot || env2.WINDIR || "C:\\Windows";
|
|
4987
5104
|
}
|
|
4988
5105
|
function windowsPowershellExe(env2 = process.env) {
|
|
4989
|
-
return
|
|
5106
|
+
return path13.join(windowsSystemRoot(env2), "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
|
|
4990
5107
|
}
|
|
4991
5108
|
function listProcessCreationTimes({ platform: platform4 = process.platform, spawn: spawn5 = spawnSync5, env: env2 = process.env, warn = console.warn } = {}) {
|
|
4992
5109
|
const map = /* @__PURE__ */ new Map();
|
|
@@ -5031,7 +5148,7 @@ function parsePosixPsLine(line) {
|
|
|
5031
5148
|
function killProcessTree(pid, { platform: platform4 = process.platform, spawn: spawn5 = spawnSync5, env: env2 = process.env } = {}) {
|
|
5032
5149
|
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
5033
5150
|
if (platform4 === "win32") {
|
|
5034
|
-
const taskkill =
|
|
5151
|
+
const taskkill = path13.join(windowsSystemRoot(env2), "System32", "taskkill.exe");
|
|
5035
5152
|
const r = spawn5(taskkill, ["/PID", String(pid), "/T", "/F"], { windowsHide: true, stdio: "ignore", timeout: 15e3 });
|
|
5036
5153
|
return !r.error && r.status === 0;
|
|
5037
5154
|
}
|
|
@@ -5567,7 +5684,8 @@ function runAgentTask({
|
|
|
5567
5684
|
// parser would work, the runner would report null, and the feature
|
|
5568
5685
|
// would measure nothing while every test passed.
|
|
5569
5686
|
tokenUsage: evt.tokenUsage ?? result.tokenUsage ?? null,
|
|
5570
|
-
modelUsage: evt.modelUsage ?? result.modelUsage ?? null
|
|
5687
|
+
modelUsage: evt.modelUsage ?? result.modelUsage ?? null,
|
|
5688
|
+
failure: evt.failure ?? result.failure ?? null
|
|
5571
5689
|
};
|
|
5572
5690
|
terminalCleanupTimer ??= armTerminalCleanup({
|
|
5573
5691
|
child,
|
|
@@ -5596,6 +5714,7 @@ function runAgentTask({
|
|
|
5596
5714
|
result,
|
|
5597
5715
|
bin,
|
|
5598
5716
|
stderrTail,
|
|
5717
|
+
classifyStderr: runner.classifyStderr,
|
|
5599
5718
|
timedOut,
|
|
5600
5719
|
killed,
|
|
5601
5720
|
cancelReason,
|
|
@@ -5720,26 +5839,15 @@ var init_claude_runner = __esm({
|
|
|
5720
5839
|
if (process.platform !== "win32") return { bin, args, spawnOptions: spawnOptions ?? this.getSpawnOptions() };
|
|
5721
5840
|
return buildWindowsClaudeLaunch({ bin, args, env: env2 });
|
|
5722
5841
|
}
|
|
5723
|
-
/**
|
|
5724
|
-
* Fill ANTHROPIC_API_KEY from the OS keychain when not already set (M4 BYO),
|
|
5725
|
-
* so a friend who ran `vo-mcp set-key` authenticates without an env var.
|
|
5726
|
-
* Explicit env wins; no key stored → unchanged (Claude Code login as before).
|
|
5727
|
-
*/
|
|
5728
5842
|
applyAuthEnv(env2 = process.env) {
|
|
5729
5843
|
return withAnthropicKey(env2);
|
|
5730
5844
|
}
|
|
5731
5845
|
costBasis(env2 = process.env) {
|
|
5732
5846
|
return claudeCostBasis(env2);
|
|
5733
5847
|
}
|
|
5734
|
-
/** Describe which Anthropic auth source the spawn will use (for runner logs). */
|
|
5735
5848
|
describeAuth(env2 = process.env) {
|
|
5736
5849
|
return describeAnthropicAuthSource(env2);
|
|
5737
5850
|
}
|
|
5738
|
-
/**
|
|
5739
|
-
* Best-effort auth check: is `claude` on PATH and can we verify login?
|
|
5740
|
-
* Never throws. If we can't cheaply detect auth, we return installed:true
|
|
5741
|
-
* and let the real spawn fail with a clearer error from the CLI itself.
|
|
5742
|
-
*/
|
|
5743
5851
|
async checkAuth() {
|
|
5744
5852
|
return checkClaudeAuth();
|
|
5745
5853
|
}
|
|
@@ -5865,6 +5973,66 @@ var init_flat_token_usage = __esm({
|
|
|
5865
5973
|
}
|
|
5866
5974
|
});
|
|
5867
5975
|
|
|
5976
|
+
// ../../scripts/virtual-office/code-runner/error-message.mjs
|
|
5977
|
+
function boundedErrorMessage(error, maxLength = 240) {
|
|
5978
|
+
try {
|
|
5979
|
+
return String(error?.message ?? error ?? "unknown error").slice(0, maxLength);
|
|
5980
|
+
} catch {
|
|
5981
|
+
return "unknown error";
|
|
5982
|
+
}
|
|
5983
|
+
}
|
|
5984
|
+
function codexFailure(code, fields = {}) {
|
|
5985
|
+
const definition = FAILURE_DEFINITIONS[code];
|
|
5986
|
+
return {
|
|
5987
|
+
schema: FAILURE_SCHEMA,
|
|
5988
|
+
agent: "codex",
|
|
5989
|
+
source: definition.source,
|
|
5990
|
+
code,
|
|
5991
|
+
...fields,
|
|
5992
|
+
operator_next_action: definition.operator_next_action
|
|
5993
|
+
};
|
|
5994
|
+
}
|
|
5995
|
+
function classifyCodexTerminalFailure(evt = {}) {
|
|
5996
|
+
const error = evt && typeof evt.error === "object" && evt.error !== null ? evt.error : null;
|
|
5997
|
+
if (!/^The ['"][^'"]+['"] model is not supported when using Codex with a ChatGPT account\.$/u.test(
|
|
5998
|
+
typeof error?.message === "string" ? error.message : ""
|
|
5999
|
+
)) return null;
|
|
6000
|
+
const status = Number(evt.status ?? error.status);
|
|
6001
|
+
return codexFailure("model_unsupported_for_account", Number.isInteger(status) ? { provider_status: status } : {});
|
|
6002
|
+
}
|
|
6003
|
+
function classifyCodexStderr(stderr = "") {
|
|
6004
|
+
if (!/(?:^|\n)\s*windows sandbox(?: failed)?: runner error:.*\bCreateProcessAsUserW failed:\s*1312\b/imu.test(String(stderr))) return null;
|
|
6005
|
+
return codexFailure("codex_runtime_launch_logon_session", { win32_error: 1312 });
|
|
6006
|
+
}
|
|
6007
|
+
function serializeRunnerFailure(record, maxLength = 2e3) {
|
|
6008
|
+
const definition = record && FAILURE_DEFINITIONS[record.code];
|
|
6009
|
+
if (!definition || record.schema !== FAILURE_SCHEMA || record.source !== definition.source) return null;
|
|
6010
|
+
const value = codexFailure(record.code, {
|
|
6011
|
+
...record.code === "model_unsupported_for_account" && Number.isInteger(record.provider_status) ? { provider_status: record.provider_status } : {},
|
|
6012
|
+
...definition.win32_error ? { win32_error: definition.win32_error } : {}
|
|
6013
|
+
});
|
|
6014
|
+
const serialized2 = JSON.stringify(value);
|
|
6015
|
+
return serialized2.length <= Math.max(0, maxLength) ? serialized2 : null;
|
|
6016
|
+
}
|
|
6017
|
+
var FAILURE_SCHEMA, FAILURE_DEFINITIONS;
|
|
6018
|
+
var init_error_message = __esm({
|
|
6019
|
+
"../../scripts/virtual-office/code-runner/error-message.mjs"() {
|
|
6020
|
+
"use strict";
|
|
6021
|
+
FAILURE_SCHEMA = "vo.runner_failure.v1";
|
|
6022
|
+
FAILURE_DEFINITIONS = {
|
|
6023
|
+
model_unsupported_for_account: {
|
|
6024
|
+
source: "terminal_event",
|
|
6025
|
+
operator_next_action: "Create a new owner-reviewed task with an account-supported Codex model; keep the preserved draft PR for review rather than resuming this task."
|
|
6026
|
+
},
|
|
6027
|
+
codex_runtime_launch_logon_session: {
|
|
6028
|
+
source: "stderr",
|
|
6029
|
+
win32_error: 1312,
|
|
6030
|
+
operator_next_action: "Repair the Codex Windows logon/session configuration on the serving host, then rerun the runner."
|
|
6031
|
+
}
|
|
6032
|
+
};
|
|
6033
|
+
}
|
|
6034
|
+
});
|
|
6035
|
+
|
|
5868
6036
|
// ../../scripts/virtual-office/code-runner/codex-runner.mjs
|
|
5869
6037
|
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
5870
6038
|
import { existsSync as existsSync10 } from "node:fs";
|
|
@@ -5965,7 +6133,8 @@ function parseCodexEvent(line) {
|
|
|
5965
6133
|
}
|
|
5966
6134
|
if (type === "turn.failed" || type === "error") {
|
|
5967
6135
|
const msg = evt.error && (evt.error.message || evt.error) || evt.message || "codex run failed";
|
|
5968
|
-
|
|
6136
|
+
const failure = classifyCodexTerminalFailure(evt);
|
|
6137
|
+
return { kind: "result", isError: true, costUsd: null, summary: String(msg), numTurns: null, ...failure ? { failure } : {} };
|
|
5969
6138
|
}
|
|
5970
6139
|
return null;
|
|
5971
6140
|
}
|
|
@@ -5976,6 +6145,7 @@ var init_codex_runner = __esm({
|
|
|
5976
6145
|
init_agent_key_store();
|
|
5977
6146
|
init_agent_auth_tier();
|
|
5978
6147
|
init_flat_token_usage();
|
|
6148
|
+
init_error_message();
|
|
5979
6149
|
CODEX_PREFER_LOGIN_ENV = "VO_RUNNER_CODEX_PREFER_LOGIN";
|
|
5980
6150
|
LEGACY_PREFER_LOGIN_ENV = "VO_RUNNER_PREFER_LOGIN";
|
|
5981
6151
|
CodexRunner = class {
|
|
@@ -5993,6 +6163,9 @@ var init_codex_runner = __esm({
|
|
|
5993
6163
|
parseEvent(line) {
|
|
5994
6164
|
return parseCodexEvent(line);
|
|
5995
6165
|
}
|
|
6166
|
+
classifyStderr(stderr) {
|
|
6167
|
+
return classifyCodexStderr(stderr);
|
|
6168
|
+
}
|
|
5996
6169
|
/**
|
|
5997
6170
|
* SECURITY: never `shell: true` — same RCE class as cursor-runner. The old
|
|
5998
6171
|
* `shell: win32 && !/\.exe$/` fell back to shell mode whenever
|
|
@@ -6014,12 +6187,6 @@ var init_codex_runner = __esm({
|
|
|
6014
6187
|
windowsVerbatimArguments: false
|
|
6015
6188
|
};
|
|
6016
6189
|
}
|
|
6017
|
-
/**
|
|
6018
|
-
* Fill the OpenAI credential env var(s) (OPENAI_API_KEY / CODEX_API_KEY) from
|
|
6019
|
-
* the OS keychain when not already set, so a BYO friend who ran
|
|
6020
|
-
* `vo-mcp set-key --provider codex` authenticates without an env var. Explicit
|
|
6021
|
-
* env wins; no key stored → unchanged (a prior `codex login` still works).
|
|
6022
|
-
*/
|
|
6023
6190
|
applyAuthEnv(env2 = process.env) {
|
|
6024
6191
|
if (isTruthyFlag2(env2[CODEX_PREFER_LOGIN_ENV]) || isTruthyFlag2(env2[LEGACY_PREFER_LOGIN_ENV])) {
|
|
6025
6192
|
const out = { ...env2 };
|
|
@@ -6032,20 +6199,9 @@ var init_codex_runner = __esm({
|
|
|
6032
6199
|
costBasis(env2 = process.env) {
|
|
6033
6200
|
return String(env2.OPENAI_API_KEY || env2.CODEX_API_KEY || "").trim() ? "vendor_billed" : "subscription_api_equivalent";
|
|
6034
6201
|
}
|
|
6035
|
-
/**
|
|
6036
|
-
* Dispatch-time billing tier for the NEXT codex spawn, from the SAME facts
|
|
6037
|
-
* checkAuth() already gathered — no extra subprocess. applyAuthEnv() is a
|
|
6038
|
-
* keychain read in this process (@napi-rs/keyring), not a spawn.
|
|
6039
|
-
*
|
|
6040
|
-
* Codex is the agent where this signal was already computed and then thrown
|
|
6041
|
-
* away: checkAuth() distinguished "API key available (no persisted ChatGPT
|
|
6042
|
-
* login)" from a real login, but only inside a `message` string that the
|
|
6043
|
-
* heartbeat schema strips before storage. This gives that fact a typed home.
|
|
6044
|
-
*/
|
|
6045
6202
|
authTier(env2 = this.env) {
|
|
6046
6203
|
return safeAuthTier(() => authTierFromCostBasis(this.costBasis(this.applyAuthEnv(env2))));
|
|
6047
6204
|
}
|
|
6048
|
-
/** Best-effort binary + persisted-login probe. Never throws or spends tokens. */
|
|
6049
6205
|
async checkAuth() {
|
|
6050
6206
|
try {
|
|
6051
6207
|
const bin = this.binary;
|
|
@@ -6937,9 +7093,9 @@ var init_rate_limit_detector_core = __esm({
|
|
|
6937
7093
|
|
|
6938
7094
|
// ../../scripts/virtual-office/code-runner/rate-limit-resume-state.mjs
|
|
6939
7095
|
import fsp10 from "node:fs/promises";
|
|
6940
|
-
import
|
|
7096
|
+
import path14 from "node:path";
|
|
6941
7097
|
async function atomicWrite(file, content) {
|
|
6942
|
-
await fsp10.mkdir(
|
|
7098
|
+
await fsp10.mkdir(path14.dirname(file), { recursive: true });
|
|
6943
7099
|
const temp = `${file}.tmp-${process.pid}-${Date.now()}`;
|
|
6944
7100
|
const handle = await fsp10.open(temp, "wx");
|
|
6945
7101
|
try {
|
|
@@ -7000,7 +7156,7 @@ function writeResumeAttempts(file, store) {
|
|
|
7000
7156
|
}
|
|
7001
7157
|
async function acquireLock(lockFile, { now = Date.now, sleep: sleep3 = delay } = {}) {
|
|
7002
7158
|
const deadline = now() + LOCK_WAIT_MS;
|
|
7003
|
-
await fsp10.mkdir(
|
|
7159
|
+
await fsp10.mkdir(path14.dirname(lockFile), { recursive: true });
|
|
7004
7160
|
for (; ; ) {
|
|
7005
7161
|
let handle;
|
|
7006
7162
|
try {
|
|
@@ -7145,6 +7301,17 @@ async function classifyFailureForResume({
|
|
|
7145
7301
|
record = recordRateLimited,
|
|
7146
7302
|
deferRecord = false
|
|
7147
7303
|
} = {}) {
|
|
7304
|
+
const structuredFailure = serializeRunnerFailure(run.failure);
|
|
7305
|
+
if (structuredFailure) {
|
|
7306
|
+
return {
|
|
7307
|
+
rateLimited: false,
|
|
7308
|
+
progress: {
|
|
7309
|
+
status: "failed",
|
|
7310
|
+
message: `runner blocker: ${run.failure.code}; operator review required`,
|
|
7311
|
+
result: structuredFailure
|
|
7312
|
+
}
|
|
7313
|
+
};
|
|
7314
|
+
}
|
|
7148
7315
|
if (enabled) {
|
|
7149
7316
|
const rl = detect(run.summary, { now });
|
|
7150
7317
|
if (rl.rateLimited) {
|
|
@@ -7219,6 +7386,7 @@ var init_rate_limit_resume = __esm({
|
|
|
7219
7386
|
"use strict";
|
|
7220
7387
|
init_rate_limit_detector_core();
|
|
7221
7388
|
init_rate_limit_resume_state();
|
|
7389
|
+
init_error_message();
|
|
7222
7390
|
}
|
|
7223
7391
|
});
|
|
7224
7392
|
|
|
@@ -7458,14 +7626,14 @@ function parsePorcelainZ(out) {
|
|
|
7458
7626
|
for (let i = 0; i < tokens.length; i += 1) {
|
|
7459
7627
|
const token2 = tokens[i];
|
|
7460
7628
|
if (!token2) continue;
|
|
7461
|
-
const
|
|
7462
|
-
if (
|
|
7629
|
+
const path23 = token2.slice(3);
|
|
7630
|
+
if (path23) files.push(path23);
|
|
7463
7631
|
if (token2[0] === "R" || token2[0] === "C") i += 1;
|
|
7464
7632
|
}
|
|
7465
7633
|
return files;
|
|
7466
7634
|
}
|
|
7467
|
-
function isAgentScratch(
|
|
7468
|
-
const normalized = String(
|
|
7635
|
+
function isAgentScratch(path23) {
|
|
7636
|
+
const normalized = String(path23 || "");
|
|
7469
7637
|
return SCRATCH_PATTERNS.some((pattern) => pattern.test(normalized));
|
|
7470
7638
|
}
|
|
7471
7639
|
var SCRATCH_PATTERNS;
|
|
@@ -7722,7 +7890,7 @@ var init_executor = __esm({
|
|
|
7722
7890
|
|
|
7723
7891
|
// ../../scripts/virtual-office/code-runner/test-gen-gate.mjs
|
|
7724
7892
|
import fs7 from "node:fs";
|
|
7725
|
-
import
|
|
7893
|
+
import path15 from "node:path";
|
|
7726
7894
|
async function postFailed(client, id, message, result) {
|
|
7727
7895
|
try {
|
|
7728
7896
|
await client.postProgress(id, {
|
|
@@ -7758,7 +7926,7 @@ async function gateTestGenTaskOrFail({ client, id, task, files, worktreeDir, env
|
|
|
7758
7926
|
}
|
|
7759
7927
|
let testSource = "";
|
|
7760
7928
|
try {
|
|
7761
|
-
testSource = fs7.readFileSync(
|
|
7929
|
+
testSource = fs7.readFileSync(path15.join(worktreeDir, testFile), "utf8");
|
|
7762
7930
|
} catch (err) {
|
|
7763
7931
|
await postFailed(client, id, `could not read generated test ${testFile}: ${err.message}`, "gate_test_unreadable");
|
|
7764
7932
|
return true;
|
|
@@ -7808,7 +7976,7 @@ var init_test_gen_gate = __esm({
|
|
|
7808
7976
|
// ../../scripts/virtual-office/code-runner/completion-gate.mjs
|
|
7809
7977
|
import { execFile } from "node:child_process";
|
|
7810
7978
|
import fs8 from "node:fs";
|
|
7811
|
-
import
|
|
7979
|
+
import path16 from "node:path";
|
|
7812
7980
|
function resolveCompletionGate(task) {
|
|
7813
7981
|
const raw = task?.completion_gate;
|
|
7814
7982
|
if (raw === void 0 || raw === null) return null;
|
|
@@ -7846,14 +8014,14 @@ function workspaceFingerprint(worktreeDir, execFileImpl = execFile) {
|
|
|
7846
8014
|
}
|
|
7847
8015
|
function readState(worktreeDir) {
|
|
7848
8016
|
try {
|
|
7849
|
-
return JSON.parse(fs8.readFileSync(
|
|
8017
|
+
return JSON.parse(fs8.readFileSync(path16.join(worktreeDir, COMPLETION_GATE_STATE_FILE), "utf8"));
|
|
7850
8018
|
} catch {
|
|
7851
8019
|
return null;
|
|
7852
8020
|
}
|
|
7853
8021
|
}
|
|
7854
8022
|
function writeState(worktreeDir, state) {
|
|
7855
8023
|
try {
|
|
7856
|
-
fs8.writeFileSync(
|
|
8024
|
+
fs8.writeFileSync(path16.join(worktreeDir, COMPLETION_GATE_STATE_FILE), `${JSON.stringify(state)}
|
|
7857
8025
|
`, "utf8");
|
|
7858
8026
|
} catch {
|
|
7859
8027
|
}
|
|
@@ -8565,16 +8733,28 @@ var init_repair_publication_strategy = __esm({
|
|
|
8565
8733
|
|
|
8566
8734
|
// ../../scripts/virtual-office/code-runner/partial-pr-continuation.mjs
|
|
8567
8735
|
function partialPrContinuationResult(run = {}, maxLength = 2e3, reason = null) {
|
|
8736
|
+
const limit = Math.max(0, Number(maxLength) || 0);
|
|
8568
8737
|
const summary = String(run.summary || "agent stopped before completing the task").trim();
|
|
8569
8738
|
const reasonMarker = reason === "rate_limited" ? `
|
|
8570
8739
|
${RATE_LIMITED_CONTINUATION_MARKER}` : "";
|
|
8571
|
-
|
|
8572
|
-
${
|
|
8740
|
+
const blockedMarker = "ALGOSUITE_TASK_OUTCOME: BLOCKED";
|
|
8741
|
+
const blockedPrefix = `${blockedMarker}${reasonMarker}
|
|
8742
|
+
`;
|
|
8743
|
+
const serializedFailure = serializeRunnerFailure(run.failure, Math.max(0, limit - blockedPrefix.length - 1));
|
|
8744
|
+
const outcomeMarker = serializedFailure ? blockedMarker : PARTIAL_PR_CONTINUATION_MARKER;
|
|
8745
|
+
const prefix = `${outcomeMarker}${reasonMarker}
|
|
8746
|
+
`;
|
|
8747
|
+
const failure = serializedFailure && outcomeMarker === blockedMarker ? serializedFailure : null;
|
|
8748
|
+
const suffix = failure ? `
|
|
8749
|
+
${failure}` : "";
|
|
8750
|
+
const summaryRoom = Math.max(0, limit - prefix.length - suffix.length);
|
|
8751
|
+
return `${prefix}${summary.slice(0, summaryRoom)}${suffix}`.slice(0, limit);
|
|
8573
8752
|
}
|
|
8574
8753
|
var PARTIAL_PR_CONTINUATION_MARKER, RATE_LIMITED_CONTINUATION_MARKER;
|
|
8575
8754
|
var init_partial_pr_continuation = __esm({
|
|
8576
8755
|
"../../scripts/virtual-office/code-runner/partial-pr-continuation.mjs"() {
|
|
8577
8756
|
"use strict";
|
|
8757
|
+
init_error_message();
|
|
8578
8758
|
PARTIAL_PR_CONTINUATION_MARKER = "ALGOSUITE_TASK_OUTCOME: NEEDS_CONTINUATION";
|
|
8579
8759
|
RATE_LIMITED_CONTINUATION_MARKER = "ALGOSUITE_CONTINUATION_REASON: RATE_LIMITED";
|
|
8580
8760
|
}
|
|
@@ -9441,7 +9621,7 @@ var init_task_prompt = __esm({
|
|
|
9441
9621
|
import { createHash as createHash4, randomUUID as randomUUID3 } from "node:crypto";
|
|
9442
9622
|
import { chmod, mkdir, mkdtemp, readFile, readdir, realpath, rm, stat, writeFile } from "node:fs/promises";
|
|
9443
9623
|
import os2 from "node:os";
|
|
9444
|
-
import
|
|
9624
|
+
import path17 from "node:path";
|
|
9445
9625
|
function safeTaskToken(taskId) {
|
|
9446
9626
|
return String(taskId || "task").replace(/[^0-9A-Za-z_-]/gu, "_").slice(0, 48) || "task";
|
|
9447
9627
|
}
|
|
@@ -9454,9 +9634,9 @@ function hasGeneratedPrefix(name) {
|
|
|
9454
9634
|
return name.startsWith(DIRECTORY_PREFIX) || name.startsWith(LEGACY_DIRECTORY_PREFIX);
|
|
9455
9635
|
}
|
|
9456
9636
|
function assertGeneratedDirectory(directory, containmentRoot) {
|
|
9457
|
-
const resolvedDirectory =
|
|
9458
|
-
const resolvedRoot =
|
|
9459
|
-
if (
|
|
9637
|
+
const resolvedDirectory = path17.resolve(directory);
|
|
9638
|
+
const resolvedRoot = path17.resolve(containmentRoot);
|
|
9639
|
+
if (path17.dirname(resolvedDirectory) !== resolvedRoot || !hasGeneratedPrefix(path17.basename(resolvedDirectory))) {
|
|
9460
9640
|
throw new Error("refusing to clean an unverified task-attachment directory");
|
|
9461
9641
|
}
|
|
9462
9642
|
return resolvedDirectory;
|
|
@@ -9465,7 +9645,7 @@ async function resolveContainmentRoot(worktreeDir) {
|
|
|
9465
9645
|
if (typeof worktreeDir !== "string" || !worktreeDir.trim()) {
|
|
9466
9646
|
throw new Error("refusing to materialize task attachments outside an agent-readable worktree: no worktreeDir given");
|
|
9467
9647
|
}
|
|
9468
|
-
const root =
|
|
9648
|
+
const root = path17.resolve(worktreeDir);
|
|
9469
9649
|
const stats = await stat(root).catch(() => null);
|
|
9470
9650
|
if (!stats?.isDirectory()) {
|
|
9471
9651
|
throw new Error(`refusing to materialize task attachments: agent worktree root is not a directory (${root})`);
|
|
@@ -9474,15 +9654,15 @@ async function resolveContainmentRoot(worktreeDir) {
|
|
|
9474
9654
|
}
|
|
9475
9655
|
async function createAttachmentDirectory(taskId, containmentRoot) {
|
|
9476
9656
|
const root = await resolveContainmentRoot(containmentRoot);
|
|
9477
|
-
const directory = await mkdtemp(
|
|
9657
|
+
const directory = await mkdtemp(path17.join(root, `${DIRECTORY_PREFIX}${safeTaskToken(taskId)}-`));
|
|
9478
9658
|
const [realRoot, realDirectory] = await Promise.all([realpath(root), realpath(directory)]);
|
|
9479
|
-
if (
|
|
9659
|
+
if (path17.dirname(realDirectory) !== realRoot) {
|
|
9480
9660
|
await rm(directory, { recursive: true, force: true }).catch(() => void 0);
|
|
9481
9661
|
throw new Error("task-attachment directory escaped the agent worktree root");
|
|
9482
9662
|
}
|
|
9483
|
-
await writeFile(
|
|
9484
|
-
const marker = JSON.stringify({ owner: MARKER_OWNER, token: randomUUID3(), directory:
|
|
9485
|
-
await writeFile(
|
|
9663
|
+
await writeFile(path17.join(directory, GITIGNORE_FILE), GITIGNORE_BODY, { encoding: "utf8", mode: 384 });
|
|
9664
|
+
const marker = JSON.stringify({ owner: MARKER_OWNER, token: randomUUID3(), directory: path17.basename(directory), created_at: (/* @__PURE__ */ new Date()).toISOString() });
|
|
9665
|
+
await writeFile(path17.join(directory, MARKER_FILE), marker, { encoding: "utf8", mode: 384 });
|
|
9486
9666
|
return { directory, marker, root, cleaned: false };
|
|
9487
9667
|
}
|
|
9488
9668
|
async function cleanupGeneratedDirectory(state) {
|
|
@@ -9496,7 +9676,7 @@ async function cleanupGeneratedDirectory(state) {
|
|
|
9496
9676
|
state.cleaned = true;
|
|
9497
9677
|
return;
|
|
9498
9678
|
}
|
|
9499
|
-
const marker = await readFile(
|
|
9679
|
+
const marker = await readFile(path17.join(directory, MARKER_FILE), "utf8").catch(() => "");
|
|
9500
9680
|
if (marker !== state.marker) throw new Error("refusing to clean a task-attachment directory without its exact marker");
|
|
9501
9681
|
await rm(directory, { recursive: true, force: true });
|
|
9502
9682
|
state.cleaned = true;
|
|
@@ -9515,7 +9695,7 @@ async function sweepStaleTaskAttachmentDirectories({
|
|
|
9515
9695
|
now = Date.now(),
|
|
9516
9696
|
maxAgeMs = DEFAULT_STALE_AGE_MS
|
|
9517
9697
|
} = {}) {
|
|
9518
|
-
const root =
|
|
9698
|
+
const root = path17.resolve(tempRoot);
|
|
9519
9699
|
if (!Number.isFinite(maxAgeMs) || maxAgeMs <= 0) throw new Error("stale attachment age must be positive");
|
|
9520
9700
|
const entries = await readdir(root, { withFileTypes: true }).catch((error) => {
|
|
9521
9701
|
if (error?.code === "ENOENT") return [];
|
|
@@ -9524,8 +9704,8 @@ async function sweepStaleTaskAttachmentDirectories({
|
|
|
9524
9704
|
let removed = 0;
|
|
9525
9705
|
for (const entry of entries) {
|
|
9526
9706
|
if (!entry.isDirectory() || !hasGeneratedPrefix(entry.name)) continue;
|
|
9527
|
-
const directory = assertGeneratedDirectory(
|
|
9528
|
-
const markerRaw = await readFile(
|
|
9707
|
+
const directory = assertGeneratedDirectory(path17.join(root, entry.name), root);
|
|
9708
|
+
const markerRaw = await readFile(path17.join(directory, MARKER_FILE), "utf8").catch(() => "");
|
|
9529
9709
|
const marker = parseOwnedMarker(markerRaw, entry.name);
|
|
9530
9710
|
if (!marker) continue;
|
|
9531
9711
|
const directoryStat = await stat(directory);
|
|
@@ -9582,8 +9762,8 @@ async function materializeTaskAttachments(client, task, { worktreeDir } = {}) {
|
|
|
9582
9762
|
const sha2562 = createHash4("sha256").update(content).digest("hex");
|
|
9583
9763
|
if (sha2562 !== ref.sha256) throw new Error(`attachment ${ref.attachment_id} sha256 mismatch`);
|
|
9584
9764
|
const name = sanitizeTaskAttachmentName(ref.name, index);
|
|
9585
|
-
const filePath =
|
|
9586
|
-
if (
|
|
9765
|
+
const filePath = path17.resolve(state.directory, name);
|
|
9766
|
+
if (path17.dirname(filePath) !== state.directory) throw new Error(`attachment ${ref.attachment_id} resolved outside its task directory`);
|
|
9587
9767
|
await writeFile(filePath, content, { flag: "wx", mode: 384 });
|
|
9588
9768
|
await chmod(filePath, 384);
|
|
9589
9769
|
files.push({ attachmentId: ref.attachment_id, name, mime: ref.mime, sizeBytes: ref.size_bytes, sha256: sha2562, path: filePath });
|
|
@@ -9668,9 +9848,9 @@ async function readSpool(spoolDir = SPOOL_DIR) {
|
|
|
9668
9848
|
}
|
|
9669
9849
|
return out;
|
|
9670
9850
|
}
|
|
9671
|
-
async function readCloudMap(
|
|
9851
|
+
async function readCloudMap(path23) {
|
|
9672
9852
|
try {
|
|
9673
|
-
return JSON.parse(await readFile2(
|
|
9853
|
+
return JSON.parse(await readFile2(path23, "utf8"));
|
|
9674
9854
|
} catch {
|
|
9675
9855
|
return {};
|
|
9676
9856
|
}
|
|
@@ -9971,9 +10151,9 @@ function backoffMs(streak, baseMs) {
|
|
|
9971
10151
|
if (streak <= 0) return 0;
|
|
9972
10152
|
return Math.min(baseMs * 2 ** Math.min(streak - 1, 20), MAX_BACKOFF_MS);
|
|
9973
10153
|
}
|
|
9974
|
-
async function loadState(
|
|
10154
|
+
async function loadState(path23) {
|
|
9975
10155
|
try {
|
|
9976
|
-
const parsed = JSON.parse(await readFile3(
|
|
10156
|
+
const parsed = JSON.parse(await readFile3(path23, "utf8"));
|
|
9977
10157
|
if (parsed && typeof parsed === "object" && Number.isInteger(parsed.byte_offset) && parsed.byte_offset >= 0) {
|
|
9978
10158
|
return { ...parsed, byte_offset: parsed.byte_offset };
|
|
9979
10159
|
}
|
|
@@ -9981,15 +10161,15 @@ async function loadState(path22) {
|
|
|
9981
10161
|
}
|
|
9982
10162
|
return { byte_offset: 0, last_event_id: null, forwarded_total: 0, rejected_total: 0, rejected_event_ids: [] };
|
|
9983
10163
|
}
|
|
9984
|
-
async function saveState(
|
|
9985
|
-
await mkdir2(dirname9(
|
|
9986
|
-
await writeFile3(
|
|
10164
|
+
async function saveState(path23, state) {
|
|
10165
|
+
await mkdir2(dirname9(path23), { recursive: true });
|
|
10166
|
+
await writeFile3(path23, JSON.stringify(state, null, 2), "utf8");
|
|
9987
10167
|
}
|
|
9988
|
-
async function readNewBytes(
|
|
9989
|
-
const st = await stat2(
|
|
10168
|
+
async function readNewBytes(path23, offset, max) {
|
|
10169
|
+
const st = await stat2(path23);
|
|
9990
10170
|
if (st.size <= offset) return { buf: Buffer.alloc(0), size: st.size };
|
|
9991
10171
|
const length = Math.min(st.size - offset, max);
|
|
9992
|
-
const fh = await open(
|
|
10172
|
+
const fh = await open(path23, "r");
|
|
9993
10173
|
try {
|
|
9994
10174
|
const buf = Buffer.alloc(length);
|
|
9995
10175
|
const { bytesRead } = await fh.read(buf, 0, length, offset);
|
|
@@ -11082,10 +11262,10 @@ function formatShadowLogLine(record) {
|
|
|
11082
11262
|
const loud = record.unexplained_fields?.length ? "!! " : "";
|
|
11083
11263
|
return `${loud}[prepared-job-shadow] ${parts.join(" ")}`;
|
|
11084
11264
|
}
|
|
11085
|
-
function appendShadowRecord(record, { path:
|
|
11265
|
+
function appendShadowRecord(record, { path: path23 = PREPARED_JOB_SHADOW_SINK, append = appendFileSync, mkdir: mkdir5 = mkdirSync8 } = {}) {
|
|
11086
11266
|
try {
|
|
11087
|
-
mkdir5(dirname10(
|
|
11088
|
-
append(
|
|
11267
|
+
mkdir5(dirname10(path23), { recursive: true });
|
|
11268
|
+
append(path23, `${JSON.stringify(record)}
|
|
11089
11269
|
`, "utf8");
|
|
11090
11270
|
return true;
|
|
11091
11271
|
} catch {
|
|
@@ -11405,7 +11585,7 @@ var init_shared = __esm({
|
|
|
11405
11585
|
// ../../scripts/virtual-office/code-runner/account-usage/claude.mjs
|
|
11406
11586
|
import fs10 from "node:fs";
|
|
11407
11587
|
import os3 from "node:os";
|
|
11408
|
-
import
|
|
11588
|
+
import path18 from "node:path";
|
|
11409
11589
|
function fileCaptureTime(filePath, explicit, statFn) {
|
|
11410
11590
|
if (typeof explicit === "string" && explicit) return explicit;
|
|
11411
11591
|
try {
|
|
@@ -11419,7 +11599,7 @@ function usageBaseUrl(env2 = process.env) {
|
|
|
11419
11599
|
return String(raw).replace(/\/+$/, "");
|
|
11420
11600
|
}
|
|
11421
11601
|
function readOAuthToken({ homeDir = os3.homedir(), read = readJson2, now = Date.now() } = {}) {
|
|
11422
|
-
const creds = read(
|
|
11602
|
+
const creds = read(path18.join(homeDir, ".claude", ".credentials.json"));
|
|
11423
11603
|
const oauth = creds && typeof creds === "object" ? creds.claudeAiOauth : null;
|
|
11424
11604
|
if (!oauth || typeof oauth !== "object") return null;
|
|
11425
11605
|
const token2 = typeof oauth.accessToken === "string" ? oauth.accessToken.trim() : "";
|
|
@@ -11429,7 +11609,7 @@ function readOAuthToken({ homeDir = os3.homedir(), read = readJson2, now = Date.
|
|
|
11429
11609
|
return token2;
|
|
11430
11610
|
}
|
|
11431
11611
|
function readAccountId({ homeDir = os3.homedir(), read = readJson2 } = {}) {
|
|
11432
|
-
const cfg = read(
|
|
11612
|
+
const cfg = read(path18.join(homeDir, ".claude.json"));
|
|
11433
11613
|
const account = cfg && typeof cfg === "object" ? cfg.oauthAccount : null;
|
|
11434
11614
|
return account && typeof account.accountUuid === "string" ? account.accountUuid : null;
|
|
11435
11615
|
}
|
|
@@ -11539,7 +11719,7 @@ function readClaudeFileUsage({
|
|
|
11539
11719
|
if (age === null || age > MAX_FILE_AGE_MS) return null;
|
|
11540
11720
|
return row;
|
|
11541
11721
|
};
|
|
11542
|
-
const statusPath =
|
|
11722
|
+
const statusPath = path18.join(homeDir, ".claude", "claude-usage.json");
|
|
11543
11723
|
const status = read(statusPath);
|
|
11544
11724
|
if (status && (status.seven_day || status.five_hour)) {
|
|
11545
11725
|
const row = fresh(makeUsageRow({
|
|
@@ -11554,7 +11734,7 @@ function readClaudeFileUsage({
|
|
|
11554
11734
|
}));
|
|
11555
11735
|
if (row) return row;
|
|
11556
11736
|
}
|
|
11557
|
-
const weeklyPath =
|
|
11737
|
+
const weeklyPath = path18.join(homeDir, ".claude", "claude-weekly-usage.json");
|
|
11558
11738
|
const weekly = read(weeklyPath);
|
|
11559
11739
|
if (weekly) {
|
|
11560
11740
|
const row = fresh(makeUsageRow({
|
|
@@ -11932,20 +12112,6 @@ var init_pr_watcher_failure_confirmation = __esm({
|
|
|
11932
12112
|
}
|
|
11933
12113
|
});
|
|
11934
12114
|
|
|
11935
|
-
// ../../scripts/virtual-office/code-runner/error-message.mjs
|
|
11936
|
-
function boundedErrorMessage(error, maxLength = 240) {
|
|
11937
|
-
try {
|
|
11938
|
-
return String(error?.message ?? error ?? "unknown error").slice(0, maxLength);
|
|
11939
|
-
} catch {
|
|
11940
|
-
return "unknown error";
|
|
11941
|
-
}
|
|
11942
|
-
}
|
|
11943
|
-
var init_error_message = __esm({
|
|
11944
|
-
"../../scripts/virtual-office/code-runner/error-message.mjs"() {
|
|
11945
|
-
"use strict";
|
|
11946
|
-
}
|
|
11947
|
-
});
|
|
11948
|
-
|
|
11949
12115
|
// ../../scripts/virtual-office/code-runner/watcher-coordination.mjs
|
|
11950
12116
|
import { createHash as createHash7 } from "node:crypto";
|
|
11951
12117
|
function ciFixOccurrenceKey({ repo, prNumber, headSha, repairAttempt }) {
|
|
@@ -12441,7 +12607,49 @@ var init_superseded_pr_source = __esm({
|
|
|
12441
12607
|
});
|
|
12442
12608
|
|
|
12443
12609
|
// ../../scripts/virtual-office/code-runner/ci-fix-prompt.mjs
|
|
12444
|
-
function
|
|
12610
|
+
function fairSplit(aLen, bLen, budget) {
|
|
12611
|
+
if (aLen + bLen <= budget) return { a: aLen, b: bLen };
|
|
12612
|
+
const half = Math.floor(budget / 2);
|
|
12613
|
+
if (aLen <= half) return { a: aLen, b: budget - aLen };
|
|
12614
|
+
if (bLen <= half) return { a: budget - bLen, b: bLen };
|
|
12615
|
+
return { a: half, b: budget - half };
|
|
12616
|
+
}
|
|
12617
|
+
function boundHead(content, allocated, label) {
|
|
12618
|
+
if (content.length <= allocated) return content;
|
|
12619
|
+
const marker = `
|
|
12620
|
+
[${label} truncated to the first ${allocated} characters available in this dispatch]`;
|
|
12621
|
+
if (marker.length >= allocated) return marker.slice(0, Math.max(0, allocated));
|
|
12622
|
+
return `${content.slice(0, allocated - marker.length)}${marker}`;
|
|
12623
|
+
}
|
|
12624
|
+
function boundTail(content, allocated, label) {
|
|
12625
|
+
if (content.length <= allocated) return content;
|
|
12626
|
+
const marker = `[${label} truncated to the final ${allocated} characters available in this dispatch]
|
|
12627
|
+
`;
|
|
12628
|
+
if (marker.length >= allocated) return marker.slice(0, Math.max(0, allocated));
|
|
12629
|
+
return `${marker}${content.slice(-(allocated - marker.length))}`;
|
|
12630
|
+
}
|
|
12631
|
+
function boundedEvidenceShares({
|
|
12632
|
+
prPatch,
|
|
12633
|
+
failedLogs,
|
|
12634
|
+
fixedOverheadChars,
|
|
12635
|
+
prNumber,
|
|
12636
|
+
maxTotalChars = CI_FIX_PROMPT_MAX_CHARS
|
|
12637
|
+
}) {
|
|
12638
|
+
const patchWanted = String(prPatch || "").trim() || NO_PATCH_EVIDENCE;
|
|
12639
|
+
const logsWanted = String(failedLogs || "").trim() || NO_LOG_EVIDENCE;
|
|
12640
|
+
const available = maxTotalChars - fixedOverheadChars;
|
|
12641
|
+
if (available < NO_PATCH_EVIDENCE.length + NO_LOG_EVIDENCE.length) {
|
|
12642
|
+
throw new Error(
|
|
12643
|
+
`CI-fix prompt for PR #${prNumber ?? "?"} cannot fit its mandatory instructions and identity (${fixedOverheadChars} chars) plus even an empty-evidence notice inside the ${maxTotalChars}-character dispatch limit \u2014 refusing to send a malformed or instructions-truncated request`
|
|
12644
|
+
);
|
|
12645
|
+
}
|
|
12646
|
+
const { a: patchShare, b: logsShare } = fairSplit(patchWanted.length, logsWanted.length, available);
|
|
12647
|
+
return {
|
|
12648
|
+
patchBody: boundHead(patchWanted, patchShare, "PR patch"),
|
|
12649
|
+
logsBody: boundTail(logsWanted, logsShare, "failed-job evidence")
|
|
12650
|
+
};
|
|
12651
|
+
}
|
|
12652
|
+
function renderHeader({ prNumber, repo, branch, headSha, failedChecks }) {
|
|
12445
12653
|
return [
|
|
12446
12654
|
`${CI_FIX_MARKER} An AlgoHQ-dispatched pull request has FAILING CI and needs a fix.`,
|
|
12447
12655
|
"",
|
|
@@ -12460,24 +12668,45 @@ function buildCiFixPrompt({ prNumber, repo, branch, headSha, failedChecks, prPat
|
|
|
12460
12668
|
"VO-ALLOW-PR-OVERLAP marker. Your work ships: finish the fix completely, leave UNCOMMITTED",
|
|
12461
12669
|
"edits, and the runner publishes for you. A denied git/gh is EXPECTED; do NOT retry it.",
|
|
12462
12670
|
"",
|
|
12463
|
-
HEADLESS_EXECUTION_CONTRACT
|
|
12671
|
+
HEADLESS_EXECUTION_CONTRACT
|
|
12672
|
+
].join("\n");
|
|
12673
|
+
}
|
|
12674
|
+
function assemblePrompt(header, patchBody, logsBody) {
|
|
12675
|
+
return [
|
|
12676
|
+
header,
|
|
12464
12677
|
"",
|
|
12465
12678
|
"## Bounded source PR patch excerpt",
|
|
12466
12679
|
"```diff",
|
|
12467
|
-
|
|
12680
|
+
patchBody,
|
|
12468
12681
|
"```",
|
|
12469
12682
|
"",
|
|
12470
12683
|
"## Failed-job evidence",
|
|
12471
12684
|
"```text",
|
|
12472
|
-
|
|
12685
|
+
logsBody,
|
|
12473
12686
|
"```"
|
|
12474
12687
|
].join("\n");
|
|
12475
12688
|
}
|
|
12689
|
+
function buildCiFixPrompt({ prNumber, repo, branch, headSha, failedChecks, prPatch, failedLogs }) {
|
|
12690
|
+
const header = renderHeader({ prNumber, repo, branch, headSha, failedChecks });
|
|
12691
|
+
const fixedOverheadChars = assemblePrompt(header, "", "").length;
|
|
12692
|
+
const { patchBody, logsBody } = boundedEvidenceShares({
|
|
12693
|
+
prPatch,
|
|
12694
|
+
failedLogs,
|
|
12695
|
+
fixedOverheadChars,
|
|
12696
|
+
prNumber,
|
|
12697
|
+
maxTotalChars: CI_FIX_PROMPT_MAX_CHARS
|
|
12698
|
+
});
|
|
12699
|
+
return assemblePrompt(header, patchBody, logsBody);
|
|
12700
|
+
}
|
|
12701
|
+
var CI_FIX_PROMPT_MAX_CHARS, NO_PATCH_EVIDENCE, NO_LOG_EVIDENCE;
|
|
12476
12702
|
var init_ci_fix_prompt = __esm({
|
|
12477
12703
|
"../../scripts/virtual-office/code-runner/ci-fix-prompt.mjs"() {
|
|
12478
12704
|
"use strict";
|
|
12479
12705
|
init_superseded_pr_source();
|
|
12480
12706
|
init_headless_execution_contract();
|
|
12707
|
+
CI_FIX_PROMPT_MAX_CHARS = 9900;
|
|
12708
|
+
NO_PATCH_EVIDENCE = "[No excerpt available; the complete exact source is still materialized in the worktree.]";
|
|
12709
|
+
NO_LOG_EVIDENCE = "[No failed-job log was available; use the named checks and patch.]";
|
|
12481
12710
|
}
|
|
12482
12711
|
});
|
|
12483
12712
|
|
|
@@ -12500,7 +12729,7 @@ function noteCiViaRest(log2) {
|
|
|
12500
12729
|
log2("watch: CI status read via REST check-runs/status (gh's GraphQL rollup needs actions:read for checkSuite.workflowRun, which the read scope does not carry)");
|
|
12501
12730
|
}
|
|
12502
12731
|
async function readCommitCiViaRest(repo, sha, { run, env: env2 }) {
|
|
12503
|
-
const api = async (
|
|
12732
|
+
const api = async (path23) => JSON.parse(await run("gh", ["api", path23], { timeout: 3e4, env: env2 }) || "{}");
|
|
12504
12733
|
const rollup = [];
|
|
12505
12734
|
let total = null;
|
|
12506
12735
|
for (let page = 1; page <= REST_MAX_PAGES && (total === null || rollup.length < total); page += 1) {
|
|
@@ -13164,9 +13393,9 @@ function buildControlHandler({ getStatus, requestStop, allowedOrigin }) {
|
|
|
13164
13393
|
res.end();
|
|
13165
13394
|
return;
|
|
13166
13395
|
}
|
|
13167
|
-
const
|
|
13396
|
+
const path23 = String(req.url || "").split("?")[0];
|
|
13168
13397
|
res.setHeader("content-type", "application/json");
|
|
13169
|
-
if (req.method === "GET" &&
|
|
13398
|
+
if (req.method === "GET" && path23 === "/status") {
|
|
13170
13399
|
let status;
|
|
13171
13400
|
try {
|
|
13172
13401
|
status = getStatus();
|
|
@@ -13177,7 +13406,7 @@ function buildControlHandler({ getStatus, requestStop, allowedOrigin }) {
|
|
|
13177
13406
|
res.end(JSON.stringify({ ok: true, ...status }));
|
|
13178
13407
|
return;
|
|
13179
13408
|
}
|
|
13180
|
-
if (req.method === "POST" &&
|
|
13409
|
+
if (req.method === "POST" && path23 === "/stop") {
|
|
13181
13410
|
if (!isControlOriginAllowed(req.headers.origin, allowedOrigin) || !req.headers["x-vo-control"]) {
|
|
13182
13411
|
res.statusCode = 403;
|
|
13183
13412
|
res.end(JSON.stringify({ ok: false, error: "forbidden" }));
|
|
@@ -13371,22 +13600,22 @@ var init_effort_mode_config = __esm({
|
|
|
13371
13600
|
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
13372
13601
|
import fs11 from "node:fs";
|
|
13373
13602
|
import os4 from "node:os";
|
|
13374
|
-
import
|
|
13603
|
+
import path19 from "node:path";
|
|
13375
13604
|
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
13376
13605
|
function userCacheRoot() {
|
|
13377
13606
|
try {
|
|
13378
13607
|
const home = os4.homedir();
|
|
13379
|
-
if (home) return
|
|
13608
|
+
if (home) return path19.join(home, ".claude");
|
|
13380
13609
|
} catch {
|
|
13381
13610
|
}
|
|
13382
|
-
return
|
|
13611
|
+
return path19.join(os4.tmpdir(), `vo-model-registry-${randomUUID6()}`);
|
|
13383
13612
|
}
|
|
13384
13613
|
function resolveCacheBaseDir(env2 = process.env, moduleDir = __dirname) {
|
|
13385
13614
|
if (env2.VO_MODEL_REGISTRY_CACHE_DIR) return env2.VO_MODEL_REGISTRY_CACHE_DIR;
|
|
13386
13615
|
if (env2.VO_RUNNER_RUNTIME_ROOT) return env2.VO_RUNNER_RUNTIME_ROOT;
|
|
13387
|
-
const segments = moduleDir.split(
|
|
13616
|
+
const segments = moduleDir.split(path19.sep);
|
|
13388
13617
|
const isRepoCheckout2 = segments.at(-1) === "virtual-office" && segments.at(-2) === "scripts";
|
|
13389
|
-
return isRepoCheckout2 ?
|
|
13618
|
+
return isRepoCheckout2 ? path19.resolve(moduleDir, "..", "..") : userCacheRoot();
|
|
13390
13619
|
}
|
|
13391
13620
|
function uniqueModels(models = []) {
|
|
13392
13621
|
return [...new Set(models.map((model) => String(model || "").trim()).filter(Boolean))];
|
|
@@ -13509,7 +13738,7 @@ function readCache(cacheFile = DEFAULT_CACHE_FILE, nowMs = Date.now(), ttlMs = D
|
|
|
13509
13738
|
}
|
|
13510
13739
|
}
|
|
13511
13740
|
function writeCache(cacheFile = DEFAULT_CACHE_FILE, payload) {
|
|
13512
|
-
fs11.mkdirSync(
|
|
13741
|
+
fs11.mkdirSync(path19.dirname(cacheFile), { recursive: true });
|
|
13513
13742
|
fs11.writeFileSync(cacheFile, JSON.stringify(payload, null, 2));
|
|
13514
13743
|
}
|
|
13515
13744
|
async function fetchRegistryCatalog({
|
|
@@ -13567,13 +13796,13 @@ var __dirname, DEFAULT_CACHE_DIR, DEFAULT_CACHE_FILE, DEFAULT_TTL_MS3, ANTHROPIC
|
|
|
13567
13796
|
var init_model_registry = __esm({
|
|
13568
13797
|
"../../scripts/virtual-office/model-registry.mjs"() {
|
|
13569
13798
|
"use strict";
|
|
13570
|
-
__dirname =
|
|
13571
|
-
DEFAULT_CACHE_DIR =
|
|
13799
|
+
__dirname = path19.dirname(fileURLToPath6(import.meta.url));
|
|
13800
|
+
DEFAULT_CACHE_DIR = path19.join(
|
|
13572
13801
|
resolveCacheBaseDir(),
|
|
13573
13802
|
".virtual-office-cache",
|
|
13574
13803
|
"model-registry"
|
|
13575
13804
|
);
|
|
13576
|
-
DEFAULT_CACHE_FILE =
|
|
13805
|
+
DEFAULT_CACHE_FILE = path19.join(DEFAULT_CACHE_DIR, "catalog.json");
|
|
13577
13806
|
DEFAULT_TTL_MS3 = 60 * 60 * 1e3;
|
|
13578
13807
|
ANTHROPIC_API_VERSION = "2023-06-01";
|
|
13579
13808
|
FAMILY_DEFINITIONS = {
|
|
@@ -14212,9 +14441,9 @@ function operatorTierToRung(tier, difficulty, thresholds) {
|
|
|
14212
14441
|
if (tier === "best" && difficulty >= thresholds.rungBounds.R5) return "R5";
|
|
14213
14442
|
return base;
|
|
14214
14443
|
}
|
|
14215
|
-
function readCodexModelsCache({ path:
|
|
14444
|
+
function readCodexModelsCache({ path: path23 = DEFAULT_CODEX_MODELS_CACHE, read = readFileSync9 } = {}) {
|
|
14216
14445
|
try {
|
|
14217
|
-
const parsed = JSON.parse(read(
|
|
14446
|
+
const parsed = JSON.parse(read(path23, "utf8"));
|
|
14218
14447
|
return Array.isArray(parsed?.models) ? parsed : null;
|
|
14219
14448
|
} catch {
|
|
14220
14449
|
return null;
|
|
@@ -14471,15 +14700,15 @@ function formatDecisionReason(decision, maxLen = 480) {
|
|
|
14471
14700
|
const s = `[${decision.routerVersion}] ${decision.taskClass} d=${decision.difficulty} c=${decision.confidence} \u2192 ${decision.rung}/${decision.tier}${decision.effort ? ` effort=${decision.effort}` : ""} turns=${decision.maxTurns} $${decision.maxBudgetUsd}${decision.flags.length ? ` [${decision.flags.join(",")}]` : ""} :: ${decision.reasons.join("; ")}`;
|
|
14472
14701
|
return s.length > maxLen ? `${s.slice(0, maxLen - 1)}\u2026` : s;
|
|
14473
14702
|
}
|
|
14474
|
-
function appendDecisionFallback(decision, { path:
|
|
14703
|
+
function appendDecisionFallback(decision, { path: path23 = DECISION_FALLBACK_PATH, append = appendFileSync2, mkdir: mkdir5 = mkdirSync9, task, thresholds, roleCostInputs } = {}) {
|
|
14475
14704
|
try {
|
|
14476
|
-
mkdir5(dirname12(
|
|
14477
|
-
append(
|
|
14705
|
+
mkdir5(dirname12(path23), { recursive: true });
|
|
14706
|
+
append(path23, `${JSON.stringify(decision)}
|
|
14478
14707
|
`, "utf8");
|
|
14479
14708
|
if (isRouterDecision(decision)) {
|
|
14480
14709
|
try {
|
|
14481
14710
|
const records = buildShadowRecords({ decision, task, thresholds: thresholds || loadThresholds(), roleCostInputs });
|
|
14482
|
-
for (const record of records) append(
|
|
14711
|
+
for (const record of records) append(path23, `${JSON.stringify(record)}
|
|
14483
14712
|
`, "utf8");
|
|
14484
14713
|
} catch {
|
|
14485
14714
|
}
|
|
@@ -15533,7 +15762,7 @@ var init_inference_task_runner = __esm({
|
|
|
15533
15762
|
// ../../scripts/virtual-office/code-runner/isolation-audit.mjs
|
|
15534
15763
|
import fs12 from "node:fs";
|
|
15535
15764
|
import fsp11 from "node:fs/promises";
|
|
15536
|
-
import
|
|
15765
|
+
import path20 from "node:path";
|
|
15537
15766
|
async function defaultRun3(command, args, cwd, options = {}) {
|
|
15538
15767
|
return runProcess2(command, args, { cwd, timeout: 6e4, ...options });
|
|
15539
15768
|
}
|
|
@@ -15546,7 +15775,7 @@ async function canonicalRootForWorktree(worktreeDir, run) {
|
|
|
15546
15775
|
"--path-format=absolute",
|
|
15547
15776
|
"--git-common-dir"
|
|
15548
15777
|
])).trim();
|
|
15549
|
-
const root =
|
|
15778
|
+
const root = path20.dirname(commonDir);
|
|
15550
15779
|
return samePath3(root, worktreeDir) ? null : root;
|
|
15551
15780
|
}
|
|
15552
15781
|
async function snapshot(root, run) {
|
|
@@ -15588,21 +15817,21 @@ async function changedPaths(root, run) {
|
|
|
15588
15817
|
}
|
|
15589
15818
|
async function quarantineCanonicalWrites({ baseline, worktreeDir, taskId, run, now }) {
|
|
15590
15819
|
const paths = await changedPaths(baseline.root, run);
|
|
15591
|
-
const quarantineDir =
|
|
15592
|
-
|
|
15820
|
+
const quarantineDir = path20.join(
|
|
15821
|
+
path20.dirname(worktreeDir),
|
|
15593
15822
|
".canonical-recovery",
|
|
15594
15823
|
`${String(taskId || "unknown").replace(/[^a-z0-9-]/gi, "-")}-${now().toISOString().replace(/[:.]/g, "-")}`
|
|
15595
15824
|
);
|
|
15596
15825
|
await fsp11.mkdir(quarantineDir, { recursive: true });
|
|
15597
15826
|
const patch = await git2(run, baseline.root, ["diff", "--binary", "HEAD"], { raw: true });
|
|
15598
|
-
await fsp11.writeFile(
|
|
15827
|
+
await fsp11.writeFile(path20.join(quarantineDir, "tracked.patch"), patch, "utf8");
|
|
15599
15828
|
for (const relative of paths.untracked) {
|
|
15600
|
-
const source =
|
|
15601
|
-
const target =
|
|
15602
|
-
await fsp11.mkdir(
|
|
15829
|
+
const source = path20.join(baseline.root, relative);
|
|
15830
|
+
const target = path20.join(quarantineDir, "untracked", relative);
|
|
15831
|
+
await fsp11.mkdir(path20.dirname(target), { recursive: true });
|
|
15603
15832
|
await fsp11.copyFile(source, target);
|
|
15604
15833
|
}
|
|
15605
|
-
await fsp11.writeFile(
|
|
15834
|
+
await fsp11.writeFile(path20.join(quarantineDir, "manifest.json"), `${JSON.stringify({
|
|
15606
15835
|
taskId,
|
|
15607
15836
|
canonicalRoot: baseline.root,
|
|
15608
15837
|
canonicalHead: baseline.head,
|
|
@@ -15624,8 +15853,8 @@ async function restoreExactCanonicalPaths(baseline, evidence, run) {
|
|
|
15624
15853
|
]);
|
|
15625
15854
|
}
|
|
15626
15855
|
for (const relative of evidence.untracked) {
|
|
15627
|
-
const target =
|
|
15628
|
-
const prefix = `${
|
|
15856
|
+
const target = path20.resolve(baseline.root, relative);
|
|
15857
|
+
const prefix = `${path20.resolve(baseline.root)}${path20.sep}`;
|
|
15629
15858
|
if (!target.startsWith(prefix) || !fs12.existsSync(target)) continue;
|
|
15630
15859
|
await fsp11.rm(target, { force: true });
|
|
15631
15860
|
}
|
|
@@ -15662,7 +15891,7 @@ var init_isolation_audit = __esm({
|
|
|
15662
15891
|
init_process_runner2();
|
|
15663
15892
|
splitZ2 = (value) => String(value || "").split("\0").map((item) => item.trim()).filter(Boolean);
|
|
15664
15893
|
samePath3 = (left, right) => {
|
|
15665
|
-
const [a, b] = [left, right].map((value) =>
|
|
15894
|
+
const [a, b] = [left, right].map((value) => path20.resolve(value));
|
|
15666
15895
|
return process.platform === "win32" ? a.toLowerCase() === b.toLowerCase() : a === b;
|
|
15667
15896
|
};
|
|
15668
15897
|
}
|
|
@@ -15984,7 +16213,11 @@ async function finalizePublishedPr({
|
|
|
15984
16213
|
const overlapBlocked = pr.overlapDraft === true;
|
|
15985
16214
|
const blockedRefs = Array.isArray(pr.overlapBlockedBy) && pr.overlapBlockedBy.length > 0 ? pr.overlapBlockedBy.map((n) => `#${n}`).join(", ") : "unresolved (see PR body for the gate report)";
|
|
15986
16215
|
const overlapNote = overlapBlocked ? ` as DRAFT \u2014 overlap-blocked by ${blockedRefs} (finished work preserved; mark ready after the overlap is resolved)` : "";
|
|
15987
|
-
const
|
|
16216
|
+
const serializedRunnerFailure = serializeRunnerFailure(run?.failure);
|
|
16217
|
+
const runnerFailure = Boolean(serializedRunnerFailure);
|
|
16218
|
+
const runnerFailureRecord = serializedRunnerFailure ? JSON.parse(serializedRunnerFailure) : null;
|
|
16219
|
+
const runnerFailureMessage = runnerFailureRecord ? `blocked by ${runnerFailureRecord.code}; operator review required: ${runnerFailureRecord.operator_next_action}` : "";
|
|
16220
|
+
const fixDispatchGuard = overlapBlocked || runnerFailure ? { allowFixDispatch: false } : {};
|
|
15988
16221
|
let resumeQueued = false;
|
|
15989
16222
|
if (cfg.watchEnabled) {
|
|
15990
16223
|
try {
|
|
@@ -15995,7 +16228,7 @@ async function finalizePublishedPr({
|
|
|
15995
16228
|
taskId: id,
|
|
15996
16229
|
operatorId: task.operator_id,
|
|
15997
16230
|
tenantId: task.tenant_id,
|
|
15998
|
-
needsContinuation: partial && !rateLimitResume && (task.continuation_attempt ?? 0) < (task.continuation_max_attempts ?? 3),
|
|
16231
|
+
needsContinuation: partial && !rateLimitResume && !runnerFailure && (task.continuation_attempt ?? 0) < (task.continuation_max_attempts ?? 3),
|
|
15999
16232
|
continuationExhausted: partial && (task.continuation_attempt ?? 0) >= (task.continuation_max_attempts ?? 3),
|
|
16000
16233
|
repairChain: task.repair_chain ?? {
|
|
16001
16234
|
root_pr_number: pr.prNumber,
|
|
@@ -16052,15 +16285,15 @@ async function finalizePublishedPr({
|
|
|
16052
16285
|
safeProgress: safeProgress2,
|
|
16053
16286
|
log: log2,
|
|
16054
16287
|
patch: {
|
|
16055
|
-
status: "pr_opened",
|
|
16056
|
-
message: `opened ${pr.prUrl}${overlapNote}${pr.newCommit === false ? " (no new commits \u2014 the branch already held every change)" : ""}`,
|
|
16288
|
+
status: runnerFailure ? "failed" : "pr_opened",
|
|
16289
|
+
message: runnerFailure ? `${runnerFailureMessage}; preserved PR ${pr.prUrl} for owner review` : `opened ${pr.prUrl}${overlapNote}${pr.newCommit === false ? " (no new commits \u2014 the branch already held every change)" : ""}`,
|
|
16057
16290
|
pr_url: pr.prUrl,
|
|
16058
16291
|
pr_number: pr.prNumber,
|
|
16059
16292
|
pr_branch: pr.branch,
|
|
16060
16293
|
result: (() => {
|
|
16061
16294
|
const prefix = overlapBlocked ? `[VO-PUBLISH-OVERLAP-BLOCKED: ${blockedRefs}] ` : "";
|
|
16062
16295
|
const room = 2e3 - prefix.length;
|
|
16063
|
-
return `${prefix}${partial ? partialPrContinuationResult(run, room, rateLimitResume ? "rate_limited" : null) : String(run.summary).slice(0, room)}`;
|
|
16296
|
+
return `${prefix}${partial || runnerFailure ? partialPrContinuationResult(run, room, rateLimitResume ? "rate_limited" : null) : String(run.summary).slice(0, room)}`;
|
|
16064
16297
|
})(),
|
|
16065
16298
|
...runOutcomePatch(run),
|
|
16066
16299
|
...terminalLedgerPatch(run)
|
|
@@ -16104,13 +16337,14 @@ var init_publication_outcome = __esm({
|
|
|
16104
16337
|
init_outcome_commit();
|
|
16105
16338
|
init_terminal_delivery();
|
|
16106
16339
|
init_rate_limit_resume();
|
|
16340
|
+
init_error_message();
|
|
16107
16341
|
defaultRunCommand4 = (cmd, args, cwd, opts = {}) => runProcess2(cmd, args, { cwd, ...opts });
|
|
16108
16342
|
}
|
|
16109
16343
|
});
|
|
16110
16344
|
|
|
16111
16345
|
// ../../scripts/virtual-office/code-runner/committed-scratch-cleanup.mjs
|
|
16112
16346
|
import fsp12 from "node:fs/promises";
|
|
16113
|
-
import
|
|
16347
|
+
import path21 from "node:path";
|
|
16114
16348
|
function defaultRun4(command, args, cwd, options = {}) {
|
|
16115
16349
|
return runProcess2(command, args, { cwd, ...options });
|
|
16116
16350
|
}
|
|
@@ -16118,13 +16352,13 @@ async function resolveSafeScratchTarget(worktreeDir, file) {
|
|
|
16118
16352
|
if (!isAgentScratch(file)) {
|
|
16119
16353
|
throw new Error(`refusing to remove non-scratch publication path: ${file}`);
|
|
16120
16354
|
}
|
|
16121
|
-
const root =
|
|
16122
|
-
const target =
|
|
16123
|
-
const relative =
|
|
16124
|
-
if (!relative || relative.startsWith(`..${
|
|
16355
|
+
const root = path21.resolve(worktreeDir);
|
|
16356
|
+
const target = path21.resolve(root, file);
|
|
16357
|
+
const relative = path21.relative(root, target);
|
|
16358
|
+
if (!relative || relative.startsWith(`..${path21.sep}`) || path21.isAbsolute(relative)) {
|
|
16125
16359
|
throw new Error(`refusing to remove publication scratch outside worktree: ${file}`);
|
|
16126
16360
|
}
|
|
16127
|
-
for (let cursor = target; cursor !== root; cursor =
|
|
16361
|
+
for (let cursor = target; cursor !== root; cursor = path21.dirname(cursor)) {
|
|
16128
16362
|
try {
|
|
16129
16363
|
if ((await fsp12.lstat(cursor)).isSymbolicLink()) {
|
|
16130
16364
|
throw new Error(`refusing to follow symlink while removing publication scratch: ${file}`);
|
|
@@ -16246,7 +16480,7 @@ var init_publication_scope = __esm({
|
|
|
16246
16480
|
// ../../scripts/virtual-office/code-runner/recovery-ledger.mjs
|
|
16247
16481
|
import fs13 from "node:fs";
|
|
16248
16482
|
import fsp13 from "node:fs/promises";
|
|
16249
|
-
import
|
|
16483
|
+
import path22 from "node:path";
|
|
16250
16484
|
function recoveryTaskId(prompt) {
|
|
16251
16485
|
const match = String(prompt || "").match(/VO_RECOVERY_FROM_CODE_TASK:\s*([0-9a-f-]{36})/i);
|
|
16252
16486
|
return match ? match[1].toLowerCase() : null;
|
|
@@ -16260,10 +16494,10 @@ function cloneLeaf(repo) {
|
|
|
16260
16494
|
function recoveryLedgerCandidates(repo, clonesRoot2) {
|
|
16261
16495
|
const leaf = cloneLeaf(repo);
|
|
16262
16496
|
if (!leaf || !clonesRoot2) return [];
|
|
16263
|
-
const canonical =
|
|
16497
|
+
const canonical = path22.join(clonesRoot2, leaf);
|
|
16264
16498
|
return [
|
|
16265
|
-
|
|
16266
|
-
|
|
16499
|
+
path22.join(clonesRoot2, ".agent-worktrees", leaf, "recovery-ledger.jsonl"),
|
|
16500
|
+
path22.join(canonical, ".agent-worktrees", "recovery-ledger.jsonl")
|
|
16267
16501
|
];
|
|
16268
16502
|
}
|
|
16269
16503
|
async function readLedger(file, readFile6) {
|
|
@@ -16441,7 +16675,16 @@ function isMaxTurnExhaustion(run = {}, maxTurns) {
|
|
|
16441
16675
|
return Number.isInteger(maxTurns) && maxTurns > 0 && Number.isInteger(run.numTurns) && run.numTurns > maxTurns;
|
|
16442
16676
|
}
|
|
16443
16677
|
function decideNoChangesTerminalStatus({ partial, run = {}, maxTurns } = {}) {
|
|
16678
|
+
const structuredFailure = serializeRunnerFailure(run.failure);
|
|
16444
16679
|
if (!partial) {
|
|
16680
|
+
if (structuredFailure) {
|
|
16681
|
+
const failure = JSON.parse(structuredFailure);
|
|
16682
|
+
return {
|
|
16683
|
+
status: "failed",
|
|
16684
|
+
message: `agent reported a runner blocker with no file changes \u2014 ${failure.code}; ${failure.operator_next_action}`,
|
|
16685
|
+
result: partialPrContinuationResult(run, RESULT_LIMIT)
|
|
16686
|
+
};
|
|
16687
|
+
}
|
|
16445
16688
|
if (reportsBlocker(run.summary)) {
|
|
16446
16689
|
return {
|
|
16447
16690
|
status: "failed",
|
|
@@ -16465,8 +16708,8 @@ function decideNoChangesTerminalStatus({ partial, run = {}, maxTurns } = {}) {
|
|
|
16465
16708
|
const cause = String(run.summary || "").trim();
|
|
16466
16709
|
return {
|
|
16467
16710
|
status: "failed",
|
|
16468
|
-
message: cause ? `agent made no file changes \u2014 ${cause.slice(0, 200)}` : "agent made no file changes",
|
|
16469
|
-
result: (cause || "no_changes").slice(0, RESULT_LIMIT)
|
|
16711
|
+
message: structuredFailure ? `agent made no file changes \u2014 ${JSON.parse(structuredFailure).code}; ${JSON.parse(structuredFailure).operator_next_action}` : cause ? `agent made no file changes \u2014 ${cause.slice(0, 200)}` : "agent made no file changes",
|
|
16712
|
+
result: structuredFailure ? partialPrContinuationResult(run, RESULT_LIMIT) : (cause || "no_changes").slice(0, RESULT_LIMIT)
|
|
16470
16713
|
};
|
|
16471
16714
|
}
|
|
16472
16715
|
async function closeSupersededSourceOnNoChanges({
|
|
@@ -16599,6 +16842,8 @@ var init_no_changes_terminal_status = __esm({
|
|
|
16599
16842
|
init_terminal_ledger_patch();
|
|
16600
16843
|
init_outcome_commit();
|
|
16601
16844
|
init_terminal_delivery();
|
|
16845
|
+
init_error_message();
|
|
16846
|
+
init_partial_pr_continuation();
|
|
16602
16847
|
RESULT_LIMIT = 2e3;
|
|
16603
16848
|
}
|
|
16604
16849
|
});
|
|
@@ -16916,6 +17161,50 @@ var init_daemon_config = __esm({
|
|
|
16916
17161
|
});
|
|
16917
17162
|
|
|
16918
17163
|
// ../../scripts/virtual-office/code-runner/task-worktree-preparation.mjs
|
|
17164
|
+
async function git3(worktreeDir, args, runCommand = runProcess2) {
|
|
17165
|
+
const result = await runCommand("git", args, { cwd: worktreeDir, timeoutMs: 6e4 });
|
|
17166
|
+
if (result.status !== 0) throw new Error(`source binding git ${args[0]} failed before model execution`);
|
|
17167
|
+
return String(result.stdout || "").trim();
|
|
17168
|
+
}
|
|
17169
|
+
function hasAffirmativePreSpawnNoSpendEvidence(parentTask) {
|
|
17170
|
+
return Boolean(parentTask && parentTask.status === "failed" && parentTask.completed_at && parentTask.execution_started_at === null && parentTask.usage_reported_at && parentTask.cost_usd === 0 && parentTask.cost_basis === "no_agent_spawned" && typeof parentTask.claimed_by === "string" && parentTask.claimed_by.length > 0 && RUNNER_INSTANCE_ID.test(parentTask.runner_instance_id ?? "") && parentTask.usage_runner_instance_id === parentTask.runner_instance_id && !parentTask.token_usage && !parentTask.model_usage && !parentTask.detached_run_economics?.length && !parentTask.resumed_from && !parentTask.pr_branch && !parentTask.pr_url && !parentTask.pr_number);
|
|
17171
|
+
}
|
|
17172
|
+
async function bindTaskExecutionSource({
|
|
17173
|
+
client,
|
|
17174
|
+
task,
|
|
17175
|
+
parentTask,
|
|
17176
|
+
worktreeDir,
|
|
17177
|
+
continuationRestore,
|
|
17178
|
+
runCommand = runProcess2
|
|
17179
|
+
}) {
|
|
17180
|
+
const baseSha = task.comparative_base_sha;
|
|
17181
|
+
if (!baseSha) return null;
|
|
17182
|
+
if (!EXACT_SHA.test(baseSha)) throw new Error("comparative base SHA is invalid");
|
|
17183
|
+
const actualSha = await git3(worktreeDir, ["rev-parse", "HEAD"], runCommand);
|
|
17184
|
+
if (!EXACT_SHA.test(actualSha)) throw new Error("worktree HEAD is not an exact commit SHA");
|
|
17185
|
+
if (!task.resumed_from) {
|
|
17186
|
+
if (actualSha !== baseSha) throw new Error("initial comparative worktree HEAD does not match requested base");
|
|
17187
|
+
} else if (continuationRestore) {
|
|
17188
|
+
if (task.pr_head_sha_at_enqueue && actualSha !== task.pr_head_sha_at_enqueue) {
|
|
17189
|
+
throw new Error("continuation worktree HEAD does not match its expected continuation head");
|
|
17190
|
+
}
|
|
17191
|
+
await git3(worktreeDir, ["merge-base", "--is-ancestor", baseSha, actualSha], runCommand);
|
|
17192
|
+
} else {
|
|
17193
|
+
if (!hasAffirmativePreSpawnNoSpendEvidence(parentTask)) {
|
|
17194
|
+
throw new Error("continuation branch is missing and prior execution state is unknown; recovery review required");
|
|
17195
|
+
}
|
|
17196
|
+
if (actualSha !== baseSha) throw new Error("safe pre-spawn continuation did not rematerialize the original base");
|
|
17197
|
+
}
|
|
17198
|
+
const acknowledgement = await client.postProgress(task.code_task_id, {
|
|
17199
|
+
execution_start_sha: actualSha,
|
|
17200
|
+
stage: "preparing_worktree",
|
|
17201
|
+
message: `Exact execution source ${actualSha} verified before model start`
|
|
17202
|
+
});
|
|
17203
|
+
if (acknowledgement?.terminal || acknowledgement?.task?.execution_start_sha !== actualSha) {
|
|
17204
|
+
throw new Error("control plane did not durably acknowledge execution_start_sha");
|
|
17205
|
+
}
|
|
17206
|
+
return { baseSha, executionStartSha: actualSha };
|
|
17207
|
+
}
|
|
16919
17208
|
async function prepareTaskWorktree({ client, task, cfg, safeProgress: safeProgress2, log: log2 }) {
|
|
16920
17209
|
const id = task.code_task_id;
|
|
16921
17210
|
await safeProgress2(client, id, runnerStagePatch(
|
|
@@ -16932,7 +17221,7 @@ async function prepareTaskWorktree({ client, task, cfg, safeProgress: safeProgre
|
|
|
16932
17221
|
const wt = await createFixWorktree(
|
|
16933
17222
|
"code-task",
|
|
16934
17223
|
{ source: id.slice(0, 8), repo: task.repo },
|
|
16935
|
-
{ githubToken }
|
|
17224
|
+
{ githubToken, baseSha: task.comparative_base_sha ?? null }
|
|
16936
17225
|
);
|
|
16937
17226
|
if (!wt.worktreeName || !wt.worktreeDir) {
|
|
16938
17227
|
throw new Error("worktree isolation failure \u2014 refusing to run in the main tree");
|
|
@@ -16962,8 +17251,16 @@ async function prepareTaskWorktree({ client, task, cfg, safeProgress: safeProgre
|
|
|
16962
17251
|
});
|
|
16963
17252
|
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" : ""}`);
|
|
16964
17253
|
}
|
|
16965
|
-
|
|
17254
|
+
const sourceBinding = await bindTaskExecutionSource({
|
|
17255
|
+
client,
|
|
17256
|
+
task,
|
|
17257
|
+
parentTask,
|
|
17258
|
+
worktreeDir: wt.worktreeDir,
|
|
17259
|
+
continuationRestore
|
|
17260
|
+
});
|
|
17261
|
+
return { wt, githubToken, agentGithubReadToken, continuationRestore, repairPlan, sourceBinding };
|
|
16966
17262
|
}
|
|
17263
|
+
var EXACT_SHA, RUNNER_INSTANCE_ID;
|
|
16967
17264
|
var init_task_worktree_preparation = __esm({
|
|
16968
17265
|
"../../scripts/virtual-office/code-runner/task-worktree-preparation.mjs"() {
|
|
16969
17266
|
"use strict";
|
|
@@ -16971,6 +17268,9 @@ var init_task_worktree_preparation = __esm({
|
|
|
16971
17268
|
init_resume_branch();
|
|
16972
17269
|
init_repair_publication_strategy();
|
|
16973
17270
|
init_task_helpers();
|
|
17271
|
+
init_process_runner2();
|
|
17272
|
+
EXACT_SHA = /^[0-9a-f]{40}$/u;
|
|
17273
|
+
RUNNER_INSTANCE_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu;
|
|
16974
17274
|
}
|
|
16975
17275
|
});
|
|
16976
17276
|
|