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

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,
@@ -947,6 +947,7 @@ import {
947
947
  rmSync,
948
948
  writeFileSync
949
949
  } from "node:fs";
950
+ import { homedir } from "node:os";
950
951
  import { dirname as dirname2, isAbsolute as isAbsolute2, join as join2, relative as relative2, resolve as resolve3 } from "node:path";
951
952
  var SLOT_ID_RE = /^vo-mcp-[0-9A-Za-z._-]{1,96}$/u;
952
953
  var ACTION_ID_RE = /^[0-9A-Za-z._-]{1,128}$/u;
@@ -962,9 +963,26 @@ function within(parent, candidate) {
962
963
  const rel = relative2(resolve3(parent), resolve3(candidate));
963
964
  return rel === "" || !rel.startsWith("..") && !isAbsolute2(rel);
964
965
  }
965
- function runtimeRootFromEnv(env = process.env) {
966
+ var APP_IDENTIFIER = "ai.algosuite.vo-runner";
967
+ var RUNNER_RUNTIME_DIR = "runner-runtime";
968
+ function defaultRuntimeRoot({ platform = process.platform, env = process.env, home = homedir() } = {}) {
969
+ if (platform === "win32") {
970
+ const appData = String(env.APPDATA || "").trim();
971
+ return appData && isAbsolute2(appData) ? join2(appData, APP_IDENTIFIER, RUNNER_RUNTIME_DIR) : null;
972
+ }
973
+ if (!home) return null;
974
+ if (platform === "darwin") {
975
+ return join2(home, "Library", "Application Support", APP_IDENTIFIER, RUNNER_RUNTIME_DIR);
976
+ }
977
+ const xdg = String(env.XDG_CONFIG_HOME || "").trim();
978
+ const base = xdg && isAbsolute2(xdg) ? xdg : join2(home, ".config");
979
+ return join2(base, APP_IDENTIFIER, RUNNER_RUNTIME_DIR);
980
+ }
981
+ function runtimeRootFromEnv(env = process.env, { platform, home } = {}) {
966
982
  const value = String(env.VO_RUNNER_RUNTIME_ROOT || "").trim();
967
- return value && isAbsolute2(value) ? resolve3(value) : null;
983
+ if (value) return isAbsolute2(value) ? resolve3(value) : null;
984
+ const derived = defaultRuntimeRoot({ platform, env, home });
985
+ return derived ? resolve3(derived) : null;
968
986
  }
969
987
  function hashFileSha512(file) {
970
988
  return `sha512-${createHash3("sha512").update(readFileSync3(file)).digest("base64")}`;
@@ -1328,6 +1346,39 @@ function validateDependencyLock(payloadRoot, expected) {
1328
1346
  }
1329
1347
  if (!foundPackage) throw new Error("installed runner package does not match registry integrity");
1330
1348
  }
1349
+ function writeAuthorizedInstallSeed(payloadRoot, tarball, runtimeAuthorization) {
1350
+ const authorization = validateRuntimeAuthorization(runtimeAuthorization);
1351
+ const stagingRoot = resolve4(payloadRoot, "..", "..");
1352
+ const tarballFromStaging = relative3(stagingRoot, resolve4(tarball));
1353
+ if (!tarballFromStaging || tarballFromStaging === ".." || tarballFromStaging.startsWith(`..${sep2}`) || isAbsolute3(tarballFromStaging)) {
1354
+ throw new Error("authorized runtime tarball escaped staging root");
1355
+ }
1356
+ const relativeTarball = relative3(payloadRoot, resolve4(tarball)).replaceAll("\\", "/");
1357
+ if (!relativeTarball || isAbsolute3(relativeTarball) || relativeTarball.includes("\n") || relativeTarball.includes("\r")) {
1358
+ throw new Error("authorized runtime tarball path invalid");
1359
+ }
1360
+ const fileSpec = `file:${relativeTarball}`;
1361
+ const packageRecord = {
1362
+ name: "algohq-runner-runtime",
1363
+ version: "0.0.0",
1364
+ private: true,
1365
+ dependencies: { [PACKAGE_NAME]: fileSpec }
1366
+ };
1367
+ const packages = structuredClone(authorization.dependency_lock.packages);
1368
+ packages[`node_modules/${PACKAGE_NAME}`].resolved = fileSpec;
1369
+ const lock = {
1370
+ name: packageRecord.name,
1371
+ version: packageRecord.version,
1372
+ lockfileVersion: authorization.dependency_lock.source_lockfile_version,
1373
+ requires: true,
1374
+ packages: { "": packageRecord, ...packages }
1375
+ };
1376
+ writeFileSync2(join3(payloadRoot, "package.json"), `${JSON.stringify(packageRecord)}
1377
+ `, { mode: 384 });
1378
+ writeFileSync2(join3(payloadRoot, "package-lock.json"), `${JSON.stringify(lock)}
1379
+ `, { mode: 384 });
1380
+ return { fileSpec, lock };
1381
+ }
1331
1382
  function buildActive(slotId, metadata, paths) {
1332
1383
  return {
1333
1384
  slot_id: slotId,
@@ -1371,19 +1422,40 @@ function installSlot({
1371
1422
  let installedSlot = false;
1372
1423
  try {
1373
1424
  mkdirSync2(payload, { recursive: true });
1374
- writeFileSync2(join3(payload, "package.json"), `${JSON.stringify({ name: "algohq-runner-runtime", version: "0.0.0", private: true })}
1425
+ let installArgs;
1426
+ if (runtimeAuthorization) {
1427
+ writeAuthorizedInstallSeed(payload, tarball, runtimeAuthorization);
1428
+ installArgs = [
1429
+ "ci",
1430
+ "--ignore-scripts",
1431
+ "--no-bin-links",
1432
+ "--no-audit",
1433
+ "--no-fund",
1434
+ `--registry=${PUBLIC_REGISTRY}`
1435
+ ];
1436
+ } else {
1437
+ writeFileSync2(join3(payload, "package.json"), `${JSON.stringify({
1438
+ name: "algohq-runner-runtime",
1439
+ version: "0.0.0",
1440
+ private: true
1441
+ })}
1375
1442
  `);
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 });
1443
+ installArgs = [
1444
+ "install",
1445
+ "--ignore-scripts",
1446
+ "--no-bin-links",
1447
+ "--no-audit",
1448
+ "--no-fund",
1449
+ "--package-lock=true",
1450
+ "--save-exact",
1451
+ `--registry=${PUBLIC_REGISTRY}`,
1452
+ tarball
1453
+ ];
1454
+ }
1455
+ const install = runner.npm(
1456
+ installArgs,
1457
+ { ...runOptions, cwd: payload, env: npmEnv, timeout: 18e4 }
1458
+ );
1387
1459
  if (install.status !== 0) throw new Error(`npm install failed: ${install.stderr || install.error?.message || install.status}`);
1388
1460
  assertNoLinks(payload);
1389
1461
  validateDependencyLock(payload, metadata);
@@ -1504,6 +1576,151 @@ function stageAndActivateBundledUpdate(options) {
1504
1576
  }
1505
1577
  }
1506
1578
 
1579
+ // ../../scripts/virtual-office/code-runner/legacy-orphan-sweep.mjs
1580
+ import { spawnSync as spawnSync4 } from "node:child_process";
1581
+
1582
+ // ../../scripts/virtual-office/code-runner/orphan-agent-reaper.mjs
1583
+ import { spawnSync as spawnSync3 } from "node:child_process";
1584
+ import { existsSync as existsSync5, mkdirSync as mkdirSync3, readdirSync as readdirSync4, readFileSync as readFileSync5, rmSync as rmSync3, writeFileSync as writeFileSync3 } from "node:fs";
1585
+ import os from "node:os";
1586
+ import path from "node:path";
1587
+ function killProcessTree(pid, { platform = process.platform, spawn: spawn2 = spawnSync3 } = {}) {
1588
+ if (!Number.isInteger(pid) || pid <= 0) return false;
1589
+ if (platform === "win32") {
1590
+ const r = spawn2("taskkill", ["/PID", String(pid), "/T", "/F"], { windowsHide: true, stdio: "ignore", timeout: 15e3 });
1591
+ return !r.error && r.status === 0;
1592
+ }
1593
+ try {
1594
+ process.kill(-pid, "SIGKILL");
1595
+ return true;
1596
+ } catch {
1597
+ try {
1598
+ process.kill(pid, "SIGKILL");
1599
+ return true;
1600
+ } catch {
1601
+ return false;
1602
+ }
1603
+ }
1604
+ }
1605
+
1606
+ // ../../scripts/virtual-office/code-runner/legacy-orphan-sweep.mjs
1607
+ var MAX_LEGACY_KILLS = 50;
1608
+ var SWEEP_RECENCY_BUFFER_MS = 5e3;
1609
+ var SIGNATURES = [
1610
+ {
1611
+ signature: "claude-headless",
1612
+ // claude-args.mjs always emits `-p --output-format stream-json --verbose`.
1613
+ test: (cl) => /(?:^|[\\/"\s])claude(?:\.exe|\.cmd|\.ps1)?(?:"|\s)/iu.test(cl) && /--output-format[\s"=]+stream-json/iu.test(cl)
1614
+ },
1615
+ {
1616
+ signature: "codex-headless",
1617
+ // openai-compatible-runner always emits `exec --json`.
1618
+ test: (cl) => /(?:^|[\\/"\s])codex(?:\.exe|\.cmd|\.ps1)?(?:"|\s)/iu.test(cl) && /\bexec\b/u.test(cl) && /--json\b/u.test(cl)
1619
+ }
1620
+ ];
1621
+ function matchAgentSignature(commandLine) {
1622
+ if (typeof commandLine !== "string" || !commandLine) return null;
1623
+ for (const { signature, test } of SIGNATURES) {
1624
+ if (test(commandLine)) return signature;
1625
+ }
1626
+ return null;
1627
+ }
1628
+ function selectLegacyOrphans({ processes, cutoffMs, protectedPids = /* @__PURE__ */ new Set() }) {
1629
+ const byPid = /* @__PURE__ */ new Map();
1630
+ for (const proc of processes) {
1631
+ if (Number.isInteger(proc?.pid) && proc.pid > 0) byPid.set(proc.pid, proc);
1632
+ }
1633
+ const kills = [];
1634
+ for (const proc of byPid.values()) {
1635
+ if (protectedPids.has(proc.pid)) continue;
1636
+ if (!(Number.isFinite(proc.creationMs) && proc.creationMs < cutoffMs)) continue;
1637
+ const signature = matchAgentSignature(proc.commandLine);
1638
+ if (!signature) continue;
1639
+ const parent = Number.isInteger(proc.ppid) && proc.ppid > 0 ? byPid.get(proc.ppid) : void 0;
1640
+ const parentDead = !parent || Number.isFinite(parent.creationMs) && parent.creationMs > proc.creationMs;
1641
+ if (!parentDead) continue;
1642
+ kills.push({ pid: proc.pid, creationMs: proc.creationMs, signature, commandLine: proc.commandLine });
1643
+ }
1644
+ kills.sort((a, b) => a.creationMs - b.creationMs);
1645
+ return { kills: kills.slice(0, MAX_LEGACY_KILLS) };
1646
+ }
1647
+ function parsePosixSweepLine(line, nowMs) {
1648
+ const match = /^\s*(\d+)\s+(\d+)\s+(\d+)\s+(.+)$/u.exec(line ?? "");
1649
+ if (!match) return null;
1650
+ const pid = Number(match[1]);
1651
+ if (!Number.isInteger(pid) || pid <= 0) return null;
1652
+ return {
1653
+ pid,
1654
+ ppid: Number(match[2]),
1655
+ creationMs: nowMs - Number(match[3]) * 1e3,
1656
+ commandLine: match[4]
1657
+ };
1658
+ }
1659
+ function listProcessesForSweep({ platform = process.platform, spawn: spawn2 = spawnSync4, nowMs = Date.now() } = {}) {
1660
+ const rows = [];
1661
+ if (platform === "win32") {
1662
+ 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 }";
1663
+ const result2 = spawn2("powershell", ["-NoProfile", "-NonInteractive", "-Command", ps], {
1664
+ windowsHide: true,
1665
+ encoding: "utf8",
1666
+ timeout: 3e4,
1667
+ maxBuffer: 64 * 1024 * 1024
1668
+ });
1669
+ if (result2.error || result2.status !== 0) return rows;
1670
+ for (const line of String(result2.stdout ?? "").split(/\r?\n/u)) {
1671
+ if (!line.trim()) continue;
1672
+ try {
1673
+ const parsed = JSON.parse(line);
1674
+ const pid = Number(parsed?.p);
1675
+ if (!Number.isInteger(pid) || pid <= 0) continue;
1676
+ rows.push({
1677
+ pid,
1678
+ ppid: Number(parsed.pp),
1679
+ creationMs: Number(parsed.c),
1680
+ commandLine: typeof parsed.cl === "string" ? parsed.cl : ""
1681
+ });
1682
+ } catch {
1683
+ }
1684
+ }
1685
+ return rows;
1686
+ }
1687
+ const result = spawn2("ps", ["-eo", "pid=,ppid=,etimes=,args="], { encoding: "utf8", timeout: 3e4, maxBuffer: 64 * 1024 * 1024 });
1688
+ if (result.error || result.status !== 0) return rows;
1689
+ for (const line of String(result.stdout ?? "").split("\n")) {
1690
+ const row = parsePosixSweepLine(line, nowMs);
1691
+ if (row) rows.push(row);
1692
+ }
1693
+ return rows;
1694
+ }
1695
+ function runLegacyOrphanSweep({
1696
+ nowMs = Date.now(),
1697
+ protectedPids = [process.pid],
1698
+ listProcesses = listProcessesForSweep,
1699
+ killTree = killProcessTree,
1700
+ log = () => {
1701
+ }
1702
+ } = {}) {
1703
+ try {
1704
+ const processes = listProcesses({ nowMs });
1705
+ const { kills } = selectLegacyOrphans({
1706
+ processes,
1707
+ cutoffMs: nowMs - SWEEP_RECENCY_BUFFER_MS,
1708
+ protectedPids: new Set(protectedPids)
1709
+ });
1710
+ const killed = [];
1711
+ for (const kill of kills) {
1712
+ const done = killTree(kill.pid);
1713
+ log(`legacy-orphan-sweep ${done ? "killed" : "FAILED to kill"} pid=${kill.pid} sig=${kill.signature} cmd=${String(kill.commandLine).slice(0, 200)}`);
1714
+ if (done) killed.push({ pid: kill.pid, signature: kill.signature });
1715
+ }
1716
+ const failed2 = kills.length - killed.length;
1717
+ 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)}`;
1718
+ return { ok: true, status: 0, detail, killed };
1719
+ } catch (error) {
1720
+ return { ok: false, status: 1, detail: `legacy sweep error: ${error instanceof Error ? error.message : String(error)}`.slice(0, 500), killed: [] };
1721
+ }
1722
+ }
1723
+
1507
1724
  // src/runner/supervisor-activation.mjs
1508
1725
  var MAX_ACK_ATTEMPTS = 3;
1509
1726
  var delay = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms));
@@ -1859,18 +2076,18 @@ async function prepareSupervisorAuth({
1859
2076
  }
1860
2077
 
1861
2078
  // src/runner/supervisor-credential-reader.mjs
1862
- import { spawnSync as spawnSync3 } from "node:child_process";
1863
- import { existsSync as existsSync5 } from "node:fs";
2079
+ import { spawnSync as spawnSync5 } from "node:child_process";
2080
+ import { existsSync as existsSync6 } from "node:fs";
1864
2081
  import { dirname as dirname3, join as join4 } from "node:path";
1865
2082
  import { fileURLToPath as fileURLToPath2 } from "node:url";
1866
2083
  function defaultCredentialHelperPath(metaUrl = import.meta.url) {
1867
2084
  const moduleDir = dirname3(fileURLToPath2(metaUrl));
1868
2085
  const bundled = join4(moduleDir, "supervisor-credential-helper.js");
1869
2086
  const source = join4(moduleDir, "..", "supervisor-credential-helper.mjs");
1870
- return existsSync5(source) ? source : bundled;
2087
+ return existsSync6(source) ? source : bundled;
1871
2088
  }
1872
2089
  function readStoredCredentialIsolated({
1873
- spawn: spawn2 = spawnSync3,
2090
+ spawn: spawn2 = spawnSync5,
1874
2091
  execPath = process.execPath,
1875
2092
  helperPath = defaultCredentialHelperPath(),
1876
2093
  helperArgs = [],
@@ -1899,7 +2116,7 @@ function readStoredCredentialIsolated({
1899
2116
  var DEFAULT_CONTROL_PLANE_URL = "https://vo-control-plane-bzjphrajaq-uc.a.run.app";
1900
2117
  var POLL_MS = 5e3;
1901
2118
  var CHILD_START_MS = 1500;
1902
- var SUPERVISOR_CAPABILITIES = ["bundled-runtime-slots-v1"];
2119
+ var SUPERVISOR_CAPABILITIES = ["bundled-runtime-slots-v1", "legacy-orphan-purge-v1"];
1903
2120
  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
2121
  var selfPath = fileURLToPath3(import.meta.url);
1905
2122
  var childEntry = join5(dirname4(selfPath), "runner-cli.js");
@@ -2062,6 +2279,9 @@ async function main() {
2062
2279
  },
2063
2280
  env: process.env,
2064
2281
  force: action.kind === "reinstall"
2282
+ }) : action.kind === "purge-orphans" ? runLegacyOrphanSweep({
2283
+ protectedPids: [process.pid],
2284
+ log: (message) => console.warn(`[vo-runner supervisor] ${message}`)
2065
2285
  }) : runHostMaintenance(action.kind, {
2066
2286
  env: clientEnv,
2067
2287
  log: (message) => console.warn(`[vo-runner supervisor] ${message}`)
@@ -2078,7 +2298,7 @@ async function main() {
2078
2298
  ...operatorId ? { operatorId } : {},
2079
2299
  ...supervisorControlIdentity,
2080
2300
  status: result.ok ? "succeeded" : "failed",
2081
- detail: result.ok ? `runner ${packageVersion()} reconnected` : `maintenance exited ${result.status}${result.detail ? `: ${result.detail}` : ""}`
2301
+ 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
2302
  });
2083
2303
  handling = false;
2084
2304
  } catch (error) {