@algosuite/vo-mcp 0.2.0-beta.11 → 0.2.0-beta.13

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) {
@@ -438,6 +491,739 @@ function runHostMaintenance(kind, {
438
491
  return { ok: status === 0, status, command: command.command, args: command.args, detail };
439
492
  }
440
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
+
441
1227
  // src/runner/supervisor-child-env.mjs
442
1228
  import { hostname as systemHostname } from "node:os";
443
1229
 
@@ -618,23 +1404,24 @@ async function prepareSupervisorAuth({
618
1404
  }
619
1405
 
620
1406
  // src/runner/supervisor-credential-reader.mjs
621
- import { spawnSync as spawnSync2 } from "node:child_process";
622
- import { existsSync as existsSync2 } from "node:fs";
623
- 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";
624
1410
  import { fileURLToPath } from "node:url";
625
1411
  function defaultCredentialHelperPath(metaUrl = import.meta.url) {
626
- const moduleDir = dirname(fileURLToPath(metaUrl));
627
- const bundled = join(moduleDir, "supervisor-credential-helper.js");
628
- const source = join(moduleDir, "..", "supervisor-credential-helper.mjs");
629
- return existsSync2(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;
630
1416
  }
631
1417
  function readStoredCredentialIsolated({
632
- spawn: spawn2 = spawnSync2,
1418
+ spawn: spawn2 = spawnSync3,
633
1419
  execPath = process.execPath,
634
1420
  helperPath = defaultCredentialHelperPath(),
1421
+ helperArgs = [],
635
1422
  env = process.env
636
1423
  } = {}) {
637
- const result = spawn2(execPath, [helperPath], {
1424
+ const result = spawn2(execPath, [helperPath, ...helperArgs], {
638
1425
  env,
639
1426
  encoding: "utf8",
640
1427
  stdio: ["ignore", "pipe", "pipe"],
@@ -657,8 +1444,11 @@ function readStoredCredentialIsolated({
657
1444
  var DEFAULT_CONTROL_PLANE_URL = "https://vo-control-plane-bzjphrajaq-uc.a.run.app";
658
1445
  var POLL_MS = 5e3;
659
1446
  var CHILD_START_MS = 1500;
660
- var childEntry = join2(dirname2(fileURLToPath2(import.meta.url)), "runner-cli.js");
661
- 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));
662
1452
  var runnerId = resolveSupervisorRunnerId(process.env);
663
1453
  function packageVersion() {
664
1454
  try {
@@ -667,6 +1457,17 @@ function packageVersion() {
667
1457
  return "unknown";
668
1458
  }
669
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
+ };
670
1471
  function spawnChild(childEnv) {
671
1472
  return spawn(process.execPath, [childEntry], {
672
1473
  env: childEnv,
@@ -674,6 +1475,13 @@ function spawnChild(childEnv) {
674
1475
  windowsHide: true
675
1476
  });
676
1477
  }
1478
+ function spawnPreviousChild(entry, childEnv) {
1479
+ return spawn(process.execPath, [entry, "runner"], {
1480
+ env: childEnv,
1481
+ stdio: "inherit",
1482
+ windowsHide: true
1483
+ });
1484
+ }
677
1485
  async function localStatus() {
678
1486
  try {
679
1487
  const port = Number(process.env.VO_CODE_RUNNER_CONTROL_PORT || 7787);
@@ -689,7 +1497,8 @@ async function waitForLocalRunner(child) {
689
1497
  const deadline = Date.now() + 15e3;
690
1498
  while (Date.now() < deadline) {
691
1499
  if (child.exitCode !== null) return false;
692
- 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;
693
1502
  await sleep(500);
694
1503
  }
695
1504
  return false;
@@ -698,7 +1507,7 @@ async function stopChild(child) {
698
1507
  if (!child || child.exitCode !== null) return;
699
1508
  child.kill(process.platform === "win32" ? void 0 : "SIGTERM");
700
1509
  await Promise.race([
701
- new Promise((resolve) => child.once("exit", resolve)),
1510
+ new Promise((resolve3) => child.once("exit", resolve3)),
702
1511
  sleep(15e3)
703
1512
  ]);
704
1513
  if (child.exitCode === null) child.kill("SIGKILL");
@@ -711,14 +1520,43 @@ async function main() {
711
1520
  storedCredential: stored,
712
1521
  controlPlaneUrl
713
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
+ });
714
1528
  const client = createControlPlaneClient({ baseUrl: controlPlaneUrl, env: clientEnv });
715
- let child = spawnChild(childEnv);
1529
+ const runtimeRoot = runtimeRootFromEnv(process.env);
1530
+ let child = null;
716
1531
  let stopping = false;
717
1532
  let handling = false;
718
1533
  const respawn = () => {
719
- if (!stopping && !handling && (!child || child.exitCode !== null)) child = spawnChild(childEnv);
1534
+ if (!stopping && !handling && (!child || child.exitCode !== null)) child = launchChild();
720
1535
  };
721
- 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
+ }
722
1560
  const shutdown = async () => {
723
1561
  if (stopping) return;
724
1562
  stopping = true;
@@ -732,7 +1570,11 @@ async function main() {
732
1570
  });
733
1571
  while (!stopping) {
734
1572
  try {
735
- const action = await client.pollRunnerControl({ runnerId, ...operatorId ? { operatorId } : {} });
1573
+ const action = await client.pollRunnerControl({
1574
+ runnerId,
1575
+ ...operatorId ? { operatorId } : {},
1576
+ ...supervisorControlIdentity
1577
+ });
736
1578
  if (!action) {
737
1579
  await sleep(POLL_MS);
738
1580
  continue;
@@ -743,6 +1585,7 @@ async function main() {
743
1585
  await client.completeRunnerControl(action.actionId, {
744
1586
  runnerId,
745
1587
  ...operatorId ? { operatorId } : {},
1588
+ ...supervisorControlIdentity,
746
1589
  status: "failed",
747
1590
  detail: `deferred safely: ${beforeStop.activeTasks} active task(s); retry when the runner is idle`
748
1591
  });
@@ -750,16 +1593,35 @@ async function main() {
750
1593
  continue;
751
1594
  }
752
1595
  await stopChild(child);
753
- 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, {
754
1611
  env: clientEnv,
755
1612
  log: (message) => console.warn(`[vo-runner supervisor] ${message}`)
756
1613
  });
757
- 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();
758
1619
  await sleep(CHILD_START_MS);
759
1620
  if (child.exitCode !== null || !await waitForLocalRunner(child)) result.ok = false;
760
1621
  await client.completeRunnerControl(action.actionId, {
761
1622
  runnerId,
762
1623
  ...operatorId ? { operatorId } : {},
1624
+ ...supervisorControlIdentity,
763
1625
  status: result.ok ? "succeeded" : "failed",
764
1626
  detail: result.ok ? `runner ${packageVersion()} reconnected` : `maintenance exited ${result.status}${result.detail ? `: ${result.detail}` : ""}`
765
1627
  });