@algosuite/vo-mcp 0.2.0-beta.10 → 0.2.0-beta.12

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.
@@ -31,9 +31,10 @@ var init_control_plane_auth_stub = __esm({
31
31
 
32
32
  // src/runner-supervisor.mjs
33
33
  import { spawn } from "node:child_process";
34
+ import { randomUUID as randomUUID3 } from "node:crypto";
34
35
  import { createRequire } from "node:module";
35
36
  import { fileURLToPath as fileURLToPath2 } from "node:url";
36
- import { dirname as dirname2, join as join2 } from "node:path";
37
+ import { dirname as dirname3, join as join4 } from "node:path";
37
38
 
38
39
  // ../../scripts/virtual-office/code-runner/control-plane-client.mjs
39
40
  var cachedFirebaseToken = null;
@@ -54,22 +55,41 @@ async function resolveBearer(env) {
54
55
  function createControlPlaneClient({
55
56
  baseUrl = process.env.VO_CONTROL_PLANE_URL || "",
56
57
  env = process.env,
57
- fetchImpl = fetch
58
+ fetchImpl = fetch,
59
+ heartbeatTimeoutMs = Math.min(
60
+ Math.max(Number(env.VO_CODE_RUNNER_HEARTBEAT_TIMEOUT_MS) || 15e3, 1e3),
61
+ 6e4
62
+ )
58
63
  } = {}) {
59
64
  if (!baseUrl) {
60
65
  throw new Error("VO_CONTROL_PLANE_URL is required for the code-runner daemon");
61
66
  }
62
67
  const root = baseUrl.replace(/\/+$/, "");
63
- async function req(method, path, body) {
68
+ async function req(method, path, body, { timeoutMs } = {}) {
64
69
  const bearer = await resolveBearer(env);
65
- return fetchImpl(`${root}${path}`, {
70
+ const controller = timeoutMs ? new AbortController() : null;
71
+ let timeoutId;
72
+ const request = Promise.resolve(fetchImpl(`${root}${path}`, {
66
73
  method,
67
74
  headers: {
68
75
  "content-type": "application/json",
69
76
  authorization: `Bearer ${bearer}`
70
77
  },
71
- body: body === void 0 ? void 0 : JSON.stringify(body)
78
+ body: body === void 0 ? void 0 : JSON.stringify(body),
79
+ ...controller ? { signal: controller.signal } : {}
80
+ }));
81
+ if (!timeoutMs) return request;
82
+ const timeout = new Promise((_, reject) => {
83
+ timeoutId = setTimeout(() => {
84
+ controller.abort();
85
+ reject(new Error(`control-plane ${path} timed out after ${timeoutMs}ms`));
86
+ }, timeoutMs);
72
87
  });
88
+ try {
89
+ return await Promise.race([request, timeout]);
90
+ } finally {
91
+ clearTimeout(timeoutId);
92
+ }
73
93
  }
74
94
  return {
75
95
  /**
@@ -85,6 +105,10 @@ function createControlPlaneClient({
85
105
  if (Array.isArray(operatorIds) && operatorIds.length > 0) body.operator_ids = operatorIds;
86
106
  if (session.runnerInstanceId) body.runner_instance_id = session.runnerInstanceId;
87
107
  if (session.runnerInstanceId && session.reconcileStale) body.reconcile_stale = true;
108
+ if (session.defaultAgent) body.default_agent = session.defaultAgent;
109
+ if (Array.isArray(session.availableAgents)) {
110
+ body.available_agents = session.availableAgents.filter((entry) => entry?.installed === true && entry?.authenticated === true).map((entry) => entry.agent);
111
+ }
88
112
  const res = await req("POST", "/api/v1/code-task/claim", body);
89
113
  if (res.status === 401) {
90
114
  cachedFirebaseToken = null;
@@ -228,13 +252,20 @@ function createControlPlaneClient({
228
252
  * authenticated operator so the web shows a TRUE "runner online" signal.
229
253
  * Best-effort caller; throws on 401/non-ok so the daemon can log + retry.
230
254
  */
231
- async postHeartbeat({ runnerId: runnerId2, operatorId, uptimeSec, activeTasks, version, daemonVersion, servedRepos, servedOperators, availableAgents, accountUsage }) {
255
+ async postHeartbeat({ runnerId: runnerId2, runnerInstanceId, operatorId, uptimeSec, activeTasks, version, daemonVersion, defaultAgent, supervisorInstanceId: supervisorInstanceId2, supervisorVersion: supervisorVersion2, supervisorCapabilities, servedRepos, servedOperators, availableAgents, accountUsage }) {
232
256
  const body = { runner_id: runnerId2 };
257
+ if (runnerInstanceId) body.runner_instance_id = runnerInstanceId;
233
258
  if (operatorId) body.operator_id = operatorId;
234
259
  if (typeof uptimeSec === "number") body.uptime_sec = uptimeSec;
235
260
  if (typeof activeTasks === "number") body.active_tasks = activeTasks;
236
261
  if (version) body.version = version;
237
262
  if (daemonVersion) body.daemon_version = daemonVersion;
263
+ if (defaultAgent) body.default_agent = defaultAgent;
264
+ if (supervisorInstanceId2) body.supervisor_instance_id = supervisorInstanceId2;
265
+ if (supervisorVersion2) body.supervisor_version = supervisorVersion2;
266
+ if (Array.isArray(supervisorCapabilities) && supervisorCapabilities.length > 0) {
267
+ body.supervisor_capabilities = supervisorCapabilities;
268
+ }
238
269
  if (Array.isArray(servedRepos) && servedRepos.length > 0) body.served_repos = servedRepos;
239
270
  if (Array.isArray(servedOperators) && servedOperators.length > 0) {
240
271
  body.served_operator_ids = servedOperators;
@@ -245,7 +276,9 @@ function createControlPlaneClient({
245
276
  if (Array.isArray(accountUsage) && accountUsage.length > 0) {
246
277
  body.account_usage = accountUsage;
247
278
  }
248
- const res = await req("POST", "/api/v1/runner/heartbeat", body);
279
+ const res = await req("POST", "/api/v1/runner/heartbeat", body, {
280
+ timeoutMs: heartbeatTimeoutMs
281
+ });
249
282
  if (res.status === 401) {
250
283
  cachedFirebaseToken = null;
251
284
  throw new Error("heartbeat unauthorized (401)");
@@ -253,10 +286,27 @@ function createControlPlaneClient({
253
286
  if (!res.ok) throw new Error(`heartbeat failed: HTTP ${res.status}`);
254
287
  return true;
255
288
  },
289
+ /** Read the server-authoritative heartbeat ledger without mutating it. */
290
+ async getRunnerStatus({ operatorId } = {}) {
291
+ const query = operatorId ? `?operator_id=${encodeURIComponent(operatorId)}` : "";
292
+ const res = await req("GET", `/api/v1/runner/status${query}`, void 0, {
293
+ timeoutMs: heartbeatTimeoutMs
294
+ });
295
+ if (res.status === 401) {
296
+ cachedFirebaseToken = null;
297
+ throw new Error("runner status unauthorized (401)");
298
+ }
299
+ if (!res.ok) throw new Error(`runner status failed: HTTP ${res.status}`);
300
+ const body = await res.json();
301
+ return Array.isArray(body?.runners) ? body.runners : [];
302
+ },
256
303
  /** Poll one authenticated runner's durable Mission Control action queue. */
257
- async pollRunnerControl({ runnerId: runnerId2, operatorId }) {
304
+ async pollRunnerControl({ runnerId: runnerId2, operatorId, supervisorInstanceId: supervisorInstanceId2, supervisorVersion: supervisorVersion2, capabilities }) {
258
305
  const body = { runner_id: runnerId2 };
259
306
  if (operatorId) body.operator_id = operatorId;
307
+ if (supervisorInstanceId2) body.supervisor_instance_id = supervisorInstanceId2;
308
+ if (supervisorVersion2) body.supervisor_version = supervisorVersion2;
309
+ if (Array.isArray(capabilities) && capabilities.length > 0) body.capabilities = capabilities;
260
310
  const res = await req("POST", "/api/v1/runner/control/poll", body);
261
311
  if (res.status === 401) {
262
312
  cachedFirebaseToken = null;
@@ -268,9 +318,12 @@ function createControlPlaneClient({
268
318
  return action && typeof action.action_id === "string" && action.action_id ? { ...action, actionId: action.action_id } : null;
269
319
  },
270
320
  /** Acknowledge a maintenance action after the host has restarted the child. */
271
- async completeRunnerControl(actionId, { runnerId: runnerId2, operatorId, status, detail }) {
321
+ async completeRunnerControl(actionId, { runnerId: runnerId2, operatorId, supervisorInstanceId: supervisorInstanceId2, supervisorVersion: supervisorVersion2, capabilities, status, detail }) {
272
322
  const body = { runner_id: runnerId2, status };
273
323
  if (operatorId) body.operator_id = operatorId;
324
+ if (supervisorInstanceId2) body.supervisor_instance_id = supervisorInstanceId2;
325
+ if (supervisorVersion2) body.supervisor_version = supervisorVersion2;
326
+ if (Array.isArray(capabilities) && capabilities.length > 0) body.capabilities = capabilities;
274
327
  if (detail) body.detail = detail;
275
328
  const res = await req("POST", `/api/v1/runner/control/${encodeURIComponent(actionId)}/complete`, body);
276
329
  if (res.status === 401) {
@@ -333,9 +386,12 @@ function createControlPlaneClient({
333
386
 
334
387
  // ../../scripts/virtual-office/code-runner/runner-host-maintenance.mjs
335
388
  import { spawnSync } from "node:child_process";
389
+ import { existsSync } from "node:fs";
390
+ import { win32 } from "node:path";
336
391
  var DEFAULT_RUNNER_PACKAGE = "@algosuite/vo-mcp@beta";
337
392
  var PACKAGE_SPEC_RE = /^@algosuite\/vo-mcp@(beta|latest|\d+(?:\.\d+){0,2}(?:-[\w.-]+)?)$/u;
338
393
  var MAX_DIAGNOSTIC_CHARS = 800;
394
+ var NPM_CLI_SUFFIX = `\\${win32.join("node_modules", "npm", "bin", "npm-cli.js").toLowerCase()}`;
339
395
  function escapeRegExp(value) {
340
396
  return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
341
397
  }
@@ -351,11 +407,46 @@ function sanitizeMaintenanceDiagnostic(raw, env = {}) {
351
407
  if (value.length <= MAX_DIAGNOSTIC_CHARS) return value;
352
408
  return `${value.slice(0, MAX_DIAGNOSTIC_CHARS - 1)}\u2026`;
353
409
  }
354
- function buildMaintenanceCommand(kind, { platform = process.platform, packageSpec = DEFAULT_RUNNER_PACKAGE } = {}) {
410
+ function isNpmCliPath(value) {
411
+ return typeof value === "string" && win32.isAbsolute(value) && win32.normalize(value).toLowerCase().endsWith(NPM_CLI_SUFFIX);
412
+ }
413
+ function resolveNpmCli({
414
+ env = process.env,
415
+ execPath = process.execPath,
416
+ fileExists = existsSync
417
+ } = {}) {
418
+ const candidates = [];
419
+ if (isNpmCliPath(env.npm_execpath)) candidates.push(env.npm_execpath);
420
+ candidates.push(win32.join(win32.dirname(execPath), "node_modules", "npm", "bin", "npm-cli.js"));
421
+ const pathValue = env.PATH ?? env.Path ?? env.path ?? "";
422
+ for (const entry of pathValue.split(";")) {
423
+ const trimmed = entry.trim();
424
+ if (!win32.isAbsolute(trimmed)) continue;
425
+ candidates.push(win32.join(trimmed, "node_modules", "npm", "bin", "npm-cli.js"));
426
+ }
427
+ const seen = /* @__PURE__ */ new Set();
428
+ for (const candidate of candidates) {
429
+ const normalized = win32.normalize(candidate);
430
+ const key = normalized.toLowerCase();
431
+ if (seen.has(key) || !isNpmCliPath(normalized)) continue;
432
+ seen.add(key);
433
+ if (fileExists(normalized)) return normalized;
434
+ }
435
+ return null;
436
+ }
437
+ function buildMaintenanceCommand(kind, {
438
+ platform = process.platform,
439
+ packageSpec = DEFAULT_RUNNER_PACKAGE,
440
+ env = process.env,
441
+ execPath = process.execPath,
442
+ fileExists = existsSync
443
+ } = {}) {
355
444
  if (!["update", "reinstall"].includes(kind)) return null;
356
445
  if (!PACKAGE_SPEC_RE.test(packageSpec)) throw new Error("unsafe runner package spec");
357
- const command = platform === "win32" ? "npm.cmd" : "npm";
358
- const args = ["install", "-g", packageSpec];
446
+ const npmCli = platform === "win32" ? resolveNpmCli({ env, execPath, fileExists }) : null;
447
+ if (platform === "win32" && !npmCli) throw new Error("trusted npm CLI not found");
448
+ const command = platform === "win32" ? execPath : "npm";
449
+ const args = [...npmCli ? [npmCli] : [], "install", "-g", packageSpec];
359
450
  if (kind === "reinstall") args.push("--force");
360
451
  return { command, args };
361
452
  }
@@ -363,12 +454,25 @@ function runHostMaintenance(kind, {
363
454
  platform = process.platform,
364
455
  packageSpec = DEFAULT_RUNNER_PACKAGE,
365
456
  env = process.env,
457
+ execPath = process.execPath,
458
+ fileExists = existsSync,
366
459
  spawn: spawn2 = spawnSync,
367
460
  log = () => {
368
461
  }
369
462
  } = {}) {
370
463
  if (kind === "reconnect") return { ok: true, status: 0, command: null, args: [] };
371
- const command = buildMaintenanceCommand(kind, { platform, packageSpec });
464
+ let command;
465
+ try {
466
+ command = buildMaintenanceCommand(kind, { platform, packageSpec, env, execPath, fileExists });
467
+ } catch (error) {
468
+ return {
469
+ ok: false,
470
+ status: 2,
471
+ command: null,
472
+ args: [],
473
+ detail: sanitizeMaintenanceDiagnostic(error instanceof Error ? error.message : String(error), env)
474
+ };
475
+ }
372
476
  if (!command) return { ok: false, status: 2, command: null, args: [] };
373
477
  log(`runner maintenance: ${command.command} ${command.args.join(" ")}`);
374
478
  const result = spawn2(command.command, command.args, {
@@ -387,6 +491,739 @@ function runHostMaintenance(kind, {
387
491
  return { ok: status === 0, status, command: command.command, args: command.args, detail };
388
492
  }
389
493
 
494
+ // src/runner/bundled-runtime-updater.mjs
495
+ import { createHash as createHash2, randomUUID as randomUUID2 } from "node:crypto";
496
+ import {
497
+ existsSync as existsSync3,
498
+ lstatSync as lstatSync2,
499
+ mkdirSync as mkdirSync2,
500
+ readFileSync as readFileSync2,
501
+ readdirSync as readdirSync2,
502
+ renameSync as renameSync2,
503
+ rmSync as rmSync2,
504
+ writeFileSync as writeFileSync2
505
+ } from "node:fs";
506
+ import { basename, isAbsolute as isAbsolute2, join as join2, resolve as resolve2 } from "node:path";
507
+ import { spawnSync as spawnSync2 } from "node:child_process";
508
+
509
+ // src/runner/bundled-runtime-store.mjs
510
+ import { createHash, randomUUID } from "node:crypto";
511
+ import {
512
+ closeSync,
513
+ existsSync as existsSync2,
514
+ fsyncSync,
515
+ lstatSync,
516
+ mkdirSync,
517
+ openSync,
518
+ readFileSync,
519
+ readdirSync,
520
+ realpathSync,
521
+ renameSync,
522
+ rmSync,
523
+ writeFileSync
524
+ } from "node:fs";
525
+ import { dirname, isAbsolute, join, relative, resolve } from "node:path";
526
+ var SLOT_ID_RE = /^vo-mcp-[0-9A-Za-z._-]{1,96}$/u;
527
+ var ACTION_ID_RE = /^[0-9A-Za-z._-]{1,128}$/u;
528
+ var INTEGRITY_RE = /^sha512-[A-Za-z0-9+/]{86}==$/u;
529
+ var VERSION_RE = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/u;
530
+ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu;
531
+ var ENTRY_REL = join("node_modules", "@algosuite", "vo-mcp", "bin", "vo-mcp");
532
+ var SUPERVISOR_REL = join("node_modules", "@algosuite", "vo-mcp", "dist", "runner-supervisor.js");
533
+ var PACKAGE_REL = join("node_modules", "@algosuite", "vo-mcp", "package.json");
534
+ var CREDENTIAL_HELPER_REL = join("node_modules", "@algosuite", "vo-mcp", "dist", "supervisor-credential-helper.js");
535
+ var MANIFEST_FILE = "runtime-manifest.json";
536
+ function within(parent, candidate) {
537
+ const rel = relative(resolve(parent), resolve(candidate));
538
+ return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
539
+ }
540
+ function runtimeRootFromEnv(env = process.env) {
541
+ const value = String(env.VO_RUNNER_RUNTIME_ROOT || "").trim();
542
+ return value && isAbsolute(value) ? resolve(value) : null;
543
+ }
544
+ function hashFileSha512(file) {
545
+ return `sha512-${createHash("sha512").update(readFileSync(file)).digest("base64")}`;
546
+ }
547
+ function hashRuntimeTree(root) {
548
+ const hasher = createHash("sha512");
549
+ const files = [];
550
+ const visit = (directory, prefix = "") => {
551
+ const rootStat = lstatSync(directory);
552
+ if (rootStat.isSymbolicLink()) throw new Error("runtime tree contains a link/reparse point");
553
+ if (!rootStat.isDirectory()) throw new Error("runtime tree root is not a directory");
554
+ for (const name of readdirSync(directory).sort((a, b) => Buffer.compare(Buffer.from(a), Buffer.from(b)))) {
555
+ const absolute = join(directory, name);
556
+ const relativePath = prefix ? `${prefix}/${name}` : name;
557
+ const stat = lstatSync(absolute);
558
+ if (stat.isSymbolicLink()) throw new Error("runtime tree contains a link/reparse point");
559
+ if (stat.isDirectory()) visit(absolute, relativePath);
560
+ else if (stat.isFile() && relativePath !== MANIFEST_FILE) files.push({ absolute, relativePath, size: stat.size });
561
+ else if (!stat.isFile()) throw new Error("runtime tree contains a non-regular file");
562
+ }
563
+ };
564
+ visit(root);
565
+ files.sort((a, b) => Buffer.compare(Buffer.from(a.relativePath), Buffer.from(b.relativePath)));
566
+ for (const file of files) {
567
+ const pathBytes = Buffer.from(file.relativePath, "utf8");
568
+ hasher.update(`${pathBytes.length}:`);
569
+ hasher.update(pathBytes);
570
+ hasher.update(`:${file.size}:`);
571
+ hasher.update(readFileSync(file.absolute));
572
+ hasher.update("\n");
573
+ }
574
+ return `sha512-${hasher.digest("base64")}`;
575
+ }
576
+ function atomicWriteJson(file, value) {
577
+ mkdirSync(dirname(file), { recursive: true });
578
+ const temp = join(dirname(file), `.${randomUUID()}.tmp`);
579
+ const fd = openSync(temp, "wx", 384);
580
+ try {
581
+ writeFileSync(fd, `${JSON.stringify(value, null, 2)}
582
+ `, "utf8");
583
+ fsyncSync(fd);
584
+ } finally {
585
+ closeSync(fd);
586
+ }
587
+ try {
588
+ renameSync(temp, file);
589
+ if (process.platform !== "win32") {
590
+ try {
591
+ const parentFd = openSync(dirname(file), "r");
592
+ try {
593
+ fsyncSync(parentFd);
594
+ } finally {
595
+ closeSync(parentFd);
596
+ }
597
+ } catch {
598
+ }
599
+ }
600
+ } finally {
601
+ rmSync(temp, { force: true });
602
+ }
603
+ }
604
+ function readActivation(runtimeRoot) {
605
+ const file = join(runtimeRoot, "current.json");
606
+ if (!existsSync2(file)) return null;
607
+ try {
608
+ const value = JSON.parse(readFileSync(file, "utf8"));
609
+ return value?.schema_version === 1 ? value : null;
610
+ } catch {
611
+ return null;
612
+ }
613
+ }
614
+ function slotPaths(runtimeRoot, slotId) {
615
+ if (!SLOT_ID_RE.test(slotId)) throw new Error("invalid runtime slot id");
616
+ const slotRoot = join(runtimeRoot, "slots", slotId);
617
+ return {
618
+ slotRoot,
619
+ entry: join(slotRoot, ENTRY_REL),
620
+ supervisor: join(slotRoot, SUPERVISOR_REL),
621
+ packageJson: join(slotRoot, PACKAGE_REL),
622
+ credentialHelper: join(slotRoot, CREDENTIAL_HELPER_REL),
623
+ manifest: join(slotRoot, MANIFEST_FILE)
624
+ };
625
+ }
626
+ function validActive(active) {
627
+ return active && SLOT_ID_RE.test(active.slot_id) && VERSION_RE.test(active.version) && INTEGRITY_RE.test(active.integrity) && INTEGRITY_RE.test(active.entry_sha512) && INTEGRITY_RE.test(active.supervisor_sha512) && INTEGRITY_RE.test(active.tree_sha512);
628
+ }
629
+ function validateSlot(runtimeRoot, active) {
630
+ if (!validActive(active)) return { ok: false, detail: "invalid activation metadata" };
631
+ const paths = slotPaths(runtimeRoot, active.slot_id);
632
+ try {
633
+ const manifest = JSON.parse(readFileSync(paths.manifest, "utf8"));
634
+ const pkg = JSON.parse(readFileSync(paths.packageJson, "utf8"));
635
+ const expected = {
636
+ slot_id: active.slot_id,
637
+ version: active.version,
638
+ integrity: active.integrity,
639
+ entry_sha512: active.entry_sha512,
640
+ supervisor_sha512: active.supervisor_sha512,
641
+ tree_sha512: active.tree_sha512
642
+ };
643
+ for (const [key, value] of Object.entries(expected)) {
644
+ if (manifest?.[key] !== value) return { ok: false, detail: `manifest ${key} mismatch` };
645
+ }
646
+ if (manifest?.schema_version !== 1 || pkg?.name !== "@algosuite/vo-mcp" || pkg?.version !== active.version) {
647
+ return { ok: false, detail: "package identity mismatch" };
648
+ }
649
+ if (lstatSync(paths.entry).isSymbolicLink() || lstatSync(paths.supervisor).isSymbolicLink() || lstatSync(paths.credentialHelper).isSymbolicLink()) {
650
+ return { ok: false, detail: "runtime entry cannot be a link" };
651
+ }
652
+ if (!within(paths.slotRoot, realpathSync(paths.entry)) || !within(paths.slotRoot, realpathSync(paths.supervisor)) || !within(paths.slotRoot, realpathSync(paths.credentialHelper))) {
653
+ return { ok: false, detail: "runtime entry escaped its slot" };
654
+ }
655
+ if (hashFileSha512(paths.entry) !== active.entry_sha512) return { ok: false, detail: "entry hash mismatch" };
656
+ if (hashFileSha512(paths.supervisor) !== active.supervisor_sha512) return { ok: false, detail: "supervisor hash mismatch" };
657
+ if (hashRuntimeTree(paths.slotRoot) !== active.tree_sha512) return { ok: false, detail: "runtime tree hash mismatch" };
658
+ return { ok: true, paths, manifest };
659
+ } catch (error) {
660
+ return { ok: false, detail: error instanceof Error ? error.message : String(error) };
661
+ }
662
+ }
663
+ function journalActivation(runtimeRoot, actionId, state, detail = "") {
664
+ if (!ACTION_ID_RE.test(actionId)) throw new Error("invalid runner action id");
665
+ atomicWriteJson(join(runtimeRoot, "transactions", `${actionId}.json`), {
666
+ schema_version: 1,
667
+ action_id: actionId,
668
+ state,
669
+ detail: String(detail).slice(0, 400),
670
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
671
+ });
672
+ }
673
+ function activateSlot(runtimeRoot, active, action) {
674
+ if (!validActive(active)) throw new Error("cannot activate invalid runtime metadata");
675
+ if (!ACTION_ID_RE.test(action.actionId)) throw new Error("invalid runner action id");
676
+ if (!UUID_RE.test(String(action.supervisorInstanceId || ""))) throw new Error("invalid claiming supervisor instance id");
677
+ const validated = validateSlot(runtimeRoot, active);
678
+ if (!validated.ok) throw new Error(`cannot activate invalid runtime slot: ${validated.detail}`);
679
+ const current = readActivation(runtimeRoot);
680
+ if (current?.pending) throw new Error("another runtime activation is still pending");
681
+ const pointer = {
682
+ schema_version: 1,
683
+ generation: randomUUID(),
684
+ active,
685
+ previous: validActive(current?.active) ? current.active : null,
686
+ pending: {
687
+ action_id: action.actionId,
688
+ runner_id: String(action.runnerId || ""),
689
+ operator_id: String(action.operatorId || ""),
690
+ supervisor_instance_id: action.supervisorInstanceId,
691
+ activated_at: (/* @__PURE__ */ new Date()).toISOString(),
692
+ ack_attempts: 0
693
+ }
694
+ };
695
+ journalActivation(runtimeRoot, action.actionId, "prepared", `${active.version} ${active.integrity}`);
696
+ atomicWriteJson(join(runtimeRoot, "current.json"), pointer);
697
+ return pointer;
698
+ }
699
+ function activationSupervisorInstanceId(runtimeRoot, fallback) {
700
+ const current = runtimeRoot ? readActivation(runtimeRoot) : null;
701
+ const pending = String(current?.pending?.supervisor_instance_id || "");
702
+ if (UUID_RE.test(pending)) return pending;
703
+ return UUID_RE.test(String(fallback || "")) ? fallback : null;
704
+ }
705
+ function recordActivationRetry(runtimeRoot, pointer, detail) {
706
+ const current = readActivation(runtimeRoot);
707
+ if (current?.generation !== pointer?.generation || current?.pending?.action_id !== pointer?.pending?.action_id) {
708
+ throw new Error("runtime activation generation changed before retry");
709
+ }
710
+ const attempts = Math.max(0, Number(current.pending.ack_attempts || 0)) + 1;
711
+ const updated = {
712
+ ...current,
713
+ pending: { ...current.pending, ack_attempts: attempts, last_error: String(detail).slice(0, 240) }
714
+ };
715
+ atomicWriteJson(join(runtimeRoot, "current.json"), updated);
716
+ journalActivation(runtimeRoot, current.pending.action_id, "ack-retry", `attempt ${attempts}: ${detail}`);
717
+ return updated;
718
+ }
719
+ function attestCurrentSupervisor({ runtimeRoot, selfPath: selfPath2, version }) {
720
+ const pointer = readActivation(runtimeRoot);
721
+ if (!pointer?.pending || !validActive(pointer.active)) return { ok: false, detail: "no pending activation" };
722
+ const validated = validateSlot(runtimeRoot, pointer.active);
723
+ if (!validated.ok) return validated;
724
+ try {
725
+ if (realpathSync(selfPath2) !== realpathSync(validated.paths.supervisor)) {
726
+ return { ok: false, detail: "running supervisor is not the activated supervisor" };
727
+ }
728
+ } catch {
729
+ return { ok: false, detail: "could not resolve running supervisor path" };
730
+ }
731
+ if (version !== pointer.active.version) return { ok: false, detail: "running supervisor version mismatch" };
732
+ return { ok: true, pointer, active: pointer.active, paths: validated.paths };
733
+ }
734
+ function finalizeActivation(runtimeRoot, pointer) {
735
+ const current = readActivation(runtimeRoot);
736
+ if (current?.generation !== pointer?.generation || current?.pending?.action_id !== pointer?.pending?.action_id) {
737
+ throw new Error("runtime activation generation changed before finalization");
738
+ }
739
+ journalActivation(runtimeRoot, pointer.pending.action_id, "attesting", `${pointer.active.version} verified`);
740
+ atomicWriteJson(join(runtimeRoot, "current.json"), {
741
+ schema_version: 1,
742
+ generation: pointer.generation,
743
+ active: pointer.active,
744
+ previous: pointer.previous || null,
745
+ pending: null
746
+ });
747
+ try {
748
+ journalActivation(runtimeRoot, pointer.pending.action_id, "attested", `${pointer.active.version} active`);
749
+ } catch {
750
+ }
751
+ }
752
+ function rollbackActivation(runtimeRoot, pointer, detail) {
753
+ const current = readActivation(runtimeRoot);
754
+ if (current?.generation !== pointer?.generation || current?.pending?.action_id !== pointer?.pending?.action_id) {
755
+ throw new Error("runtime activation generation changed before rollback");
756
+ }
757
+ journalActivation(runtimeRoot, pointer.pending.action_id, "rolling-back", detail);
758
+ const rolledBack = {
759
+ schema_version: 1,
760
+ generation: randomUUID(),
761
+ active: validActive(pointer?.previous) ? pointer.previous : null,
762
+ previous: null,
763
+ pending: {
764
+ ...pointer.pending,
765
+ terminal_status: "failed",
766
+ terminal_detail: String(detail).slice(0, 400),
767
+ rolled_back_at: (/* @__PURE__ */ new Date()).toISOString()
768
+ }
769
+ };
770
+ atomicWriteJson(join(runtimeRoot, "current.json"), rolledBack);
771
+ try {
772
+ journalActivation(runtimeRoot, pointer.pending.action_id, "rolled-back", detail);
773
+ } catch {
774
+ }
775
+ return rolledBack;
776
+ }
777
+ function acknowledgeActivationFailure(runtimeRoot, pointer) {
778
+ const current = readActivation(runtimeRoot);
779
+ if (current?.generation !== pointer?.generation || current?.pending?.action_id !== pointer?.pending?.action_id || current?.pending?.terminal_status !== "failed") {
780
+ throw new Error("runtime rollback acknowledgement obligation changed");
781
+ }
782
+ atomicWriteJson(join(runtimeRoot, "current.json"), { ...current, pending: null });
783
+ try {
784
+ journalActivation(runtimeRoot, pointer.pending.action_id, "failure-acknowledged", pointer.pending.terminal_detail);
785
+ } catch {
786
+ }
787
+ }
788
+
789
+ // src/runner/bundled-runtime-updater.mjs
790
+ var PACKAGE_NAME = "@algosuite/vo-mcp";
791
+ var PACKAGE_SPEC_RE2 = /^@algosuite\/vo-mcp@\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/u;
792
+ var INTEGRITY_RE2 = /^sha512-[A-Za-z0-9+/]{86}==$/u;
793
+ var MAX_TARBALL_BYTES = 100 * 1024 * 1024;
794
+ var PUBLIC_REGISTRY = "https://registry.npmjs.org/";
795
+ function buildMinimalMaintenanceEnv(env = process.env, runtimeRoot = "") {
796
+ const allowed = /* @__PURE__ */ new Set([
797
+ "PATH",
798
+ "Path",
799
+ "path",
800
+ "PATHEXT",
801
+ "SystemRoot",
802
+ "SYSTEMROOT",
803
+ "WINDIR",
804
+ "COMSPEC",
805
+ "TEMP",
806
+ "TMP",
807
+ "TMPDIR",
808
+ "HOME",
809
+ "USERPROFILE",
810
+ "APPDATA",
811
+ "LOCALAPPDATA",
812
+ "ProgramFiles",
813
+ "ProgramFiles(x86)",
814
+ "ProgramW6432",
815
+ "LANG",
816
+ "LC_ALL"
817
+ ]);
818
+ const clean = {};
819
+ for (const [key, value] of Object.entries(env)) {
820
+ if (allowed.has(key) && typeof value === "string") clean[key] = value;
821
+ }
822
+ return {
823
+ ...clean,
824
+ npm_config_ignore_scripts: "true",
825
+ npm_config_bin_links: "false",
826
+ npm_config_audit: "false",
827
+ npm_config_fund: "false",
828
+ npm_config_update_notifier: "false",
829
+ npm_config_registry: PUBLIC_REGISTRY,
830
+ ...runtimeRoot ? {
831
+ npm_config_userconfig: join2(runtimeRoot, "maintenance", "user.npmrc"),
832
+ npm_config_globalconfig: join2(runtimeRoot, "maintenance", "global.npmrc"),
833
+ npm_config_cache: join2(runtimeRoot, "maintenance", "npm-cache")
834
+ } : {}
835
+ };
836
+ }
837
+ function defaultRun(command, args, options) {
838
+ return spawnSync2(command, args, {
839
+ cwd: options.cwd,
840
+ encoding: "utf8",
841
+ env: options.env,
842
+ shell: false,
843
+ windowsHide: true,
844
+ stdio: ["ignore", "pipe", "pipe"],
845
+ timeout: options.timeout ?? 12e4
846
+ });
847
+ }
848
+ function commandRunner({ platform, execPath, env, fileExists, run }) {
849
+ const npmCli = platform === "win32" ? resolveNpmCli({ env, execPath, fileExists }) : null;
850
+ if (platform === "win32" && !npmCli) throw new Error("trusted npm CLI not found");
851
+ const npmCommand = platform === "win32" ? execPath : "npm";
852
+ const prefix = npmCli ? [npmCli] : [];
853
+ return {
854
+ npm(args, options) {
855
+ return run(npmCommand, [...prefix, ...args], options);
856
+ },
857
+ node(args, options) {
858
+ return run(execPath, args, options);
859
+ }
860
+ };
861
+ }
862
+ function parseJsonOutput(result, operation) {
863
+ if (result?.status !== 0) {
864
+ throw new Error(`${operation} failed: ${String(result?.stderr || result?.error?.message || `exit ${result?.status ?? 1}`)}`);
865
+ }
866
+ try {
867
+ return JSON.parse(String(result.stdout || ""));
868
+ } catch {
869
+ throw new Error(`${operation} returned invalid JSON`);
870
+ }
871
+ }
872
+ function tarballIntegrity(file) {
873
+ return `sha512-${createHash2("sha512").update(readFileSync2(file)).digest("base64")}`;
874
+ }
875
+ function assertNoLinks(root) {
876
+ const pending = [root];
877
+ while (pending.length) {
878
+ const current = pending.pop();
879
+ const stat = lstatSync2(current);
880
+ if (stat.isSymbolicLink()) throw new Error("installed runtime contains a link/reparse point");
881
+ if (!stat.isDirectory()) continue;
882
+ for (const entry of readdirSync2(current)) pending.push(join2(current, entry));
883
+ }
884
+ }
885
+ function validateDependencyLock(payloadRoot, expected) {
886
+ const lock = JSON.parse(readFileSync2(join2(payloadRoot, "package-lock.json"), "utf8"));
887
+ if (Number(lock.lockfileVersion) < 3 || !lock.packages || typeof lock.packages !== "object") {
888
+ throw new Error("runtime dependency lock is missing or unsupported");
889
+ }
890
+ let foundPackage = false;
891
+ for (const [key, item] of Object.entries(lock.packages)) {
892
+ if (!key) continue;
893
+ if (item?.link === true) throw new Error(`runtime dependency lock contains link: ${key}`);
894
+ const isRunner = key.replaceAll("\\", "/").endsWith("node_modules/@algosuite/vo-mcp");
895
+ if (isRunner) {
896
+ foundPackage = item.version === expected.version && item.integrity === expected.integrity;
897
+ continue;
898
+ }
899
+ if (!INTEGRITY_RE2.test(String(item?.integrity || ""))) throw new Error(`dependency lacks sha512 integrity: ${key}`);
900
+ if (!String(item?.resolved || "").startsWith("https://registry.npmjs.org/")) {
901
+ throw new Error(`dependency is not registry-pinned: ${key}`);
902
+ }
903
+ }
904
+ if (!foundPackage) throw new Error("installed runner package does not match registry integrity");
905
+ }
906
+ function buildActive(slotId, metadata, paths) {
907
+ return {
908
+ slot_id: slotId,
909
+ version: metadata.version,
910
+ integrity: metadata.integrity,
911
+ entry_sha512: hashFileSha512(paths.entry),
912
+ supervisor_sha512: hashFileSha512(paths.supervisor),
913
+ tree_sha512: hashRuntimeTree(paths.slotRoot ?? paths.payloadRoot)
914
+ };
915
+ }
916
+ function installSlot({ runtimeRoot, metadata, tarball, runner, npmEnv, runOptions, force }) {
917
+ const digest = createHash2("sha256").update(metadata.integrity).digest("hex").slice(0, 16);
918
+ const suffix = force ? `${digest}-${randomUUID2().slice(0, 8)}` : digest;
919
+ const slotId = `vo-mcp-${metadata.version}-${suffix}`;
920
+ const finalPaths = slotPaths(runtimeRoot, slotId);
921
+ if (!force && existsSync3(finalPaths.slotRoot)) {
922
+ const manifest = JSON.parse(readFileSync2(finalPaths.manifest, "utf8"));
923
+ const active = buildActive(slotId, metadata, finalPaths);
924
+ const validated = validateSlot(runtimeRoot, active);
925
+ if (validated.ok && manifest.integrity === metadata.integrity) return { active, created: false };
926
+ }
927
+ const staging = join2(runtimeRoot, "staging", randomUUID2());
928
+ const payload = join2(staging, "payload");
929
+ let installedSlot = false;
930
+ try {
931
+ mkdirSync2(payload, { recursive: true });
932
+ writeFileSync2(join2(payload, "package.json"), `${JSON.stringify({ name: "algohq-runner-runtime", version: "0.0.0", private: true })}
933
+ `);
934
+ const install = runner.npm([
935
+ "install",
936
+ "--ignore-scripts",
937
+ "--no-bin-links",
938
+ "--no-audit",
939
+ "--no-fund",
940
+ "--package-lock=true",
941
+ "--save-exact",
942
+ `--registry=${PUBLIC_REGISTRY}`,
943
+ tarball
944
+ ], { ...runOptions, cwd: payload, env: npmEnv, timeout: 18e4 });
945
+ if (install.status !== 0) throw new Error(`npm install failed: ${install.stderr || install.error?.message || install.status}`);
946
+ assertNoLinks(payload);
947
+ validateDependencyLock(payload, metadata);
948
+ const stagedPaths = {
949
+ entry: join2(payload, "node_modules", "@algosuite", "vo-mcp", "bin", "vo-mcp"),
950
+ supervisor: join2(payload, "node_modules", "@algosuite", "vo-mcp", "dist", "runner-supervisor.js"),
951
+ packageJson: join2(payload, "node_modules", "@algosuite", "vo-mcp", "package.json"),
952
+ credentialHelper: join2(payload, "node_modules", "@algosuite", "vo-mcp", "dist", "supervisor-credential-helper.js"),
953
+ slotRoot: payload
954
+ };
955
+ const pkg = JSON.parse(readFileSync2(stagedPaths.packageJson, "utf8"));
956
+ if (pkg.name !== PACKAGE_NAME || pkg.version !== metadata.version) throw new Error("installed package identity mismatch");
957
+ if (!lstatSync2(stagedPaths.credentialHelper).isFile()) throw new Error("installed credential helper is missing");
958
+ const smoke = runner.node([stagedPaths.entry, "runner", "--version"], { ...runOptions, cwd: payload, env: npmEnv, timeout: 3e4 });
959
+ if (smoke.status !== 0 || String(smoke.stdout || "").trim() !== `vo-mcp runner ${metadata.version}`) {
960
+ throw new Error("bundled runtime smoke check failed");
961
+ }
962
+ const active = buildActive(slotId, metadata, stagedPaths);
963
+ atomicWriteJson(join2(payload, "runtime-manifest.json"), { schema_version: 1, ...active });
964
+ mkdirSync2(join2(runtimeRoot, "slots"), { recursive: true });
965
+ if (existsSync3(finalPaths.slotRoot)) throw new Error("immutable runtime slot already exists");
966
+ renameSync2(payload, finalPaths.slotRoot);
967
+ installedSlot = true;
968
+ const validated = validateSlot(runtimeRoot, active);
969
+ if (!validated.ok) throw new Error(`staged runtime validation failed: ${validated.detail}`);
970
+ return { active, created: true };
971
+ } catch (error) {
972
+ if (installedSlot) rmSync2(finalPaths.slotRoot, { recursive: true, force: true });
973
+ throw error;
974
+ } finally {
975
+ rmSync2(staging, { recursive: true, force: true });
976
+ }
977
+ }
978
+ function stageBundledRuntimeSlot(options) {
979
+ const {
980
+ runtimeRoot,
981
+ packageSpec,
982
+ expectedVersion,
983
+ expectedIntegrity,
984
+ platform = process.platform,
985
+ execPath = process.execPath,
986
+ env = process.env,
987
+ fileExists = existsSync3,
988
+ run = defaultRun,
989
+ force = false
990
+ } = options;
991
+ if (!runtimeRoot || !isAbsolute2(runtimeRoot)) return { ok: false, status: 2, detail: "bundled runtime root unavailable" };
992
+ if (!expectedVersion || !PACKAGE_SPEC_RE2.test(`${PACKAGE_NAME}@${expectedVersion}`)) {
993
+ return { ok: false, status: 2, detail: "invalid expected runner version" };
994
+ }
995
+ if (!expectedIntegrity || !INTEGRITY_RE2.test(expectedIntegrity)) {
996
+ return { ok: false, status: 2, detail: "invalid expected runner integrity" };
997
+ }
998
+ const exactSpec = `${PACKAGE_NAME}@${expectedVersion}`;
999
+ if (packageSpec !== exactSpec) return { ok: false, status: 2, detail: "runner package spec does not match authorized version" };
1000
+ const resolvedRoot = resolve2(runtimeRoot);
1001
+ const npmEnv = buildMinimalMaintenanceEnv(env, resolvedRoot);
1002
+ const runOptions = { env: npmEnv, cwd: resolvedRoot };
1003
+ let tarDir = null;
1004
+ try {
1005
+ mkdirSync2(resolvedRoot, { recursive: true });
1006
+ mkdirSync2(join2(resolvedRoot, "maintenance"), { recursive: true });
1007
+ writeFileSync2(npmEnv.npm_config_userconfig, "", { mode: 384 });
1008
+ writeFileSync2(npmEnv.npm_config_globalconfig, "", { mode: 384 });
1009
+ const runner = commandRunner({ platform, execPath, env: npmEnv, fileExists, run });
1010
+ const metadata = { version: expectedVersion, integrity: expectedIntegrity };
1011
+ tarDir = join2(resolvedRoot, "staging", randomUUID2());
1012
+ mkdirSync2(tarDir, { recursive: true });
1013
+ const packed = parseJsonOutput(runner.npm([
1014
+ "pack",
1015
+ exactSpec,
1016
+ "--ignore-scripts",
1017
+ "--json",
1018
+ "--pack-destination",
1019
+ tarDir,
1020
+ `--registry=${PUBLIC_REGISTRY}`
1021
+ ], runOptions), "npm pack");
1022
+ const record = Array.isArray(packed) ? packed[0] : packed;
1023
+ const tarball = join2(tarDir, basename(String(record?.filename || "")));
1024
+ if (!existsSync3(tarball) || !basename(tarball).endsWith(".tgz")) throw new Error("npm pack returned no tarball");
1025
+ if (lstatSync2(tarball).size > MAX_TARBALL_BYTES) throw new Error("runner package tarball exceeds size limit");
1026
+ if (record.integrity !== metadata.integrity || tarballIntegrity(tarball) !== metadata.integrity) {
1027
+ throw new Error("runner package sha512 integrity mismatch");
1028
+ }
1029
+ const installed = installSlot({ runtimeRoot: resolvedRoot, metadata, tarball, runner, npmEnv, runOptions, force });
1030
+ return { ok: true, status: 0, ...installed };
1031
+ } catch (error) {
1032
+ return { ok: false, status: 1, detail: sanitizeMaintenanceDiagnostic(error instanceof Error ? error.message : String(error), env) };
1033
+ } finally {
1034
+ if (tarDir) rmSync2(tarDir, { recursive: true, force: true });
1035
+ }
1036
+ }
1037
+ function stageAndActivateBundledUpdate(options) {
1038
+ const staged = stageBundledRuntimeSlot(options);
1039
+ if (!staged.ok) return staged;
1040
+ try {
1041
+ activateSlot(resolve2(options.runtimeRoot), staged.active, options.action);
1042
+ return { ...staged, handoff: true };
1043
+ } catch (error) {
1044
+ return {
1045
+ ok: false,
1046
+ status: 1,
1047
+ detail: sanitizeMaintenanceDiagnostic(error instanceof Error ? error.message : String(error), options.env ?? process.env)
1048
+ };
1049
+ }
1050
+ }
1051
+
1052
+ // src/runner/supervisor-activation.mjs
1053
+ var MAX_ACK_ATTEMPTS = 3;
1054
+ var delay = (ms) => new Promise((resolve3) => setTimeout(resolve3, ms));
1055
+ async function waitForAuthoritativeRunnerHeartbeat({
1056
+ client,
1057
+ runnerId: runnerId2,
1058
+ operatorId,
1059
+ runnerInstanceId,
1060
+ excludeRunnerInstanceId,
1061
+ daemonVersion,
1062
+ supervisorIdentity,
1063
+ timeoutMs = 6e4,
1064
+ pollMs = 1e3
1065
+ }) {
1066
+ if (!runnerInstanceId && !excludeRunnerInstanceId) throw new Error("runner instance identity proof unavailable");
1067
+ const deadline = Date.now() + timeoutMs;
1068
+ while (Date.now() < deadline) {
1069
+ try {
1070
+ const runners = await client.getRunnerStatus({ ...operatorId ? { operatorId } : {} });
1071
+ const runner = runners.find((item) => item?.runner_id === runnerId2 && item.status === "online" && (runnerInstanceId ? item.runner_meta?.runner_instance_id === runnerInstanceId : Boolean(item.runner_meta?.runner_instance_id) && item.runner_meta.runner_instance_id !== excludeRunnerInstanceId) && item.runner_meta?.daemon_version === daemonVersion && item.runner_meta?.supervisor_instance_id === supervisorIdentity.supervisorInstanceId && item.runner_meta?.supervisor_version === supervisorIdentity.supervisorVersion && supervisorIdentity.capabilities.every((value) => item.runner_meta?.supervisor_capabilities?.includes(value)) && Array.isArray(item.runner_meta?.available_agents) && item.runner_meta.available_agents.some((agent) => agent.agent === item.runner_meta.default_agent && agent.installed === true && agent.authenticated === true));
1072
+ if (runner) return runner;
1073
+ } catch {
1074
+ }
1075
+ await delay(pollMs);
1076
+ }
1077
+ throw new Error("authoritative child heartbeat attestation timed out");
1078
+ }
1079
+ async function finishPendingActivation({
1080
+ client,
1081
+ child,
1082
+ runtimeRoot,
1083
+ operatorId,
1084
+ runnerId: runnerId2,
1085
+ selfPath: selfPath2,
1086
+ packageVersion: packageVersion2,
1087
+ supervisorIdentity,
1088
+ waitForLocalRunner: waitForLocalRunner2,
1089
+ localStatus: localStatus2,
1090
+ waitForCloudRunner = waitForAuthoritativeRunnerHeartbeat,
1091
+ cloudTimeoutMs,
1092
+ cloudPollMs,
1093
+ stopChild: stopChild2,
1094
+ launchPreviousChild,
1095
+ log = () => {
1096
+ }
1097
+ }) {
1098
+ const pointer = runtimeRoot ? readActivation(runtimeRoot) : null;
1099
+ if (!pointer?.pending) return true;
1100
+ const runnerIdForAction = pointer.pending.runner_id || runnerId2;
1101
+ const operatorIdForAction = pointer.pending.operator_id || operatorId;
1102
+ if (pointer.pending.terminal_status === "failed") {
1103
+ try {
1104
+ await client.completeRunnerControl(pointer.pending.action_id, {
1105
+ runnerId: runnerIdForAction,
1106
+ ...operatorIdForAction ? { operatorId: operatorIdForAction } : {},
1107
+ ...supervisorIdentity,
1108
+ status: "failed",
1109
+ detail: pointer.pending.terminal_detail
1110
+ });
1111
+ acknowledgeActivationFailure(runtimeRoot, pointer);
1112
+ return true;
1113
+ } catch (error) {
1114
+ log(`activation failure acknowledgement remains pending: ${error instanceof Error ? error.message : String(error)}`);
1115
+ await stopChild2(child);
1116
+ return false;
1117
+ }
1118
+ }
1119
+ let attestation;
1120
+ try {
1121
+ attestation = attestCurrentSupervisor({ runtimeRoot, selfPath: selfPath2, version: packageVersion2 });
1122
+ if (!attestation.ok) throw new Error(attestation.detail);
1123
+ if (!await waitForLocalRunner2(child)) throw new Error("activated runner did not become locally ready");
1124
+ } catch (error) {
1125
+ let detail = `activation attestation failed: ${error instanceof Error ? error.message : String(error)}`.slice(0, 400);
1126
+ let rolledBack = null;
1127
+ try {
1128
+ rolledBack = rollbackActivation(runtimeRoot, pointer, detail);
1129
+ } catch (rollbackError) {
1130
+ detail = `${detail}; rollback pending: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`.slice(0, 400);
1131
+ }
1132
+ try {
1133
+ await client.completeRunnerControl(pointer.pending.action_id, {
1134
+ runnerId: runnerIdForAction,
1135
+ ...operatorIdForAction ? { operatorId: operatorIdForAction } : {},
1136
+ ...supervisorIdentity,
1137
+ status: "failed",
1138
+ detail
1139
+ });
1140
+ if (rolledBack) acknowledgeActivationFailure(runtimeRoot, rolledBack);
1141
+ } catch {
1142
+ } finally {
1143
+ await stopChild2(child);
1144
+ }
1145
+ return false;
1146
+ }
1147
+ let activatedRunnerInstanceId = null;
1148
+ try {
1149
+ const status = await localStatus2();
1150
+ activatedRunnerInstanceId = status?.runnerInstanceId || null;
1151
+ await waitForCloudRunner({
1152
+ client,
1153
+ runnerId: runnerIdForAction,
1154
+ operatorId: operatorIdForAction,
1155
+ runnerInstanceId: status?.runnerInstanceId,
1156
+ daemonVersion: `vo-mcp/${attestation.active.version}`,
1157
+ supervisorIdentity,
1158
+ timeoutMs: cloudTimeoutMs,
1159
+ pollMs: cloudPollMs
1160
+ });
1161
+ await client.completeRunnerControl(pointer.pending.action_id, {
1162
+ runnerId: runnerIdForAction,
1163
+ ...operatorIdForAction ? { operatorId: operatorIdForAction } : {},
1164
+ ...supervisorIdentity,
1165
+ status: "succeeded",
1166
+ detail: `attested new supervisor ${attestation.active.version} ${attestation.active.integrity}`
1167
+ });
1168
+ finalizeActivation(runtimeRoot, attestation.pointer);
1169
+ return true;
1170
+ } catch (error) {
1171
+ const failure = `activation acknowledgement failed: ${error instanceof Error ? error.message : String(error)}`.slice(0, 400);
1172
+ let retry;
1173
+ try {
1174
+ retry = recordActivationRetry(runtimeRoot, attestation.pointer, failure);
1175
+ } catch (retryError) {
1176
+ log(`activation retry could not be recorded: ${retryError instanceof Error ? retryError.message : String(retryError)}`);
1177
+ await stopChild2(child);
1178
+ return false;
1179
+ }
1180
+ if (retry.pending.ack_attempts < MAX_ACK_ATTEMPTS) {
1181
+ log(`activation acknowledgement pending (${retry.pending.ack_attempts}/${MAX_ACK_ATTEMPTS})`);
1182
+ await stopChild2(child);
1183
+ return false;
1184
+ }
1185
+ const previous = validateSlot(runtimeRoot, retry.previous);
1186
+ let detail = `${failure}; retry limit reached; previous runtime restored`.slice(0, 400);
1187
+ let rollbackChild = null;
1188
+ let rolledBack = null;
1189
+ try {
1190
+ if (!previous.ok) throw new Error(`previous runtime invalid: ${previous.detail}`, { cause: error });
1191
+ rolledBack = rollbackActivation(runtimeRoot, retry, detail);
1192
+ await stopChild2(child);
1193
+ rollbackChild = launchPreviousChild(previous.paths.entry);
1194
+ if (!await waitForLocalRunner2(rollbackChild)) throw new Error("previous runner did not become locally ready", { cause: error });
1195
+ const status = await localStatus2();
1196
+ await waitForCloudRunner({
1197
+ client,
1198
+ runnerId: runnerIdForAction,
1199
+ operatorId: operatorIdForAction,
1200
+ runnerInstanceId: status?.runnerInstanceId,
1201
+ excludeRunnerInstanceId: status?.runnerInstanceId ? void 0 : activatedRunnerInstanceId,
1202
+ daemonVersion: `vo-mcp/${retry.previous.version}`,
1203
+ supervisorIdentity,
1204
+ timeoutMs: cloudTimeoutMs,
1205
+ pollMs: cloudPollMs
1206
+ });
1207
+ } catch (rollbackError) {
1208
+ detail = `${detail}; rollback proof failed: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`.slice(0, 400);
1209
+ }
1210
+ try {
1211
+ await client.completeRunnerControl(retry.pending.action_id, {
1212
+ runnerId: runnerIdForAction,
1213
+ ...operatorIdForAction ? { operatorId: operatorIdForAction } : {},
1214
+ ...supervisorIdentity,
1215
+ status: "failed",
1216
+ detail
1217
+ });
1218
+ if (rolledBack) acknowledgeActivationFailure(runtimeRoot, rolledBack);
1219
+ } catch {
1220
+ }
1221
+ if (rollbackChild) await stopChild2(rollbackChild);
1222
+ else await stopChild2(child);
1223
+ return false;
1224
+ }
1225
+ }
1226
+
390
1227
  // src/runner/supervisor-child-env.mjs
391
1228
  import { hostname as systemHostname } from "node:os";
392
1229
 
@@ -567,23 +1404,24 @@ async function prepareSupervisorAuth({
567
1404
  }
568
1405
 
569
1406
  // src/runner/supervisor-credential-reader.mjs
570
- import { spawnSync as spawnSync2 } from "node:child_process";
571
- import { existsSync } from "node:fs";
572
- import { dirname, join } from "node:path";
1407
+ import { spawnSync as spawnSync3 } from "node:child_process";
1408
+ import { existsSync as existsSync4 } from "node:fs";
1409
+ import { dirname as dirname2, join as join3 } from "node:path";
573
1410
  import { fileURLToPath } from "node:url";
574
1411
  function defaultCredentialHelperPath(metaUrl = import.meta.url) {
575
- const moduleDir = dirname(fileURLToPath(metaUrl));
576
- const bundled = join(moduleDir, "supervisor-credential-helper.js");
577
- const source = join(moduleDir, "..", "supervisor-credential-helper.mjs");
578
- return existsSync(source) ? source : bundled;
1412
+ const moduleDir = dirname2(fileURLToPath(metaUrl));
1413
+ const bundled = join3(moduleDir, "supervisor-credential-helper.js");
1414
+ const source = join3(moduleDir, "..", "supervisor-credential-helper.mjs");
1415
+ return existsSync4(source) ? source : bundled;
579
1416
  }
580
1417
  function readStoredCredentialIsolated({
581
- spawn: spawn2 = spawnSync2,
1418
+ spawn: spawn2 = spawnSync3,
582
1419
  execPath = process.execPath,
583
1420
  helperPath = defaultCredentialHelperPath(),
1421
+ helperArgs = [],
584
1422
  env = process.env
585
1423
  } = {}) {
586
- const result = spawn2(execPath, [helperPath], {
1424
+ const result = spawn2(execPath, [helperPath, ...helperArgs], {
587
1425
  env,
588
1426
  encoding: "utf8",
589
1427
  stdio: ["ignore", "pipe", "pipe"],
@@ -606,8 +1444,11 @@ function readStoredCredentialIsolated({
606
1444
  var DEFAULT_CONTROL_PLANE_URL = "https://vo-control-plane-bzjphrajaq-uc.a.run.app";
607
1445
  var POLL_MS = 5e3;
608
1446
  var CHILD_START_MS = 1500;
609
- var childEntry = join2(dirname2(fileURLToPath2(import.meta.url)), "runner-cli.js");
610
- var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
1447
+ var SUPERVISOR_CAPABILITIES = ["bundled-runtime-slots-v1"];
1448
+ 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;
1449
+ var selfPath = fileURLToPath2(import.meta.url);
1450
+ var childEntry = join4(dirname3(selfPath), "runner-cli.js");
1451
+ var sleep = (ms) => new Promise((resolve3) => setTimeout(resolve3, ms));
611
1452
  var runnerId = resolveSupervisorRunnerId(process.env);
612
1453
  function packageVersion() {
613
1454
  try {
@@ -616,6 +1457,17 @@ function packageVersion() {
616
1457
  return "unknown";
617
1458
  }
618
1459
  }
1460
+ var requestedSupervisorInstanceId = UUID_RE2.test(String(process.env.VO_RUNNER_SUPERVISOR_INSTANCE_ID || "")) ? process.env.VO_RUNNER_SUPERVISOR_INSTANCE_ID : randomUUID3();
1461
+ var supervisorInstanceId = activationSupervisorInstanceId(
1462
+ runtimeRootFromEnv(process.env),
1463
+ requestedSupervisorInstanceId
1464
+ );
1465
+ var supervisorVersion = packageVersion();
1466
+ var supervisorControlIdentity = {
1467
+ supervisorInstanceId,
1468
+ supervisorVersion,
1469
+ capabilities: SUPERVISOR_CAPABILITIES
1470
+ };
619
1471
  function spawnChild(childEnv) {
620
1472
  return spawn(process.execPath, [childEntry], {
621
1473
  env: childEnv,
@@ -623,6 +1475,13 @@ function spawnChild(childEnv) {
623
1475
  windowsHide: true
624
1476
  });
625
1477
  }
1478
+ function spawnPreviousChild(entry, childEnv) {
1479
+ return spawn(process.execPath, [entry, "runner"], {
1480
+ env: childEnv,
1481
+ stdio: "inherit",
1482
+ windowsHide: true
1483
+ });
1484
+ }
626
1485
  async function localStatus() {
627
1486
  try {
628
1487
  const port = Number(process.env.VO_CODE_RUNNER_CONTROL_PORT || 7787);
@@ -638,7 +1497,8 @@ async function waitForLocalRunner(child) {
638
1497
  const deadline = Date.now() + 15e3;
639
1498
  while (Date.now() < deadline) {
640
1499
  if (child.exitCode !== null) return false;
641
- if ((await localStatus())?.running === true) return true;
1500
+ const status = await localStatus();
1501
+ if (status?.running === true && Number(status.pid) === Number(child.pid)) return true;
642
1502
  await sleep(500);
643
1503
  }
644
1504
  return false;
@@ -647,7 +1507,7 @@ async function stopChild(child) {
647
1507
  if (!child || child.exitCode !== null) return;
648
1508
  child.kill(process.platform === "win32" ? void 0 : "SIGTERM");
649
1509
  await Promise.race([
650
- new Promise((resolve) => child.once("exit", resolve)),
1510
+ new Promise((resolve3) => child.once("exit", resolve3)),
651
1511
  sleep(15e3)
652
1512
  ]);
653
1513
  if (child.exitCode === null) child.kill("SIGKILL");
@@ -660,14 +1520,43 @@ async function main() {
660
1520
  storedCredential: stored,
661
1521
  controlPlaneUrl
662
1522
  });
1523
+ Object.assign(childEnv, {
1524
+ VO_RUNNER_SUPERVISOR_INSTANCE_ID: supervisorInstanceId,
1525
+ VO_RUNNER_SUPERVISOR_VERSION: supervisorVersion,
1526
+ VO_RUNNER_SUPERVISOR_CAPABILITIES: SUPERVISOR_CAPABILITIES.join(",")
1527
+ });
663
1528
  const client = createControlPlaneClient({ baseUrl: controlPlaneUrl, env: clientEnv });
664
- let child = spawnChild(childEnv);
1529
+ const runtimeRoot = runtimeRootFromEnv(process.env);
1530
+ let child = null;
665
1531
  let stopping = false;
666
1532
  let handling = false;
667
1533
  const respawn = () => {
668
- if (!stopping && !handling && (!child || child.exitCode !== null)) child = spawnChild(childEnv);
1534
+ if (!stopping && !handling && (!child || child.exitCode !== null)) child = launchChild();
669
1535
  };
670
- child.on("exit", () => setTimeout(respawn, 2e3));
1536
+ const launchChild = () => {
1537
+ const next = spawnChild(childEnv);
1538
+ next.on("exit", () => setTimeout(respawn, 2e3));
1539
+ return next;
1540
+ };
1541
+ child = launchChild();
1542
+ if (!await finishPendingActivation({
1543
+ client,
1544
+ child,
1545
+ runtimeRoot,
1546
+ operatorId,
1547
+ runnerId,
1548
+ selfPath,
1549
+ packageVersion: supervisorVersion,
1550
+ supervisorIdentity: supervisorControlIdentity,
1551
+ waitForLocalRunner,
1552
+ localStatus,
1553
+ stopChild,
1554
+ launchPreviousChild: (entry) => spawnPreviousChild(entry, childEnv),
1555
+ log: (message) => console.error(`[vo-runner supervisor] ${message}`)
1556
+ })) {
1557
+ stopping = true;
1558
+ return;
1559
+ }
671
1560
  const shutdown = async () => {
672
1561
  if (stopping) return;
673
1562
  stopping = true;
@@ -681,7 +1570,11 @@ async function main() {
681
1570
  });
682
1571
  while (!stopping) {
683
1572
  try {
684
- const action = await client.pollRunnerControl({ runnerId, ...operatorId ? { operatorId } : {} });
1573
+ const action = await client.pollRunnerControl({
1574
+ runnerId,
1575
+ ...operatorId ? { operatorId } : {},
1576
+ ...supervisorControlIdentity
1577
+ });
685
1578
  if (!action) {
686
1579
  await sleep(POLL_MS);
687
1580
  continue;
@@ -692,6 +1585,7 @@ async function main() {
692
1585
  await client.completeRunnerControl(action.actionId, {
693
1586
  runnerId,
694
1587
  ...operatorId ? { operatorId } : {},
1588
+ ...supervisorControlIdentity,
695
1589
  status: "failed",
696
1590
  detail: `deferred safely: ${beforeStop.activeTasks} active task(s); retry when the runner is idle`
697
1591
  });
@@ -699,16 +1593,35 @@ async function main() {
699
1593
  continue;
700
1594
  }
701
1595
  await stopChild(child);
702
- const result = runHostMaintenance(action.kind, {
1596
+ const bundledAction = action.kind === "update" || action.kind === "reinstall";
1597
+ const result = bundledAction ? stageAndActivateBundledUpdate({
1598
+ runtimeRoot,
1599
+ packageSpec: `@algosuite/vo-mcp@${action.desired_package_version}`,
1600
+ expectedVersion: action.desired_package_version,
1601
+ expectedIntegrity: action.desired_package_integrity,
1602
+ action: {
1603
+ actionId: action.actionId,
1604
+ runnerId,
1605
+ operatorId: operatorId || "",
1606
+ supervisorInstanceId
1607
+ },
1608
+ env: process.env,
1609
+ force: action.kind === "reinstall"
1610
+ }) : runHostMaintenance(action.kind, {
703
1611
  env: clientEnv,
704
1612
  log: (message) => console.warn(`[vo-runner supervisor] ${message}`)
705
1613
  });
706
- child = spawnChild(childEnv);
1614
+ if (result.ok && result.handoff) {
1615
+ console.warn(`[vo-runner supervisor] activated ${result.active.version}; exiting for new-process attestation`);
1616
+ return;
1617
+ }
1618
+ child = launchChild();
707
1619
  await sleep(CHILD_START_MS);
708
1620
  if (child.exitCode !== null || !await waitForLocalRunner(child)) result.ok = false;
709
1621
  await client.completeRunnerControl(action.actionId, {
710
1622
  runnerId,
711
1623
  ...operatorId ? { operatorId } : {},
1624
+ ...supervisorControlIdentity,
712
1625
  status: result.ok ? "succeeded" : "failed",
713
1626
  detail: result.ok ? `runner ${packageVersion()} reconnected` : `maintenance exited ${result.status}${result.detail ? `: ${result.detail}` : ""}`
714
1627
  });