@algosuite/vo-mcp 0.2.0-beta.7 → 0.2.0-beta.9
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/README.md +26 -2
- package/bin/vo-mcp +6 -3
- package/dist/cli.js +245 -4
- package/dist/cli.js.map +4 -4
- package/dist/index.js +166 -1
- package/dist/index.js.map +3 -3
- package/dist/install-cli.js +196 -54
- package/dist/install-cli.js.map +4 -4
- package/dist/runner-cli.js +1094 -179
- package/dist/runner-cli.js.map +4 -4
- package/dist/runner-supervisor.js +726 -0
- package/dist/runner-supervisor.js.map +7 -0
- package/dist/supervisor-credential-helper.js +125 -0
- package/dist/supervisor-credential-helper.js.map +7 -0
- package/package.json +4 -2
package/dist/runner-cli.js
CHANGED
|
@@ -1297,15 +1297,41 @@ var init_pnpm_hydration = __esm({
|
|
|
1297
1297
|
}
|
|
1298
1298
|
});
|
|
1299
1299
|
|
|
1300
|
-
// src/runner/worktree-
|
|
1300
|
+
// src/runner/worktree-paths.mjs
|
|
1301
1301
|
import { createHash as createHash2 } from "node:crypto";
|
|
1302
|
-
import fs3 from "node:fs";
|
|
1303
|
-
import fsp6 from "node:fs/promises";
|
|
1304
1302
|
import path5 from "node:path";
|
|
1305
|
-
function
|
|
1303
|
+
function samePath(left, right) {
|
|
1304
|
+
const a = path5.resolve(left);
|
|
1305
|
+
const b = path5.resolve(right);
|
|
1306
|
+
return process.platform === "win32" ? a.toLowerCase() === b.toLowerCase() : a === b;
|
|
1307
|
+
}
|
|
1308
|
+
function worktreePoolForRoot(root, { clonesRootDir = process.env.VO_CODE_RUNNER_CLONES_ROOT || "" } = {}) {
|
|
1309
|
+
const canonicalRoot = path5.resolve(root);
|
|
1310
|
+
if (clonesRootDir) {
|
|
1311
|
+
const clonePool = path5.resolve(clonesRootDir);
|
|
1312
|
+
if (samePath(path5.dirname(canonicalRoot), clonePool)) {
|
|
1313
|
+
return path5.join(clonePool, ".agent-worktrees", path5.basename(canonicalRoot));
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
return path5.join(canonicalRoot, ".agent-worktrees");
|
|
1317
|
+
}
|
|
1318
|
+
function worktreeDirForName(root, worktreeName, options = {}) {
|
|
1306
1319
|
const leaf = createHash2("sha256").update(String(worktreeName)).digest("hex").slice(0, 16);
|
|
1307
|
-
return path5.join(root,
|
|
1320
|
+
return path5.join(worktreePoolForRoot(root, options), leaf);
|
|
1308
1321
|
}
|
|
1322
|
+
function recoveryLedgerPathForRoot(root, options = {}) {
|
|
1323
|
+
return path5.join(worktreePoolForRoot(root, options), "recovery-ledger.jsonl");
|
|
1324
|
+
}
|
|
1325
|
+
var init_worktree_paths = __esm({
|
|
1326
|
+
"src/runner/worktree-paths.mjs"() {
|
|
1327
|
+
"use strict";
|
|
1328
|
+
}
|
|
1329
|
+
});
|
|
1330
|
+
|
|
1331
|
+
// src/runner/worktree-cleanup.mjs
|
|
1332
|
+
import fs3 from "node:fs";
|
|
1333
|
+
import fsp6 from "node:fs/promises";
|
|
1334
|
+
import path6 from "node:path";
|
|
1309
1335
|
function stateFromEntry(entry) {
|
|
1310
1336
|
return {
|
|
1311
1337
|
root: entry.root,
|
|
@@ -1344,16 +1370,16 @@ function pruneSuccessfulStates(nowMs = Date.now()) {
|
|
|
1344
1370
|
}
|
|
1345
1371
|
}
|
|
1346
1372
|
function assertTrackedCleanupPath(entry) {
|
|
1347
|
-
const poolRoot =
|
|
1373
|
+
const poolRoot = worktreePoolForRoot(entry.root);
|
|
1348
1374
|
const expected = worktreeDirForName(entry.root, entry.worktreeName);
|
|
1349
|
-
const resolvedPool =
|
|
1350
|
-
const resolvedTarget =
|
|
1351
|
-
if (!resolvedTarget.startsWith(`${resolvedPool}${
|
|
1375
|
+
const resolvedPool = path6.resolve(poolRoot);
|
|
1376
|
+
const resolvedTarget = path6.resolve(entry.worktreeDir);
|
|
1377
|
+
if (!resolvedTarget.startsWith(`${resolvedPool}${path6.sep}`)) {
|
|
1352
1378
|
const error = new Error(`cleanup refused outside managed pool: ${entry.worktreeDir}`);
|
|
1353
1379
|
error.cleanupFatal = true;
|
|
1354
1380
|
throw error;
|
|
1355
1381
|
}
|
|
1356
|
-
if (resolvedTarget !==
|
|
1382
|
+
if (resolvedTarget !== path6.resolve(expected)) {
|
|
1357
1383
|
const error = new Error(`cleanup refused for unexpected tracked path: ${entry.worktreeDir}`);
|
|
1358
1384
|
error.cleanupFatal = true;
|
|
1359
1385
|
throw error;
|
|
@@ -1367,8 +1393,8 @@ async function worktreeStillRegistered2(root, worktreeDir, gitRunner) {
|
|
|
1367
1393
|
if (result.status !== 0) {
|
|
1368
1394
|
throw new Error(`git worktree list --porcelain failed during cleanup verification: ${summarizeProcessFailure(result)}`);
|
|
1369
1395
|
}
|
|
1370
|
-
const registered = String(result.stdout || "").split(/\r?\n/u).filter((line) => line.startsWith("worktree ")).map((line) =>
|
|
1371
|
-
return registered.includes(
|
|
1396
|
+
const registered = String(result.stdout || "").split(/\r?\n/u).filter((line) => line.startsWith("worktree ")).map((line) => path6.resolve(line.slice("worktree ".length).trim()));
|
|
1397
|
+
return registered.includes(path6.resolve(worktreeDir));
|
|
1372
1398
|
}
|
|
1373
1399
|
function cleanupBackoff(attempt) {
|
|
1374
1400
|
return 250 * attempt;
|
|
@@ -1478,7 +1504,7 @@ function scheduleTrackedCleanup(entry, options = {}) {
|
|
|
1478
1504
|
}
|
|
1479
1505
|
function pendingCleanupDirs() {
|
|
1480
1506
|
return new Set(
|
|
1481
|
-
[...CLEANUP_STATES.values()].filter((state) => state.status === "pending").map((state) =>
|
|
1507
|
+
[...CLEANUP_STATES.values()].filter((state) => state.status === "pending").map((state) => path6.resolve(state.worktreeDir))
|
|
1482
1508
|
);
|
|
1483
1509
|
}
|
|
1484
1510
|
var CLEANUP_STATES, CLEANUP_PROMISES, SUCCESS_HISTORY_LIMIT, SUCCESS_HISTORY_TTL_MS, DEFAULT_CLEANUP_ATTEMPTS;
|
|
@@ -1487,6 +1513,7 @@ var init_worktree_cleanup = __esm({
|
|
|
1487
1513
|
"use strict";
|
|
1488
1514
|
init_pnpm_link_detach();
|
|
1489
1515
|
init_process_runner();
|
|
1516
|
+
init_worktree_paths();
|
|
1490
1517
|
CLEANUP_STATES = /* @__PURE__ */ new Map();
|
|
1491
1518
|
CLEANUP_PROMISES = /* @__PURE__ */ new Map();
|
|
1492
1519
|
SUCCESS_HISTORY_LIMIT = 50;
|
|
@@ -1498,13 +1525,13 @@ var init_worktree_cleanup = __esm({
|
|
|
1498
1525
|
// src/runner/task-root-prepare.mjs
|
|
1499
1526
|
import fs4 from "node:fs";
|
|
1500
1527
|
import fsp7 from "node:fs/promises";
|
|
1501
|
-
import
|
|
1528
|
+
import path7 from "node:path";
|
|
1502
1529
|
function prepLockDir(root) {
|
|
1503
|
-
return
|
|
1530
|
+
return path7.join(root, ".agent-worktrees", "runner-root-prep.lock");
|
|
1504
1531
|
}
|
|
1505
1532
|
function readLockMeta(lockDir) {
|
|
1506
1533
|
try {
|
|
1507
|
-
return JSON.parse(fs4.readFileSync(
|
|
1534
|
+
return JSON.parse(fs4.readFileSync(path7.join(lockDir, "owner.json"), "utf8"));
|
|
1508
1535
|
} catch {
|
|
1509
1536
|
return null;
|
|
1510
1537
|
}
|
|
@@ -1524,9 +1551,9 @@ async function acquirePrepLock(root, options = {}) {
|
|
|
1524
1551
|
const staleMs = options.lockStaleMs ?? PREP_LOCK_STALE_MS;
|
|
1525
1552
|
const sleep3 = options.sleep || sleepMs;
|
|
1526
1553
|
const lockDir = prepLockDir(root);
|
|
1527
|
-
const ownerPath =
|
|
1554
|
+
const ownerPath = path7.join(lockDir, "owner.json");
|
|
1528
1555
|
const deadline = nowMs() + waitMs;
|
|
1529
|
-
fs4.mkdirSync(
|
|
1556
|
+
fs4.mkdirSync(path7.dirname(lockDir), { recursive: true });
|
|
1530
1557
|
for (; ; ) {
|
|
1531
1558
|
try {
|
|
1532
1559
|
fs4.mkdirSync(lockDir);
|
|
@@ -1574,10 +1601,104 @@ async function gitText(root, args, options = {}) {
|
|
|
1574
1601
|
}
|
|
1575
1602
|
return String(result.stdout || "").trim();
|
|
1576
1603
|
}
|
|
1577
|
-
|
|
1604
|
+
function canonicalRecoveryDir(root, options = {}) {
|
|
1605
|
+
const now = options.now || (() => /* @__PURE__ */ new Date());
|
|
1606
|
+
return path7.join(
|
|
1607
|
+
options.managedPool || path7.join(root, ".agent-worktrees"),
|
|
1608
|
+
".canonical-recovery",
|
|
1609
|
+
`preexisting-${now().toISOString().replace(/[:.]/gu, "-")}`
|
|
1610
|
+
);
|
|
1611
|
+
}
|
|
1612
|
+
function canonicalPath(root, relative) {
|
|
1613
|
+
const resolvedRoot = path7.resolve(root);
|
|
1614
|
+
const target = path7.resolve(root, relative);
|
|
1615
|
+
const prefix = `${resolvedRoot}${path7.sep}`;
|
|
1616
|
+
if (!target.startsWith(prefix)) {
|
|
1617
|
+
throw new Error(`canonical recovery path escaped the runner clone: ${relative}`);
|
|
1618
|
+
}
|
|
1619
|
+
return target;
|
|
1620
|
+
}
|
|
1621
|
+
async function changedCanonicalPaths(root, options = {}) {
|
|
1622
|
+
const [trackedResult, untrackedResult] = await Promise.all([
|
|
1623
|
+
git(root, ["-c", "core.quotepath=false", "diff", "--name-only", "-z", "HEAD"], options),
|
|
1624
|
+
git(root, ["-c", "core.quotepath=false", "ls-files", "--others", "--exclude-standard", "-z"], options)
|
|
1625
|
+
]);
|
|
1626
|
+
if (trackedResult.status !== 0 || untrackedResult.status !== 0) {
|
|
1627
|
+
throw new Error("could not enumerate canonical clone residue before recovery");
|
|
1628
|
+
}
|
|
1629
|
+
return {
|
|
1630
|
+
tracked: splitZ(trackedResult.stdout),
|
|
1631
|
+
untracked: splitZ(untrackedResult.stdout)
|
|
1632
|
+
};
|
|
1633
|
+
}
|
|
1634
|
+
async function recoverManagedCanonicalResidue(root, options = {}) {
|
|
1635
|
+
const paths = await changedCanonicalPaths(root, options);
|
|
1636
|
+
const quarantineDir = canonicalRecoveryDir(root, options);
|
|
1637
|
+
const headSha = await gitText(root, ["rev-parse", "HEAD"], options);
|
|
1638
|
+
await fsp7.mkdir(quarantineDir, { recursive: true });
|
|
1639
|
+
const patchResult = await git(root, ["diff", "--binary", "HEAD"], options);
|
|
1640
|
+
if (patchResult.status !== 0) {
|
|
1641
|
+
throw new Error(`could not preserve canonical tracked changes: ${summarizeProcessFailure(patchResult)}`);
|
|
1642
|
+
}
|
|
1643
|
+
await fsp7.writeFile(path7.join(quarantineDir, "tracked.patch"), String(patchResult.stdout || ""), "utf8");
|
|
1644
|
+
const symlinks = [];
|
|
1645
|
+
for (const relative of paths.untracked) {
|
|
1646
|
+
const source = canonicalPath(root, relative);
|
|
1647
|
+
const stat = await fsp7.lstat(source);
|
|
1648
|
+
if (stat.isSymbolicLink()) {
|
|
1649
|
+
symlinks.push({ path: relative, target: await fsp7.readlink(source) });
|
|
1650
|
+
continue;
|
|
1651
|
+
}
|
|
1652
|
+
if (!stat.isFile()) {
|
|
1653
|
+
throw new Error(`canonical recovery refuses unsupported untracked entry: ${relative}`);
|
|
1654
|
+
}
|
|
1655
|
+
const target = canonicalPath(path7.join(quarantineDir, "untracked"), relative);
|
|
1656
|
+
await fsp7.mkdir(path7.dirname(target), { recursive: true });
|
|
1657
|
+
await fsp7.copyFile(source, target);
|
|
1658
|
+
}
|
|
1659
|
+
await fsp7.writeFile(path7.join(quarantineDir, "manifest.json"), `${JSON.stringify({
|
|
1660
|
+
recoveredAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1661
|
+
canonicalRoot: path7.resolve(root),
|
|
1662
|
+
canonicalHead: headSha,
|
|
1663
|
+
tracked: paths.tracked,
|
|
1664
|
+
untracked: paths.untracked,
|
|
1665
|
+
symlinks
|
|
1666
|
+
}, null, 2)}
|
|
1667
|
+
`, "utf8");
|
|
1668
|
+
if (paths.tracked.length > 0) {
|
|
1669
|
+
const restored = await git(root, [
|
|
1670
|
+
"restore",
|
|
1671
|
+
`--source=${headSha}`,
|
|
1672
|
+
"--staged",
|
|
1673
|
+
"--worktree",
|
|
1674
|
+
"--",
|
|
1675
|
+
...paths.tracked
|
|
1676
|
+
], options);
|
|
1677
|
+
if (restored.status !== 0) {
|
|
1678
|
+
throw new Error(`could not restore canonical tracked changes; evidence: ${quarantineDir}`);
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
for (const relative of paths.untracked) {
|
|
1682
|
+
await fsp7.rm(canonicalPath(root, relative), { force: true });
|
|
1683
|
+
}
|
|
1578
1684
|
const status = await gitText(root, ["status", "--porcelain"], options);
|
|
1579
1685
|
if (status) {
|
|
1580
|
-
throw new Error(`canonical clone
|
|
1686
|
+
throw new Error(`canonical clone recovery did not restore a clean tree; evidence: ${quarantineDir}`);
|
|
1687
|
+
}
|
|
1688
|
+
(options.logger || console.error)(
|
|
1689
|
+
`[vo-mcp runner] preserved and recovered ${paths.tracked.length + paths.untracked.length} preexisting canonical-clone write(s): ${quarantineDir}`
|
|
1690
|
+
);
|
|
1691
|
+
return { quarantineDir, ...paths };
|
|
1692
|
+
}
|
|
1693
|
+
async function alignCanonicalClone(root, options = {}) {
|
|
1694
|
+
let status = await gitText(root, ["status", "--porcelain"], options);
|
|
1695
|
+
if (status) {
|
|
1696
|
+
if (options.recoverDirtyCanonical !== true) {
|
|
1697
|
+
throw new Error(`canonical clone is dirty: ${status.split(/\r?\n/u, 1)[0]}`);
|
|
1698
|
+
}
|
|
1699
|
+
await recoverManagedCanonicalResidue(root, options);
|
|
1700
|
+
status = await gitText(root, ["status", "--porcelain"], options);
|
|
1701
|
+
if (status) throw new Error("canonical clone remained dirty after managed recovery");
|
|
1581
1702
|
}
|
|
1582
1703
|
const branch = await gitText(root, ["branch", "--show-current"], options);
|
|
1583
1704
|
if (branch !== "main") {
|
|
@@ -1609,11 +1730,11 @@ async function registeredWorktreeDirs(root, options = {}) {
|
|
|
1609
1730
|
throw new Error(`git worktree list --porcelain failed while checking managed residue: ${summarizeProcessFailure(listed)}`);
|
|
1610
1731
|
}
|
|
1611
1732
|
return new Set(
|
|
1612
|
-
String(listed.stdout || "").split(/\r?\n/u).filter((line) => line.startsWith("worktree ")).map((line) =>
|
|
1733
|
+
String(listed.stdout || "").split(/\r?\n/u).filter((line) => line.startsWith("worktree ")).map((line) => path7.resolve(line.slice("worktree ".length).trim()))
|
|
1613
1734
|
);
|
|
1614
1735
|
}
|
|
1615
1736
|
async function reportLegacyResiduals(root, options = {}) {
|
|
1616
|
-
const managedRoot =
|
|
1737
|
+
const managedRoot = path7.join(root, ".agent-worktrees");
|
|
1617
1738
|
if (!fs4.existsSync(managedRoot)) return [];
|
|
1618
1739
|
const registered = await registeredWorktreeDirs(root, options);
|
|
1619
1740
|
const pending = pendingCleanupDirs();
|
|
@@ -1621,7 +1742,7 @@ async function reportLegacyResiduals(root, options = {}) {
|
|
|
1621
1742
|
for (const entry of fs4.readdirSync(managedRoot, { withFileTypes: true })) {
|
|
1622
1743
|
if (!entry.isDirectory()) continue;
|
|
1623
1744
|
if (shouldIgnoreManagedEntry(entry.name)) continue;
|
|
1624
|
-
const absolute =
|
|
1745
|
+
const absolute = path7.resolve(path7.join(managedRoot, entry.name));
|
|
1625
1746
|
if (registered.has(absolute)) continue;
|
|
1626
1747
|
if (pending.has(absolute)) continue;
|
|
1627
1748
|
found.push(absolute);
|
|
@@ -1649,7 +1770,7 @@ async function prepareTaskRoot(root, options = {}) {
|
|
|
1649
1770
|
await release();
|
|
1650
1771
|
}
|
|
1651
1772
|
}
|
|
1652
|
-
var PREP_LOCK_WAIT_MS, PREP_LOCK_STALE_MS, REPORTED_RESIDUAL_SNAPSHOTS, IGNORED_MANAGED_ENTRIES;
|
|
1773
|
+
var PREP_LOCK_WAIT_MS, PREP_LOCK_STALE_MS, REPORTED_RESIDUAL_SNAPSHOTS, IGNORED_MANAGED_ENTRIES, splitZ;
|
|
1653
1774
|
var init_task_root_prepare = __esm({
|
|
1654
1775
|
"src/runner/task-root-prepare.mjs"() {
|
|
1655
1776
|
"use strict";
|
|
@@ -1660,19 +1781,20 @@ var init_task_root_prepare = __esm({
|
|
|
1660
1781
|
PREP_LOCK_STALE_MS = 45 * 60 * 1e3;
|
|
1661
1782
|
REPORTED_RESIDUAL_SNAPSHOTS = /* @__PURE__ */ new Set();
|
|
1662
1783
|
IGNORED_MANAGED_ENTRIES = /* @__PURE__ */ new Set([
|
|
1784
|
+
".canonical-recovery",
|
|
1663
1785
|
"recovery-ledger.jsonl",
|
|
1664
1786
|
"runner-pnpm-hydration.json",
|
|
1665
1787
|
"runner-node-modules-quarantine",
|
|
1666
1788
|
"runner-root-prep.lock"
|
|
1667
1789
|
]);
|
|
1790
|
+
splitZ = (value) => String(value || "").split("\0").filter((item) => item.length > 0);
|
|
1668
1791
|
}
|
|
1669
1792
|
});
|
|
1670
1793
|
|
|
1671
1794
|
// src/runner/worktree-helper.mjs
|
|
1672
|
-
import { createHash as createHash3 } from "node:crypto";
|
|
1673
1795
|
import fs5 from "node:fs";
|
|
1674
1796
|
import fsp8 from "node:fs/promises";
|
|
1675
|
-
import
|
|
1797
|
+
import path8 from "node:path";
|
|
1676
1798
|
function repoRoot() {
|
|
1677
1799
|
return process.env.VO_CODE_RUNNER_REPO || process.cwd();
|
|
1678
1800
|
}
|
|
@@ -1683,16 +1805,12 @@ function sanitize(value, fallback) {
|
|
|
1683
1805
|
const cleaned = String(value || "").trim().toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
1684
1806
|
return cleaned || fallback;
|
|
1685
1807
|
}
|
|
1686
|
-
function worktreeDirForName2(root, worktreeName) {
|
|
1687
|
-
const leaf = createHash3("sha256").update(String(worktreeName)).digest("hex").slice(0, 16);
|
|
1688
|
-
return path7.join(root, ".agent-worktrees", leaf);
|
|
1689
|
-
}
|
|
1690
1808
|
function cloneDirForSlug(repoSlug, clonesRootDir) {
|
|
1691
1809
|
if (!clonesRootDir || !repoSlug || !VALID_REPO_SLUG.test(String(repoSlug))) return null;
|
|
1692
1810
|
const [owner, name] = String(repoSlug).split("/");
|
|
1693
1811
|
if (owner === "." || owner === ".." || name === "." || name === "..") return null;
|
|
1694
1812
|
if (owner.startsWith("-") || name.startsWith("-")) return null;
|
|
1695
|
-
return
|
|
1813
|
+
return path8.join(clonesRootDir, `${sanitize(owner, "owner")}__${sanitize(name, "repo")}`);
|
|
1696
1814
|
}
|
|
1697
1815
|
function cloneLockDir(dir) {
|
|
1698
1816
|
return `${dir}.clone-lock`;
|
|
@@ -1707,7 +1825,7 @@ async function pathExists5(target) {
|
|
|
1707
1825
|
}
|
|
1708
1826
|
async function readLockMeta2(lockDir) {
|
|
1709
1827
|
try {
|
|
1710
|
-
return JSON.parse(await fsp8.readFile(
|
|
1828
|
+
return JSON.parse(await fsp8.readFile(path8.join(lockDir, "owner.json"), "utf8"));
|
|
1711
1829
|
} catch {
|
|
1712
1830
|
return null;
|
|
1713
1831
|
}
|
|
@@ -1728,11 +1846,11 @@ async function acquireCloneLock(dir, options = {}) {
|
|
|
1728
1846
|
const sleep3 = options.sleep || sleepMs;
|
|
1729
1847
|
const lockDir = cloneLockDir(dir);
|
|
1730
1848
|
const deadline = nowMs() + waitMs;
|
|
1731
|
-
await fsp8.mkdir(
|
|
1849
|
+
await fsp8.mkdir(path8.dirname(lockDir), { recursive: true });
|
|
1732
1850
|
for (; ; ) {
|
|
1733
1851
|
try {
|
|
1734
1852
|
await fsp8.mkdir(lockDir);
|
|
1735
|
-
await fsp8.writeFile(
|
|
1853
|
+
await fsp8.writeFile(path8.join(lockDir, "owner.json"), `${JSON.stringify({
|
|
1736
1854
|
pid: process.pid,
|
|
1737
1855
|
createdAt: new Date(nowMs()).toISOString(),
|
|
1738
1856
|
dir
|
|
@@ -1761,7 +1879,7 @@ async function acquireCloneLock(dir, options = {}) {
|
|
|
1761
1879
|
}
|
|
1762
1880
|
}
|
|
1763
1881
|
async function isUsableGitClone(dir, runner = runProcess) {
|
|
1764
|
-
if (!await pathExists5(
|
|
1882
|
+
if (!await pathExists5(path8.join(dir, ".git"))) return false;
|
|
1765
1883
|
const result = await runner("git", ["-C", dir, "rev-parse", "HEAD"], { timeoutMs: 1e4 });
|
|
1766
1884
|
return result.status === 0 && Boolean(String(result.stdout || "").trim());
|
|
1767
1885
|
}
|
|
@@ -1782,7 +1900,7 @@ async function ensureUsableClone(repoSlug, dir, options = {}) {
|
|
|
1782
1900
|
const maxAttempts = options.maxAttempts || 5;
|
|
1783
1901
|
const raceWaitMs = options.raceWaitMs ?? 1e4;
|
|
1784
1902
|
let lastError = null;
|
|
1785
|
-
await fsp8.mkdir(
|
|
1903
|
+
await fsp8.mkdir(path8.dirname(dir), { recursive: true });
|
|
1786
1904
|
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
1787
1905
|
if (await pathExists5(dir)) {
|
|
1788
1906
|
if (await waitForUsableClone(dir, runner, sleep3, raceWaitMs)) return dir;
|
|
@@ -1794,7 +1912,7 @@ async function ensureUsableClone(repoSlug, dir, options = {}) {
|
|
|
1794
1912
|
["clone", "--no-tags", `https://github.com/${owner}/${name}.git`, tmpDir],
|
|
1795
1913
|
{ timeoutMs: 6e5 }
|
|
1796
1914
|
);
|
|
1797
|
-
if (clone.status !== 0 || !await pathExists5(
|
|
1915
|
+
if (clone.status !== 0 || !await pathExists5(path8.join(tmpDir, ".git"))) {
|
|
1798
1916
|
await fsp8.rm(tmpDir, { recursive: true, force: true });
|
|
1799
1917
|
lastError = new Error(`[vo-mcp runner] clone failed for ${repoSlug}: ${describeGitFailure(clone)}`);
|
|
1800
1918
|
continue;
|
|
@@ -1821,7 +1939,7 @@ async function ensureUsableClone(repoSlug, dir, options = {}) {
|
|
|
1821
1939
|
}
|
|
1822
1940
|
async function resolveTaskRoot(repoSlug) {
|
|
1823
1941
|
const root = clonesRoot();
|
|
1824
|
-
if (root && !
|
|
1942
|
+
if (root && !path8.isAbsolute(root)) {
|
|
1825
1943
|
throw new Error(`[vo-mcp runner] VO_CODE_RUNNER_CLONES_ROOT must be an absolute path (got '${root}')`);
|
|
1826
1944
|
}
|
|
1827
1945
|
const dir = cloneDirForSlug(repoSlug, root);
|
|
@@ -1866,9 +1984,12 @@ async function createFixWorktree(kind, error = {}, options = {}) {
|
|
|
1866
1984
|
const unique = `${process.pid}-${Math.random().toString(36).slice(2, 8)}`;
|
|
1867
1985
|
const worktreeName = `${safeKind}-${safeTarget}-${stamp}-${unique}`;
|
|
1868
1986
|
const branchName = `vo/${worktreeName}`;
|
|
1869
|
-
const worktreeDir =
|
|
1987
|
+
const worktreeDir = worktreeDirForName(root, worktreeName);
|
|
1870
1988
|
const dependencyOwnership = createDependencyOwnershipTracker(worktreeDir);
|
|
1871
|
-
const prep = await prepare(root
|
|
1989
|
+
const prep = await prepare(root, {
|
|
1990
|
+
recoverDirtyCanonical: multiRepo,
|
|
1991
|
+
managedPool: path8.dirname(worktreeDir)
|
|
1992
|
+
});
|
|
1872
1993
|
await processRunner("git", ["config", "core.longpaths", "true"], { cwd: root, timeoutMs: 3e4 });
|
|
1873
1994
|
const add = await addWorktree({ root, branchName, worktreeDir });
|
|
1874
1995
|
if (!add.ok) {
|
|
@@ -1920,7 +2041,7 @@ function preserveFailedWorktree(worktreeName, meta = {}) {
|
|
|
1920
2041
|
const tracked = TRACKED_WORKTREES.get(worktreeName);
|
|
1921
2042
|
if (tracked) TRACKED_WORKTREES.delete(worktreeName);
|
|
1922
2043
|
const root = tracked ? tracked.root : repoRoot();
|
|
1923
|
-
const worktreeDir = tracked ? tracked.worktreeDir :
|
|
2044
|
+
const worktreeDir = tracked ? tracked.worktreeDir : worktreeDirForName(root, worktreeName);
|
|
1924
2045
|
const entry = {
|
|
1925
2046
|
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1926
2047
|
worktreeName,
|
|
@@ -1932,8 +2053,8 @@ function preserveFailedWorktree(worktreeName, meta = {}) {
|
|
|
1932
2053
|
reason: String(meta.reason || "task failed").slice(0, 300)
|
|
1933
2054
|
};
|
|
1934
2055
|
try {
|
|
1935
|
-
const ledger =
|
|
1936
|
-
fs5.mkdirSync(
|
|
2056
|
+
const ledger = recoveryLedgerPathForRoot(root);
|
|
2057
|
+
fs5.mkdirSync(path8.dirname(ledger), { recursive: true });
|
|
1937
2058
|
fs5.appendFileSync(ledger, `${JSON.stringify(entry)}
|
|
1938
2059
|
`, "utf8");
|
|
1939
2060
|
console.error(`[vo-mcp runner] Preserved worktree ${worktreeName} (reason: ${entry.reason})`);
|
|
@@ -1951,6 +2072,8 @@ var init_worktree_helper = __esm({
|
|
|
1951
2072
|
init_pnpm_hydration();
|
|
1952
2073
|
init_task_root_prepare();
|
|
1953
2074
|
init_worktree_cleanup();
|
|
2075
|
+
init_worktree_paths();
|
|
2076
|
+
init_worktree_paths();
|
|
1954
2077
|
VALID_REPO_SLUG = /^[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/u;
|
|
1955
2078
|
DEFAULT_CLONE_LOCK_WAIT_MS = 12e4;
|
|
1956
2079
|
DEFAULT_CLONE_LOCK_STALE_MS = 30 * 60 * 1e3;
|
|
@@ -2008,9 +2131,9 @@ function createControlPlaneClient({
|
|
|
2008
2131
|
throw new Error("VO_CONTROL_PLANE_URL is required for the code-runner daemon");
|
|
2009
2132
|
}
|
|
2010
2133
|
const root = baseUrl.replace(/\/+$/, "");
|
|
2011
|
-
async function req(method,
|
|
2134
|
+
async function req(method, path16, body) {
|
|
2012
2135
|
const bearer = await resolveBearer(env2);
|
|
2013
|
-
return fetchImpl(`${root}${
|
|
2136
|
+
return fetchImpl(`${root}${path16}`, {
|
|
2014
2137
|
method,
|
|
2015
2138
|
headers: {
|
|
2016
2139
|
"content-type": "application/json",
|
|
@@ -2200,6 +2323,34 @@ function createControlPlaneClient({
|
|
|
2200
2323
|
if (!res.ok) throw new Error(`heartbeat failed: HTTP ${res.status}`);
|
|
2201
2324
|
return true;
|
|
2202
2325
|
},
|
|
2326
|
+
/** Poll one authenticated runner's durable Mission Control action queue. */
|
|
2327
|
+
async pollRunnerControl({ runnerId, operatorId }) {
|
|
2328
|
+
const body = { runner_id: runnerId };
|
|
2329
|
+
if (operatorId) body.operator_id = operatorId;
|
|
2330
|
+
const res = await req("POST", "/api/v1/runner/control/poll", body);
|
|
2331
|
+
if (res.status === 401) {
|
|
2332
|
+
cachedFirebaseToken = null;
|
|
2333
|
+
throw new Error("runner control poll unauthorized (401)");
|
|
2334
|
+
}
|
|
2335
|
+
if (!res.ok) throw new Error(`runner control poll failed: HTTP ${res.status}`);
|
|
2336
|
+
const json = await res.json();
|
|
2337
|
+
const action = json?.action;
|
|
2338
|
+
return action && typeof action.action_id === "string" && action.action_id ? { ...action, actionId: action.action_id } : null;
|
|
2339
|
+
},
|
|
2340
|
+
/** Acknowledge a maintenance action after the host has restarted the child. */
|
|
2341
|
+
async completeRunnerControl(actionId, { runnerId, operatorId, status, detail }) {
|
|
2342
|
+
const body = { runner_id: runnerId, status };
|
|
2343
|
+
if (operatorId) body.operator_id = operatorId;
|
|
2344
|
+
if (detail) body.detail = detail;
|
|
2345
|
+
const res = await req("POST", `/api/v1/runner/control/${encodeURIComponent(actionId)}/complete`, body);
|
|
2346
|
+
if (res.status === 401) {
|
|
2347
|
+
cachedFirebaseToken = null;
|
|
2348
|
+
throw new Error("runner control completion unauthorized (401)");
|
|
2349
|
+
}
|
|
2350
|
+
if (!res.ok) throw new Error(`runner control completion failed: HTTP ${res.status}`);
|
|
2351
|
+
const json = await res.json();
|
|
2352
|
+
return json?.action || null;
|
|
2353
|
+
},
|
|
2203
2354
|
/**
|
|
2204
2355
|
* Mint a short-lived (~1h), repo-scoped GitHub App installation token for
|
|
2205
2356
|
* THIS runner's operator (M3). The control-plane keys the mint on the
|
|
@@ -2257,9 +2408,121 @@ var init_control_plane_client = __esm({
|
|
|
2257
2408
|
}
|
|
2258
2409
|
});
|
|
2259
2410
|
|
|
2411
|
+
// ../../scripts/virtual-office/code-runner/windows-claude-launch.mjs
|
|
2412
|
+
import { existsSync as existsSync2, realpathSync } from "node:fs";
|
|
2413
|
+
import { win32 as path9 } from "node:path";
|
|
2414
|
+
import { spawnSync } from "node:child_process";
|
|
2415
|
+
function pathValue(env2) {
|
|
2416
|
+
for (const key of ["Path", "PATH", "path"]) {
|
|
2417
|
+
if (typeof env2?.[key] === "string") return env2[key];
|
|
2418
|
+
}
|
|
2419
|
+
return "";
|
|
2420
|
+
}
|
|
2421
|
+
function cleanPathSegment(value) {
|
|
2422
|
+
const trimmed = String(value || "").trim();
|
|
2423
|
+
return trimmed.startsWith('"') && trimmed.endsWith('"') ? trimmed.slice(1, -1) : trimmed;
|
|
2424
|
+
}
|
|
2425
|
+
function pathCandidates(bin, env2) {
|
|
2426
|
+
if (path9.isAbsolute(bin) || /[\\/]/u.test(bin)) {
|
|
2427
|
+
return [path9.resolve(bin)];
|
|
2428
|
+
}
|
|
2429
|
+
const extension = path9.extname(bin);
|
|
2430
|
+
return pathValue(env2).split(";").map(cleanPathSegment).filter(Boolean).flatMap((directory) => extension ? [path9.join(directory, bin)] : [
|
|
2431
|
+
path9.join(directory, `${bin}.exe`),
|
|
2432
|
+
path9.join(directory, `${bin}.cmd`),
|
|
2433
|
+
path9.join(directory, `${bin}.ps1`),
|
|
2434
|
+
path9.join(directory, bin)
|
|
2435
|
+
]);
|
|
2436
|
+
}
|
|
2437
|
+
function canonicalExistingPath(candidate, exists, canonicalize) {
|
|
2438
|
+
if (!exists(candidate)) return null;
|
|
2439
|
+
try {
|
|
2440
|
+
return canonicalize(candidate);
|
|
2441
|
+
} catch {
|
|
2442
|
+
return null;
|
|
2443
|
+
}
|
|
2444
|
+
}
|
|
2445
|
+
function resolveWindowsClaudeExecutable({
|
|
2446
|
+
bin = "claude",
|
|
2447
|
+
env: env2 = process.env,
|
|
2448
|
+
exists = existsSync2,
|
|
2449
|
+
canonicalize = realpathSync
|
|
2450
|
+
} = {}) {
|
|
2451
|
+
const requested = String(bin || "").trim();
|
|
2452
|
+
if (!requested || requested.includes("\0")) {
|
|
2453
|
+
throw new TypeError("Claude executable must be a non-empty path without NUL bytes");
|
|
2454
|
+
}
|
|
2455
|
+
for (const candidate of pathCandidates(requested, env2)) {
|
|
2456
|
+
const found = canonicalExistingPath(candidate, exists, canonicalize);
|
|
2457
|
+
if (!found) continue;
|
|
2458
|
+
if (path9.extname(found).toLowerCase() === ".exe") return found;
|
|
2459
|
+
const native = path9.join(path9.dirname(found), ...NATIVE_CLAUDE_PARTS);
|
|
2460
|
+
const resolvedNative = canonicalExistingPath(native, exists, canonicalize);
|
|
2461
|
+
if (resolvedNative) return resolvedNative;
|
|
2462
|
+
}
|
|
2463
|
+
const error = new Error(
|
|
2464
|
+
`Could not resolve a native claude.exe for "${requested}". Update Claude Code with npm install -g @anthropic-ai/claude-code; the VO runner will not execute a shell-only .cmd/.ps1 shim.`
|
|
2465
|
+
);
|
|
2466
|
+
error.code = "ENOENT";
|
|
2467
|
+
throw error;
|
|
2468
|
+
}
|
|
2469
|
+
function buildWindowsClaudeLaunch({
|
|
2470
|
+
bin = "claude",
|
|
2471
|
+
args = [],
|
|
2472
|
+
env: env2 = process.env
|
|
2473
|
+
} = {}) {
|
|
2474
|
+
return {
|
|
2475
|
+
bin: resolveWindowsClaudeExecutable({ bin, env: env2 }),
|
|
2476
|
+
args: Array.from(args, (value) => String(value)),
|
|
2477
|
+
spawnOptions: {
|
|
2478
|
+
shell: false,
|
|
2479
|
+
windowsHide: true,
|
|
2480
|
+
windowsVerbatimArguments: false
|
|
2481
|
+
}
|
|
2482
|
+
};
|
|
2483
|
+
}
|
|
2484
|
+
function spawnClaudeSync(args = [], options = {}) {
|
|
2485
|
+
if (process.platform !== "win32") {
|
|
2486
|
+
return spawnSync("claude", args, { windowsHide: true, ...options });
|
|
2487
|
+
}
|
|
2488
|
+
try {
|
|
2489
|
+
const launch = buildWindowsClaudeLaunch({
|
|
2490
|
+
bin: "claude",
|
|
2491
|
+
args,
|
|
2492
|
+
env: options.env || process.env
|
|
2493
|
+
});
|
|
2494
|
+
return spawnSync(launch.bin, launch.args, {
|
|
2495
|
+
...options,
|
|
2496
|
+
...launch.spawnOptions
|
|
2497
|
+
});
|
|
2498
|
+
} catch (error) {
|
|
2499
|
+
return {
|
|
2500
|
+
error,
|
|
2501
|
+
status: null,
|
|
2502
|
+
signal: null,
|
|
2503
|
+
output: null,
|
|
2504
|
+
stdout: null,
|
|
2505
|
+
stderr: null
|
|
2506
|
+
};
|
|
2507
|
+
}
|
|
2508
|
+
}
|
|
2509
|
+
var NATIVE_CLAUDE_PARTS;
|
|
2510
|
+
var init_windows_claude_launch = __esm({
|
|
2511
|
+
"../../scripts/virtual-office/code-runner/windows-claude-launch.mjs"() {
|
|
2512
|
+
"use strict";
|
|
2513
|
+
NATIVE_CLAUDE_PARTS = [
|
|
2514
|
+
"node_modules",
|
|
2515
|
+
"@anthropic-ai",
|
|
2516
|
+
"claude-code",
|
|
2517
|
+
"bin",
|
|
2518
|
+
"claude.exe"
|
|
2519
|
+
];
|
|
2520
|
+
}
|
|
2521
|
+
});
|
|
2522
|
+
|
|
2260
2523
|
// ../../scripts/virtual-office/code-runner/anthropic-key-store.mjs
|
|
2261
2524
|
import { createRequire as createRequire2 } from "node:module";
|
|
2262
|
-
import { spawnSync } from "node:child_process";
|
|
2525
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
2263
2526
|
function defaultEntryCtor() {
|
|
2264
2527
|
if (_loadTried) return _entryCtor;
|
|
2265
2528
|
_loadTried = true;
|
|
@@ -2283,7 +2546,7 @@ function isTruthyFlag(v) {
|
|
|
2283
2546
|
return s === "1" || s === "true" || s === "yes" || s === "on";
|
|
2284
2547
|
}
|
|
2285
2548
|
function withAnthropicKey(baseEnv = {}, { getKey = getAnthropicKey } = {}) {
|
|
2286
|
-
if (isTruthyFlag(baseEnv[PREFER_LOGIN_ENV])) {
|
|
2549
|
+
if (isTruthyFlag(baseEnv[CLAUDE_PREFER_LOGIN_ENV]) || isTruthyFlag(baseEnv[PREFER_LOGIN_ENV])) {
|
|
2287
2550
|
const next = { ...baseEnv };
|
|
2288
2551
|
delete next.ANTHROPIC_API_KEY;
|
|
2289
2552
|
return next;
|
|
@@ -2293,7 +2556,7 @@ function withAnthropicKey(baseEnv = {}, { getKey = getAnthropicKey } = {}) {
|
|
|
2293
2556
|
return key ? { ...baseEnv, ANTHROPIC_API_KEY: key } : { ...baseEnv };
|
|
2294
2557
|
}
|
|
2295
2558
|
function describeAnthropicAuthSource(baseEnv = {}, { getKey = getAnthropicKey } = {}) {
|
|
2296
|
-
if (isTruthyFlag(baseEnv[PREFER_LOGIN_ENV])) {
|
|
2559
|
+
if (isTruthyFlag(baseEnv[CLAUDE_PREFER_LOGIN_ENV]) || isTruthyFlag(baseEnv[PREFER_LOGIN_ENV])) {
|
|
2297
2560
|
return "claude auth login (VO_RUNNER_PREFER_LOGIN set \u2014 any API key ignored)";
|
|
2298
2561
|
}
|
|
2299
2562
|
if (baseEnv.ANTHROPIC_API_KEY) return "ANTHROPIC_API_KEY from environment";
|
|
@@ -2306,29 +2569,31 @@ function augmentAuthError(summary) {
|
|
|
2306
2569
|
return `${s}
|
|
2307
2570
|
\u21B3 Anthropic auth failed on the runner. The \`claude\` CLI is a SEPARATE install/login from the Claude Desktop app and the Claude Code IDE extension \u2014 signing into those does NOT authenticate it. Fix: run \`claude auth login\` (Claude subscription) on the runner machine, or clear any stale ANTHROPIC_API_KEY (env / OS keychain / .env.local) and set VO_RUNNER_PREFER_LOGIN=1 \u2014 then restart the runner. Verify with \`claude -p "say hi"\`.`;
|
|
2308
2571
|
}
|
|
2309
|
-
function probeClaudeLoginState({
|
|
2572
|
+
function probeClaudeLoginState({
|
|
2573
|
+
spawn: spawn4 = spawnSync2,
|
|
2574
|
+
buildWindowsLaunch = buildWindowsClaudeLaunch,
|
|
2575
|
+
platform = process.platform
|
|
2576
|
+
} = {}) {
|
|
2310
2577
|
try {
|
|
2311
|
-
const
|
|
2312
|
-
|
|
2313
|
-
windowsHide: true,
|
|
2314
|
-
timeout: 5e3,
|
|
2315
|
-
encoding: "utf8"
|
|
2316
|
-
});
|
|
2578
|
+
const launch = platform === "win32" ? buildWindowsLaunch({ bin: "claude", args: ["auth", "status"] }) : { bin: "claude", args: ["auth", "status"], spawnOptions: { windowsHide: true } };
|
|
2579
|
+
const st = spawn4(launch.bin, launch.args, { ...launch.spawnOptions, timeout: 5e3, encoding: "utf8" });
|
|
2317
2580
|
const parsed = JSON.parse(String(st.stdout || "").trim() || "{}");
|
|
2318
2581
|
return typeof parsed.loggedIn === "boolean" ? parsed.loggedIn : null;
|
|
2319
2582
|
} catch {
|
|
2320
2583
|
return null;
|
|
2321
2584
|
}
|
|
2322
2585
|
}
|
|
2323
|
-
var require2, KEY_SERVICE, KEY_ACCOUNT, _entryCtor, _loadTried, PREFER_LOGIN_ENV, AUTH_ERROR_RE;
|
|
2586
|
+
var require2, KEY_SERVICE, KEY_ACCOUNT, _entryCtor, _loadTried, PREFER_LOGIN_ENV, CLAUDE_PREFER_LOGIN_ENV, AUTH_ERROR_RE;
|
|
2324
2587
|
var init_anthropic_key_store = __esm({
|
|
2325
2588
|
"../../scripts/virtual-office/code-runner/anthropic-key-store.mjs"() {
|
|
2326
2589
|
"use strict";
|
|
2590
|
+
init_windows_claude_launch();
|
|
2327
2591
|
require2 = createRequire2(import.meta.url);
|
|
2328
2592
|
KEY_SERVICE = "algosuite-vo";
|
|
2329
2593
|
KEY_ACCOUNT = "anthropic-api-key";
|
|
2330
2594
|
_loadTried = false;
|
|
2331
2595
|
PREFER_LOGIN_ENV = "VO_RUNNER_PREFER_LOGIN";
|
|
2596
|
+
CLAUDE_PREFER_LOGIN_ENV = "VO_RUNNER_CLAUDE_PREFER_LOGIN";
|
|
2332
2597
|
AUTH_ERROR_RE = /\b401\b|invalid[^.]{0,24}(authentication|credential)|authentication_error|unauthorized|not[ _-]?authenticated/i;
|
|
2333
2598
|
}
|
|
2334
2599
|
});
|
|
@@ -2416,7 +2681,7 @@ var init_agent_task_stream = __esm({
|
|
|
2416
2681
|
});
|
|
2417
2682
|
|
|
2418
2683
|
// ../../scripts/virtual-office/code-runner/sandbox/sandbox-docker.mjs
|
|
2419
|
-
import { spawnSync as
|
|
2684
|
+
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
2420
2685
|
function buildDockerArgs({
|
|
2421
2686
|
worktreeDir,
|
|
2422
2687
|
image = DEFAULT_SANDBOX_IMAGE,
|
|
@@ -2501,15 +2766,17 @@ var init_context7_mcp = __esm({
|
|
|
2501
2766
|
|
|
2502
2767
|
// ../../scripts/virtual-office/code-runner/claude-args.mjs
|
|
2503
2768
|
function buildClaudeArgs({ permissionMode = DEFAULT_PERMISSION_MODE, maxTurns, model, effort, maxBudgetUsd, env: env2 = process.env } = {}) {
|
|
2769
|
+
const effectivePermissionMode = String(permissionMode || DEFAULT_PERMISSION_MODE);
|
|
2770
|
+
const allowedTools = effectivePermissionMode === DEFAULT_PERMISSION_MODE ? `${VO_SESSION_STATE_TOOL},${VO_HEADLESS_PNPM_TOOL},${VO_HEADLESS_PNPM_FROM_DIR_TOOL}` : VO_SESSION_STATE_TOOL;
|
|
2504
2771
|
const args = [
|
|
2505
2772
|
"-p",
|
|
2506
2773
|
"--output-format",
|
|
2507
2774
|
"stream-json",
|
|
2508
2775
|
"--verbose",
|
|
2509
2776
|
"--permission-mode",
|
|
2510
|
-
|
|
2777
|
+
effectivePermissionMode,
|
|
2511
2778
|
"--allowedTools",
|
|
2512
|
-
|
|
2779
|
+
allowedTools
|
|
2513
2780
|
];
|
|
2514
2781
|
if (Number.isInteger(maxTurns) && maxTurns > 0) {
|
|
2515
2782
|
args.push("--max-turns", String(maxTurns));
|
|
@@ -2526,22 +2793,24 @@ function buildClaudeArgs({ permissionMode = DEFAULT_PERMISSION_MODE, maxTurns, m
|
|
|
2526
2793
|
args.push(...context7McpArgs(env2));
|
|
2527
2794
|
return args;
|
|
2528
2795
|
}
|
|
2529
|
-
var DEFAULT_PERMISSION_MODE, VO_SESSION_STATE_TOOL;
|
|
2796
|
+
var DEFAULT_PERMISSION_MODE, VO_SESSION_STATE_TOOL, VO_HEADLESS_PNPM_TOOL, VO_HEADLESS_PNPM_FROM_DIR_TOOL;
|
|
2530
2797
|
var init_claude_args = __esm({
|
|
2531
2798
|
"../../scripts/virtual-office/code-runner/claude-args.mjs"() {
|
|
2532
2799
|
"use strict";
|
|
2533
2800
|
init_context7_mcp();
|
|
2534
2801
|
DEFAULT_PERMISSION_MODE = "acceptEdits";
|
|
2535
2802
|
VO_SESSION_STATE_TOOL = "mcp__vo-mcp__vo_report_session_state";
|
|
2803
|
+
VO_HEADLESS_PNPM_TOOL = "Bash(pnpm *)";
|
|
2804
|
+
VO_HEADLESS_PNPM_FROM_DIR_TOOL = "Bash(pnpm --dir *)";
|
|
2536
2805
|
}
|
|
2537
2806
|
});
|
|
2538
2807
|
|
|
2539
2808
|
// ../../scripts/virtual-office/code-runner/terminal-process-cleanup.mjs
|
|
2540
|
-
import { spawnSync as
|
|
2809
|
+
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
2541
2810
|
function terminateAgentProcessTree({
|
|
2542
2811
|
child,
|
|
2543
2812
|
platform = process.platform,
|
|
2544
|
-
spawn: spawn4 =
|
|
2813
|
+
spawn: spawn4 = spawnSync4
|
|
2545
2814
|
} = {}) {
|
|
2546
2815
|
if (!child || !Number.isInteger(child.pid) || child.pid <= 0) return false;
|
|
2547
2816
|
if (platform === "win32") {
|
|
@@ -2577,7 +2846,7 @@ var init_terminal_process_cleanup = __esm({
|
|
|
2577
2846
|
});
|
|
2578
2847
|
|
|
2579
2848
|
// ../../scripts/virtual-office/code-runner/claude-runner.mjs
|
|
2580
|
-
import { spawn as spawn2
|
|
2849
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
2581
2850
|
function extractText(content) {
|
|
2582
2851
|
if (typeof content === "string") return content.trim();
|
|
2583
2852
|
if (Array.isArray(content)) {
|
|
@@ -2657,7 +2926,9 @@ function runAgentTask({
|
|
|
2657
2926
|
});
|
|
2658
2927
|
spawnBin = sandbox.dockerBin || "docker";
|
|
2659
2928
|
spawnOpts = { windowsHide: true };
|
|
2660
|
-
}
|
|
2929
|
+
} else if (typeof runner.prepareSpawn === "function") {
|
|
2930
|
+
({ bin: spawnBin, args: spawnArgs, spawnOptions: spawnOpts } = runner.prepareSpawn({ bin: spawnBin, args: spawnArgs, spawnOptions: spawnOpts, env: spawnEnv }));
|
|
2931
|
+
} else if (typeof runner.prepareSpawnArgs === "function") spawnArgs = runner.prepareSpawnArgs(spawnArgs);
|
|
2661
2932
|
const child = spawnImpl(spawnBin, spawnArgs, {
|
|
2662
2933
|
cwd,
|
|
2663
2934
|
env: spawnEnv,
|
|
@@ -2791,6 +3062,7 @@ var init_claude_runner = __esm({
|
|
|
2791
3062
|
init_agent_task_stream();
|
|
2792
3063
|
init_sandbox_docker();
|
|
2793
3064
|
init_claude_args();
|
|
3065
|
+
init_windows_claude_launch();
|
|
2794
3066
|
init_terminal_process_cleanup();
|
|
2795
3067
|
ClaudeRunner = class {
|
|
2796
3068
|
get binary() {
|
|
@@ -2803,10 +3075,11 @@ var init_claude_runner = __esm({
|
|
|
2803
3075
|
return parseStreamEvent(line);
|
|
2804
3076
|
}
|
|
2805
3077
|
getSpawnOptions() {
|
|
2806
|
-
return {
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
};
|
|
3078
|
+
return { shell: false, windowsHide: true };
|
|
3079
|
+
}
|
|
3080
|
+
prepareSpawn({ bin, args, spawnOptions, env: env2 = process.env } = {}) {
|
|
3081
|
+
if (process.platform !== "win32") return { bin, args, spawnOptions: spawnOptions ?? this.getSpawnOptions() };
|
|
3082
|
+
return buildWindowsClaudeLaunch({ bin, args, env: env2 });
|
|
2810
3083
|
}
|
|
2811
3084
|
/**
|
|
2812
3085
|
* Fill ANTHROPIC_API_KEY from the OS keychain when not already set (M4 BYO),
|
|
@@ -2827,12 +3100,7 @@ var init_claude_runner = __esm({
|
|
|
2827
3100
|
*/
|
|
2828
3101
|
async checkAuth() {
|
|
2829
3102
|
try {
|
|
2830
|
-
const probe =
|
|
2831
|
-
shell: process.platform === "win32",
|
|
2832
|
-
windowsHide: true,
|
|
2833
|
-
timeout: 3e3,
|
|
2834
|
-
stdio: "ignore"
|
|
2835
|
-
});
|
|
3103
|
+
const probe = spawnClaudeSync(["--version"], { timeout: 3e3, stdio: "ignore" });
|
|
2836
3104
|
if (probe.error) {
|
|
2837
3105
|
return {
|
|
2838
3106
|
installed: false,
|
|
@@ -2946,6 +3214,7 @@ var init_agent_key_store = __esm({
|
|
|
2946
3214
|
// ../../scripts/virtual-office/code-runner/codex-runner.mjs
|
|
2947
3215
|
var codex_runner_exports = {};
|
|
2948
3216
|
__export(codex_runner_exports, {
|
|
3217
|
+
CODEX_PREFER_LOGIN_ENV: () => CODEX_PREFER_LOGIN_ENV,
|
|
2949
3218
|
CodexRunner: () => CodexRunner,
|
|
2950
3219
|
buildCodexArgs: () => buildCodexArgs,
|
|
2951
3220
|
codexRunner: () => codexRunner,
|
|
@@ -2953,17 +3222,23 @@ __export(codex_runner_exports, {
|
|
|
2953
3222
|
resolveCodexBinary: () => resolveCodexBinary
|
|
2954
3223
|
});
|
|
2955
3224
|
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
2956
|
-
import { existsSync as
|
|
2957
|
-
import {
|
|
3225
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
3226
|
+
import { win32 } from "node:path";
|
|
3227
|
+
function isTruthyFlag2(value) {
|
|
3228
|
+
return ["1", "true", "yes", "on"].includes(String(value ?? "").trim().toLowerCase());
|
|
3229
|
+
}
|
|
2958
3230
|
function resolveCodexBinary({
|
|
2959
3231
|
env: env2 = process.env,
|
|
2960
3232
|
platform = process.platform,
|
|
2961
|
-
exists =
|
|
3233
|
+
exists = existsSync3
|
|
2962
3234
|
} = {}) {
|
|
2963
3235
|
if (platform !== "win32") return "codex";
|
|
2964
3236
|
const appData = String(env2.APPDATA || "").trim();
|
|
3237
|
+
const userProfile = String(env2.USERPROFILE || "").trim();
|
|
3238
|
+
const localAppData = String(env2.LOCALAPPDATA || "").trim();
|
|
3239
|
+
const candidates = [];
|
|
2965
3240
|
if (appData) {
|
|
2966
|
-
|
|
3241
|
+
candidates.push(win32.join(
|
|
2967
3242
|
appData,
|
|
2968
3243
|
"npm",
|
|
2969
3244
|
"node_modules",
|
|
@@ -2976,9 +3251,17 @@ function resolveCodexBinary({
|
|
|
2976
3251
|
"x86_64-pc-windows-msvc",
|
|
2977
3252
|
"bin",
|
|
2978
3253
|
"codex.exe"
|
|
2979
|
-
);
|
|
2980
|
-
|
|
3254
|
+
));
|
|
3255
|
+
}
|
|
3256
|
+
if (userProfile) {
|
|
3257
|
+
candidates.push(win32.join(userProfile, ".local", "bin", "codex.exe"));
|
|
3258
|
+
candidates.push(win32.join(userProfile, ".codex", "bin", "codex.exe"));
|
|
2981
3259
|
}
|
|
3260
|
+
if (localAppData) {
|
|
3261
|
+
candidates.push(win32.join(localAppData, "Microsoft", "WindowsApps", "codex.exe"));
|
|
3262
|
+
}
|
|
3263
|
+
const absolute = candidates.find((candidate) => exists(candidate));
|
|
3264
|
+
if (absolute) return absolute;
|
|
2982
3265
|
return "codex";
|
|
2983
3266
|
}
|
|
2984
3267
|
function buildCodexArgs({ model, effort } = {}) {
|
|
@@ -3029,14 +3312,21 @@ function parseCodexEvent(line) {
|
|
|
3029
3312
|
}
|
|
3030
3313
|
return null;
|
|
3031
3314
|
}
|
|
3032
|
-
var CodexRunner, codexRunner;
|
|
3315
|
+
var CODEX_PREFER_LOGIN_ENV, LEGACY_PREFER_LOGIN_ENV, CodexRunner, codexRunner;
|
|
3033
3316
|
var init_codex_runner = __esm({
|
|
3034
3317
|
"../../scripts/virtual-office/code-runner/codex-runner.mjs"() {
|
|
3035
3318
|
"use strict";
|
|
3036
3319
|
init_agent_key_store();
|
|
3320
|
+
CODEX_PREFER_LOGIN_ENV = "VO_RUNNER_CODEX_PREFER_LOGIN";
|
|
3321
|
+
LEGACY_PREFER_LOGIN_ENV = "VO_RUNNER_PREFER_LOGIN";
|
|
3037
3322
|
CodexRunner = class {
|
|
3323
|
+
constructor({ spawn: spawn4 = spawnSync5, resolveBinary = resolveCodexBinary, env: env2 = process.env } = {}) {
|
|
3324
|
+
this.spawn = spawn4;
|
|
3325
|
+
this.resolveBinary = resolveBinary;
|
|
3326
|
+
this.env = env2;
|
|
3327
|
+
}
|
|
3038
3328
|
get binary() {
|
|
3039
|
-
return
|
|
3329
|
+
return this.resolveBinary();
|
|
3040
3330
|
}
|
|
3041
3331
|
buildArgs(opts = {}) {
|
|
3042
3332
|
return buildCodexArgs(opts);
|
|
@@ -3058,25 +3348,58 @@ var init_codex_runner = __esm({
|
|
|
3058
3348
|
* env wins; no key stored → unchanged (a prior `codex login` still works).
|
|
3059
3349
|
*/
|
|
3060
3350
|
applyAuthEnv(env2 = process.env) {
|
|
3351
|
+
if (isTruthyFlag2(env2[CODEX_PREFER_LOGIN_ENV]) || isTruthyFlag2(env2[LEGACY_PREFER_LOGIN_ENV])) {
|
|
3352
|
+
const out = { ...env2 };
|
|
3353
|
+
delete out.OPENAI_API_KEY;
|
|
3354
|
+
delete out.CODEX_API_KEY;
|
|
3355
|
+
return out;
|
|
3356
|
+
}
|
|
3061
3357
|
return withAgentKey("openai", env2);
|
|
3062
3358
|
}
|
|
3063
|
-
/** Best-effort
|
|
3359
|
+
/** Best-effort binary + persisted-login probe. Never throws or spends tokens. */
|
|
3064
3360
|
async checkAuth() {
|
|
3065
3361
|
try {
|
|
3066
3362
|
const bin = this.binary;
|
|
3067
|
-
const
|
|
3363
|
+
const version = this.spawn(bin, ["--version"], {
|
|
3068
3364
|
...this.getSpawnOptions({ bin }),
|
|
3069
3365
|
windowsHide: true,
|
|
3070
3366
|
timeout: 3e3,
|
|
3071
3367
|
stdio: "ignore"
|
|
3072
3368
|
});
|
|
3073
|
-
if (error) {
|
|
3074
|
-
return { installed: false, authenticated: false, message: `codex not found on PATH: ${error.message}` };
|
|
3369
|
+
if (version.error) {
|
|
3370
|
+
return { installed: false, authenticated: false, message: `codex not found on PATH: ${version.error.message}` };
|
|
3075
3371
|
}
|
|
3076
|
-
if (status !== 0) {
|
|
3372
|
+
if (version.status !== 0) {
|
|
3077
3373
|
return { installed: true, authenticated: false, message: "codex exists but --version failed (auth unclear)" };
|
|
3078
3374
|
}
|
|
3079
|
-
|
|
3375
|
+
const login = this.spawn(bin, ["login", "status"], {
|
|
3376
|
+
...this.getSpawnOptions({ bin }),
|
|
3377
|
+
windowsHide: true,
|
|
3378
|
+
timeout: 5e3,
|
|
3379
|
+
encoding: "utf8"
|
|
3380
|
+
});
|
|
3381
|
+
const output = `${login.stdout || ""}
|
|
3382
|
+
${login.stderr || ""}`.trim();
|
|
3383
|
+
if (login.error || login.status !== 0) {
|
|
3384
|
+
const authEnv = this.applyAuthEnv(this.env);
|
|
3385
|
+
if (authEnv.OPENAI_API_KEY || authEnv.CODEX_API_KEY) {
|
|
3386
|
+
return {
|
|
3387
|
+
installed: true,
|
|
3388
|
+
authenticated: true,
|
|
3389
|
+
message: "codex API key available (no persisted ChatGPT login)"
|
|
3390
|
+
};
|
|
3391
|
+
}
|
|
3392
|
+
return {
|
|
3393
|
+
installed: true,
|
|
3394
|
+
authenticated: false,
|
|
3395
|
+
message: output || login.error?.message || "codex is installed but not logged in"
|
|
3396
|
+
};
|
|
3397
|
+
}
|
|
3398
|
+
return {
|
|
3399
|
+
installed: true,
|
|
3400
|
+
authenticated: true,
|
|
3401
|
+
message: output || "codex login status succeeded"
|
|
3402
|
+
};
|
|
3080
3403
|
} catch (err) {
|
|
3081
3404
|
return { installed: false, authenticated: false, message: `checkAuth probe failed: ${err.message}` };
|
|
3082
3405
|
}
|
|
@@ -3262,19 +3585,19 @@ var init_grok_model_catalog = __esm({
|
|
|
3262
3585
|
|
|
3263
3586
|
// ../../scripts/virtual-office/code-runner/grok-runner.mjs
|
|
3264
3587
|
import { spawnSync as spawnSync7 } from "node:child_process";
|
|
3265
|
-
import { existsSync as
|
|
3266
|
-
import { join as
|
|
3588
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
3589
|
+
import { join as join2, win32 as win322 } from "node:path";
|
|
3267
3590
|
function resolveGrokBinary({
|
|
3268
3591
|
env: env2 = process.env,
|
|
3269
3592
|
platform = process.platform,
|
|
3270
|
-
exists =
|
|
3593
|
+
exists = existsSync4
|
|
3271
3594
|
} = {}) {
|
|
3272
3595
|
const explicit = String(env2.VO_CODE_RUNNER_GROK_BIN || env2.GROK_BIN || "").trim();
|
|
3273
3596
|
if (explicit) return explicit;
|
|
3274
3597
|
if (platform !== "win32") return "grok";
|
|
3275
3598
|
const home = String(env2.USERPROFILE || "").trim() || String(env2.HOME || "").trim() || (env2.HOMEDRIVE && env2.HOMEPATH ? `${env2.HOMEDRIVE}${env2.HOMEPATH}` : "");
|
|
3276
3599
|
if (home) {
|
|
3277
|
-
const pathJoin = platform === "win32" ?
|
|
3600
|
+
const pathJoin = platform === "win32" ? win322.join : join2;
|
|
3278
3601
|
const installedBinary = pathJoin(home, ".grok", "bin", "grok.exe");
|
|
3279
3602
|
if (exists(installedBinary)) return installedBinary;
|
|
3280
3603
|
}
|
|
@@ -3867,9 +4190,9 @@ var init_rate_limit_detector_core = __esm({
|
|
|
3867
4190
|
// ../../scripts/virtual-office/code-runner/rate-limit-resume.mjs
|
|
3868
4191
|
import { appendFileSync, mkdirSync as mkdirSync2 } from "node:fs";
|
|
3869
4192
|
import { homedir as homedir2 } from "node:os";
|
|
3870
|
-
import { join as
|
|
4193
|
+
import { join as join3, dirname as dirname2 } from "node:path";
|
|
3871
4194
|
function resumeQueuePath() {
|
|
3872
|
-
return
|
|
4195
|
+
return join3(homedir2(), ".claude", "resume-queue.jsonl");
|
|
3873
4196
|
}
|
|
3874
4197
|
function buildResumeEntry({ task = {}, resumeAfter = null, summary = "", at } = {}) {
|
|
3875
4198
|
return {
|
|
@@ -3940,6 +4263,7 @@ var init_rate_limit_resume = __esm({
|
|
|
3940
4263
|
function isTransientGitError(err) {
|
|
3941
4264
|
if (!err) return false;
|
|
3942
4265
|
if (err.code && TRANSIENT_CODES.has(err.code)) return true;
|
|
4266
|
+
if (err.code === "UNKNOWN" && /^spawn(?:\s|$)/i.test(String(err.syscall || ""))) return true;
|
|
3943
4267
|
const msg = String(err.message || err);
|
|
3944
4268
|
if (/non-fast-forward|fast[- ]forward|\(fetch first\)|permission denied|authentication failed|\b40[134]\b|merge conflict|nothing to commit|did not match any/i.test(msg)) {
|
|
3945
4269
|
return false;
|
|
@@ -3980,7 +4304,7 @@ var init_auto_merge = __esm({
|
|
|
3980
4304
|
|
|
3981
4305
|
// ../../scripts/virtual-office/code-runner/pr-overlap-gate.mjs
|
|
3982
4306
|
import { spawnSync as spawnSync8 } from "node:child_process";
|
|
3983
|
-
import
|
|
4307
|
+
import path10 from "node:path";
|
|
3984
4308
|
var init_pr_overlap_gate = __esm({
|
|
3985
4309
|
"../../scripts/virtual-office/code-runner/pr-overlap-gate.mjs"() {
|
|
3986
4310
|
"use strict";
|
|
@@ -3995,14 +4319,14 @@ function parsePorcelainZ(out) {
|
|
|
3995
4319
|
for (let i = 0; i < tokens.length; i += 1) {
|
|
3996
4320
|
const tok = tokens[i];
|
|
3997
4321
|
if (!tok) continue;
|
|
3998
|
-
const
|
|
3999
|
-
if (
|
|
4322
|
+
const path16 = tok.slice(3);
|
|
4323
|
+
if (path16) files.push(path16);
|
|
4000
4324
|
if (tok[0] === "R" || tok[0] === "C") i += 1;
|
|
4001
4325
|
}
|
|
4002
4326
|
return files;
|
|
4003
4327
|
}
|
|
4004
|
-
function isAgentScratch(
|
|
4005
|
-
const p = String(
|
|
4328
|
+
function isAgentScratch(path16) {
|
|
4329
|
+
const p = String(path16 || "");
|
|
4006
4330
|
return SCRATCH_PATTERNS.some((re) => re.test(p));
|
|
4007
4331
|
}
|
|
4008
4332
|
function isMaxTurnsResult(summary) {
|
|
@@ -4063,7 +4387,7 @@ var init_publish = __esm({
|
|
|
4063
4387
|
// ../../scripts/virtual-office/code-runner/process-runner.mjs
|
|
4064
4388
|
import { spawn as spawn3 } from "node:child_process";
|
|
4065
4389
|
function buildStepLabel(cmd, args = []) {
|
|
4066
|
-
return
|
|
4390
|
+
return [cmd, ...args.slice(0, 2)].filter(Boolean).join(" ");
|
|
4067
4391
|
}
|
|
4068
4392
|
function buildExitError(cmd, args, { status, signal, stderr, stdout }) {
|
|
4069
4393
|
const err = new Error(
|
|
@@ -4082,6 +4406,18 @@ function buildTimeoutError(cmd, args, timeout, { stderr, stdout } = {}) {
|
|
|
4082
4406
|
err.stdout = stdout;
|
|
4083
4407
|
return err;
|
|
4084
4408
|
}
|
|
4409
|
+
function buildSpawnError(cmd, args, cwd, err) {
|
|
4410
|
+
const code = err?.code ? String(err.code) : "unknown";
|
|
4411
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
4412
|
+
const failure = new Error(
|
|
4413
|
+
`${buildStepLabel(cmd, args)} spawn failed${cwd ? ` in ${cwd}` : ""} (${code}): ${detail}`,
|
|
4414
|
+
{ cause: err }
|
|
4415
|
+
);
|
|
4416
|
+
for (const key of ["code", "syscall", "path", "spawnargs"]) {
|
|
4417
|
+
if (err?.[key] !== void 0) failure[key] = err[key];
|
|
4418
|
+
}
|
|
4419
|
+
return failure;
|
|
4420
|
+
}
|
|
4085
4421
|
function runProcess2(cmd, args, {
|
|
4086
4422
|
cwd,
|
|
4087
4423
|
timeout = 18e4,
|
|
@@ -4127,7 +4463,7 @@ function runProcess2(cmd, args, {
|
|
|
4127
4463
|
stderr += chunk.toString();
|
|
4128
4464
|
});
|
|
4129
4465
|
child.on("error", (err) => {
|
|
4130
|
-
settle(reject, err);
|
|
4466
|
+
settle(reject, buildSpawnError(cmd, args, cwd, err));
|
|
4131
4467
|
});
|
|
4132
4468
|
child.on("close", (status, signal) => {
|
|
4133
4469
|
const result = {
|
|
@@ -4181,7 +4517,7 @@ var init_process_runner2 = __esm({
|
|
|
4181
4517
|
});
|
|
4182
4518
|
|
|
4183
4519
|
// ../../scripts/virtual-office/code-runner/publish-async.mjs
|
|
4184
|
-
import
|
|
4520
|
+
import path11 from "node:path";
|
|
4185
4521
|
function compactTitle(value, max = 100) {
|
|
4186
4522
|
return String(value || "").replace(/\s+/g, " ").trim().slice(0, max) || "code-task";
|
|
4187
4523
|
}
|
|
@@ -4222,10 +4558,15 @@ async function resolveOrCreateBranchAsync(worktreeDir, branchPrefix, runCommand
|
|
|
4222
4558
|
}
|
|
4223
4559
|
return branch;
|
|
4224
4560
|
}
|
|
4225
|
-
async function runLocalPrOverlapGateAsync(worktreeDir, files, { env: env2 = process.env, excludePrNumber = null } = {}) {
|
|
4226
|
-
const scriptPath =
|
|
4561
|
+
async function runLocalPrOverlapGateAsync(worktreeDir, files, { branch = "", env: env2 = process.env, excludePrNumber = null } = {}) {
|
|
4562
|
+
const scriptPath = path11.join(worktreeDir, "scripts", "ci", "check-local-pr-overlap.mjs");
|
|
4227
4563
|
try {
|
|
4228
|
-
const output = await runProcess2("node", [
|
|
4564
|
+
const output = await runProcess2("node", [
|
|
4565
|
+
scriptPath,
|
|
4566
|
+
"--stdin",
|
|
4567
|
+
...branch ? ["--branch", String(branch)] : [],
|
|
4568
|
+
...excludePrNumber ? ["--exclude-pr", String(excludePrNumber)] : []
|
|
4569
|
+
], {
|
|
4229
4570
|
cwd: worktreeDir,
|
|
4230
4571
|
env: env2,
|
|
4231
4572
|
input: JSON.stringify([...new Set((files || []).map((file) => String(file || "").trim()).filter(Boolean))]),
|
|
@@ -4327,22 +4668,51 @@ async function markPrReadyAsync(worktreeDir, prNumber, githubToken = null, { run
|
|
|
4327
4668
|
env: githubToken ? installationTokenEnv(githubToken) : void 0
|
|
4328
4669
|
});
|
|
4329
4670
|
}
|
|
4330
|
-
async function closeSupersededPrAsync(worktreeDir, prNumber,
|
|
4671
|
+
async function closeSupersededPrAsync(worktreeDir, prNumber, replacementUrl, githubToken = null, { runCommand = defaultRunCommand } = {}) {
|
|
4331
4672
|
if (!Number.isInteger(prNumber) || prNumber <= 0) return false;
|
|
4673
|
+
if (!/^https:\/\/github\.com\/[^/]+\/[^/]+\/pull\/\d+$/u.test(String(replacementUrl || ""))) return false;
|
|
4332
4674
|
const env2 = githubToken ? installationTokenEnv(githubToken) : void 0;
|
|
4333
4675
|
const raw = await runCommand("gh", ["pr", "view", String(prNumber), "--json", "state"], worktreeDir, { env: env2 });
|
|
4334
4676
|
const state = JSON.parse(raw || "{}")?.state;
|
|
4335
4677
|
if (state !== "OPEN") return false;
|
|
4336
4678
|
await runCommand(
|
|
4337
4679
|
"gh",
|
|
4338
|
-
["pr", "close", String(prNumber), "--comment", `
|
|
4680
|
+
["pr", "close", String(prNumber), "--comment", `Superseded by ${replacementUrl}, rebuilt from current main by VO repair.`],
|
|
4339
4681
|
worktreeDir,
|
|
4340
4682
|
{ env: env2, timeout: 6e4 }
|
|
4341
4683
|
);
|
|
4342
4684
|
return true;
|
|
4343
4685
|
}
|
|
4344
|
-
async function
|
|
4345
|
-
|
|
4686
|
+
async function cleanupSupersededPrAsync({
|
|
4687
|
+
worktreeDir,
|
|
4688
|
+
supersedesPrNumber,
|
|
4689
|
+
replacementUrl,
|
|
4690
|
+
replacementNumber,
|
|
4691
|
+
githubToken,
|
|
4692
|
+
runCommand,
|
|
4693
|
+
onCleanupWarning
|
|
4694
|
+
}) {
|
|
4695
|
+
if (!supersedesPrNumber) return {};
|
|
4696
|
+
if (Number(supersedesPrNumber) === Number(replacementNumber)) {
|
|
4697
|
+
return { supersededPrClosed: false };
|
|
4698
|
+
}
|
|
4699
|
+
try {
|
|
4700
|
+
const closed = await retryTransientAsync(
|
|
4701
|
+
() => closeSupersededPrAsync(worktreeDir, supersedesPrNumber, replacementUrl, githubToken, { runCommand }),
|
|
4702
|
+
{ onRetry: gitRetryLog("gh pr close superseded") }
|
|
4703
|
+
);
|
|
4704
|
+
return { supersededPrClosed: closed };
|
|
4705
|
+
} catch (err) {
|
|
4706
|
+
const message = err && err.message ? err.message : String(err);
|
|
4707
|
+
onCleanupWarning?.(
|
|
4708
|
+
`[publish] replacement PR #${replacementNumber} is open; source PR #${supersedesPrNumber} cleanup failed: ${message}`
|
|
4709
|
+
);
|
|
4710
|
+
return { supersededPrClosed: false, supersededPrCloseError: message };
|
|
4711
|
+
}
|
|
4712
|
+
}
|
|
4713
|
+
async function pushBranchAsync(worktreeDir, branch, githubToken, { runCommand = defaultRunCommand, allowAmbientFallback = false, remoteBranch = branch } = {}) {
|
|
4714
|
+
const pushRef = remoteBranch && remoteBranch !== branch ? `HEAD:refs/heads/${remoteBranch}` : branch;
|
|
4715
|
+
const { primary, fallback } = pushPlan(pushRef, githubToken, { allowAmbientFallback });
|
|
4346
4716
|
try {
|
|
4347
4717
|
await runCommand("git", primary.args, worktreeDir, { env: primary.env });
|
|
4348
4718
|
return primary.tokenUsed;
|
|
@@ -4364,7 +4734,9 @@ async function openCodeTaskPrAsync(worktreeDir, files, {
|
|
|
4364
4734
|
allowAmbientGithubFallback = false,
|
|
4365
4735
|
draft = false,
|
|
4366
4736
|
armAutoMerge = false,
|
|
4737
|
+
targetBranch = null,
|
|
4367
4738
|
supersedesPrNumber = null,
|
|
4739
|
+
onCleanupWarning = console.error,
|
|
4368
4740
|
runCommand = defaultRunCommand,
|
|
4369
4741
|
runOverlapGate = runLocalPrOverlapGateAsync
|
|
4370
4742
|
} = {}) {
|
|
@@ -4385,7 +4757,9 @@ async function openCodeTaskPrAsync(worktreeDir, files, {
|
|
|
4385
4757
|
branch = committed.branch;
|
|
4386
4758
|
truncated = committed.truncated;
|
|
4387
4759
|
}
|
|
4760
|
+
const prBranch = String(targetBranch || branch).trim() || branch;
|
|
4388
4761
|
const overlap = await runOverlapGate(worktreeDir, files.filter((file) => !isAgentScratch(file)), {
|
|
4762
|
+
branch: prBranch,
|
|
4389
4763
|
env: githubToken ? installationTokenEnv(githubToken) : process.env,
|
|
4390
4764
|
excludePrNumber: supersedesPrNumber
|
|
4391
4765
|
});
|
|
@@ -4394,18 +4768,13 @@ ${overlap.output}`);
|
|
|
4394
4768
|
const tokenUsed = await retryTransientAsync(
|
|
4395
4769
|
() => pushBranchAsync(worktreeDir, branch, githubToken, {
|
|
4396
4770
|
runCommand,
|
|
4397
|
-
allowAmbientFallback: allowAmbientGithubFallback
|
|
4771
|
+
allowAmbientFallback: allowAmbientGithubFallback,
|
|
4772
|
+
remoteBranch: prBranch
|
|
4398
4773
|
}),
|
|
4399
4774
|
{ onRetry: gitRetryLog("git push") }
|
|
4400
4775
|
);
|
|
4401
4776
|
const authToken = tokenUsed ? githubToken : null;
|
|
4402
|
-
|
|
4403
|
-
await retryTransientAsync(
|
|
4404
|
-
() => closeSupersededPrAsync(worktreeDir, supersedesPrNumber, branch, authToken, { runCommand }),
|
|
4405
|
-
{ onRetry: gitRetryLog("gh pr close superseded") }
|
|
4406
|
-
);
|
|
4407
|
-
}
|
|
4408
|
-
const existing = await existingPrUrlAsync(worktreeDir, branch, authToken, { runCommand });
|
|
4777
|
+
const existing = await existingPrUrlAsync(worktreeDir, prBranch, authToken, { runCommand });
|
|
4409
4778
|
if (existing) {
|
|
4410
4779
|
if (!draft && existing.isDraft) {
|
|
4411
4780
|
await retryTransientAsync(
|
|
@@ -4421,20 +4790,30 @@ ${overlap.output}`);
|
|
|
4421
4790
|
draft,
|
|
4422
4791
|
runCommand
|
|
4423
4792
|
});
|
|
4793
|
+
const superseded2 = await cleanupSupersededPrAsync({
|
|
4794
|
+
worktreeDir,
|
|
4795
|
+
supersedesPrNumber,
|
|
4796
|
+
replacementUrl: existing.url,
|
|
4797
|
+
replacementNumber: existing.number,
|
|
4798
|
+
githubToken: authToken,
|
|
4799
|
+
runCommand,
|
|
4800
|
+
onCleanupWarning
|
|
4801
|
+
});
|
|
4424
4802
|
return {
|
|
4425
4803
|
prUrl: existing.url,
|
|
4426
4804
|
prNumber: existing.number,
|
|
4427
|
-
branch,
|
|
4805
|
+
branch: prBranch,
|
|
4428
4806
|
truncated,
|
|
4429
4807
|
resumed: true,
|
|
4430
4808
|
markedReady: !draft && existing.isDraft,
|
|
4431
|
-
...autoMerge2
|
|
4809
|
+
...autoMerge2,
|
|
4810
|
+
...superseded2
|
|
4432
4811
|
};
|
|
4433
4812
|
}
|
|
4434
4813
|
const out = await retryTransientAsync(
|
|
4435
4814
|
() => runCommand(
|
|
4436
4815
|
"gh",
|
|
4437
|
-
["pr", "create", "--base", "main", "--head",
|
|
4816
|
+
["pr", "create", "--base", "main", "--head", prBranch, "--title", compactTitle(title), "--body", String(body || ""), ...draft ? ["--draft"] : []],
|
|
4438
4817
|
worktreeDir,
|
|
4439
4818
|
{ env: authToken ? installationTokenEnv(authToken) : void 0 }
|
|
4440
4819
|
),
|
|
@@ -4443,6 +4822,7 @@ ${overlap.output}`);
|
|
|
4443
4822
|
const match = out.match(/https:\/\/github\.com\/[^/]+\/[^/]+\/pull\/(\d+)/);
|
|
4444
4823
|
if (!match) throw new Error("gh pr create returned no parseable PR URL");
|
|
4445
4824
|
const prNumber = Number(match[1]);
|
|
4825
|
+
const prUrl = match[0];
|
|
4446
4826
|
const autoMerge = await maybeArmAutoMergeAsync({
|
|
4447
4827
|
worktreeDir,
|
|
4448
4828
|
prNumber,
|
|
@@ -4451,7 +4831,16 @@ ${overlap.output}`);
|
|
|
4451
4831
|
draft,
|
|
4452
4832
|
runCommand
|
|
4453
4833
|
});
|
|
4454
|
-
|
|
4834
|
+
const superseded = await cleanupSupersededPrAsync({
|
|
4835
|
+
worktreeDir,
|
|
4836
|
+
supersedesPrNumber,
|
|
4837
|
+
replacementUrl: prUrl,
|
|
4838
|
+
replacementNumber: prNumber,
|
|
4839
|
+
githubToken: authToken,
|
|
4840
|
+
runCommand,
|
|
4841
|
+
onCleanupWarning
|
|
4842
|
+
});
|
|
4843
|
+
return { prUrl, prNumber, branch: prBranch, truncated, ...autoMerge, ...superseded };
|
|
4455
4844
|
}
|
|
4456
4845
|
var sleep;
|
|
4457
4846
|
var init_publish_async = __esm({
|
|
@@ -4465,6 +4854,111 @@ var init_publish_async = __esm({
|
|
|
4465
4854
|
}
|
|
4466
4855
|
});
|
|
4467
4856
|
|
|
4857
|
+
// ../../scripts/virtual-office/code-runner/resume-branch.mjs
|
|
4858
|
+
function compactBranchSegment(value) {
|
|
4859
|
+
return String(value || "").trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48) || "resume";
|
|
4860
|
+
}
|
|
4861
|
+
function defaultRunCommand2(cmd, args, cwd, opts = {}) {
|
|
4862
|
+
return runProcess2(cmd, args, { cwd, ...opts });
|
|
4863
|
+
}
|
|
4864
|
+
function buildResumeLocalBranchName(remoteBranch, {
|
|
4865
|
+
now = () => /* @__PURE__ */ new Date(),
|
|
4866
|
+
pid = process.pid,
|
|
4867
|
+
random = Math.random
|
|
4868
|
+
} = {}) {
|
|
4869
|
+
const stamp = now().toISOString().replace(/[:.]/g, "-");
|
|
4870
|
+
const unique = `${pid}-${random().toString(36).slice(2, 8)}`;
|
|
4871
|
+
return `vo/resume-${compactBranchSegment(remoteBranch)}-${stamp}-${unique}`;
|
|
4872
|
+
}
|
|
4873
|
+
async function assertBranchName(worktreeDir, branch, { env: env2, runCommand = defaultRunCommand2 } = {}) {
|
|
4874
|
+
const candidate = String(branch || "").trim();
|
|
4875
|
+
if (!candidate) throw new Error("resume branch is required");
|
|
4876
|
+
await runCommand("git", ["check-ref-format", "--branch", candidate], worktreeDir, {
|
|
4877
|
+
env: env2,
|
|
4878
|
+
timeout: 3e4
|
|
4879
|
+
});
|
|
4880
|
+
return candidate;
|
|
4881
|
+
}
|
|
4882
|
+
async function restoreContinuationBranch(worktreeDir, remoteBranch, {
|
|
4883
|
+
env: env2,
|
|
4884
|
+
runCommand = defaultRunCommand2,
|
|
4885
|
+
localBranchName
|
|
4886
|
+
} = {}) {
|
|
4887
|
+
const branch = await assertBranchName(worktreeDir, remoteBranch, { env: env2, runCommand });
|
|
4888
|
+
const localBranch = await assertBranchName(
|
|
4889
|
+
worktreeDir,
|
|
4890
|
+
localBranchName || buildResumeLocalBranchName(branch),
|
|
4891
|
+
{ env: env2, runCommand }
|
|
4892
|
+
);
|
|
4893
|
+
await runCommand(
|
|
4894
|
+
"git",
|
|
4895
|
+
["fetch", "origin", `${branch}:refs/remotes/origin/${branch}`],
|
|
4896
|
+
worktreeDir,
|
|
4897
|
+
{ env: env2, timeout: 12e4 }
|
|
4898
|
+
);
|
|
4899
|
+
await runCommand("git", ["checkout", "-b", localBranch, `origin/${branch}`], worktreeDir, {
|
|
4900
|
+
env: env2,
|
|
4901
|
+
timeout: 6e4
|
|
4902
|
+
});
|
|
4903
|
+
return { localBranch, remoteBranch: branch };
|
|
4904
|
+
}
|
|
4905
|
+
function parseHeadRefName(output) {
|
|
4906
|
+
const parsed = JSON.parse(String(output || "{}"));
|
|
4907
|
+
const branch = String(parsed?.headRefName || "").trim();
|
|
4908
|
+
return branch || null;
|
|
4909
|
+
}
|
|
4910
|
+
async function resolvePrHeadBranch(worktreeDir, {
|
|
4911
|
+
repo,
|
|
4912
|
+
prNumber,
|
|
4913
|
+
githubToken = null,
|
|
4914
|
+
allowAmbientGithubFallback = false,
|
|
4915
|
+
runCommand = defaultRunCommand2
|
|
4916
|
+
} = {}) {
|
|
4917
|
+
if (!repo || !Number.isInteger(prNumber) || prNumber <= 0) return null;
|
|
4918
|
+
const args = ["pr", "view", String(prNumber), "-R", String(repo), "--json", "headRefName"];
|
|
4919
|
+
try {
|
|
4920
|
+
const output = await runCommand("gh", args, worktreeDir, {
|
|
4921
|
+
env: githubToken ? installationTokenEnv(githubToken) : void 0,
|
|
4922
|
+
timeout: 6e4
|
|
4923
|
+
});
|
|
4924
|
+
return parseHeadRefName(output);
|
|
4925
|
+
} catch (error) {
|
|
4926
|
+
if (!githubToken || !allowAmbientGithubFallback) throw error;
|
|
4927
|
+
}
|
|
4928
|
+
const fallback = await runCommand("gh", args, worktreeDir, { timeout: 6e4 });
|
|
4929
|
+
return parseHeadRefName(fallback);
|
|
4930
|
+
}
|
|
4931
|
+
async function prepareContinuationBranch(worktreeDir, {
|
|
4932
|
+
task,
|
|
4933
|
+
parentTask,
|
|
4934
|
+
githubToken = null,
|
|
4935
|
+
allowAmbientGithubFallback = false,
|
|
4936
|
+
runCommand = defaultRunCommand2
|
|
4937
|
+
} = {}) {
|
|
4938
|
+
const continuationBranch = task?.pr_branch || parentTask?.pr_branch || (parentTask?.pr_number ? await resolvePrHeadBranch(worktreeDir, {
|
|
4939
|
+
repo: task?.repo,
|
|
4940
|
+
prNumber: parentTask.pr_number,
|
|
4941
|
+
githubToken,
|
|
4942
|
+
allowAmbientGithubFallback,
|
|
4943
|
+
runCommand
|
|
4944
|
+
}) : null);
|
|
4945
|
+
if (!continuationBranch && (task?.pr_branch || parentTask?.pr_url || parentTask?.pr_number)) {
|
|
4946
|
+
throw new Error(`continuation PR branch could not be resolved for task ${task?.code_task_id || "unknown"}`);
|
|
4947
|
+
}
|
|
4948
|
+
if (!continuationBranch) return null;
|
|
4949
|
+
return restoreContinuationBranch(worktreeDir, continuationBranch, {
|
|
4950
|
+
env: githubToken ? installationTokenEnv(githubToken) : void 0,
|
|
4951
|
+
runCommand
|
|
4952
|
+
});
|
|
4953
|
+
}
|
|
4954
|
+
var init_resume_branch = __esm({
|
|
4955
|
+
"../../scripts/virtual-office/code-runner/resume-branch.mjs"() {
|
|
4956
|
+
"use strict";
|
|
4957
|
+
init_publish();
|
|
4958
|
+
init_process_runner2();
|
|
4959
|
+
}
|
|
4960
|
+
});
|
|
4961
|
+
|
|
4468
4962
|
// ../../scripts/virtual-office/code-runner/dispatch-onboarding.mjs
|
|
4469
4963
|
function buildDispatchOnboarding({ repo = "Algosuite-ai/Nexus" } = {}) {
|
|
4470
4964
|
const reads = MANDATORY_READS.map((r, i) => ` ${i + 1}. ${r}`).join("\n");
|
|
@@ -4545,6 +5039,7 @@ var init_dispatch_onboarding = __esm({
|
|
|
4545
5039
|
"A handoff or roadmap line is a CLAIM, not evidence \u2014 verify shipped state against `git show origin/main:<path>`, never the stale local main tree.",
|
|
4546
5040
|
`MANDATORY FOR EVERY VO PR (cloud-run/vo-*, packages/vo-mcp, packages/consensus-engine, packages/vo-ratchets, packages/vo-arch-defaults, scripts/virtual-office, vo-claude-plugin): record a dated Change-log entry IN THE SAME PR via EITHER appending to the "\xA7 10 Change log" of docs/vo/vo-roadmap-2026-05-26.md OR (PREFERRED) creating docs/vo/roadmap-log/<YYYY-MM-DD>-<short-slug>.md (fragments avoid conflicts when PRs ship concurrently) and flip any status the work shipped. CI enforces this (check-vo-roadmap-discipline.mjs); bypass ONLY via "VO-ROADMAP-ALLOW: <reason>" in the PR body. The roadmap is the single source of truth \u2014 if you didn't update it, you didn't ship. Finish line = MERGED + DEPLOYED + LIVE-VERIFIED.`,
|
|
4547
5041
|
"Every UI change ships against docs/current/ui-trust-standard.md and adds VO QA tester coverage; verify in a real browser, not selector-presence.",
|
|
5042
|
+
"UNATTENDED VERIFICATION: no human can approve shell prompts. Run `pnpm ...` directly from the worktree root. For a standalone nested project with its own pnpm-lock.yaml, use `pnpm --dir <project> install --frozen-lockfile --prefer-offline --ignore-scripts --config.confirmModulesPurge=false`, then `pnpm --dir <project> ...` for its focused tests/type-check. These two pnpm forms are pre-authorized; do not skip local verification or wait for approval.",
|
|
4548
5043
|
'PR \u2192 LIVE is YOUR job end-to-end \u2014 the operator must NEVER be the one to discover a red PR or a backed-up deploy. Own every PR from branch \u2192 CI \u2192 merge \u2192 functions deploy \u2192 LIVE-VERIFIED. "Done" = the functions you changed are actually SERVING in prod in every region; prove it with `node scripts/ci/prove-pr-live.mjs --pr <N>` \u2014 a merge / green deploy checkmark / homepage 200 is NOT proof. If a function staled, re-deploy ONLY the affected functions (targeted), never a full deploy. If you hit a usage/rate limit, STOP cleanly with the PR obligation OPEN \u2014 the watchdog auto-resumes when it resets; do not abandon it. See docs/current/pr-live-stewardship-doctrine.md.',
|
|
4549
5044
|
`CONTEXT DEPTH IS NOT A REASON TO STOP. "I'm deep in context / fresh context would be better / I'll checkpoint" is the SAME premature-stop failure as doing 20 minutes of work instead of 6 hours \u2014 there is no quality cliff before compaction and the harness carries work forward. Keep BUILDING until the task is genuinely DONE; delicate or fleet-governing work means be CAREFUL, not stop. The ONLY valid pauses are real blockers: an operator decision is required, a dependency is not merged, or a hard external wait.`
|
|
4550
5045
|
];
|
|
@@ -4646,11 +5141,11 @@ var init_task_prompt = __esm({
|
|
|
4646
5141
|
|
|
4647
5142
|
// ../../scripts/virtual-office/code-runner/session-spool-forwarder.mjs
|
|
4648
5143
|
import { homedir as homedir3 } from "node:os";
|
|
4649
|
-
import { join as
|
|
5144
|
+
import { join as join4 } from "node:path";
|
|
4650
5145
|
import { readdir, readFile, unlink, writeFile } from "node:fs/promises";
|
|
4651
|
-
import { createHash as
|
|
5146
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
4652
5147
|
function deriveUuid(seed) {
|
|
4653
|
-
const h =
|
|
5148
|
+
const h = createHash3("sha256").update(seed).digest("hex");
|
|
4654
5149
|
return `${h.slice(0, 8)}-${h.slice(8, 12)}-5${h.slice(13, 16)}-${(parseInt(h.slice(16, 18), 16) & 63 | 128).toString(16)}${h.slice(18, 20)}-${h.slice(20, 32)}`;
|
|
4655
5150
|
}
|
|
4656
5151
|
function spoolToCloud(record, ids) {
|
|
@@ -4678,18 +5173,18 @@ async function readSpool(spoolDir = SPOOL_DIR) {
|
|
|
4678
5173
|
for (const f of files) {
|
|
4679
5174
|
if (!f.endsWith(".json")) continue;
|
|
4680
5175
|
try {
|
|
4681
|
-
const record = JSON.parse(await readFile(
|
|
5176
|
+
const record = JSON.parse(await readFile(join4(spoolDir, f), "utf8"));
|
|
4682
5177
|
if (record && typeof record.session_key === "string") {
|
|
4683
|
-
out.push({ full:
|
|
5178
|
+
out.push({ full: join4(spoolDir, f), record });
|
|
4684
5179
|
}
|
|
4685
5180
|
} catch {
|
|
4686
5181
|
}
|
|
4687
5182
|
}
|
|
4688
5183
|
return out;
|
|
4689
5184
|
}
|
|
4690
|
-
async function readCloudMap(
|
|
5185
|
+
async function readCloudMap(path16) {
|
|
4691
5186
|
try {
|
|
4692
|
-
return JSON.parse(await readFile(
|
|
5187
|
+
return JSON.parse(await readFile(path16, "utf8"));
|
|
4693
5188
|
} catch {
|
|
4694
5189
|
return {};
|
|
4695
5190
|
}
|
|
@@ -4762,8 +5257,8 @@ var SPOOL_DIR, CLOUD_MAP_FILE, STALE_MS, ACTIVE_SILENCE_MS;
|
|
|
4762
5257
|
var init_session_spool_forwarder = __esm({
|
|
4763
5258
|
"../../scripts/virtual-office/code-runner/session-spool-forwarder.mjs"() {
|
|
4764
5259
|
"use strict";
|
|
4765
|
-
SPOOL_DIR =
|
|
4766
|
-
CLOUD_MAP_FILE =
|
|
5260
|
+
SPOOL_DIR = join4(homedir3(), ".vo", "session-spool");
|
|
5261
|
+
CLOUD_MAP_FILE = join4(homedir3(), ".vo", "session-cloud-map.json");
|
|
4767
5262
|
STALE_MS = 60 * 60 * 1e3;
|
|
4768
5263
|
ACTIVE_SILENCE_MS = 10 * 60 * 1e3;
|
|
4769
5264
|
}
|
|
@@ -4832,13 +5327,13 @@ var init_rate_limit_resume_scheduler_core = __esm({
|
|
|
4832
5327
|
});
|
|
4833
5328
|
|
|
4834
5329
|
// ../../scripts/virtual-office/code-runner/rate-limit-resume-scheduler.mjs
|
|
4835
|
-
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as
|
|
4836
|
-
import { dirname as dirname3, join as
|
|
5330
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "node:fs";
|
|
5331
|
+
import { dirname as dirname3, join as join5, resolve } from "node:path";
|
|
4837
5332
|
function log(msg) {
|
|
4838
5333
|
console.log(`[rate-limit-scheduler ${(/* @__PURE__ */ new Date()).toISOString()}] ${msg}`);
|
|
4839
5334
|
}
|
|
4840
5335
|
function readQueue(queuePath) {
|
|
4841
|
-
if (!
|
|
5336
|
+
if (!existsSync5(queuePath)) return [];
|
|
4842
5337
|
const content = readFileSync2(queuePath, "utf-8");
|
|
4843
5338
|
const lines = content.split("\n").filter((l) => l.trim());
|
|
4844
5339
|
const entries = [];
|
|
@@ -4857,11 +5352,11 @@ function writeQueue(queuePath, entries) {
|
|
|
4857
5352
|
writeFileSync2(queuePath, lines + (entries.length > 0 ? "\n" : ""), "utf-8");
|
|
4858
5353
|
}
|
|
4859
5354
|
function attemptsStorePath() {
|
|
4860
|
-
return
|
|
5355
|
+
return join5(dirname3(resumeQueuePath()), "resume-attempts.json");
|
|
4861
5356
|
}
|
|
4862
5357
|
function readAttemptsStore() {
|
|
4863
5358
|
const p = attemptsStorePath();
|
|
4864
|
-
if (!
|
|
5359
|
+
if (!existsSync5(p)) return {};
|
|
4865
5360
|
try {
|
|
4866
5361
|
const parsed = JSON.parse(readFileSync2(p, "utf-8"));
|
|
4867
5362
|
return parsed && typeof parsed === "object" ? parsed : {};
|
|
@@ -5103,7 +5598,7 @@ var init_agent_availability = __esm({
|
|
|
5103
5598
|
// ../../scripts/virtual-office/code-runner/account-usage.mjs
|
|
5104
5599
|
import fs6 from "node:fs";
|
|
5105
5600
|
import os from "node:os";
|
|
5106
|
-
import
|
|
5601
|
+
import path12 from "node:path";
|
|
5107
5602
|
function readClaudeUsage({ homeDir = os.homedir(), read: rawRead = readJson } = {}) {
|
|
5108
5603
|
const read = (p) => {
|
|
5109
5604
|
try {
|
|
@@ -5112,7 +5607,7 @@ function readClaudeUsage({ homeDir = os.homedir(), read: rawRead = readJson } =
|
|
|
5112
5607
|
return null;
|
|
5113
5608
|
}
|
|
5114
5609
|
};
|
|
5115
|
-
const status = read(
|
|
5610
|
+
const status = read(path12.join(homeDir, ".claude", "claude-usage.json"));
|
|
5116
5611
|
if (status && (status.seven_day || status.five_hour)) {
|
|
5117
5612
|
const entry = {
|
|
5118
5613
|
agent: "claude",
|
|
@@ -5121,7 +5616,7 @@ function readClaudeUsage({ homeDir = os.homedir(), read: rawRead = readJson } =
|
|
|
5121
5616
|
};
|
|
5122
5617
|
if (entry.seven_day_used_pct !== null || entry.five_hour_used_pct !== null) return entry;
|
|
5123
5618
|
}
|
|
5124
|
-
const weekly = read(
|
|
5619
|
+
const weekly = read(path12.join(homeDir, ".claude", "claude-weekly-usage.json"));
|
|
5125
5620
|
if (weekly) {
|
|
5126
5621
|
const entry = {
|
|
5127
5622
|
agent: "claude",
|
|
@@ -5252,9 +5747,93 @@ var init_ci_repair_evidence = __esm({
|
|
|
5252
5747
|
}
|
|
5253
5748
|
});
|
|
5254
5749
|
|
|
5750
|
+
// ../../scripts/virtual-office/code-runner/pr-watcher-failure-confirmation.mjs
|
|
5751
|
+
function reset(entry) {
|
|
5752
|
+
delete entry.repairFailureFingerprint;
|
|
5753
|
+
delete entry.repairFailureObservations;
|
|
5754
|
+
delete entry.repairFailureFirstSeenAt;
|
|
5755
|
+
}
|
|
5756
|
+
function repairFailureFingerprint(pr) {
|
|
5757
|
+
if (pr?.ci !== "failing" || pr.hasPendingChecks) return null;
|
|
5758
|
+
const headSha = String(pr.headSha || "").trim().toLowerCase();
|
|
5759
|
+
const failedChecks = [...new Set((pr.failedChecks || []).map((name) => String(name).trim()).filter(Boolean))].sort();
|
|
5760
|
+
if (!headSha || failedChecks.length === 0) return null;
|
|
5761
|
+
return JSON.stringify({ headSha, failedChecks });
|
|
5762
|
+
}
|
|
5763
|
+
function observeRepairFailure(entry, pr, observedAt) {
|
|
5764
|
+
const fingerprint = repairFailureFingerprint(pr);
|
|
5765
|
+
if (!fingerprint) {
|
|
5766
|
+
reset(entry);
|
|
5767
|
+
return {
|
|
5768
|
+
confirmed: false,
|
|
5769
|
+
fingerprint: null,
|
|
5770
|
+
observations: 0,
|
|
5771
|
+
required: REPAIR_FAILURE_CONFIRMATIONS_REQUIRED,
|
|
5772
|
+
reason: pr?.hasPendingChecks ? "checks still pending" : pr?.ci === "failing" ? "missing exact head/check evidence" : "not failing"
|
|
5773
|
+
};
|
|
5774
|
+
}
|
|
5775
|
+
if (entry.repairFailureFingerprint === fingerprint) {
|
|
5776
|
+
entry.repairFailureObservations = Math.min(
|
|
5777
|
+
REPAIR_FAILURE_CONFIRMATIONS_REQUIRED,
|
|
5778
|
+
Math.max(1, Number(entry.repairFailureObservations) || 1) + 1
|
|
5779
|
+
);
|
|
5780
|
+
} else {
|
|
5781
|
+
entry.repairFailureFingerprint = fingerprint;
|
|
5782
|
+
entry.repairFailureObservations = 1;
|
|
5783
|
+
entry.repairFailureFirstSeenAt = observedAt;
|
|
5784
|
+
}
|
|
5785
|
+
return {
|
|
5786
|
+
confirmed: entry.repairFailureObservations >= REPAIR_FAILURE_CONFIRMATIONS_REQUIRED,
|
|
5787
|
+
fingerprint,
|
|
5788
|
+
observations: entry.repairFailureObservations,
|
|
5789
|
+
required: REPAIR_FAILURE_CONFIRMATIONS_REQUIRED,
|
|
5790
|
+
reason: entry.repairFailureObservations === 1 ? "first observation" : "stable failure confirmed"
|
|
5791
|
+
};
|
|
5792
|
+
}
|
|
5793
|
+
var REPAIR_FAILURE_CONFIRMATIONS_REQUIRED;
|
|
5794
|
+
var init_pr_watcher_failure_confirmation = __esm({
|
|
5795
|
+
"../../scripts/virtual-office/code-runner/pr-watcher-failure-confirmation.mjs"() {
|
|
5796
|
+
"use strict";
|
|
5797
|
+
REPAIR_FAILURE_CONFIRMATIONS_REQUIRED = 2;
|
|
5798
|
+
}
|
|
5799
|
+
});
|
|
5800
|
+
|
|
5801
|
+
// ../../scripts/virtual-office/code-runner/superseded-pr-source.mjs
|
|
5802
|
+
function supersededSourcePrNumber(prompt) {
|
|
5803
|
+
const text = String(prompt || "");
|
|
5804
|
+
if (text.includes(CI_FIX_MARKER)) {
|
|
5805
|
+
const match = text.match(/\bPR:\s*#(\d+)\b/u);
|
|
5806
|
+
return match ? Number(match[1]) : null;
|
|
5807
|
+
}
|
|
5808
|
+
const repairMatch = text.match(/^REPAIR MISSION:\s*PR\s+#(\d+)\b/iu);
|
|
5809
|
+
if (repairMatch) return Number(repairMatch[1]);
|
|
5810
|
+
const recoverySupersedeMatch = text.match(
|
|
5811
|
+
/^VO_RECOVERY_FROM_CODE_TASK:\s*[0-9a-f-]{36}\n\nSupersede draft PR #(\d+) from a fresh current origin\/main branch\b/iu
|
|
5812
|
+
);
|
|
5813
|
+
if (recoverySupersedeMatch) return Number(recoverySupersedeMatch[1]);
|
|
5814
|
+
const restoredContextMatch = text.match(
|
|
5815
|
+
/^(?:The previous run reached its max-turn cap after opening a partial draft PR\.|The previous run left a draft PR\.)\nThe VO runner will restore the existing draft PR context before you start \(draft PR https:\/\/github\.com\/[^/]+\/[^/]+\/pull\/(\d+), PR #(\d+)\)\. Continue that work and finish it\./u
|
|
5816
|
+
);
|
|
5817
|
+
if (restoredContextMatch) {
|
|
5818
|
+
return restoredContextMatch[1] === restoredContextMatch[2] ? Number(restoredContextMatch[1]) : null;
|
|
5819
|
+
}
|
|
5820
|
+
const continuationMatch = text.match(
|
|
5821
|
+
/^(?:The previous run reached its max-turn cap after opening a partial draft PR\.|The previous run left a draft PR\.)\nContinue the work already started on the branch for PR #(\d+) \(draft PR https:\/\/github\.com\/[^/]+\/[^/]+\/pull\/(\d+)\); check it out and finish it\./u
|
|
5822
|
+
);
|
|
5823
|
+
if (!continuationMatch || continuationMatch[1] !== continuationMatch[2]) return null;
|
|
5824
|
+
return Number(continuationMatch[1]);
|
|
5825
|
+
}
|
|
5826
|
+
var CI_FIX_MARKER;
|
|
5827
|
+
var init_superseded_pr_source = __esm({
|
|
5828
|
+
"../../scripts/virtual-office/code-runner/superseded-pr-source.mjs"() {
|
|
5829
|
+
"use strict";
|
|
5830
|
+
CI_FIX_MARKER = "[VO-CI-FIX]";
|
|
5831
|
+
}
|
|
5832
|
+
});
|
|
5833
|
+
|
|
5255
5834
|
// ../../scripts/virtual-office/code-runner/pr-watcher.mjs
|
|
5256
5835
|
import { homedir as homedir4 } from "node:os";
|
|
5257
|
-
import { join as
|
|
5836
|
+
import { join as join6 } from "node:path";
|
|
5258
5837
|
import { readFile as readFile2, writeFile as writeFile2, mkdir } from "node:fs/promises";
|
|
5259
5838
|
import { spawnSync as spawnSync11 } from "node:child_process";
|
|
5260
5839
|
function ghViewPr(prNumber, repo) {
|
|
@@ -5294,6 +5873,7 @@ function parsePrCiStatus(view) {
|
|
|
5294
5873
|
ci,
|
|
5295
5874
|
failedChecks,
|
|
5296
5875
|
failedCheckLinks,
|
|
5876
|
+
hasPendingChecks: pending,
|
|
5297
5877
|
branch: view && view.headRefName || null,
|
|
5298
5878
|
headSha: view && view.headRefOid || null,
|
|
5299
5879
|
url: view && view.url || null,
|
|
@@ -5338,11 +5918,6 @@ function buildCiFixPrompt({ prNumber, repo, branch, headSha, failedChecks, prPat
|
|
|
5338
5918
|
"```"
|
|
5339
5919
|
].join("\n");
|
|
5340
5920
|
}
|
|
5341
|
-
function ciFixSourcePrNumber(prompt) {
|
|
5342
|
-
if (!String(prompt || "").includes(CI_FIX_MARKER)) return null;
|
|
5343
|
-
const match = String(prompt).match(/\bPR:\s*#(\d+)\b/u);
|
|
5344
|
-
return match ? Number(match[1]) : null;
|
|
5345
|
-
}
|
|
5346
5921
|
async function readState(stateFile) {
|
|
5347
5922
|
try {
|
|
5348
5923
|
const parsed = JSON.parse(await readFile2(stateFile, "utf8"));
|
|
@@ -5353,7 +5928,7 @@ async function readState(stateFile) {
|
|
|
5353
5928
|
}
|
|
5354
5929
|
async function writeState(stateFile, state) {
|
|
5355
5930
|
try {
|
|
5356
|
-
await mkdir(
|
|
5931
|
+
await mkdir(join6(stateFile, ".."), { recursive: true });
|
|
5357
5932
|
await writeFile2(stateFile, JSON.stringify(state, null, 2), "utf8");
|
|
5358
5933
|
} catch {
|
|
5359
5934
|
}
|
|
@@ -5410,7 +5985,8 @@ async function runWatchCycle({
|
|
|
5410
5985
|
checked += 1;
|
|
5411
5986
|
const pr = parsePrCiStatus(view);
|
|
5412
5987
|
entry.lastCi = pr.ci;
|
|
5413
|
-
const
|
|
5988
|
+
const confirmation = observeRepairFailure(entry, pr, now());
|
|
5989
|
+
const proposedAction = decideWatchAction(
|
|
5414
5990
|
pr,
|
|
5415
5991
|
entry.fixAttempts,
|
|
5416
5992
|
maxFixAttempts,
|
|
@@ -5418,6 +5994,10 @@ async function runWatchCycle({
|
|
|
5418
5994
|
maxResumeAttempts,
|
|
5419
5995
|
autoMergeEnabled
|
|
5420
5996
|
);
|
|
5997
|
+
const action = proposedAction === "fix" && !confirmation.confirmed ? "wait" : proposedAction;
|
|
5998
|
+
if (proposedAction === "fix" && action === "wait") {
|
|
5999
|
+
log3(`watch: pr #${prNumber} CI repair waiting for stable exact-head failure ${confirmation.observations}/${confirmation.required} (${confirmation.reason})`);
|
|
6000
|
+
}
|
|
5421
6001
|
if (action === "untrack") {
|
|
5422
6002
|
delete state[prNumber];
|
|
5423
6003
|
untracked += 1;
|
|
@@ -5545,13 +6125,15 @@ function makeWatchRunner({
|
|
|
5545
6125
|
stateFile
|
|
5546
6126
|
});
|
|
5547
6127
|
}
|
|
5548
|
-
var
|
|
6128
|
+
var DEFAULT_STATE_FILE, FAIL_CONCLUSIONS, STALE_MS2, MAX_ENQUEUE_ERRORS;
|
|
5549
6129
|
var init_pr_watcher = __esm({
|
|
5550
6130
|
"../../scripts/virtual-office/code-runner/pr-watcher.mjs"() {
|
|
5551
6131
|
"use strict";
|
|
5552
6132
|
init_ci_repair_evidence();
|
|
5553
|
-
|
|
5554
|
-
|
|
6133
|
+
init_pr_watcher_failure_confirmation();
|
|
6134
|
+
init_superseded_pr_source();
|
|
6135
|
+
init_superseded_pr_source();
|
|
6136
|
+
DEFAULT_STATE_FILE = join6(homedir4(), ".vo", "dispatched-prs.json");
|
|
5555
6137
|
FAIL_CONCLUSIONS = /* @__PURE__ */ new Set([
|
|
5556
6138
|
"FAILURE",
|
|
5557
6139
|
"TIMED_OUT",
|
|
@@ -5626,9 +6208,9 @@ function buildControlHandler({ getStatus, requestStop, allowedOrigin }) {
|
|
|
5626
6208
|
res.end();
|
|
5627
6209
|
return;
|
|
5628
6210
|
}
|
|
5629
|
-
const
|
|
6211
|
+
const path16 = String(req.url || "").split("?")[0];
|
|
5630
6212
|
res.setHeader("content-type", "application/json");
|
|
5631
|
-
if (req.method === "GET" &&
|
|
6213
|
+
if (req.method === "GET" && path16 === "/status") {
|
|
5632
6214
|
let status;
|
|
5633
6215
|
try {
|
|
5634
6216
|
status = getStatus();
|
|
@@ -5639,7 +6221,7 @@ function buildControlHandler({ getStatus, requestStop, allowedOrigin }) {
|
|
|
5639
6221
|
res.end(JSON.stringify({ ok: true, ...status }));
|
|
5640
6222
|
return;
|
|
5641
6223
|
}
|
|
5642
|
-
if (req.method === "POST" &&
|
|
6224
|
+
if (req.method === "POST" && path16 === "/stop") {
|
|
5643
6225
|
if (!isControlOriginAllowed(req.headers.origin, allowedOrigin) || !req.headers["x-vo-control"]) {
|
|
5644
6226
|
res.statusCode = 403;
|
|
5645
6227
|
res.end(JSON.stringify({ ok: false, error: "forbidden" }));
|
|
@@ -5757,35 +6339,35 @@ var init_effort_mode_config = __esm({
|
|
|
5757
6339
|
fast: {
|
|
5758
6340
|
tier: "cheap",
|
|
5759
6341
|
permissionMode: "acceptEdits",
|
|
5760
|
-
maxTurns:
|
|
6342
|
+
maxTurns: 80,
|
|
5761
6343
|
thinkingDirective: "",
|
|
5762
6344
|
multiAgentInstruction: ""
|
|
5763
6345
|
},
|
|
5764
6346
|
standard: {
|
|
5765
6347
|
tier: "mid",
|
|
5766
6348
|
permissionMode: "acceptEdits",
|
|
5767
|
-
maxTurns:
|
|
6349
|
+
maxTurns: 200,
|
|
5768
6350
|
thinkingDirective: "",
|
|
5769
6351
|
multiAgentInstruction: ""
|
|
5770
6352
|
},
|
|
5771
6353
|
deep: {
|
|
5772
6354
|
tier: "best",
|
|
5773
6355
|
permissionMode: "acceptEdits",
|
|
5774
|
-
maxTurns:
|
|
6356
|
+
maxTurns: 300,
|
|
5775
6357
|
thinkingDirective: "Think step-by-step. Verify assumptions against source code. Check edge cases.",
|
|
5776
6358
|
multiAgentInstruction: ""
|
|
5777
6359
|
},
|
|
5778
6360
|
ultra: {
|
|
5779
6361
|
tier: "best",
|
|
5780
6362
|
permissionMode: "acceptEdits",
|
|
5781
|
-
maxTurns:
|
|
6363
|
+
maxTurns: 500,
|
|
5782
6364
|
thinkingDirective: "Think step-by-step. Exhaustively verify every assumption against source code and documentation. Adversarially review your own work.",
|
|
5783
6365
|
multiAgentInstruction: "If this task needs multiple phases (research, build, verify), propose a plan first."
|
|
5784
6366
|
},
|
|
5785
6367
|
ultracode: {
|
|
5786
6368
|
tier: "best",
|
|
5787
|
-
permissionMode: "
|
|
5788
|
-
maxTurns:
|
|
6369
|
+
permissionMode: "acceptEdits",
|
|
6370
|
+
maxTurns: 800,
|
|
5789
6371
|
thinkingDirective: "Think step-by-step. Exhaustively verify every assumption against source code and documentation. Build worked examples to validate correctness. Adversarially review your own work.",
|
|
5790
6372
|
multiAgentInstruction: "Decompose this work into parallel research, build, and verification streams; use workflow orchestration where it helps."
|
|
5791
6373
|
}
|
|
@@ -5796,7 +6378,7 @@ var init_effort_mode_config = __esm({
|
|
|
5796
6378
|
|
|
5797
6379
|
// ../../scripts/virtual-office/model-registry.mjs
|
|
5798
6380
|
import fs7 from "node:fs";
|
|
5799
|
-
import
|
|
6381
|
+
import path13 from "node:path";
|
|
5800
6382
|
import { fileURLToPath } from "node:url";
|
|
5801
6383
|
function uniqueModels(models = []) {
|
|
5802
6384
|
return [...new Set(models.map((model) => String(model || "").trim()).filter(Boolean))];
|
|
@@ -5919,7 +6501,7 @@ function readCache(cacheFile = DEFAULT_CACHE_FILE, nowMs = Date.now(), ttlMs = D
|
|
|
5919
6501
|
}
|
|
5920
6502
|
}
|
|
5921
6503
|
function writeCache(cacheFile = DEFAULT_CACHE_FILE, payload) {
|
|
5922
|
-
fs7.mkdirSync(
|
|
6504
|
+
fs7.mkdirSync(path13.dirname(cacheFile), { recursive: true });
|
|
5923
6505
|
fs7.writeFileSync(cacheFile, JSON.stringify(payload, null, 2));
|
|
5924
6506
|
}
|
|
5925
6507
|
async function fetchRegistryCatalog({
|
|
@@ -5977,10 +6559,10 @@ var __dirname, ROOT, DEFAULT_CACHE_DIR, DEFAULT_CACHE_FILE, DEFAULT_TTL_MS3, ANT
|
|
|
5977
6559
|
var init_model_registry = __esm({
|
|
5978
6560
|
"../../scripts/virtual-office/model-registry.mjs"() {
|
|
5979
6561
|
"use strict";
|
|
5980
|
-
__dirname =
|
|
5981
|
-
ROOT =
|
|
5982
|
-
DEFAULT_CACHE_DIR =
|
|
5983
|
-
DEFAULT_CACHE_FILE =
|
|
6562
|
+
__dirname = path13.dirname(fileURLToPath(import.meta.url));
|
|
6563
|
+
ROOT = path13.resolve(__dirname, "..", "..");
|
|
6564
|
+
DEFAULT_CACHE_DIR = path13.join(ROOT, ".virtual-office-cache", "model-registry");
|
|
6565
|
+
DEFAULT_CACHE_FILE = path13.join(DEFAULT_CACHE_DIR, "catalog.json");
|
|
5984
6566
|
DEFAULT_TTL_MS3 = 60 * 60 * 1e3;
|
|
5985
6567
|
ANTHROPIC_API_VERSION = "2023-06-01";
|
|
5986
6568
|
FAMILY_DEFINITIONS = {
|
|
@@ -6535,7 +7117,7 @@ var init_classify_task = __esm({
|
|
|
6535
7117
|
// ../../scripts/virtual-office/code-runner/auto-router/effort-policy.mjs
|
|
6536
7118
|
import { readFileSync as readFileSync3 } from "node:fs";
|
|
6537
7119
|
import { homedir as homedir5 } from "node:os";
|
|
6538
|
-
import { join as
|
|
7120
|
+
import { join as join7 } from "node:path";
|
|
6539
7121
|
function difficultyToRung(difficulty, thresholds) {
|
|
6540
7122
|
const b = thresholds.rungBounds;
|
|
6541
7123
|
if (difficulty >= b.R5) return "R5";
|
|
@@ -6560,9 +7142,9 @@ function operatorTierToRung(tier, difficulty, thresholds) {
|
|
|
6560
7142
|
if (tier === "best" && difficulty >= thresholds.rungBounds.R5) return "R5";
|
|
6561
7143
|
return base;
|
|
6562
7144
|
}
|
|
6563
|
-
function readCodexModelsCache({ path:
|
|
7145
|
+
function readCodexModelsCache({ path: path16 = DEFAULT_CODEX_MODELS_CACHE, read = readFileSync3 } = {}) {
|
|
6564
7146
|
try {
|
|
6565
|
-
const parsed = JSON.parse(read(
|
|
7147
|
+
const parsed = JSON.parse(read(path16, "utf8"));
|
|
6566
7148
|
return Array.isArray(parsed?.models) ? parsed : null;
|
|
6567
7149
|
} catch {
|
|
6568
7150
|
return null;
|
|
@@ -6616,14 +7198,14 @@ var init_effort_policy = __esm({
|
|
|
6616
7198
|
init_meta_model_catalog();
|
|
6617
7199
|
RUNG_ORDER = ["R1", "R2", "R3", "R4", "R5"];
|
|
6618
7200
|
rungIndex = (rung) => RUNG_ORDER.indexOf(rung);
|
|
6619
|
-
DEFAULT_CODEX_MODELS_CACHE =
|
|
7201
|
+
DEFAULT_CODEX_MODELS_CACHE = join7(homedir5(), ".codex", "models_cache.json");
|
|
6620
7202
|
}
|
|
6621
7203
|
});
|
|
6622
7204
|
|
|
6623
7205
|
// ../../scripts/virtual-office/code-runner/auto-router/auto-router.mjs
|
|
6624
7206
|
import { readFileSync as readFileSync4, appendFileSync as appendFileSync2, mkdirSync as mkdirSync4 } from "node:fs";
|
|
6625
7207
|
import { homedir as homedir6 } from "node:os";
|
|
6626
|
-
import { join as
|
|
7208
|
+
import { join as join8, dirname as dirname4 } from "node:path";
|
|
6627
7209
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
6628
7210
|
function getAutoRouterMode(env2 = process.env) {
|
|
6629
7211
|
const raw = String(env2.VO_CODE_RUNNER_AUTO_ROUTER || "").trim().toLowerCase();
|
|
@@ -6632,7 +7214,7 @@ function getAutoRouterMode(env2 = process.env) {
|
|
|
6632
7214
|
function loadThresholds() {
|
|
6633
7215
|
if (!cachedThresholds) {
|
|
6634
7216
|
const here = dirname4(fileURLToPath2(import.meta.url));
|
|
6635
|
-
cachedThresholds = JSON.parse(readFileSync4(
|
|
7217
|
+
cachedThresholds = JSON.parse(readFileSync4(join8(here, "thresholds.json"), "utf8"));
|
|
6636
7218
|
}
|
|
6637
7219
|
return cachedThresholds;
|
|
6638
7220
|
}
|
|
@@ -6705,7 +7287,7 @@ var init_auto_router = __esm({
|
|
|
6705
7287
|
init_classify_task();
|
|
6706
7288
|
init_effort_policy();
|
|
6707
7289
|
ROUTER_VERSION = "0.1.0";
|
|
6708
|
-
DECISION_FALLBACK_PATH =
|
|
7290
|
+
DECISION_FALLBACK_PATH = join8(homedir6(), ".claude", "vo-auto-router-decisions.jsonl");
|
|
6709
7291
|
MODES = /* @__PURE__ */ new Set(["off", "shadow", "on"]);
|
|
6710
7292
|
cachedThresholds = null;
|
|
6711
7293
|
}
|
|
@@ -6978,6 +7560,320 @@ var init_agent_process_env = __esm({
|
|
|
6978
7560
|
}
|
|
6979
7561
|
});
|
|
6980
7562
|
|
|
7563
|
+
// ../../scripts/virtual-office/code-runner/isolation-audit.mjs
|
|
7564
|
+
import fs8 from "node:fs";
|
|
7565
|
+
import fsp9 from "node:fs/promises";
|
|
7566
|
+
import path14 from "node:path";
|
|
7567
|
+
async function defaultRun(command, args, cwd, options = {}) {
|
|
7568
|
+
return runProcess2(command, args, { cwd, timeout: 6e4, ...options });
|
|
7569
|
+
}
|
|
7570
|
+
async function git2(run, cwd, args, options = {}) {
|
|
7571
|
+
return run("git", args, cwd, options);
|
|
7572
|
+
}
|
|
7573
|
+
async function canonicalRootForWorktree(worktreeDir, run) {
|
|
7574
|
+
const commonDir = String(await git2(run, worktreeDir, [
|
|
7575
|
+
"rev-parse",
|
|
7576
|
+
"--path-format=absolute",
|
|
7577
|
+
"--git-common-dir"
|
|
7578
|
+
])).trim();
|
|
7579
|
+
const root = path14.dirname(commonDir);
|
|
7580
|
+
return samePath2(root, worktreeDir) ? null : root;
|
|
7581
|
+
}
|
|
7582
|
+
async function snapshot(root, run) {
|
|
7583
|
+
const [head, status] = await Promise.all([
|
|
7584
|
+
git2(run, root, ["rev-parse", "HEAD"]),
|
|
7585
|
+
git2(run, root, ["-c", "core.quotepath=false", "status", "--porcelain=v1", "-z"], { raw: true })
|
|
7586
|
+
]);
|
|
7587
|
+
return { head: String(head).trim(), status: String(status) };
|
|
7588
|
+
}
|
|
7589
|
+
async function isVerifiedRemoteFastForward(baseline, current, run) {
|
|
7590
|
+
if (current.status) return false;
|
|
7591
|
+
try {
|
|
7592
|
+
const branch = String(await git2(run, baseline.root, ["branch", "--show-current"])).trim();
|
|
7593
|
+
if (branch !== "main") return false;
|
|
7594
|
+
await git2(run, baseline.root, ["fetch", "--quiet", "origin", "main"]);
|
|
7595
|
+
const remoteHead = String(await git2(run, baseline.root, ["rev-parse", "FETCH_HEAD"])).trim();
|
|
7596
|
+
await git2(run, baseline.root, ["merge-base", "--is-ancestor", baseline.head, current.head]);
|
|
7597
|
+
await git2(run, baseline.root, ["merge-base", "--is-ancestor", current.head, remoteHead]);
|
|
7598
|
+
return true;
|
|
7599
|
+
} catch {
|
|
7600
|
+
return false;
|
|
7601
|
+
}
|
|
7602
|
+
}
|
|
7603
|
+
async function captureCanonicalBaseline(worktreeDir, { run = defaultRun } = {}) {
|
|
7604
|
+
const root = await canonicalRootForWorktree(worktreeDir, run);
|
|
7605
|
+
if (!root) return { root: null, head: null, status: "", standalone: true };
|
|
7606
|
+
const state = await snapshot(root, run);
|
|
7607
|
+
if (state.status) {
|
|
7608
|
+
throw new Error(`canonical clone is dirty before agent launch; refusing task execution: ${root}`);
|
|
7609
|
+
}
|
|
7610
|
+
return { root, ...state };
|
|
7611
|
+
}
|
|
7612
|
+
async function changedPaths(root, run) {
|
|
7613
|
+
const [tracked, untracked] = await Promise.all([
|
|
7614
|
+
git2(run, root, ["-c", "core.quotepath=false", "diff", "--name-only", "-z", "HEAD"], { raw: true }),
|
|
7615
|
+
git2(run, root, ["-c", "core.quotepath=false", "ls-files", "--others", "--exclude-standard", "-z"], { raw: true })
|
|
7616
|
+
]);
|
|
7617
|
+
return { tracked: splitZ2(tracked), untracked: splitZ2(untracked) };
|
|
7618
|
+
}
|
|
7619
|
+
async function quarantineCanonicalWrites({ baseline, worktreeDir, taskId, run, now }) {
|
|
7620
|
+
const paths = await changedPaths(baseline.root, run);
|
|
7621
|
+
const quarantineDir = path14.join(
|
|
7622
|
+
path14.dirname(worktreeDir),
|
|
7623
|
+
".canonical-recovery",
|
|
7624
|
+
`${String(taskId || "unknown").replace(/[^a-z0-9-]/gi, "-")}-${now().toISOString().replace(/[:.]/g, "-")}`
|
|
7625
|
+
);
|
|
7626
|
+
await fsp9.mkdir(quarantineDir, { recursive: true });
|
|
7627
|
+
const patch = await git2(run, baseline.root, ["diff", "--binary", "HEAD"], { raw: true });
|
|
7628
|
+
await fsp9.writeFile(path14.join(quarantineDir, "tracked.patch"), patch, "utf8");
|
|
7629
|
+
for (const relative of paths.untracked) {
|
|
7630
|
+
const source = path14.join(baseline.root, relative);
|
|
7631
|
+
const target = path14.join(quarantineDir, "untracked", relative);
|
|
7632
|
+
await fsp9.mkdir(path14.dirname(target), { recursive: true });
|
|
7633
|
+
await fsp9.copyFile(source, target);
|
|
7634
|
+
}
|
|
7635
|
+
await fsp9.writeFile(path14.join(quarantineDir, "manifest.json"), `${JSON.stringify({
|
|
7636
|
+
taskId,
|
|
7637
|
+
canonicalRoot: baseline.root,
|
|
7638
|
+
canonicalHead: baseline.head,
|
|
7639
|
+
tracked: paths.tracked,
|
|
7640
|
+
untracked: paths.untracked
|
|
7641
|
+
}, null, 2)}
|
|
7642
|
+
`, "utf8");
|
|
7643
|
+
return { quarantineDir, ...paths };
|
|
7644
|
+
}
|
|
7645
|
+
async function restoreExactCanonicalPaths(baseline, evidence, run) {
|
|
7646
|
+
if (evidence.tracked.length > 0) {
|
|
7647
|
+
await git2(run, baseline.root, [
|
|
7648
|
+
"restore",
|
|
7649
|
+
`--source=${baseline.head}`,
|
|
7650
|
+
"--staged",
|
|
7651
|
+
"--worktree",
|
|
7652
|
+
"--",
|
|
7653
|
+
...evidence.tracked
|
|
7654
|
+
]);
|
|
7655
|
+
}
|
|
7656
|
+
for (const relative of evidence.untracked) {
|
|
7657
|
+
const target = path14.resolve(baseline.root, relative);
|
|
7658
|
+
const prefix = `${path14.resolve(baseline.root)}${path14.sep}`;
|
|
7659
|
+
if (!target.startsWith(prefix) || !fs8.existsSync(target)) continue;
|
|
7660
|
+
await fsp9.rm(target, { force: true });
|
|
7661
|
+
}
|
|
7662
|
+
}
|
|
7663
|
+
async function assertCanonicalIsolation(baseline, { worktreeDir, taskId, run = defaultRun, now = () => /* @__PURE__ */ new Date() } = {}) {
|
|
7664
|
+
if (baseline.standalone) return { ok: true, standalone: true };
|
|
7665
|
+
const current = await snapshot(baseline.root, run);
|
|
7666
|
+
if (current.head === baseline.head && !current.status) return { ok: true };
|
|
7667
|
+
if (current.head !== baseline.head) {
|
|
7668
|
+
if (await isVerifiedRemoteFastForward(baseline, current, run)) {
|
|
7669
|
+
return {
|
|
7670
|
+
ok: true,
|
|
7671
|
+
canonicalFastForward: true,
|
|
7672
|
+
fromHead: baseline.head,
|
|
7673
|
+
toHead: current.head
|
|
7674
|
+
};
|
|
7675
|
+
}
|
|
7676
|
+
throw new Error(`canonical clone HEAD changed during task ${taskId}; manual recovery required: ${baseline.root}`);
|
|
7677
|
+
}
|
|
7678
|
+
const evidence = await quarantineCanonicalWrites({ baseline, worktreeDir, taskId, run, now });
|
|
7679
|
+
await restoreExactCanonicalPaths(baseline, evidence, run);
|
|
7680
|
+
const restored = await snapshot(baseline.root, run);
|
|
7681
|
+
if (restored.head !== baseline.head || restored.status) {
|
|
7682
|
+
throw new Error(`canonical clone recovery could not restore the exact baseline; evidence: ${evidence.quarantineDir}`);
|
|
7683
|
+
}
|
|
7684
|
+
throw new Error(
|
|
7685
|
+
`agent attempted ${evidence.tracked.length + evidence.untracked.length} canonical-clone write(s); writes were quarantined and the clone was restored exactly: ${evidence.quarantineDir}`
|
|
7686
|
+
);
|
|
7687
|
+
}
|
|
7688
|
+
var splitZ2, samePath2;
|
|
7689
|
+
var init_isolation_audit = __esm({
|
|
7690
|
+
"../../scripts/virtual-office/code-runner/isolation-audit.mjs"() {
|
|
7691
|
+
"use strict";
|
|
7692
|
+
init_process_runner2();
|
|
7693
|
+
splitZ2 = (value) => String(value || "").split("\0").map((item) => item.trim()).filter(Boolean);
|
|
7694
|
+
samePath2 = (left, right) => {
|
|
7695
|
+
const [a, b] = [left, right].map((value) => path14.resolve(value));
|
|
7696
|
+
return process.platform === "win32" ? a.toLowerCase() === b.toLowerCase() : a === b;
|
|
7697
|
+
};
|
|
7698
|
+
}
|
|
7699
|
+
});
|
|
7700
|
+
|
|
7701
|
+
// ../../scripts/virtual-office/code-runner/recovery-ledger.mjs
|
|
7702
|
+
import fs9 from "node:fs";
|
|
7703
|
+
import fsp10 from "node:fs/promises";
|
|
7704
|
+
import path15 from "node:path";
|
|
7705
|
+
function recoveryTaskId(prompt) {
|
|
7706
|
+
const match = String(prompt || "").match(/VO_RECOVERY_FROM_CODE_TASK:\s*([0-9a-f-]{36})/i);
|
|
7707
|
+
return match ? match[1].toLowerCase() : null;
|
|
7708
|
+
}
|
|
7709
|
+
function cloneLeaf(repo) {
|
|
7710
|
+
const [owner, name] = String(repo || "").split("/");
|
|
7711
|
+
if (!owner || !name) return null;
|
|
7712
|
+
const clean = (value) => value.toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
7713
|
+
return `${clean(owner)}__${clean(name)}`;
|
|
7714
|
+
}
|
|
7715
|
+
function recoveryLedgerCandidates(repo, clonesRoot2) {
|
|
7716
|
+
const leaf = cloneLeaf(repo);
|
|
7717
|
+
if (!leaf || !clonesRoot2) return [];
|
|
7718
|
+
const canonical = path15.join(clonesRoot2, leaf);
|
|
7719
|
+
return [
|
|
7720
|
+
path15.join(clonesRoot2, ".agent-worktrees", leaf, "recovery-ledger.jsonl"),
|
|
7721
|
+
path15.join(canonical, ".agent-worktrees", "recovery-ledger.jsonl")
|
|
7722
|
+
];
|
|
7723
|
+
}
|
|
7724
|
+
async function readLedger(file, readFile3) {
|
|
7725
|
+
try {
|
|
7726
|
+
return String(await readFile3(file, "utf8")).split(/\r?\n/).filter(Boolean).map((line) => JSON.parse(line));
|
|
7727
|
+
} catch {
|
|
7728
|
+
return [];
|
|
7729
|
+
}
|
|
7730
|
+
}
|
|
7731
|
+
async function findPreservedRecovery(task, {
|
|
7732
|
+
clonesRoot: clonesRoot2 = process.env.VO_CODE_RUNNER_CLONES_ROOT || "",
|
|
7733
|
+
readFile: readFile3 = fsp10.readFile,
|
|
7734
|
+
exists = fs9.existsSync
|
|
7735
|
+
} = {}) {
|
|
7736
|
+
const originalTaskId = recoveryTaskId(task.prompt);
|
|
7737
|
+
if (!originalTaskId) return null;
|
|
7738
|
+
for (const ledgerPath of recoveryLedgerCandidates(task.repo, clonesRoot2)) {
|
|
7739
|
+
const entries = await readLedger(ledgerPath, readFile3);
|
|
7740
|
+
const resolved = entries.some((entry) => RESOLVED_RECOVERY_TYPES.has(entry.type) && entry.taskId === originalTaskId);
|
|
7741
|
+
const preserved = [...entries].reverse().find((entry) => entry.taskId === originalTaskId && entry.worktreeDir);
|
|
7742
|
+
if (!resolved && preserved && exists(preserved.worktreeDir)) {
|
|
7743
|
+
return { originalTaskId, ledgerPath, preserved };
|
|
7744
|
+
}
|
|
7745
|
+
}
|
|
7746
|
+
return null;
|
|
7747
|
+
}
|
|
7748
|
+
async function recoverPreservedCodeTask({
|
|
7749
|
+
task,
|
|
7750
|
+
cfg,
|
|
7751
|
+
client,
|
|
7752
|
+
log: log3,
|
|
7753
|
+
find = findPreservedRecovery,
|
|
7754
|
+
listChanged = listChangedFilesAsync,
|
|
7755
|
+
listCommitted = listCommittedFilesAsync,
|
|
7756
|
+
openPr = openCodeTaskPrAsync,
|
|
7757
|
+
appendFile = fsp10.appendFile,
|
|
7758
|
+
track = trackDispatchedPr
|
|
7759
|
+
} = {}) {
|
|
7760
|
+
const recovery = await find(task);
|
|
7761
|
+
if (!recovery) return null;
|
|
7762
|
+
const cwd = recovery.preserved.worktreeDir;
|
|
7763
|
+
let files = (await listChanged(cwd)).filter((file) => !isAgentScratch(file));
|
|
7764
|
+
let alreadyCommitted = false;
|
|
7765
|
+
if (files.length === 0) {
|
|
7766
|
+
files = (await listCommitted(cwd)).filter((file) => !isAgentScratch(file));
|
|
7767
|
+
alreadyCommitted = files.length > 0;
|
|
7768
|
+
}
|
|
7769
|
+
if (files.length === 0) {
|
|
7770
|
+
await appendFile(recovery.ledgerPath, `${JSON.stringify({
|
|
7771
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7772
|
+
type: "recovery_skipped_no_files",
|
|
7773
|
+
taskId: recovery.originalTaskId,
|
|
7774
|
+
resumedTaskId: task.code_task_id,
|
|
7775
|
+
worktreeDir: cwd
|
|
7776
|
+
})}
|
|
7777
|
+
`, "utf8");
|
|
7778
|
+
log3(`task ${task.code_task_id}: preserved task ${recovery.originalTaskId} has no recoverable files; starting fresh`);
|
|
7779
|
+
return null;
|
|
7780
|
+
}
|
|
7781
|
+
const token2 = (await client.getInstallationToken({ required: cfg.requireGithubAppAuth }))?.token ?? null;
|
|
7782
|
+
const run = { summary: `Recovered preserved work from task ${recovery.originalTaskId}.` };
|
|
7783
|
+
const supersedesPrNumber = supersededSourcePrNumber(recovery.preserved.prompt || task.prompt);
|
|
7784
|
+
const targetBranch = supersedesPrNumber && task.pr_branch ? task.pr_branch : null;
|
|
7785
|
+
const pr = await openPr(cwd, files, {
|
|
7786
|
+
title: `code-task recovery: ${String(task.prompt).split("\n").find((line) => line && !line.startsWith(RECOVERY_MARKER)) || task.prompt}`,
|
|
7787
|
+
body: buildPrBody(task, run, files, { armAutoMerge: cfg.armAutoMerge }),
|
|
7788
|
+
alreadyCommitted,
|
|
7789
|
+
githubToken: token2,
|
|
7790
|
+
allowAmbientGithubFallback: cfg.allowAmbientGithub,
|
|
7791
|
+
draft: false,
|
|
7792
|
+
armAutoMerge: false,
|
|
7793
|
+
supersedesPrNumber,
|
|
7794
|
+
targetBranch
|
|
7795
|
+
});
|
|
7796
|
+
await appendFile(recovery.ledgerPath, `${JSON.stringify({
|
|
7797
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7798
|
+
type: "recovered",
|
|
7799
|
+
taskId: recovery.originalTaskId,
|
|
7800
|
+
resumedTaskId: task.code_task_id,
|
|
7801
|
+
prUrl: pr.prUrl,
|
|
7802
|
+
prNumber: pr.prNumber
|
|
7803
|
+
})}
|
|
7804
|
+
`, "utf8");
|
|
7805
|
+
await client.postProgress(task.code_task_id, {
|
|
7806
|
+
status: "pr_opened",
|
|
7807
|
+
stage: "opening_pr",
|
|
7808
|
+
pr_url: pr.prUrl,
|
|
7809
|
+
pr_number: pr.prNumber,
|
|
7810
|
+
message: `recovered preserved work from ${recovery.originalTaskId} and opened ${pr.prUrl}`,
|
|
7811
|
+
result: run.summary
|
|
7812
|
+
});
|
|
7813
|
+
log3(`task ${task.code_task_id} recovered ${recovery.originalTaskId} \u2192 PR ${pr.prUrl}`);
|
|
7814
|
+
if (cfg.watchEnabled) {
|
|
7815
|
+
await track({
|
|
7816
|
+
prNumber: pr.prNumber,
|
|
7817
|
+
repo: task.repo,
|
|
7818
|
+
branch: pr.branch,
|
|
7819
|
+
taskId: task.code_task_id,
|
|
7820
|
+
operatorId: task.operator_id,
|
|
7821
|
+
tenantId: task.tenant_id,
|
|
7822
|
+
needsContinuation: false,
|
|
7823
|
+
allowFixDispatch: !String(task.prompt || "").includes(CI_FIX_MARKER)
|
|
7824
|
+
}).catch((e) => log3(`watch: track failed for recovered #${pr.prNumber}: ${e.message}`));
|
|
7825
|
+
}
|
|
7826
|
+
return { ...pr, files, originalTaskId: recovery.originalTaskId };
|
|
7827
|
+
}
|
|
7828
|
+
var RECOVERY_MARKER, RESOLVED_RECOVERY_TYPES;
|
|
7829
|
+
var init_recovery_ledger = __esm({
|
|
7830
|
+
"../../scripts/virtual-office/code-runner/recovery-ledger.mjs"() {
|
|
7831
|
+
"use strict";
|
|
7832
|
+
init_publish();
|
|
7833
|
+
init_publish_async();
|
|
7834
|
+
init_task_helpers();
|
|
7835
|
+
init_pr_watcher();
|
|
7836
|
+
init_superseded_pr_source();
|
|
7837
|
+
RECOVERY_MARKER = "VO_RECOVERY_FROM_CODE_TASK:";
|
|
7838
|
+
RESOLVED_RECOVERY_TYPES = /* @__PURE__ */ new Set(["recovered", "recovery_skipped_no_files"]);
|
|
7839
|
+
}
|
|
7840
|
+
});
|
|
7841
|
+
|
|
7842
|
+
// ../../scripts/virtual-office/code-runner/no-changes-terminal-status.mjs
|
|
7843
|
+
function isMaxTurnExhaustion(run = {}, maxTurns) {
|
|
7844
|
+
const summary = String(run.summary || "").trim().toLowerCase();
|
|
7845
|
+
if (summary === "error_max_turns" || summary === "inconclusive_max_turns") return true;
|
|
7846
|
+
return Number.isInteger(maxTurns) && maxTurns > 0 && Number.isInteger(run.numTurns) && run.numTurns > maxTurns;
|
|
7847
|
+
}
|
|
7848
|
+
function decideNoChangesTerminalStatus({ partial, run = {}, maxTurns } = {}) {
|
|
7849
|
+
if (!partial) {
|
|
7850
|
+
return {
|
|
7851
|
+
status: "no_changes_needed",
|
|
7852
|
+
message: "agent completed \u2014 no change needed (already fixed / nothing to do)",
|
|
7853
|
+
result: String(run.summary || "no_changes_needed").slice(0, RESULT_LIMIT)
|
|
7854
|
+
};
|
|
7855
|
+
}
|
|
7856
|
+
if (isMaxTurnExhaustion(run, maxTurns)) {
|
|
7857
|
+
return {
|
|
7858
|
+
status: "failed",
|
|
7859
|
+
message: "agent reached the max-turn limit before producing a verified change",
|
|
7860
|
+
result: "inconclusive_max_turns"
|
|
7861
|
+
};
|
|
7862
|
+
}
|
|
7863
|
+
return {
|
|
7864
|
+
status: "failed",
|
|
7865
|
+
message: "agent made no file changes",
|
|
7866
|
+
result: "no_changes"
|
|
7867
|
+
};
|
|
7868
|
+
}
|
|
7869
|
+
var RESULT_LIMIT;
|
|
7870
|
+
var init_no_changes_terminal_status = __esm({
|
|
7871
|
+
"../../scripts/virtual-office/code-runner/no-changes-terminal-status.mjs"() {
|
|
7872
|
+
"use strict";
|
|
7873
|
+
RESULT_LIMIT = 2e3;
|
|
7874
|
+
}
|
|
7875
|
+
});
|
|
7876
|
+
|
|
6981
7877
|
// ../../scripts/virtual-office/code-runner-daemon.mjs
|
|
6982
7878
|
var code_runner_daemon_exports = {};
|
|
6983
7879
|
__export(code_runner_daemon_exports, {
|
|
@@ -7027,10 +7923,23 @@ async function processOneTask(client, task, cfg) {
|
|
|
7027
7923
|
let worktreeName = "";
|
|
7028
7924
|
let preserveReason = null;
|
|
7029
7925
|
try {
|
|
7926
|
+
if (await recoverPreservedCodeTask({ task, cfg, client, log: log2 })) return;
|
|
7030
7927
|
await safeProgress(client, id, runnerStagePatch("preparing_worktree", `${cfg.runnerId} preparing an isolated worktree for ${task.repo}`));
|
|
7031
7928
|
const wt = await Promise.resolve(createFixWorktree("code-task", { source: id.slice(0, 8), repo: task.repo }));
|
|
7032
7929
|
worktreeName = wt.worktreeName;
|
|
7033
7930
|
if (!worktreeName || !wt.worktreeDir) throw new Error("worktree isolation failure \u2014 refusing to run in the main tree");
|
|
7931
|
+
const githubToken = (await client.getInstallationToken({ required: cfg.requireGithubAppAuth }))?.token ?? null;
|
|
7932
|
+
const parentTask = task.resumed_from ? await client.getTask(task.resumed_from).catch(() => null) : null;
|
|
7933
|
+
const continuationRestore = await prepareContinuationBranch(wt.worktreeDir, {
|
|
7934
|
+
task,
|
|
7935
|
+
parentTask,
|
|
7936
|
+
githubToken,
|
|
7937
|
+
allowAmbientGithubFallback: cfg.allowAmbientGithub
|
|
7938
|
+
});
|
|
7939
|
+
if (continuationRestore) {
|
|
7940
|
+
log2(`task ${id}: restored continuation branch ${continuationRestore.remoteBranch} into ${continuationRestore.localBranch}`);
|
|
7941
|
+
}
|
|
7942
|
+
const canonicalBaseline = await captureCanonicalBaseline(wt.worktreeDir);
|
|
7034
7943
|
const sel = resolveTaskRunner(task, cfg, process.env, { warn: (m) => log2(`agent-select: ${m}`) });
|
|
7035
7944
|
const { dispatchMode, routerMode, tier, model, permissionMode: effectivePermissionMode, maxTurns: effectiveMaxTurns, effort: effectiveEffort, maxBudgetUsd: effectiveMaxBudgetUsd, prompt: effortPrompt, routerDecision } = await resolveEffortDispatch({ client, task, agent: sel.agent, env: process.env, basePrompt: await composeCodeTaskPrompt(client, task, {
|
|
7036
7945
|
log: log2,
|
|
@@ -7063,6 +7972,7 @@ async function processOneTask(client, task, cfg) {
|
|
|
7063
7972
|
cancelPollMs: cfg.cancelPollMs,
|
|
7064
7973
|
maxWallClockMs: cfg.maxWallClockMs
|
|
7065
7974
|
});
|
|
7975
|
+
await assertCanonicalIsolation(canonicalBaseline, { worktreeDir: wt.worktreeDir, taskId: id });
|
|
7066
7976
|
if (run.killed) {
|
|
7067
7977
|
preserveReason = "cancelled by operator \u2014 work preserved for recovery";
|
|
7068
7978
|
log2(`task ${id} cancelled by operator`);
|
|
@@ -7113,10 +8023,9 @@ async function processOneTask(client, task, cfg) {
|
|
|
7113
8023
|
log2(`task ${id}: dropped ${scratch.length} scratch file(s): ${scratch.join(", ")}`);
|
|
7114
8024
|
}
|
|
7115
8025
|
if (files.length === 0) {
|
|
8026
|
+
const terminal = decideNoChangesTerminalStatus({ partial, run, maxTurns: effectiveMaxTurns });
|
|
7116
8027
|
await safeProgress(client, id, {
|
|
7117
|
-
|
|
7118
|
-
message: partial ? "agent made no file changes" : "agent completed \u2014 no change needed (already fixed / nothing to do)",
|
|
7119
|
-
result: partial ? "no_changes" : String(run.summary || "no_changes_needed").slice(0, 2e3),
|
|
8028
|
+
...terminal,
|
|
7120
8029
|
cost_usd: numOrUndef(run.costUsd),
|
|
7121
8030
|
num_turns: numOrUndef(run.numTurns)
|
|
7122
8031
|
});
|
|
@@ -7129,7 +8038,6 @@ async function processOneTask(client, task, cfg) {
|
|
|
7129
8038
|
return;
|
|
7130
8039
|
}
|
|
7131
8040
|
await safeProgress(client, id, runnerStagePatch("opening_pr", `opening PR for ${files.length} changed file(s)`));
|
|
7132
|
-
const githubToken = (await client.getInstallationToken({ required: cfg.requireGithubAppAuth }))?.token ?? null;
|
|
7133
8041
|
const pr = await openCodeTaskPrAsync(wt.worktreeDir, files, {
|
|
7134
8042
|
title: `${partial ? `${partialPrTitlePrefix(run)} \u2014 ` : ""}code-task: ${task.prompt}`,
|
|
7135
8043
|
body: buildPrBody(task, run, files, { armAutoMerge: cfg.armAutoMerge }),
|
|
@@ -7139,19 +8047,22 @@ async function processOneTask(client, task, cfg) {
|
|
|
7139
8047
|
draft: partial,
|
|
7140
8048
|
armAutoMerge: false,
|
|
7141
8049
|
// watcher waits for green CI, then uses exact-SHA consensus merge
|
|
7142
|
-
|
|
8050
|
+
targetBranch: continuationRestore?.remoteBranch || void 0,
|
|
8051
|
+
supersedesPrNumber: supersededSourcePrNumber(task.prompt)
|
|
7143
8052
|
});
|
|
7144
8053
|
const autoMergeMessage = pr.autoMergeArmed ? "; auto-merge armed" : pr.autoMergeError ? `; auto-merge arm failed: ${String(pr.autoMergeError).slice(0, 220)}` : "";
|
|
8054
|
+
const cleanupMessage = pr.supersededPrCloseError ? `; replacement published, source PR cleanup pending: ${String(pr.supersededPrCloseError).slice(0, 180)}` : "";
|
|
7145
8055
|
await safeProgress(client, id, {
|
|
7146
8056
|
status: "pr_opened",
|
|
7147
|
-
message: `opened ${pr.prUrl}${autoMergeMessage}`,
|
|
8057
|
+
message: `opened ${pr.prUrl}${autoMergeMessage}${cleanupMessage}`,
|
|
7148
8058
|
pr_url: pr.prUrl,
|
|
7149
8059
|
pr_number: pr.prNumber,
|
|
8060
|
+
pr_branch: pr.branch,
|
|
7150
8061
|
result: String(run.summary).slice(0, 2e3),
|
|
7151
8062
|
cost_usd: numOrUndef(run.costUsd),
|
|
7152
8063
|
num_turns: numOrUndef(run.numTurns)
|
|
7153
8064
|
});
|
|
7154
|
-
log2(`task ${id} \u2192 PR ${pr.prUrl}${autoMergeMessage}`);
|
|
8065
|
+
log2(`task ${id} \u2192 PR ${pr.prUrl}${autoMergeMessage}${cleanupMessage}`);
|
|
7155
8066
|
if (cfg.watchEnabled) {
|
|
7156
8067
|
await trackDispatchedPr({
|
|
7157
8068
|
prNumber: pr.prNumber,
|
|
@@ -7280,13 +8191,14 @@ var init_code_runner_daemon = __esm({
|
|
|
7280
8191
|
init_resolve_runner();
|
|
7281
8192
|
init_rate_limit_resume();
|
|
7282
8193
|
init_publish();
|
|
7283
|
-
init_publish();
|
|
7284
8194
|
init_publish_async();
|
|
8195
|
+
init_resume_branch();
|
|
7285
8196
|
init_task_prompt();
|
|
7286
8197
|
init_loop_ticks();
|
|
7287
8198
|
init_agent_availability();
|
|
7288
8199
|
init_account_usage();
|
|
7289
8200
|
init_pr_watcher();
|
|
8201
|
+
init_superseded_pr_source();
|
|
7290
8202
|
init_watch_cycle_coordinator();
|
|
7291
8203
|
init_control_server();
|
|
7292
8204
|
init_apply_effort_mode();
|
|
@@ -7294,6 +8206,9 @@ var init_code_runner_daemon = __esm({
|
|
|
7294
8206
|
init_reconnect_backoff();
|
|
7295
8207
|
init_task_helpers();
|
|
7296
8208
|
init_agent_process_env();
|
|
8209
|
+
init_isolation_audit();
|
|
8210
|
+
init_recovery_ledger();
|
|
8211
|
+
init_no_changes_terminal_status();
|
|
7297
8212
|
RATE_LIMIT_RESUME_ENABLED = process.env.VO_RATE_LIMIT_RESUME === "1";
|
|
7298
8213
|
parseList = (s) => String(s || "").split(/[\s,]+/).map((x) => x.trim()).filter(Boolean);
|
|
7299
8214
|
sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
|