@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.
- package/dist/cli.js +6 -0
- package/dist/cli.js.map +2 -2
- package/dist/index.js +6 -0
- package/dist/index.js.map +2 -2
- package/dist/install-cli.js.map +2 -2
- package/dist/login-cli.js.map +2 -2
- package/dist/pair-cli.js.map +2 -2
- package/dist/runner-cli.js +183 -19
- package/dist/runner-cli.js.map +3 -3
- package/dist/runner-supervisor.js +890 -28
- package/dist/runner-supervisor.js.map +4 -4
- package/dist/supervisor-credential-helper.js +8 -2
- package/dist/supervisor-credential-helper.js.map +2 -2
- package/package.json +1 -1
|
@@ -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
|
|
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
|
-
|
|
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
|
|
622
|
-
import { existsSync as
|
|
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 =
|
|
627
|
-
const bundled =
|
|
628
|
-
const source =
|
|
629
|
-
return
|
|
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 =
|
|
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
|
|
661
|
-
var
|
|
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
|
-
|
|
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((
|
|
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
|
-
|
|
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 =
|
|
1534
|
+
if (!stopping && !handling && (!child || child.exitCode !== null)) child = launchChild();
|
|
720
1535
|
};
|
|
721
|
-
|
|
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({
|
|
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
|
|
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
|
-
|
|
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
|
});
|