@algosuite/vo-mcp 0.2.0-beta.16 → 0.2.0-beta.17

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.
@@ -66,11 +66,11 @@ function createControlPlaneClient({
66
66
  throw new Error("VO_CONTROL_PLANE_URL is required for the code-runner daemon");
67
67
  }
68
68
  const root = resolvedBaseUrl.replace(/\/+$/, "");
69
- async function req(method, path, body, { timeoutMs } = {}) {
69
+ async function req(method, path2, body, { timeoutMs } = {}) {
70
70
  const bearer = await resolveBearer(env);
71
71
  const controller = timeoutMs ? new AbortController() : null;
72
72
  let timeoutId;
73
- const request = Promise.resolve(fetchImpl(`${root}${path}`, {
73
+ const request = Promise.resolve(fetchImpl(`${root}${path2}`, {
74
74
  method,
75
75
  headers: {
76
76
  "content-type": "application/json",
@@ -83,7 +83,7 @@ function createControlPlaneClient({
83
83
  const timeout = new Promise((_, reject) => {
84
84
  timeoutId = setTimeout(() => {
85
85
  controller.abort();
86
- reject(new Error(`control-plane ${path} timed out after ${timeoutMs}ms`));
86
+ reject(new Error(`control-plane ${path2} timed out after ${timeoutMs}ms`));
87
87
  }, timeoutMs);
88
88
  });
89
89
  try {
@@ -202,8 +202,8 @@ function createControlPlaneClient({
202
202
  return json ? json.task : null;
203
203
  },
204
204
  async downloadTaskAttachment(taskId, attachmentId) {
205
- const path = `/api/v1/code-task/${encodeURIComponent(taskId)}/attachment/${encodeURIComponent(attachmentId)}`;
206
- const res = await req("GET", path);
205
+ const path2 = `/api/v1/code-task/${encodeURIComponent(taskId)}/attachment/${encodeURIComponent(attachmentId)}`;
206
+ const res = await req("GET", path2);
207
207
  if (res.status === 401) cachedFirebaseToken = null;
208
208
  if (!res.ok) throw new Error(`attachment download failed: HTTP ${res.status}`);
209
209
  return Buffer.from(await res.arrayBuffer());
@@ -511,7 +511,7 @@ import {
511
511
  rmSync as rmSync2,
512
512
  writeFileSync as writeFileSync2
513
513
  } from "node:fs";
514
- import { basename, isAbsolute as isAbsolute3, join as join3, resolve as resolve4 } from "node:path";
514
+ import { basename, isAbsolute as isAbsolute3, join as join3, relative as relative3, resolve as resolve4, sep as sep2 } from "node:path";
515
515
  import { spawnSync as spawnSync2 } from "node:child_process";
516
516
 
517
517
  // ../../scripts/virtual-office/runner-bootstrap/runtime-authorization.mjs
@@ -647,10 +647,10 @@ function validateLock(lock, authorization) {
647
647
  }
648
648
  return { actual, expected };
649
649
  }
650
- function checkPathKind(path, expectedKind, fsOps, label) {
651
- if (!fsOps.exists(path)) throw new Error(`staged runtime ${label} missing`);
652
- const stats = fsOps.lstat(path);
653
- if (isReparseStat(stats) || fsOps.isReparsePoint(path, stats)) {
650
+ function checkPathKind(path2, expectedKind, fsOps, label) {
651
+ if (!fsOps.exists(path2)) throw new Error(`staged runtime ${label} missing`);
652
+ const stats = fsOps.lstat(path2);
653
+ if (isReparseStat(stats) || fsOps.isReparsePoint(path2, stats)) {
654
654
  throw new Error(`staged runtime ${label} is a reparse point`);
655
655
  }
656
656
  if (expectedKind === "directory" && !stats.isDirectory()) {
@@ -668,30 +668,30 @@ function verifyInstalledPackages(payloadRoot, packageRecords, platform, fsOps) {
668
668
  const omitted = [];
669
669
  let installed = 0;
670
670
  for (const [key, entry] of Object.entries(packageRecords)) {
671
- const path = resolve(payloadRoot, key);
672
- assertContained(payloadRoot, path, "package path");
671
+ const path2 = resolve(payloadRoot, key);
672
+ assertContained(payloadRoot, path2, "package path");
673
673
  const shouldOmit = isOmittedOptionalPackage(entry, platform);
674
674
  if (shouldOmit) {
675
675
  omitted.push(key);
676
- if (fsOps.exists(path)) throw new Error(`staged runtime optional package should be omitted: ${key}`);
676
+ if (fsOps.exists(path2)) throw new Error(`staged runtime optional package should be omitted: ${key}`);
677
677
  continue;
678
678
  }
679
- checkPathKind(path, "directory", fsOps, `installed package ${key}`);
679
+ checkPathKind(path2, "directory", fsOps, `installed package ${key}`);
680
680
  installed += 1;
681
681
  }
682
682
  return { installed, omitted: omitted.sort() };
683
683
  }
684
- function treeRecord(kind, path, stats, fileHash = "") {
685
- if (kind === "d") return `d ${path}\r
684
+ function treeRecord(kind, path2, stats, fileHash = "") {
685
+ if (kind === "d") return `d ${path2}\r
686
686
  `;
687
- return `f ${path} ${stats.size} ${fileHash}\r
687
+ return `f ${path2} ${stats.size} ${fileHash}\r
688
688
  `;
689
689
  }
690
690
  function computeInstalledTree(nodeModulesRoot, fsOps = {}) {
691
691
  const ops = {
692
692
  exists: existsSync2,
693
693
  lstat: lstatSync,
694
- readdir: (path) => readdirSync(path, { withFileTypes: true }),
694
+ readdir: (path2) => readdirSync(path2, { withFileTypes: true }),
695
695
  readFile: readFileSync,
696
696
  isReparsePoint: () => false,
697
697
  listReparsePoints: listWindowsReparsePoints,
@@ -1328,6 +1328,39 @@ function validateDependencyLock(payloadRoot, expected) {
1328
1328
  }
1329
1329
  if (!foundPackage) throw new Error("installed runner package does not match registry integrity");
1330
1330
  }
1331
+ function writeAuthorizedInstallSeed(payloadRoot, tarball, runtimeAuthorization) {
1332
+ const authorization = validateRuntimeAuthorization(runtimeAuthorization);
1333
+ const stagingRoot = resolve4(payloadRoot, "..", "..");
1334
+ const tarballFromStaging = relative3(stagingRoot, resolve4(tarball));
1335
+ if (!tarballFromStaging || tarballFromStaging === ".." || tarballFromStaging.startsWith(`..${sep2}`) || isAbsolute3(tarballFromStaging)) {
1336
+ throw new Error("authorized runtime tarball escaped staging root");
1337
+ }
1338
+ const relativeTarball = relative3(payloadRoot, resolve4(tarball)).replaceAll("\\", "/");
1339
+ if (!relativeTarball || isAbsolute3(relativeTarball) || relativeTarball.includes("\n") || relativeTarball.includes("\r")) {
1340
+ throw new Error("authorized runtime tarball path invalid");
1341
+ }
1342
+ const fileSpec = `file:${relativeTarball}`;
1343
+ const packageRecord = {
1344
+ name: "algohq-runner-runtime",
1345
+ version: "0.0.0",
1346
+ private: true,
1347
+ dependencies: { [PACKAGE_NAME]: fileSpec }
1348
+ };
1349
+ const packages = structuredClone(authorization.dependency_lock.packages);
1350
+ packages[`node_modules/${PACKAGE_NAME}`].resolved = fileSpec;
1351
+ const lock = {
1352
+ name: packageRecord.name,
1353
+ version: packageRecord.version,
1354
+ lockfileVersion: authorization.dependency_lock.source_lockfile_version,
1355
+ requires: true,
1356
+ packages: { "": packageRecord, ...packages }
1357
+ };
1358
+ writeFileSync2(join3(payloadRoot, "package.json"), `${JSON.stringify(packageRecord)}
1359
+ `, { mode: 384 });
1360
+ writeFileSync2(join3(payloadRoot, "package-lock.json"), `${JSON.stringify(lock)}
1361
+ `, { mode: 384 });
1362
+ return { fileSpec, lock };
1363
+ }
1331
1364
  function buildActive(slotId, metadata, paths) {
1332
1365
  return {
1333
1366
  slot_id: slotId,
@@ -1371,19 +1404,40 @@ function installSlot({
1371
1404
  let installedSlot = false;
1372
1405
  try {
1373
1406
  mkdirSync2(payload, { recursive: true });
1374
- writeFileSync2(join3(payload, "package.json"), `${JSON.stringify({ name: "algohq-runner-runtime", version: "0.0.0", private: true })}
1407
+ let installArgs;
1408
+ if (runtimeAuthorization) {
1409
+ writeAuthorizedInstallSeed(payload, tarball, runtimeAuthorization);
1410
+ installArgs = [
1411
+ "ci",
1412
+ "--ignore-scripts",
1413
+ "--no-bin-links",
1414
+ "--no-audit",
1415
+ "--no-fund",
1416
+ `--registry=${PUBLIC_REGISTRY}`
1417
+ ];
1418
+ } else {
1419
+ writeFileSync2(join3(payload, "package.json"), `${JSON.stringify({
1420
+ name: "algohq-runner-runtime",
1421
+ version: "0.0.0",
1422
+ private: true
1423
+ })}
1375
1424
  `);
1376
- const install = runner.npm([
1377
- "install",
1378
- "--ignore-scripts",
1379
- "--no-bin-links",
1380
- "--no-audit",
1381
- "--no-fund",
1382
- "--package-lock=true",
1383
- "--save-exact",
1384
- `--registry=${PUBLIC_REGISTRY}`,
1385
- tarball
1386
- ], { ...runOptions, cwd: payload, env: npmEnv, timeout: 18e4 });
1425
+ installArgs = [
1426
+ "install",
1427
+ "--ignore-scripts",
1428
+ "--no-bin-links",
1429
+ "--no-audit",
1430
+ "--no-fund",
1431
+ "--package-lock=true",
1432
+ "--save-exact",
1433
+ `--registry=${PUBLIC_REGISTRY}`,
1434
+ tarball
1435
+ ];
1436
+ }
1437
+ const install = runner.npm(
1438
+ installArgs,
1439
+ { ...runOptions, cwd: payload, env: npmEnv, timeout: 18e4 }
1440
+ );
1387
1441
  if (install.status !== 0) throw new Error(`npm install failed: ${install.stderr || install.error?.message || install.status}`);
1388
1442
  assertNoLinks(payload);
1389
1443
  validateDependencyLock(payload, metadata);
@@ -1504,6 +1558,151 @@ function stageAndActivateBundledUpdate(options) {
1504
1558
  }
1505
1559
  }
1506
1560
 
1561
+ // ../../scripts/virtual-office/code-runner/legacy-orphan-sweep.mjs
1562
+ import { spawnSync as spawnSync4 } from "node:child_process";
1563
+
1564
+ // ../../scripts/virtual-office/code-runner/orphan-agent-reaper.mjs
1565
+ import { spawnSync as spawnSync3 } from "node:child_process";
1566
+ import { existsSync as existsSync5, mkdirSync as mkdirSync3, readdirSync as readdirSync4, readFileSync as readFileSync5, rmSync as rmSync3, writeFileSync as writeFileSync3 } from "node:fs";
1567
+ import os from "node:os";
1568
+ import path from "node:path";
1569
+ function killProcessTree(pid, { platform = process.platform, spawn: spawn2 = spawnSync3 } = {}) {
1570
+ if (!Number.isInteger(pid) || pid <= 0) return false;
1571
+ if (platform === "win32") {
1572
+ const r = spawn2("taskkill", ["/PID", String(pid), "/T", "/F"], { windowsHide: true, stdio: "ignore", timeout: 15e3 });
1573
+ return !r.error && r.status === 0;
1574
+ }
1575
+ try {
1576
+ process.kill(-pid, "SIGKILL");
1577
+ return true;
1578
+ } catch {
1579
+ try {
1580
+ process.kill(pid, "SIGKILL");
1581
+ return true;
1582
+ } catch {
1583
+ return false;
1584
+ }
1585
+ }
1586
+ }
1587
+
1588
+ // ../../scripts/virtual-office/code-runner/legacy-orphan-sweep.mjs
1589
+ var MAX_LEGACY_KILLS = 50;
1590
+ var SWEEP_RECENCY_BUFFER_MS = 5e3;
1591
+ var SIGNATURES = [
1592
+ {
1593
+ signature: "claude-headless",
1594
+ // claude-args.mjs always emits `-p --output-format stream-json --verbose`.
1595
+ test: (cl) => /(?:^|[\\/"\s])claude(?:\.exe|\.cmd|\.ps1)?(?:"|\s)/iu.test(cl) && /--output-format[\s"=]+stream-json/iu.test(cl)
1596
+ },
1597
+ {
1598
+ signature: "codex-headless",
1599
+ // openai-compatible-runner always emits `exec --json`.
1600
+ test: (cl) => /(?:^|[\\/"\s])codex(?:\.exe|\.cmd|\.ps1)?(?:"|\s)/iu.test(cl) && /\bexec\b/u.test(cl) && /--json\b/u.test(cl)
1601
+ }
1602
+ ];
1603
+ function matchAgentSignature(commandLine) {
1604
+ if (typeof commandLine !== "string" || !commandLine) return null;
1605
+ for (const { signature, test } of SIGNATURES) {
1606
+ if (test(commandLine)) return signature;
1607
+ }
1608
+ return null;
1609
+ }
1610
+ function selectLegacyOrphans({ processes, cutoffMs, protectedPids = /* @__PURE__ */ new Set() }) {
1611
+ const byPid = /* @__PURE__ */ new Map();
1612
+ for (const proc of processes) {
1613
+ if (Number.isInteger(proc?.pid) && proc.pid > 0) byPid.set(proc.pid, proc);
1614
+ }
1615
+ const kills = [];
1616
+ for (const proc of byPid.values()) {
1617
+ if (protectedPids.has(proc.pid)) continue;
1618
+ if (!(Number.isFinite(proc.creationMs) && proc.creationMs < cutoffMs)) continue;
1619
+ const signature = matchAgentSignature(proc.commandLine);
1620
+ if (!signature) continue;
1621
+ const parent = Number.isInteger(proc.ppid) && proc.ppid > 0 ? byPid.get(proc.ppid) : void 0;
1622
+ const parentDead = !parent || Number.isFinite(parent.creationMs) && parent.creationMs > proc.creationMs;
1623
+ if (!parentDead) continue;
1624
+ kills.push({ pid: proc.pid, creationMs: proc.creationMs, signature, commandLine: proc.commandLine });
1625
+ }
1626
+ kills.sort((a, b) => a.creationMs - b.creationMs);
1627
+ return { kills: kills.slice(0, MAX_LEGACY_KILLS) };
1628
+ }
1629
+ function parsePosixSweepLine(line, nowMs) {
1630
+ const match = /^\s*(\d+)\s+(\d+)\s+(\d+)\s+(.+)$/u.exec(line ?? "");
1631
+ if (!match) return null;
1632
+ const pid = Number(match[1]);
1633
+ if (!Number.isInteger(pid) || pid <= 0) return null;
1634
+ return {
1635
+ pid,
1636
+ ppid: Number(match[2]),
1637
+ creationMs: nowMs - Number(match[3]) * 1e3,
1638
+ commandLine: match[4]
1639
+ };
1640
+ }
1641
+ function listProcessesForSweep({ platform = process.platform, spawn: spawn2 = spawnSync4, nowMs = Date.now() } = {}) {
1642
+ const rows = [];
1643
+ if (platform === "win32") {
1644
+ const ps = "Get-CimInstance Win32_Process | Where-Object { $_.CreationDate } | ForEach-Object { @{ p = $_.ProcessId; pp = $_.ParentProcessId; c = (([DateTimeOffset]$_.CreationDate.ToUniversalTime()).ToUnixTimeMilliseconds()); cl = [string]$_.CommandLine } | ConvertTo-Json -Compress }";
1645
+ const result2 = spawn2("powershell", ["-NoProfile", "-NonInteractive", "-Command", ps], {
1646
+ windowsHide: true,
1647
+ encoding: "utf8",
1648
+ timeout: 3e4,
1649
+ maxBuffer: 64 * 1024 * 1024
1650
+ });
1651
+ if (result2.error || result2.status !== 0) return rows;
1652
+ for (const line of String(result2.stdout ?? "").split(/\r?\n/u)) {
1653
+ if (!line.trim()) continue;
1654
+ try {
1655
+ const parsed = JSON.parse(line);
1656
+ const pid = Number(parsed?.p);
1657
+ if (!Number.isInteger(pid) || pid <= 0) continue;
1658
+ rows.push({
1659
+ pid,
1660
+ ppid: Number(parsed.pp),
1661
+ creationMs: Number(parsed.c),
1662
+ commandLine: typeof parsed.cl === "string" ? parsed.cl : ""
1663
+ });
1664
+ } catch {
1665
+ }
1666
+ }
1667
+ return rows;
1668
+ }
1669
+ const result = spawn2("ps", ["-eo", "pid=,ppid=,etimes=,args="], { encoding: "utf8", timeout: 3e4, maxBuffer: 64 * 1024 * 1024 });
1670
+ if (result.error || result.status !== 0) return rows;
1671
+ for (const line of String(result.stdout ?? "").split("\n")) {
1672
+ const row = parsePosixSweepLine(line, nowMs);
1673
+ if (row) rows.push(row);
1674
+ }
1675
+ return rows;
1676
+ }
1677
+ function runLegacyOrphanSweep({
1678
+ nowMs = Date.now(),
1679
+ protectedPids = [process.pid],
1680
+ listProcesses = listProcessesForSweep,
1681
+ killTree = killProcessTree,
1682
+ log = () => {
1683
+ }
1684
+ } = {}) {
1685
+ try {
1686
+ const processes = listProcesses({ nowMs });
1687
+ const { kills } = selectLegacyOrphans({
1688
+ processes,
1689
+ cutoffMs: nowMs - SWEEP_RECENCY_BUFFER_MS,
1690
+ protectedPids: new Set(protectedPids)
1691
+ });
1692
+ const killed = [];
1693
+ for (const kill of kills) {
1694
+ const done = killTree(kill.pid);
1695
+ log(`legacy-orphan-sweep ${done ? "killed" : "FAILED to kill"} pid=${kill.pid} sig=${kill.signature} cmd=${String(kill.commandLine).slice(0, 200)}`);
1696
+ if (done) killed.push({ pid: kill.pid, signature: kill.signature });
1697
+ }
1698
+ const failed2 = kills.length - killed.length;
1699
+ const detail = kills.length === 0 ? `no orphaned agent processes matched the sweep criteria (${processes.length} scanned)` : `purged ${killed.length} of ${kills.length} orphaned agent process tree(s)${failed2 > 0 ? ` (${failed2} kill(s) failed)` : ""}: ${killed.map((k) => `${k.pid}:${k.signature}`).join(", ").slice(0, 700)}`;
1700
+ return { ok: true, status: 0, detail, killed };
1701
+ } catch (error) {
1702
+ return { ok: false, status: 1, detail: `legacy sweep error: ${error instanceof Error ? error.message : String(error)}`.slice(0, 500), killed: [] };
1703
+ }
1704
+ }
1705
+
1507
1706
  // src/runner/supervisor-activation.mjs
1508
1707
  var MAX_ACK_ATTEMPTS = 3;
1509
1708
  var delay = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms));
@@ -1859,18 +2058,18 @@ async function prepareSupervisorAuth({
1859
2058
  }
1860
2059
 
1861
2060
  // src/runner/supervisor-credential-reader.mjs
1862
- import { spawnSync as spawnSync3 } from "node:child_process";
1863
- import { existsSync as existsSync5 } from "node:fs";
2061
+ import { spawnSync as spawnSync5 } from "node:child_process";
2062
+ import { existsSync as existsSync6 } from "node:fs";
1864
2063
  import { dirname as dirname3, join as join4 } from "node:path";
1865
2064
  import { fileURLToPath as fileURLToPath2 } from "node:url";
1866
2065
  function defaultCredentialHelperPath(metaUrl = import.meta.url) {
1867
2066
  const moduleDir = dirname3(fileURLToPath2(metaUrl));
1868
2067
  const bundled = join4(moduleDir, "supervisor-credential-helper.js");
1869
2068
  const source = join4(moduleDir, "..", "supervisor-credential-helper.mjs");
1870
- return existsSync5(source) ? source : bundled;
2069
+ return existsSync6(source) ? source : bundled;
1871
2070
  }
1872
2071
  function readStoredCredentialIsolated({
1873
- spawn: spawn2 = spawnSync3,
2072
+ spawn: spawn2 = spawnSync5,
1874
2073
  execPath = process.execPath,
1875
2074
  helperPath = defaultCredentialHelperPath(),
1876
2075
  helperArgs = [],
@@ -1899,7 +2098,7 @@ function readStoredCredentialIsolated({
1899
2098
  var DEFAULT_CONTROL_PLANE_URL = "https://vo-control-plane-bzjphrajaq-uc.a.run.app";
1900
2099
  var POLL_MS = 5e3;
1901
2100
  var CHILD_START_MS = 1500;
1902
- var SUPERVISOR_CAPABILITIES = ["bundled-runtime-slots-v1"];
2101
+ var SUPERVISOR_CAPABILITIES = ["bundled-runtime-slots-v1", "legacy-orphan-purge-v1"];
1903
2102
  var UUID_RE2 = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu;
1904
2103
  var selfPath = fileURLToPath3(import.meta.url);
1905
2104
  var childEntry = join5(dirname4(selfPath), "runner-cli.js");
@@ -2062,6 +2261,9 @@ async function main() {
2062
2261
  },
2063
2262
  env: process.env,
2064
2263
  force: action.kind === "reinstall"
2264
+ }) : action.kind === "purge-orphans" ? runLegacyOrphanSweep({
2265
+ protectedPids: [process.pid],
2266
+ log: (message) => console.warn(`[vo-runner supervisor] ${message}`)
2065
2267
  }) : runHostMaintenance(action.kind, {
2066
2268
  env: clientEnv,
2067
2269
  log: (message) => console.warn(`[vo-runner supervisor] ${message}`)
@@ -2078,7 +2280,7 @@ async function main() {
2078
2280
  ...operatorId ? { operatorId } : {},
2079
2281
  ...supervisorControlIdentity,
2080
2282
  status: result.ok ? "succeeded" : "failed",
2081
- detail: result.ok ? `runner ${packageVersion()} reconnected` : `maintenance exited ${result.status}${result.detail ? `: ${result.detail}` : ""}`
2283
+ detail: result.ok ? result.detail ? `${result.detail}; runner ${packageVersion()} reconnected`.slice(0, 1e3) : `runner ${packageVersion()} reconnected` : `maintenance exited ${result.status}${result.detail ? `: ${result.detail}` : ""}`
2082
2284
  });
2083
2285
  handling = false;
2084
2286
  } catch (error) {