@algosuite/vo-mcp 0.2.0-beta.7 → 0.2.0-beta.71

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.
@@ -0,0 +1,3780 @@
1
+ import { createRequire as __cr } from 'module'; const require = __cr(import.meta.url);
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __esm = (fn, res, err) => function __init() {
5
+ if (err) throw err[0];
6
+ try {
7
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
8
+ } catch (e) {
9
+ throw err = [e], e;
10
+ }
11
+ };
12
+ var __export = (target, all) => {
13
+ for (var name in all)
14
+ __defProp(target, name, { get: all[name], enumerable: true });
15
+ };
16
+
17
+ // src/runner/control-plane-auth-stub.mjs
18
+ var control_plane_auth_stub_exports = {};
19
+ __export(control_plane_auth_stub_exports, {
20
+ getFirebaseAuth: () => getFirebaseAuth
21
+ });
22
+ async function getFirebaseAuth() {
23
+ throw new Error(
24
+ "vo-mcp runner: no control-plane credential. Run `vo-mcp login` first \u2014 the runner authenticates with your stored vo_credential (or set VO_CONTROL_PLANE_ADMIN_TOKEN)."
25
+ );
26
+ }
27
+ var init_control_plane_auth_stub = __esm({
28
+ "src/runner/control-plane-auth-stub.mjs"() {
29
+ }
30
+ });
31
+
32
+ // src/runner-supervisor.mjs
33
+ import { spawn } from "node:child_process";
34
+ import { randomUUID as randomUUID4 } from "node:crypto";
35
+ import { createRequire } from "node:module";
36
+ import { fileURLToPath as fileURLToPath3 } from "node:url";
37
+ import { dirname as dirname4, join as join5 } from "node:path";
38
+
39
+ // ../../scripts/virtual-office/code-runner/installation-token.mjs
40
+ var READ_TOKEN_TIMEOUT_MS = 15e3;
41
+ async function fetchInstallationToken({ req, required = false, readOnly = false, repo = null }) {
42
+ const fail = (reason) => {
43
+ if (required) throw new Error(`installation-token required: ${reason}`);
44
+ return null;
45
+ };
46
+ try {
47
+ const res = await req(
48
+ "POST",
49
+ "/api/v1/github/installation-token",
50
+ readOnly ? { scope: "read", ...repo ? { repo } : {} } : {},
51
+ readOnly ? { timeoutMs: READ_TOKEN_TIMEOUT_MS } : {}
52
+ );
53
+ if (!res.ok) return fail(`HTTP ${res.status}`);
54
+ const json = await res.json();
55
+ if (!json || !json.token) return fail("missing token");
56
+ if (readOnly && json.scope !== "read") return fail("control plane did not confirm a read-only grant");
57
+ return { token: json.token, expiresAt: json.expires_at || null, ...typeof json.ci_readable === "boolean" ? { ciReadable: json.ci_readable } : {} };
58
+ } catch (err) {
59
+ if (required) throw err;
60
+ return null;
61
+ }
62
+ }
63
+
64
+ // ../../scripts/virtual-office/code-runner/control-plane-heartbeat-body.mjs
65
+ function buildRunnerHeartbeatBody({
66
+ runnerId: runnerId2,
67
+ runnerInstanceId,
68
+ operatorId,
69
+ uptimeSec,
70
+ activeTasks,
71
+ maxConcurrency,
72
+ effectiveConcurrency,
73
+ measuredTaskSlots,
74
+ measuredCpuSlots,
75
+ measuredMemorySlots,
76
+ version,
77
+ daemonVersion,
78
+ nodeVersion,
79
+ defaultAgent,
80
+ supervisorInstanceId: supervisorInstanceId2,
81
+ supervisorVersion: supervisorVersion2,
82
+ supervisorCapabilities,
83
+ servedRepos,
84
+ servedOperators,
85
+ availableAgents,
86
+ accountUsage,
87
+ availableLocalModels,
88
+ supportedTaskKinds,
89
+ prepared_job_shadow: preparedJobShadow
90
+ } = {}) {
91
+ const body = { runner_id: runnerId2, ...preparedJobShadow ? { prepared_job_shadow: preparedJobShadow } : {} };
92
+ if (runnerInstanceId) body.runner_instance_id = runnerInstanceId;
93
+ if (operatorId) body.operator_id = operatorId;
94
+ if (typeof uptimeSec === "number") body.uptime_sec = uptimeSec;
95
+ if (typeof activeTasks === "number") body.active_tasks = activeTasks;
96
+ if (typeof maxConcurrency === "number") body.max_concurrency = maxConcurrency;
97
+ if (typeof effectiveConcurrency === "number") body.effective_concurrency = effectiveConcurrency;
98
+ if (typeof measuredTaskSlots === "number") body.measured_task_slots = measuredTaskSlots;
99
+ if (typeof measuredCpuSlots === "number") body.measured_cpu_slots = measuredCpuSlots;
100
+ if (typeof measuredMemorySlots === "number") body.measured_memory_slots = measuredMemorySlots;
101
+ if (version) body.version = version;
102
+ if (daemonVersion) body.daemon_version = daemonVersion;
103
+ if (nodeVersion) body.node_version = nodeVersion;
104
+ if (defaultAgent) body.default_agent = defaultAgent;
105
+ if (supervisorInstanceId2) body.supervisor_instance_id = supervisorInstanceId2;
106
+ if (supervisorVersion2) body.supervisor_version = supervisorVersion2;
107
+ if (Array.isArray(supervisorCapabilities) && supervisorCapabilities.length > 0) {
108
+ body.supervisor_capabilities = supervisorCapabilities;
109
+ }
110
+ if (Array.isArray(servedRepos) && servedRepos.length > 0) body.served_repos = servedRepos;
111
+ if (Array.isArray(servedOperators) && servedOperators.length > 0) {
112
+ body.served_operator_ids = servedOperators;
113
+ }
114
+ if (Array.isArray(availableAgents) && availableAgents.length > 0) {
115
+ body.available_agents = availableAgents;
116
+ }
117
+ if (Array.isArray(accountUsage) && accountUsage.length > 0) {
118
+ body.account_usage = accountUsage;
119
+ }
120
+ if (Array.isArray(availableLocalModels) && availableLocalModels.length > 0) {
121
+ body.available_local_models = availableLocalModels;
122
+ }
123
+ if (Array.isArray(supportedTaskKinds) && supportedTaskKinds.length > 0) {
124
+ body.supported_task_kinds = supportedTaskKinds;
125
+ }
126
+ return body;
127
+ }
128
+
129
+ // ../../scripts/virtual-office/code-runner/control-plane-promote.mjs
130
+ async function promoteDraftPrRequest(req, prNumber, automationContext, onUnauthorized = () => {
131
+ }) {
132
+ const res = await req("POST", "/api/v1/admin/pr/promote-draft", { prNumber, automationContext }, { timeoutMs: 6e4 });
133
+ if (res.status === 401) {
134
+ onUnauthorized();
135
+ throw new Error("promote-draft unauthorized (401)");
136
+ }
137
+ const json = await res.json().catch(() => ({}));
138
+ if (res.ok && json?.ok === true) {
139
+ return {
140
+ status: json.promoted === true ? json.auto_merge_disarmed === true ? "promoted (auto-merge disarmed)" : "promoted" : json.already_ready === true ? "already_ready" : "unchanged",
141
+ headSha: typeof json.head_sha === "string" ? json.head_sha : null,
142
+ reason: typeof json.blocked_reason === "string" ? json.blocked_reason : null
143
+ };
144
+ }
145
+ const code = typeof json?.error === "string" ? json.error : null;
146
+ const err = new Error(`promote-draft failed: HTTP ${res.status}${code ? ` (${code})` : ""}${json?.reason ? ` \u2014 ${json.reason}` : ""}`);
147
+ err.status = res.status;
148
+ err.code = code;
149
+ throw err;
150
+ }
151
+
152
+ // ../../scripts/virtual-office/code-runner/control-plane-task-list.mjs
153
+ var PAGE_SIZE = 500;
154
+ async function listAllPrOpenedTasks(request) {
155
+ const tasks = [];
156
+ let beforeCreatedAt = "";
157
+ let beforeId = "";
158
+ for (; ; ) {
159
+ const params = new URLSearchParams({
160
+ status: "pr_opened",
161
+ limit: String(PAGE_SIZE),
162
+ runner_adoption: "1"
163
+ });
164
+ if (beforeCreatedAt) {
165
+ params.set("before_created_at", beforeCreatedAt);
166
+ params.set("before_id", beforeId);
167
+ }
168
+ const res = await request("GET", `/api/v1/code-task?${params}`);
169
+ if (!res.ok) throw new Error(`listPrOpenedTasks failed: HTTP ${res.status}`);
170
+ const json = await res.json();
171
+ const page = Array.isArray(json?.tasks) ? json.tasks : [];
172
+ tasks.push(...page);
173
+ if (page.length < PAGE_SIZE) return tasks;
174
+ const last = page.at(-1);
175
+ if (!last?.created_at || !last?.code_task_id) {
176
+ throw new Error("listPrOpenedTasks pagination cursor missing");
177
+ }
178
+ beforeCreatedAt = last.created_at;
179
+ beforeId = last.code_task_id;
180
+ }
181
+ }
182
+
183
+ // ../../scripts/virtual-office/code-runner/control-plane-resume.mjs
184
+ async function resumeCodeTaskRequest(req, taskId, { automaticRateLimit = false, automaticContinuation = false } = {}, onUnauthorized = () => {
185
+ }) {
186
+ const res = await req(
187
+ "POST",
188
+ `/api/v1/code-task/${encodeURIComponent(taskId)}/resume`,
189
+ automaticRateLimit ? { automatic_rate_limit: true } : automaticContinuation ? { automatic_continuation: true } : {}
190
+ );
191
+ if (res.status === 401) {
192
+ onUnauthorized();
193
+ throw new Error("resume unauthorized (401)");
194
+ }
195
+ if (!res.ok) {
196
+ let code = null;
197
+ try {
198
+ const body = await res.json();
199
+ code = typeof body?.error === "string" ? body.error : null;
200
+ } catch {
201
+ }
202
+ const err = new Error(`resume failed: HTTP ${res.status}${code ? ` (${code})` : ""}`);
203
+ err.status = res.status;
204
+ err.code = code;
205
+ throw err;
206
+ }
207
+ const json = await res.json();
208
+ const task = json && json.task ? json.task : null;
209
+ if (task && typeof json.deduplicated === "boolean") Object.defineProperty(task, "deduplicated", { value: json.deduplicated, enumerable: false });
210
+ return task;
211
+ }
212
+
213
+ // ../../scripts/virtual-office/code-runner/control-plane-autonomous-admission.mjs
214
+ function makeAutonomousDispatchAdmissionClient(req, timeoutMs, onUnauthorized = () => {
215
+ }) {
216
+ return {
217
+ async reserveAutonomousDispatchBudget({ requestedBudgetUsd, reservationId, occurrenceKey }) {
218
+ const res = await req("POST", "/api/v1/autonomous-dispatch/admission", {
219
+ requested_budget_usd: requestedBudgetUsd,
220
+ reservation_id: reservationId,
221
+ dispatch_occurrence_key: occurrenceKey
222
+ }, { timeoutMs });
223
+ if (res.status === 401) {
224
+ onUnauthorized();
225
+ throw new Error("autonomous dispatch admission unauthorized (401)");
226
+ }
227
+ if (!res.ok) throw new Error(`autonomous dispatch admission failed: HTTP ${res.status}`);
228
+ const body = await res.json();
229
+ return {
230
+ allowed: body?.allowed === true,
231
+ reason: typeof body?.reason === "string" ? body.reason : ""
232
+ };
233
+ },
234
+ async releaseAutonomousDispatchBudget(reservationId) {
235
+ const res = await req("POST", "/api/v1/autonomous-dispatch/reservation/release", {
236
+ reservation_id: reservationId
237
+ }, { timeoutMs });
238
+ if (res.status === 401) {
239
+ onUnauthorized();
240
+ throw new Error("autonomous dispatch release unauthorized (401)");
241
+ }
242
+ if (!res.ok) throw new Error(`autonomous dispatch release failed: HTTP ${res.status}`);
243
+ return true;
244
+ }
245
+ };
246
+ }
247
+
248
+ // ../../scripts/virtual-office/code-runner/control-plane-merge.mjs
249
+ async function mergeVerifiedPrRequest(req, prNumber, automationContext, onUnauthorized) {
250
+ const res = await req(
251
+ "POST",
252
+ "/api/v1/admin/pr/merge",
253
+ { prNumber, automationContext },
254
+ { timeoutMs: 12e4 }
255
+ );
256
+ if (res.status === 401) {
257
+ onUnauthorized();
258
+ throw new Error("gated merge unauthorized (401)");
259
+ }
260
+ const json = await res.json().catch(() => ({}));
261
+ if (res.ok && json?.ok === true) {
262
+ const result = json?.result && typeof json.result === "object" ? json.result : {};
263
+ const status = result.merged === true || result.status === "merged" ? "merged" : result.status === "auto-merge-enabled" || String(result.action || "").includes("auto-merge") ? "queued" : "accepted";
264
+ return {
265
+ status,
266
+ detail: typeof result.detail === "string" ? result.detail : null,
267
+ actionReceiptId: typeof json.action_receipt_id === "string" ? json.action_receipt_id : null
268
+ };
269
+ }
270
+ const captureStoreRetry = json?.action_status === "not_attempted" && (json?.error === "capture_preflight_unavailable" || json?.error === "decision_outcome_intent_failed");
271
+ if (res.status === 503 && (json?.error === "verify_unavailable" || json?.error === "merge_unavailable" || captureStoreRetry)) {
272
+ return { status: "retry", reason: json.reason || json.message || json.error || "verification unavailable" };
273
+ }
274
+ return {
275
+ status: "blocked",
276
+ reason: json?.reason || json?.message || json?.error || `HTTP ${res.status}`,
277
+ actionReceiptId: typeof json?.action_receipt_id === "string" ? json.action_receipt_id : null
278
+ };
279
+ }
280
+
281
+ // ../../scripts/virtual-office/code-runner/control-plane-weekly-tokens.mjs
282
+ async function postWeeklyTokensRequest(taskReq, { operatorId, runnerId: runnerId2, tokens, claudeWeeklyPct, claudeWeeklyResetsAt }, onUnauthorized = () => {
283
+ }) {
284
+ const body = {
285
+ operator_id: operatorId,
286
+ runner_id: runnerId2,
287
+ input_tokens: tokens.input_tokens,
288
+ output_tokens: tokens.output_tokens,
289
+ cache_creation_tokens: tokens.cache_creation_tokens,
290
+ cache_read_tokens: tokens.cache_read_tokens
291
+ };
292
+ if (typeof claudeWeeklyPct === "number") {
293
+ body.claude_weekly_pct = claudeWeeklyPct;
294
+ }
295
+ if (claudeWeeklyResetsAt !== void 0) {
296
+ body.claude_weekly_resets_at = claudeWeeklyResetsAt;
297
+ }
298
+ const res = await taskReq("POST", "/api/v1/weekly-tokens", body);
299
+ if (res.status === 401) {
300
+ onUnauthorized();
301
+ throw new Error("weekly-tokens unauthorized (401)");
302
+ }
303
+ if (!res.ok) throw new Error(`weekly-tokens failed: HTTP ${res.status}`);
304
+ return true;
305
+ }
306
+
307
+ // ../../scripts/virtual-office/code-runner/control-plane-telemetry-relay.mjs
308
+ var TELEMETRY_RELAY_TIMEOUT_MS = 3e4;
309
+ async function relayTelemetryEventsRequest(req, { events, source }, onUnauthorized = () => {
310
+ }) {
311
+ const res = await req("POST", "/api/v1/telemetry/relay", { events, ...source ? { source } : {} }, {
312
+ timeoutMs: TELEMETRY_RELAY_TIMEOUT_MS
313
+ });
314
+ if (res.status === 401) onUnauthorized();
315
+ let body = null;
316
+ try {
317
+ body = await res.json();
318
+ } catch {
319
+ body = null;
320
+ }
321
+ return { status: res.status, body };
322
+ }
323
+
324
+ // ../../scripts/virtual-office/code-runner/claim-gate-notice.mjs
325
+ var REASON_HELP = {
326
+ daemon_version_below_floor: "this daemon is older than the approved release target \u2014 it idles until the governed updater brings it current (operator override: VO_RUNNER_CLAIM_MIN_DAEMON_VERSION / VO_RUNNER_CLAIM_VERSION_GATE=off on the control plane)",
327
+ daemon_version_unreported: "this daemon reports no parseable version in its heartbeat \u2014 too old for the updater to manage, so it may not claim work",
328
+ no_fresh_heartbeat: "the control plane has no fresh heartbeat from this runner \u2014 claims resume once heartbeats land",
329
+ runner_denylisted: "this runner id is on the operator quarantine list (VO_RUNNER_CLAIM_DENYLIST)"
330
+ };
331
+ function describeClaimGate(gate) {
332
+ if (!gate || gate.allowed !== false) return null;
333
+ const reason = String(gate.reason || "denied");
334
+ const floor = gate.floor_version ? ` (floor ${gate.floor_version})` : "";
335
+ return `claim gate: DENIED \u2014 ${reason}${floor}: ${(Object.hasOwn(REASON_HELP, reason) ? REASON_HELP[reason] : null) ?? "the control plane refused this runner's claims"}`;
336
+ }
337
+ function makeClaimGateNotice({ log = () => {
338
+ } } = {}) {
339
+ let last = null;
340
+ let current = null;
341
+ return {
342
+ current: () => current,
343
+ observe(json) {
344
+ const gate = json && typeof json === "object" ? json.claim_gate : null;
345
+ const denied = gate && gate.allowed === false ? gate : null;
346
+ current = denied ? { ...denied, observed_at: (/* @__PURE__ */ new Date()).toISOString() } : null;
347
+ const signature = denied ? `${denied.reason}|${denied.floor_version ?? ""}` : null;
348
+ if (signature === last) return;
349
+ if (denied) log(describeClaimGate(denied));
350
+ else if (last !== null) log("claim gate: allowed again \u2014 this runner may claim work");
351
+ last = signature;
352
+ }
353
+ };
354
+ }
355
+
356
+ // ../../scripts/virtual-office/code-runner/control-plane-knowledge-context.mjs
357
+ var MIN_KNOWLEDGE_CONTEXT_TIMEOUT_MS = 15e3;
358
+ var KNOWLEDGE_CONTEXT_QUERY_MAX_CHARS = 2e3;
359
+ var RETRY_DELAYS_MS = [2e3, 6e3];
360
+ var MAX_ATTEMPTS = RETRY_DELAYS_MS.length + 1;
361
+ function canonicalizeKnowledgeContextQuery(value) {
362
+ return typeof value === "string" ? value.trimStart().slice(0, KNOWLEDGE_CONTEXT_QUERY_MAX_CHARS) : "";
363
+ }
364
+ function isRetryableStatus(status) {
365
+ return status >= 500 && status <= 599;
366
+ }
367
+ function defaultSleep(ms) {
368
+ return new Promise((resolve5) => {
369
+ setTimeout(resolve5, ms);
370
+ });
371
+ }
372
+ async function getTaskKnowledgeContextRequest(req, taskId, { query, knowledgeRequestId } = {}, {
373
+ taskRequestTimeoutMs,
374
+ invalidateToken = () => {
375
+ },
376
+ sleep: sleep2 = defaultSleep,
377
+ log = () => {
378
+ }
379
+ } = {}) {
380
+ const body = {};
381
+ const canonicalQuery = canonicalizeKnowledgeContextQuery(query);
382
+ if (canonicalQuery.trim()) body.query = canonicalQuery;
383
+ if (typeof knowledgeRequestId === "string" && knowledgeRequestId) body.knowledge_request_id = knowledgeRequestId;
384
+ const path2 = `/api/v1/code-task/${encodeURIComponent(taskId)}/knowledge-context`;
385
+ const timeoutMs = Math.max(Number(taskRequestTimeoutMs) || 0, MIN_KNOWLEDGE_CONTEXT_TIMEOUT_MS);
386
+ for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt += 1) {
387
+ let res;
388
+ let cause;
389
+ try {
390
+ res = await req("POST", path2, body, { timeoutMs });
391
+ } catch (err) {
392
+ cause = err;
393
+ }
394
+ if (!cause) {
395
+ if (res.status === 401) {
396
+ invalidateToken();
397
+ throw new Error("knowledge-context unauthorized (401)");
398
+ }
399
+ if (res.status === 404) return null;
400
+ if (res.ok) return res.json();
401
+ if (!isRetryableStatus(res.status)) {
402
+ throw new Error(`knowledge-context failed: HTTP ${res.status}`);
403
+ }
404
+ cause = new Error(`knowledge-context failed: HTTP ${res.status}`);
405
+ }
406
+ if (attempt === MAX_ATTEMPTS) throw cause;
407
+ const delayMs = RETRY_DELAYS_MS[attempt - 1];
408
+ log(`knowledge-context attempt ${attempt}/${MAX_ATTEMPTS} failed (${cause.message}); retrying in ${delayMs}ms`);
409
+ await sleep2(delayMs);
410
+ }
411
+ throw new Error("knowledge-context retry loop exited unexpectedly");
412
+ }
413
+
414
+ // ../../scripts/virtual-office/code-runner/control-plane-prepared-job.mjs
415
+ var PREPARED_JOB_ENV_QUERY_KEYS = [
416
+ "VO_CODE_RUNNER_NO_WEB",
417
+ "VO_CODE_RUNNER_NO_WORKFLOW",
418
+ "VO_CODE_RUNNER_NO_CONSENSUS",
419
+ "VO_CODE_RUNNER_PERMISSION_MODE",
420
+ "VO_CODE_RUNNER_DEFAULT_BUDGET_USD",
421
+ "VO_CODE_RUNNER_META_REASONING_EFFORT",
422
+ "VO_CODE_RUNNER_ALLOW_MISSING_KNOWLEDGE_CONTEXT",
423
+ "VO_ENABLE_CONTEXT7"
424
+ ];
425
+ var PREPARED_JOB_ENV_VALUE_MAX = 64;
426
+ function preparedJobQuery({ agent = "claude", env = {} } = {}) {
427
+ const params = new URLSearchParams();
428
+ params.set("agent", String(agent));
429
+ const sent = [];
430
+ const dropped = [];
431
+ for (const key of PREPARED_JOB_ENV_QUERY_KEYS) {
432
+ const raw = env?.[key];
433
+ if (typeof raw !== "string" || raw.length === 0) continue;
434
+ if (raw.length > PREPARED_JOB_ENV_VALUE_MAX) {
435
+ dropped.push(key);
436
+ continue;
437
+ }
438
+ params.set(key, raw);
439
+ sent.push(key);
440
+ }
441
+ return { query: params.toString(), sent, dropped };
442
+ }
443
+ async function refusalCode(res) {
444
+ try {
445
+ const body = await res.json();
446
+ return typeof body?.error === "string" && body.error ? body.error : null;
447
+ } catch {
448
+ return null;
449
+ }
450
+ }
451
+ async function getPreparedJobRequest(req, taskId, options = {}, invalidateToken = () => {
452
+ }) {
453
+ const { agent = "claude", env = {}, timeoutMs = 15e3 } = options;
454
+ const { query, sent, dropped } = preparedJobQuery({ agent, env });
455
+ const envMeta = { envSent: sent, envDropped: dropped };
456
+ if (typeof taskId !== "string" || taskId.length === 0) {
457
+ return { ok: false, reason: "missing_task_id", status: 0, ...envMeta };
458
+ }
459
+ const path2 = `/api/v1/code-task/${encodeURIComponent(taskId)}/prepared-job?${query}`;
460
+ let res;
461
+ try {
462
+ res = await req("GET", path2, void 0, { timeoutMs });
463
+ } catch (err) {
464
+ return { ok: false, reason: `transport: ${err?.message || String(err)}`, status: 0, ...envMeta };
465
+ }
466
+ if (res?.status === 401) {
467
+ try {
468
+ invalidateToken();
469
+ } catch {
470
+ }
471
+ return { ok: false, reason: "unauthorized", status: 401, ...envMeta };
472
+ }
473
+ if (!res?.ok) {
474
+ const code = await refusalCode(res);
475
+ return { ok: false, reason: code || `http_${res?.status ?? "unknown"}`, status: res?.status ?? 0, ...envMeta };
476
+ }
477
+ let body;
478
+ try {
479
+ body = await res.json();
480
+ } catch (err) {
481
+ return { ok: false, reason: `unreadable_body: ${err?.message || String(err)}`, status: res.status, ...envMeta };
482
+ }
483
+ const job = body?.prepared_job;
484
+ if (!job || typeof job !== "object") {
485
+ return { ok: false, reason: "no_prepared_job_in_body", status: res.status, ...envMeta };
486
+ }
487
+ return {
488
+ ok: true,
489
+ job,
490
+ composition: body?.composition && typeof body.composition === "object" ? body.composition : {},
491
+ ...envMeta
492
+ };
493
+ }
494
+
495
+ // ../../scripts/virtual-office/code-runner/control-plane-client.mjs
496
+ var cachedFirebaseToken = null;
497
+ var ClaimAuthorityChangedError = class extends Error {
498
+ constructor() {
499
+ super("code-task claim authority changed");
500
+ this.name = "ClaimAuthorityChangedError";
501
+ this.code = "code_task_claim_authority_changed";
502
+ }
503
+ };
504
+ async function resolveBearer(env) {
505
+ const adminToken = env.VO_CONTROL_PLANE_ADMIN_TOKEN;
506
+ if (adminToken) return adminToken;
507
+ if (cachedFirebaseToken) return cachedFirebaseToken;
508
+ const { getFirebaseAuth: getFirebaseAuth2 } = await Promise.resolve().then(() => (init_control_plane_auth_stub(), control_plane_auth_stub_exports));
509
+ const auth = await getFirebaseAuth2({ env });
510
+ if (!auth || !auth.idToken) {
511
+ throw new Error(
512
+ "no control-plane credential: set VO_CONTROL_PLANE_ADMIN_TOKEN, or SMOKE_EMAIL/SMOKE_PASSWORD/SMOKE_API_KEY"
513
+ );
514
+ }
515
+ cachedFirebaseToken = auth.idToken;
516
+ return cachedFirebaseToken;
517
+ }
518
+ function createControlPlaneClient({
519
+ baseUrl,
520
+ env = process.env,
521
+ fetchImpl = fetch,
522
+ heartbeatTimeoutMs = Math.min(
523
+ Math.max(Number(env.VO_CODE_RUNNER_HEARTBEAT_TIMEOUT_MS) || 15e3, 1e3),
524
+ 6e4
525
+ ),
526
+ taskRequestTimeoutMs = Math.min(
527
+ Math.max(Number(env.VO_CODE_RUNNER_TASK_REQUEST_TIMEOUT_MS) || 5e3, 100),
528
+ 6e4
529
+ ),
530
+ runnerId: runnerId2,
531
+ runnerInstanceId,
532
+ sleep: sleep2
533
+ } = {}) {
534
+ const resolvedBaseUrl = baseUrl ?? env.VO_CONTROL_PLANE_URL ?? "";
535
+ if (!resolvedBaseUrl) throw new Error("VO_CONTROL_PLANE_URL is required for the code-runner daemon");
536
+ const root = resolvedBaseUrl.replace(/\/+$/, "");
537
+ const claimOccurrences = /* @__PURE__ */ new Map();
538
+ async function req(method, path2, body, { timeoutMs } = {}) {
539
+ const bearer = await resolveBearer(env);
540
+ const controller = timeoutMs ? new AbortController() : null;
541
+ let timeoutId;
542
+ const request = Promise.resolve(fetchImpl(`${root}${path2}`, {
543
+ method,
544
+ headers: {
545
+ "content-type": "application/json",
546
+ authorization: `Bearer ${bearer}`
547
+ },
548
+ body: body === void 0 ? void 0 : JSON.stringify(body),
549
+ ...controller ? { signal: controller.signal } : {}
550
+ }));
551
+ if (!timeoutMs) return request;
552
+ const timeout = new Promise((_, reject) => {
553
+ timeoutId = setTimeout(() => {
554
+ controller.abort();
555
+ reject(new Error(`control-plane ${path2} timed out after ${timeoutMs}ms`));
556
+ }, timeoutMs);
557
+ });
558
+ try {
559
+ return await Promise.race([request, timeout]);
560
+ } finally {
561
+ clearTimeout(timeoutId);
562
+ }
563
+ }
564
+ const taskReq = (method, path2, body, options = {}) => req(method, path2, body, { timeoutMs: taskRequestTimeoutMs, ...options });
565
+ const claimGate = makeClaimGateNotice({ log: (m) => console.warn(`[code-runner ${(/* @__PURE__ */ new Date()).toISOString()}] ${m}`) });
566
+ return {
567
+ getClaimGate: () => claimGate.current(),
568
+ // last DENIED claim-gate verdict (null when allowed) — for /status + tests
569
+ ...makeAutonomousDispatchAdmissionClient(
570
+ req,
571
+ taskRequestTimeoutMs,
572
+ () => {
573
+ cachedFirebaseToken = null;
574
+ }
575
+ ),
576
+ /**
577
+ * Claim the next pending task. Returns the task or null (empty queue).
578
+ * `repos` (optional `owner/name` list) and `operatorIds` (optional
579
+ * `operator_id` list) scope the claim so this daemon only picks up tasks it
580
+ * serves — the control-plane filters by both (logical AND), so another
581
+ * operator's task never lands on (or bills) this machine.
582
+ */
583
+ async claim(runnerId3, repos, operatorIds, session = {}) {
584
+ const body = { runner_id: runnerId3 };
585
+ if (Array.isArray(repos) && repos.length > 0) body.repos = repos;
586
+ if (Array.isArray(operatorIds) && operatorIds.length > 0) body.operator_ids = operatorIds;
587
+ if (session.runnerInstanceId) {
588
+ body.runner_instance_id = session.runnerInstanceId;
589
+ body.runner_progress_protocol_version = 2;
590
+ }
591
+ if (session.runnerInstanceId && session.reconcileStale) body.reconcile_stale = true;
592
+ if (session.defaultAgent) body.default_agent = session.defaultAgent;
593
+ if (Array.isArray(session.availableAgents)) {
594
+ body.available_agents = session.availableAgents.filter((entry) => entry?.installed === true && entry?.authenticated === true).map((entry) => entry.agent);
595
+ }
596
+ const res = await taskReq("POST", "/api/v1/code-task/claim", body);
597
+ if (res.status === 401) {
598
+ cachedFirebaseToken = null;
599
+ throw new Error("claim unauthorized (401)");
600
+ }
601
+ if (!res.ok) throw new Error(`claim failed: HTTP ${res.status}`);
602
+ const json = await res.json();
603
+ claimGate.observe(json);
604
+ const task = json && json.task ? json.task : null;
605
+ if (task?.claim_occurrence_id) claimOccurrences.set(task.code_task_id, task.claim_occurrence_id);
606
+ return task;
607
+ },
608
+ /**
609
+ * Enqueue a new code-task (used by the PR watcher to auto-dispatch a CI fix).
610
+ * Server derives operator/tenant from the daemon's authenticated principal.
611
+ * Returns the created task, or throws on a non-2xx response.
612
+ */
613
+ async enqueueCodeTask({ repo, prompt, max_budget_usd, max_turns, dispatch_mode, tier, agent, model, dispatch_occurrence_key, autonomous_reservation_id, on_behalf_of_operator_id, repair_pr_number, repair_kind, repair_head_sha, repair_chain }) {
614
+ const body = { repo, prompt };
615
+ if (typeof max_budget_usd === "number") body.max_budget_usd = max_budget_usd;
616
+ if (typeof max_turns === "number") body.max_turns = max_turns;
617
+ for (const [key, value] of Object.entries({ dispatch_mode, tier, agent, model, repair_kind, repair_head_sha })) {
618
+ if (value) body[key] = value;
619
+ }
620
+ if (dispatch_occurrence_key) body.dispatch_occurrence_key = dispatch_occurrence_key;
621
+ if (autonomous_reservation_id) body.autonomous_reservation_id = autonomous_reservation_id;
622
+ if (on_behalf_of_operator_id) body.on_behalf_of_operator_id = on_behalf_of_operator_id;
623
+ if (Number.isInteger(repair_pr_number) && repair_pr_number > 0) body.repair_pr_number = repair_pr_number;
624
+ if (repair_chain) body.repair_chain = repair_chain;
625
+ const res = await taskReq("POST", "/api/v1/code-task", body);
626
+ if (res.status === 401) {
627
+ cachedFirebaseToken = null;
628
+ throw new Error("enqueue unauthorized (401)");
629
+ }
630
+ if (!res.ok) {
631
+ let code = null;
632
+ try {
633
+ const errBody = await res.json();
634
+ code = typeof errBody?.error === "string" ? errBody.error : null;
635
+ } catch {
636
+ }
637
+ const err = new Error(`enqueue failed: HTTP ${res.status}${code ? ` (${code})` : ""}`);
638
+ err.status = res.status;
639
+ err.code = code;
640
+ throw err;
641
+ }
642
+ const json = await res.json();
643
+ const task = json && json.task ? json.task : null;
644
+ if (task && typeof json.deduplicated === "boolean") Object.defineProperty(task, "deduplicated", { value: json.deduplicated, enumerable: false });
645
+ return task;
646
+ },
647
+ /**
648
+ * Resume a failed/cancelled/max-turn partial code-task. The PR watcher uses
649
+ * this after the runner opens a partial draft PR and CI is no longer pending.
650
+ */
651
+ async resumeCodeTask(taskId, { automaticRateLimit = false, automaticContinuation = false } = {}) {
652
+ return resumeCodeTaskRequest(taskReq, taskId, { automaticRateLimit, automaticContinuation }, () => {
653
+ cachedFirebaseToken = null;
654
+ });
655
+ },
656
+ /**
657
+ * Send a CI-green PR through the production verify-before-act merge route.
658
+ * The server inspects the current diff, applies deterministic blockers, runs
659
+ * consensus, records a receipt, and direct-merges only the inspected SHA.
660
+ */
661
+ /** F35: promote a PARTIAL draft to READY via the plane (admin-only; server re-checks; never merges). */
662
+ promoteDraftPr: (prNumber, automationContext) => promoteDraftPrRequest(req, prNumber, automationContext, () => {
663
+ cachedFirebaseToken = null;
664
+ }),
665
+ async mergeVerifiedPr(prNumber, automationContext) {
666
+ return mergeVerifiedPrRequest(
667
+ req,
668
+ prNumber,
669
+ automationContext,
670
+ () => {
671
+ cachedFirebaseToken = null;
672
+ }
673
+ );
674
+ },
675
+ async postProgress(taskId, patch) {
676
+ const progress = {
677
+ ...patch,
678
+ ...patch.runner_id ? {} : runnerId2 ? { runner_id: runnerId2 } : {},
679
+ ...patch.runner_instance_id ? {} : runnerInstanceId ? { runner_instance_id: runnerInstanceId } : {},
680
+ ...patch.claim_occurrence_id ? {} : claimOccurrences.has(taskId) ? { claim_occurrence_id: claimOccurrences.get(taskId) } : {}
681
+ };
682
+ const res = await taskReq("PATCH", `/api/v1/code-task/${taskId}/progress`, progress);
683
+ if (res.status === 409) {
684
+ const conflict = await res.json().catch(() => ({}));
685
+ if (conflict?.error === "code_task_claim_authority_changed") {
686
+ throw new ClaimAuthorityChangedError();
687
+ }
688
+ return { terminal: true };
689
+ }
690
+ if (res.status === 404) return { terminal: true, missing: true };
691
+ if (!res.ok) throw new Error(`progress failed: HTTP ${res.status}`);
692
+ const json = await res.json();
693
+ return { task: json && json.task };
694
+ },
695
+ async getTask(taskId) {
696
+ const res = await taskReq("GET", `/api/v1/code-task/${taskId}`);
697
+ if (res.status === 404) return null;
698
+ if (!res.ok) throw new Error(`getTask failed: HTTP ${res.status}`);
699
+ const json = await res.json();
700
+ return json ? json.task : null;
701
+ },
702
+ async getAssignedSkill(task) {
703
+ const name = task?.skill_invocation?.skill;
704
+ if (!runnerId2 || !runnerInstanceId || task?.claimed_by !== runnerId2 || task?.runner_instance_id !== runnerInstanceId || !task?.claim_occurrence_id || !name) {
705
+ throw new ClaimAuthorityChangedError();
706
+ }
707
+ const res = await taskReq("POST", `/api/v1/code-task/${encodeURIComponent(task.code_task_id)}/assigned-skill`, {
708
+ runner_id: runnerId2,
709
+ runner_instance_id: runnerInstanceId,
710
+ claim_occurrence_id: task.claim_occurrence_id,
711
+ skill: name
712
+ });
713
+ if (res.status === 401) {
714
+ cachedFirebaseToken = null;
715
+ throw new Error("skill corpus unauthorized (401)");
716
+ }
717
+ if (res.status === 409) {
718
+ const conflict = await res.json().catch(() => ({}));
719
+ if (conflict?.error === "code_task_claim_authority_changed") {
720
+ throw new ClaimAuthorityChangedError();
721
+ }
722
+ }
723
+ if (res.status === 404) throw new Error(`skill not found: ${name}`);
724
+ if (!res.ok) throw new Error(`skill corpus failed: HTTP ${res.status}`);
725
+ const json = await res.json();
726
+ if (json?.corpus_available !== true || !json.skill) {
727
+ throw new Error(`skill corpus unavailable: ${String(json?.reason || "unknown")}`);
728
+ }
729
+ return json.skill;
730
+ },
731
+ async listPrOpenedTasks() {
732
+ return listAllPrOpenedTasks(taskReq);
733
+ },
734
+ async downloadTaskAttachment(taskId, attachmentId) {
735
+ const path2 = `/api/v1/code-task/${encodeURIComponent(taskId)}/attachment/${encodeURIComponent(attachmentId)}`;
736
+ const res = await taskReq("GET", path2);
737
+ if (res.status === 401) cachedFirebaseToken = null;
738
+ if (!res.ok) throw new Error(`attachment download failed: HTTP ${res.status}`);
739
+ return Buffer.from(await res.arrayBuffer());
740
+ },
741
+ // Raised per-attempt timeout (>=15s) + bounded retry — see control-plane-knowledge-context.mjs.
742
+ async getTaskKnowledgeContext(taskId, { query, knowledgeRequestId } = {}) {
743
+ return getTaskKnowledgeContextRequest(req, taskId, { query, knowledgeRequestId }, {
744
+ taskRequestTimeoutMs,
745
+ invalidateToken: () => {
746
+ cachedFirebaseToken = null;
747
+ },
748
+ sleep: sleep2,
749
+ log: (m) => console.warn(`[code-runner ${(/* @__PURE__ */ new Date()).toISOString()}] ${m}`)
750
+ });
751
+ },
752
+ /** ADR-004 § 11.1b: the plane's prepared job, for SHADOW comparison. Never throws. */
753
+ getPreparedJob: (taskId, options) => getPreparedJobRequest(req, taskId, options, () => {
754
+ cachedFirebaseToken = null;
755
+ }),
756
+ /** Weekly Claude token usage report — see control-plane-weekly-tokens.mjs. */
757
+ async postWeeklyTokens(report) {
758
+ return postWeeklyTokensRequest(taskReq, report, () => {
759
+ cachedFirebaseToken = null;
760
+ });
761
+ },
762
+ /**
763
+ * Relay a batch of this machine's local vo-mcp events to vo-telemetry via
764
+ * the control plane (telemetry-forwarder.mjs). Returns { status, body };
765
+ * the forwarder owns backoff/disable policy. See control-plane-telemetry-relay.mjs.
766
+ */
767
+ async relayTelemetryEvents(batch) {
768
+ return relayTelemetryEventsRequest(req, batch, () => {
769
+ cachedFirebaseToken = null;
770
+ });
771
+ },
772
+ /**
773
+ * Send a liveness heartbeat (M2). The control-plane upserts it under the
774
+ * authenticated operator so the web shows a TRUE "runner online" signal.
775
+ * Best-effort caller; throws on 401/non-ok so the daemon can log + retry.
776
+ */
777
+ async postHeartbeat(heartbeat) {
778
+ const body = buildRunnerHeartbeatBody(heartbeat);
779
+ const res = await req("POST", "/api/v1/runner/heartbeat", body, {
780
+ timeoutMs: heartbeatTimeoutMs
781
+ });
782
+ if (res.status === 401) {
783
+ cachedFirebaseToken = null;
784
+ throw new Error("heartbeat unauthorized (401)");
785
+ }
786
+ if (!res.ok) {
787
+ let detail = "";
788
+ try {
789
+ const body2 = await res.json();
790
+ if (Array.isArray(body2?.issue_paths) && body2.issue_paths.length > 0) {
791
+ detail = ` (rejected fields: ${body2.issue_paths.join(", ")})`;
792
+ }
793
+ } catch {
794
+ }
795
+ throw new Error(`heartbeat failed: HTTP ${res.status}${detail}`);
796
+ }
797
+ return res.json();
798
+ },
799
+ async getRunnerStatus({ operatorId } = {}) {
800
+ const query = operatorId ? `?operator_id=${encodeURIComponent(operatorId)}` : "";
801
+ const res = await req("GET", `/api/v1/runner/status${query}`, void 0, {
802
+ timeoutMs: heartbeatTimeoutMs
803
+ });
804
+ if (res.status === 401) {
805
+ cachedFirebaseToken = null;
806
+ throw new Error("runner status unauthorized (401)");
807
+ }
808
+ if (!res.ok) throw new Error(`runner status failed: HTTP ${res.status}`);
809
+ const body = await res.json();
810
+ return Array.isArray(body?.runners) ? body.runners : [];
811
+ },
812
+ async pollRunnerControl({ runnerId: runnerId3, operatorId, supervisorInstanceId: supervisorInstanceId2, supervisorVersion: supervisorVersion2, capabilities }) {
813
+ const body = { runner_id: runnerId3 };
814
+ if (operatorId) body.operator_id = operatorId;
815
+ if (supervisorInstanceId2) body.supervisor_instance_id = supervisorInstanceId2;
816
+ if (supervisorVersion2) body.supervisor_version = supervisorVersion2;
817
+ if (Array.isArray(capabilities) && capabilities.length > 0) body.capabilities = capabilities;
818
+ const res = await taskReq("POST", "/api/v1/runner/control/poll", body);
819
+ if (res.status === 401) {
820
+ cachedFirebaseToken = null;
821
+ throw new Error("runner control poll unauthorized (401)");
822
+ }
823
+ if (!res.ok) throw new Error(`runner control poll failed: HTTP ${res.status}`);
824
+ const json = await res.json();
825
+ const action = json?.action;
826
+ return action && typeof action.action_id === "string" && action.action_id ? { ...action, actionId: action.action_id } : null;
827
+ },
828
+ async completeRunnerControl(actionId, { runnerId: runnerId3, operatorId, supervisorInstanceId: supervisorInstanceId2, supervisorVersion: supervisorVersion2, capabilities, status, detail }) {
829
+ const body = { runner_id: runnerId3, status };
830
+ if (operatorId) body.operator_id = operatorId;
831
+ if (supervisorInstanceId2) body.supervisor_instance_id = supervisorInstanceId2;
832
+ if (supervisorVersion2) body.supervisor_version = supervisorVersion2;
833
+ if (Array.isArray(capabilities) && capabilities.length > 0) body.capabilities = capabilities;
834
+ if (detail) body.detail = detail;
835
+ const res = await taskReq("POST", `/api/v1/runner/control/${encodeURIComponent(actionId)}/complete`, body);
836
+ if (res.status === 401) {
837
+ cachedFirebaseToken = null;
838
+ throw new Error("runner control completion unauthorized (401)");
839
+ }
840
+ if (!res.ok) throw new Error(`runner control completion failed: HTTP ${res.status}`);
841
+ const json = await res.json();
842
+ return json?.action || null;
843
+ },
844
+ /** Mint a GitHub App installation token — see installation-token.mjs. */
845
+ async getInstallationToken({ required = false, readOnly = false, repo = null } = {}) {
846
+ return fetchInstallationToken({ req: taskReq, required, readOnly, repo });
847
+ },
848
+ /**
849
+ * Read the operator's dispatch-mode config (Fast→Ultracode effort setting).
850
+ * Returns the mode string ('fast'|'standard'|'deep'|'ultra'|'marathon'; 'ultracode' legacy),
851
+ * defaulting to 'standard' on any error. Never throws — best-effort.
852
+ */
853
+ async getDispatchMode() {
854
+ try {
855
+ const res = await taskReq("GET", "/api/v1/dispatch-mode-config");
856
+ if (!res.ok) return "standard";
857
+ const json = await res.json();
858
+ return json?.dispatchMode || "standard";
859
+ } catch {
860
+ return "standard";
861
+ }
862
+ }
863
+ };
864
+ }
865
+
866
+ // ../../scripts/virtual-office/code-runner/runner-host-maintenance.mjs
867
+ import { spawnSync } from "node:child_process";
868
+ import { existsSync } from "node:fs";
869
+ import { win32 } from "node:path";
870
+ var DEFAULT_RUNNER_PACKAGE = "@algosuite/vo-mcp@beta";
871
+ var PACKAGE_SPEC_RE = /^@algosuite\/vo-mcp@(beta|latest|\d+(?:\.\d+){0,2}(?:-[\w.-]+)?)$/u;
872
+ var MAX_DIAGNOSTIC_CHARS = 800;
873
+ var NPM_CLI_SUFFIX = `\\${win32.join("node_modules", "npm", "bin", "npm-cli.js").toLowerCase()}`;
874
+ function escapeRegExp(value) {
875
+ return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
876
+ }
877
+ function sanitizeMaintenanceDiagnostic(raw, env = {}) {
878
+ let value = String(raw || "").replace(/\b(?:vocred|npm|gh[oprsu])_[A-Za-z0-9._-]+\b/gu, "[REDACTED]").replace(/\bBearer\s+\S+/giu, "Bearer [REDACTED]").replace(/\b(_?authToken|token|password|secret|credential)(\s*[=:]\s*)\S+/giu, "$1$2[REDACTED]");
879
+ for (const [name, secret] of Object.entries(env)) {
880
+ if (!/(?:TOKEN|KEY|SECRET|PASSWORD|CREDENTIAL)/iu.test(name)) continue;
881
+ const text = String(secret || "");
882
+ if (text.length < 4) continue;
883
+ value = value.replace(new RegExp(escapeRegExp(text), "gu"), "[REDACTED]");
884
+ }
885
+ value = value.replace(/\s+/gu, " ").trim();
886
+ if (value.length <= MAX_DIAGNOSTIC_CHARS) return value;
887
+ return `${value.slice(0, MAX_DIAGNOSTIC_CHARS - 1)}\u2026`;
888
+ }
889
+ function isNpmCliPath(value) {
890
+ return typeof value === "string" && win32.isAbsolute(value) && win32.normalize(value).toLowerCase().endsWith(NPM_CLI_SUFFIX);
891
+ }
892
+ function resolveNpmCli({
893
+ env = process.env,
894
+ execPath = process.execPath,
895
+ fileExists = existsSync
896
+ } = {}) {
897
+ const candidates = [];
898
+ if (isNpmCliPath(env.npm_execpath)) candidates.push(env.npm_execpath);
899
+ candidates.push(win32.join(win32.dirname(execPath), "node_modules", "npm", "bin", "npm-cli.js"));
900
+ const pathValue = env.PATH ?? env.Path ?? env.path ?? "";
901
+ for (const entry of pathValue.split(";")) {
902
+ const trimmed = entry.trim();
903
+ if (!win32.isAbsolute(trimmed)) continue;
904
+ candidates.push(win32.join(trimmed, "node_modules", "npm", "bin", "npm-cli.js"));
905
+ }
906
+ const seen = /* @__PURE__ */ new Set();
907
+ for (const candidate of candidates) {
908
+ const normalized = win32.normalize(candidate);
909
+ const key = normalized.toLowerCase();
910
+ if (seen.has(key) || !isNpmCliPath(normalized)) continue;
911
+ seen.add(key);
912
+ if (fileExists(normalized)) return normalized;
913
+ }
914
+ return null;
915
+ }
916
+ function buildMaintenanceCommand(kind, {
917
+ platform = process.platform,
918
+ packageSpec = DEFAULT_RUNNER_PACKAGE,
919
+ env = process.env,
920
+ execPath = process.execPath,
921
+ fileExists = existsSync
922
+ } = {}) {
923
+ if (!["update", "reinstall"].includes(kind)) return null;
924
+ if (!PACKAGE_SPEC_RE.test(packageSpec)) throw new Error("unsafe runner package spec");
925
+ const npmCli = platform === "win32" ? resolveNpmCli({ env, execPath, fileExists }) : null;
926
+ if (platform === "win32" && !npmCli) throw new Error("trusted npm CLI not found");
927
+ const command = platform === "win32" ? execPath : "npm";
928
+ const args = [...npmCli ? [npmCli] : [], "install", "-g", packageSpec];
929
+ if (kind === "reinstall") args.push("--force");
930
+ return { command, args };
931
+ }
932
+ function runHostMaintenance(kind, {
933
+ platform = process.platform,
934
+ packageSpec = DEFAULT_RUNNER_PACKAGE,
935
+ env = process.env,
936
+ execPath = process.execPath,
937
+ fileExists = existsSync,
938
+ spawn: spawn2 = spawnSync,
939
+ log = () => {
940
+ }
941
+ } = {}) {
942
+ if (kind === "reconnect") return { ok: true, status: 0, command: null, args: [] };
943
+ let command;
944
+ try {
945
+ command = buildMaintenanceCommand(kind, { platform, packageSpec, env, execPath, fileExists });
946
+ } catch (error) {
947
+ return {
948
+ ok: false,
949
+ status: 2,
950
+ command: null,
951
+ args: [],
952
+ detail: sanitizeMaintenanceDiagnostic(error instanceof Error ? error.message : String(error), env)
953
+ };
954
+ }
955
+ if (!command) return { ok: false, status: 2, command: null, args: [] };
956
+ log(`runner maintenance: ${command.command} ${command.args.join(" ")}`);
957
+ const result = spawn2(command.command, command.args, {
958
+ stdio: ["ignore", "pipe", "pipe"],
959
+ encoding: "utf8",
960
+ env,
961
+ shell: false,
962
+ windowsHide: true
963
+ });
964
+ const status = typeof result.status === "number" ? result.status : 1;
965
+ const detail = sanitizeMaintenanceDiagnostic(
966
+ [result.stderr, result.stdout, result.error?.message].filter(Boolean).join("\n"),
967
+ env
968
+ );
969
+ if (detail) log(`runner maintenance result: ${detail}`);
970
+ return { ok: status === 0, status, command: command.command, args: command.args, detail };
971
+ }
972
+
973
+ // src/runner/bundled-runtime-updater.mjs
974
+ import { createHash as createHash4, randomUUID as randomUUID2 } from "node:crypto";
975
+ import {
976
+ existsSync as existsSync4,
977
+ lstatSync as lstatSync3,
978
+ mkdirSync as mkdirSync2,
979
+ readFileSync as readFileSync4,
980
+ readdirSync as readdirSync3,
981
+ renameSync as renameSync2,
982
+ rmSync as rmSync2,
983
+ writeFileSync as writeFileSync2
984
+ } from "node:fs";
985
+ import { basename, isAbsolute as isAbsolute3, join as join3, relative as relative3, resolve as resolve4, sep as sep2 } from "node:path";
986
+ import { spawnSync as spawnSync2 } from "node:child_process";
987
+
988
+ // ../../scripts/virtual-office/runner-bootstrap/runtime-authorization.mjs
989
+ import { createHash as createHash2 } from "node:crypto";
990
+ import { readFileSync as readFileSync2 } from "node:fs";
991
+ import { dirname, posix, resolve as resolve2 } from "node:path";
992
+ import { fileURLToPath } from "node:url";
993
+
994
+ // ../../scripts/virtual-office/runner-bootstrap/runtime-staged-tree.mjs
995
+ import { execFileSync } from "node:child_process";
996
+ import { createHash } from "node:crypto";
997
+ import {
998
+ existsSync as existsSync2,
999
+ lstatSync,
1000
+ readFileSync,
1001
+ readdirSync
1002
+ } from "node:fs";
1003
+ import { isAbsolute, join, relative, resolve, sep, win32 as win322 } from "node:path";
1004
+ var WINDOWS_REPARSE_ATTRIBUTE = 1024;
1005
+ var RUNNER_LOCK_KEY = "node_modules/@algosuite/vo-mcp";
1006
+ var INSTALLED_TREE_ALGORITHM = "algohq-node-modules-manifest-sha256-v1";
1007
+ function canonical(value) {
1008
+ if (Array.isArray(value)) return value.map(canonical);
1009
+ if (value && typeof value === "object") {
1010
+ return Object.fromEntries(Object.keys(value).sort().map((key) => [key, canonical(value[key])]));
1011
+ }
1012
+ return value;
1013
+ }
1014
+ function canonicalJson(value) {
1015
+ return JSON.stringify(canonical(value));
1016
+ }
1017
+ function hasFileAttribute(value, bit) {
1018
+ if (typeof value === "bigint") return (value & BigInt(bit)) !== 0n;
1019
+ return Number.isSafeInteger(value) && (value & bit) !== 0;
1020
+ }
1021
+ function isReparseStat(stats) {
1022
+ if (!stats || typeof stats !== "object") return false;
1023
+ if (typeof stats.isSymbolicLink === "function" && stats.isSymbolicLink()) return true;
1024
+ if (stats.reparseTag !== void 0 && stats.reparseTag !== null && stats.reparseTag !== 0) return true;
1025
+ return ["fileAttributes", "fileAttribute", "attributes"].some((key) => hasFileAttribute(stats[key], WINDOWS_REPARSE_ATTRIBUTE));
1026
+ }
1027
+ function platformConstraintAllows(values, target) {
1028
+ if (!Array.isArray(values) || values.length === 0) return true;
1029
+ if (values.some((value) => value === `!${target}`)) return false;
1030
+ const positive = values.filter((value) => typeof value === "string" && !value.startsWith("!"));
1031
+ return positive.length === 0 || positive.includes(target);
1032
+ }
1033
+ function isOmittedOptionalPackage(entry, platform = { os: "win32", arch: "x64" }) {
1034
+ return entry?.optional === true && (!platformConstraintAllows(entry.os, platform.os) || !platformConstraintAllows(entry.cpu, platform.arch));
1035
+ }
1036
+ function assertContained(root, candidate, label) {
1037
+ const rel = relative(root, candidate);
1038
+ if (rel === "" || !rel.startsWith(`..${sep}`) && rel !== ".." && !isAbsolute(rel)) return;
1039
+ throw new Error(`staged runtime ${label} escapes the payload root`);
1040
+ }
1041
+ function normalizeReportedPath(root, candidate) {
1042
+ const absolute = resolve(String(candidate || ""));
1043
+ assertContained(root, absolute, "reparse point");
1044
+ return absolute;
1045
+ }
1046
+ function listWindowsReparsePoints(root, {
1047
+ execFile = execFileSync,
1048
+ env = process.env,
1049
+ platform = process.platform
1050
+ } = {}) {
1051
+ if (platform !== "win32") return [];
1052
+ const systemRoot = String(env.SystemRoot || env.SYSTEMROOT || "");
1053
+ if (!win322.isAbsolute(systemRoot) || win322.normalize(systemRoot) !== systemRoot) {
1054
+ throw new Error("staged runtime trusted PowerShell root unavailable");
1055
+ }
1056
+ const system32 = win322.join(systemRoot, "System32");
1057
+ const powershell = win322.join(system32, "WindowsPowerShell", "v1.0", "powershell.exe");
1058
+ const script = [
1059
+ '$ErrorActionPreference = "Stop"',
1060
+ "$root = [IO.Path]::GetFullPath($env:ALGOHQ_REPARSE_ROOT)",
1061
+ "$items = @((Get-Item -LiteralPath $root -Force)) + @(Get-ChildItem -LiteralPath $root -Force -Recurse)",
1062
+ "foreach ($item in $items) {",
1063
+ " if (($item.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0) {",
1064
+ " [Console]::Out.WriteLine($item.FullName)",
1065
+ " }",
1066
+ "}"
1067
+ ].join("; ");
1068
+ const output = execFile(powershell, [
1069
+ "-NoLogo",
1070
+ "-NoProfile",
1071
+ "-NonInteractive",
1072
+ "-Command",
1073
+ script
1074
+ ], {
1075
+ cwd: system32,
1076
+ encoding: "utf8",
1077
+ env: { SystemRoot: systemRoot, ALGOHQ_REPARSE_ROOT: win322.resolve(root) },
1078
+ windowsHide: true
1079
+ });
1080
+ return String(output || "").split(/\r?\n/u).filter(Boolean).map((item) => normalizeReportedPath(root, item));
1081
+ }
1082
+ function normalizedRunnerRecord(actual, expected) {
1083
+ const normalized = structuredClone(actual);
1084
+ if (normalized?.resolved === expected?.resolved) return normalized;
1085
+ if (typeof normalized?.resolved !== "string" || !normalized.resolved.startsWith("file:")) {
1086
+ return normalized;
1087
+ }
1088
+ const fileName = normalized.resolved.slice("file:".length).replaceAll("\\", "/").split("/").at(-1);
1089
+ const expectedFileNames = /* @__PURE__ */ new Set([
1090
+ String(expected.resolved || "").split("/").at(-1),
1091
+ `algosuite-vo-mcp-${expected.version}.tgz`
1092
+ ]);
1093
+ if (expectedFileNames.has(fileName)) normalized.resolved = expected.resolved;
1094
+ return normalized;
1095
+ }
1096
+ function validateLock(lock, authorization) {
1097
+ if (!lock || typeof lock !== "object" || Array.isArray(lock)) {
1098
+ throw new Error("staged runtime package-lock must be an object");
1099
+ }
1100
+ if (lock.lockfileVersion !== authorization.dependency_lock.source_lockfile_version) {
1101
+ throw new Error("staged runtime package-lock version mismatch");
1102
+ }
1103
+ if (!lock.packages || typeof lock.packages !== "object" || Array.isArray(lock.packages)) {
1104
+ throw new Error("staged runtime package-lock package map missing");
1105
+ }
1106
+ const expected = authorization.dependency_lock.packages;
1107
+ const actual = Object.fromEntries(Object.entries(lock.packages).filter(([key]) => key !== ""));
1108
+ const expectedKeys = Object.keys(expected).sort();
1109
+ const actualKeys = Object.keys(actual).sort();
1110
+ if (canonicalJson(actualKeys) !== canonicalJson(expectedKeys)) {
1111
+ throw new Error("staged runtime package-lock package set mismatch");
1112
+ }
1113
+ for (const key of expectedKeys) {
1114
+ const record = key === RUNNER_LOCK_KEY ? normalizedRunnerRecord(actual[key], expected[key]) : actual[key];
1115
+ if (canonicalJson(record) !== canonicalJson(expected[key])) {
1116
+ throw new Error(`staged runtime package-lock record mismatch: ${key}`);
1117
+ }
1118
+ }
1119
+ return { actual, expected };
1120
+ }
1121
+ function checkPathKind(path2, expectedKind, fsOps, label) {
1122
+ if (!fsOps.exists(path2)) throw new Error(`staged runtime ${label} missing`);
1123
+ const stats = fsOps.lstat(path2);
1124
+ if (isReparseStat(stats) || fsOps.isReparsePoint(path2, stats)) {
1125
+ throw new Error(`staged runtime ${label} is a reparse point`);
1126
+ }
1127
+ if (expectedKind === "directory" && !stats.isDirectory()) {
1128
+ throw new Error(`staged runtime ${label} is not a directory`);
1129
+ }
1130
+ if (expectedKind === "file" && !stats.isFile()) {
1131
+ throw new Error(`staged runtime ${label} is not a regular file`);
1132
+ }
1133
+ if (expectedKind === "file" && Number(stats.nlink) > 1) {
1134
+ throw new Error(`staged runtime ${label} is hardlinked`);
1135
+ }
1136
+ return stats;
1137
+ }
1138
+ function verifyInstalledPackages(payloadRoot, packageRecords, platform, fsOps) {
1139
+ const omitted = [];
1140
+ let installed = 0;
1141
+ for (const [key, entry] of Object.entries(packageRecords)) {
1142
+ const path2 = resolve(payloadRoot, key);
1143
+ assertContained(payloadRoot, path2, "package path");
1144
+ const shouldOmit = isOmittedOptionalPackage(entry, platform);
1145
+ if (shouldOmit) {
1146
+ omitted.push(key);
1147
+ if (fsOps.exists(path2)) throw new Error(`staged runtime optional package should be omitted: ${key}`);
1148
+ continue;
1149
+ }
1150
+ checkPathKind(path2, "directory", fsOps, `installed package ${key}`);
1151
+ installed += 1;
1152
+ }
1153
+ return { installed, omitted: omitted.sort() };
1154
+ }
1155
+ function treeRecord(kind, path2, stats, fileHash = "") {
1156
+ if (kind === "d") return `d ${path2}\r
1157
+ `;
1158
+ return `f ${path2} ${stats.size} ${fileHash}\r
1159
+ `;
1160
+ }
1161
+ function computeInstalledTree(nodeModulesRoot, fsOps = {}) {
1162
+ const ops = {
1163
+ exists: existsSync2,
1164
+ lstat: lstatSync,
1165
+ readdir: (path2) => readdirSync(path2, { withFileTypes: true }),
1166
+ readFile: readFileSync,
1167
+ isReparsePoint: () => false,
1168
+ listReparsePoints: listWindowsReparsePoints,
1169
+ ...fsOps
1170
+ };
1171
+ const root = resolve(nodeModulesRoot);
1172
+ checkPathKind(root, "directory", ops, "node_modules root");
1173
+ const reported = ops.listReparsePoints(root);
1174
+ if (!Array.isArray(reported)) throw new Error("staged runtime reparse probe returned an invalid result");
1175
+ if (reported.length > 0) throw new Error("staged runtime tree contains a Windows reparse point");
1176
+ let fileCount = 0;
1177
+ let directoryCount = 1;
1178
+ let byteCount = 0;
1179
+ const entries = [];
1180
+ function walk(absolute, relativePath) {
1181
+ for (const entry of ops.readdir(absolute)) {
1182
+ const childRelative = relativePath ? `${relativePath}/${entry.name}` : entry.name;
1183
+ if (childRelative === ".package-lock.json") continue;
1184
+ const child = resolve(absolute, entry.name);
1185
+ assertContained(root, child, "tree entry");
1186
+ const stats = ops.lstat(child);
1187
+ if (isReparseStat(stats) || ops.isReparsePoint(child, stats)) {
1188
+ throw new Error(`staged runtime tree contains a reparse point: ${childRelative}`);
1189
+ }
1190
+ if (stats.isDirectory()) {
1191
+ directoryCount += 1;
1192
+ entries.push({ kind: "d", path: childRelative, stats });
1193
+ walk(child, childRelative);
1194
+ } else if (stats.isFile()) {
1195
+ if (Number(stats.nlink) > 1) {
1196
+ throw new Error(`staged runtime tree contains a hardlinked file: ${childRelative}`);
1197
+ }
1198
+ fileCount += 1;
1199
+ byteCount += Number(stats.size);
1200
+ const digest = createHash("sha256").update(ops.readFile(child)).digest("hex");
1201
+ entries.push({ kind: "f", path: childRelative, stats, digest });
1202
+ } else {
1203
+ throw new Error(`staged runtime tree contains a non-file entry: ${childRelative}`);
1204
+ }
1205
+ }
1206
+ }
1207
+ walk(root, "");
1208
+ entries.sort((left, right) => Buffer.compare(Buffer.from(left.path), Buffer.from(right.path)));
1209
+ const manifest = treeRecord("d", "", { size: 0 }) + entries.map((entry) => treeRecord(entry.kind, entry.path, entry.stats, entry.digest)).join("");
1210
+ return {
1211
+ algorithm: INSTALLED_TREE_ALGORITHM,
1212
+ sha256: createHash("sha256").update(manifest, "utf8").digest("hex"),
1213
+ file_count: fileCount,
1214
+ directory_count: directoryCount,
1215
+ byte_count: byteCount,
1216
+ canonical_manifest_byte_count: Buffer.byteLength(manifest),
1217
+ reparse_point_count: 0,
1218
+ hardlinked_file_count: 0
1219
+ };
1220
+ }
1221
+ function compareStagedRuntime({
1222
+ payloadRoot,
1223
+ nodeModulesRoot = join(payloadRoot, "node_modules"),
1224
+ packageLockFile = join(payloadRoot, "package-lock.json"),
1225
+ authorization,
1226
+ fsOps = {}
1227
+ }) {
1228
+ const payload = resolve(payloadRoot);
1229
+ const modules = resolve(nodeModulesRoot);
1230
+ const lockFile = resolve(packageLockFile);
1231
+ if (modules !== resolve(payload, "node_modules") || lockFile !== resolve(payload, "package-lock.json")) {
1232
+ throw new Error("staged runtime paths do not identify one canonical payload");
1233
+ }
1234
+ const ops = {
1235
+ exists: existsSync2,
1236
+ lstat: lstatSync,
1237
+ readFile: readFileSync,
1238
+ isReparsePoint: () => false,
1239
+ ...fsOps
1240
+ };
1241
+ checkPathKind(lockFile, "file", ops, "package-lock");
1242
+ const lock = JSON.parse(ops.readFile(lockFile, "utf8"));
1243
+ const { expected } = validateLock(lock, authorization);
1244
+ const platform = { os: authorization.platform.os, arch: authorization.platform.arch };
1245
+ const packages = verifyInstalledPackages(payload, expected, platform, ops);
1246
+ const declaredOmitted = [...authorization.dependency_lock.windows_x64_omitted_optional_entries].sort();
1247
+ if (canonicalJson(packages.omitted) !== canonicalJson(declaredOmitted)) {
1248
+ throw new Error("staged runtime optional omission list mismatch");
1249
+ }
1250
+ if (packages.installed !== Object.keys(expected).length - packages.omitted.length) {
1251
+ throw new Error("staged runtime installed package count mismatch");
1252
+ }
1253
+ const tree = computeInstalledTree(modules, fsOps);
1254
+ if (canonicalJson(tree) !== canonicalJson(authorization.installed_tree)) {
1255
+ throw new Error("staged runtime installed tree mismatch");
1256
+ }
1257
+ return { ok: true, packageCount: Object.keys(expected).length, ...packages, tree };
1258
+ }
1259
+
1260
+ // ../../scripts/virtual-office/runner-bootstrap/runtime-authorization.mjs
1261
+ var HERE = dirname(fileURLToPath(import.meta.url));
1262
+ var BETA13_WINDOWS_X64_AUTHORIZATION = resolve2(
1263
+ HERE,
1264
+ "authorizations",
1265
+ "vo-mcp-0.2.0-beta.13-win32-x64.json"
1266
+ );
1267
+ var EXPECTED = Object.freeze({
1268
+ authorizationId: "vo-mcp-0.2.0-beta.13-win32-x64-v1",
1269
+ packageName: "@algosuite/vo-mcp",
1270
+ version: "0.2.0-beta.13",
1271
+ registry: "https://registry.npmjs.org/",
1272
+ tarballUrl: "https://registry.npmjs.org/@algosuite/vo-mcp/-/vo-mcp-0.2.0-beta.13.tgz",
1273
+ integrity: "sha512-TQa5VaEFsaleHbAZZp+zS4u5U/0zQBIGOiPDGn9IqfZfdMltH2dY81nTftvu5EUABthE1xTCrdSzJjO9mzkNag==",
1274
+ tarballSha256: "c1e39e8bb2df7f53f48e46e77ffb617452a01849469ff4757b4384a478c1cbff",
1275
+ npmShasumSha1: "f076649b31294aa38deb7852f38a889e0d0fbe44",
1276
+ gitHead: "ba00db90720416fa7474581eb823c6255221880f",
1277
+ sourceLockSha256: "f8bbb5e81a0057ee2d60145580ec07f7e7055d999bdd038515c6c4e88af6cc92",
1278
+ canonicalEntriesSha256: "7f6b2f93ccb93ad625ed244dc59ac556792e478178fc690ad2ca2c6f3a2325d2",
1279
+ entryCount: 107,
1280
+ optionalEntryCount: 13,
1281
+ installedEntryCount: 96,
1282
+ treeAlgorithm: "algohq-node-modules-manifest-sha256-v1",
1283
+ treeSha256: "9c8b0749d7ae1e2c132ef08c5b5655673ae88432859c561806276c9eb18f4874",
1284
+ treeFileCount: 3538,
1285
+ treeDirectoryCount: 553,
1286
+ treeByteCount: 20566445,
1287
+ treeManifestByteCount: 412062
1288
+ });
1289
+ var SRI_RE = /^sha512-[A-Za-z0-9+/]{86}==$/u;
1290
+ var SHA256_RE = /^[a-f0-9]{64}$/u;
1291
+ function canonical2(value) {
1292
+ if (Array.isArray(value)) return value.map(canonical2);
1293
+ if (value && typeof value === "object") {
1294
+ return Object.fromEntries(Object.keys(value).sort().map((key) => [key, canonical2(value[key])]));
1295
+ }
1296
+ return value;
1297
+ }
1298
+ function sha256Canonical(value) {
1299
+ return createHash2("sha256").update(`${JSON.stringify(canonical2(value))}
1300
+ `, "utf8").digest("hex");
1301
+ }
1302
+ function assertEqual(actual, expected, label) {
1303
+ if (actual !== expected) throw new Error(`runtime authorization ${label} mismatch`);
1304
+ }
1305
+ function isOmittedOnWindowsX64(entry) {
1306
+ return isOmittedOptionalPackage(entry, { os: "win32", arch: "x64" });
1307
+ }
1308
+ function validateRuntimeAuthorization(value) {
1309
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
1310
+ throw new Error("runtime authorization must be an object");
1311
+ }
1312
+ assertEqual(value.schema_version, 1, "schema version");
1313
+ assertEqual(value.authorization_id, EXPECTED.authorizationId, "id");
1314
+ assertEqual(value.package?.name, EXPECTED.packageName, "package name");
1315
+ assertEqual(value.package?.version, EXPECTED.version, "package version");
1316
+ assertEqual(value.package?.registry, EXPECTED.registry, "registry");
1317
+ assertEqual(value.package?.tarball_url, EXPECTED.tarballUrl, "tarball URL");
1318
+ assertEqual(value.package?.sri_sha512, EXPECTED.integrity, "package integrity");
1319
+ assertEqual(value.package?.tarball_sha256, EXPECTED.tarballSha256, "tarball sha256");
1320
+ assertEqual(value.package?.npm_shasum_sha1, EXPECTED.npmShasumSha1, "npm shasum");
1321
+ assertEqual(value.package?.git_head, EXPECTED.gitHead, "git head");
1322
+ assertEqual(value.package?.packed_file_count, 25, "packed file count");
1323
+ assertEqual(value.package?.packed_bytes, 846151, "packed byte count");
1324
+ assertEqual(value.package?.unpacked_bytes, 3350387, "unpacked byte count");
1325
+ assertEqual(value.platform?.os, "win32", "operating system");
1326
+ assertEqual(value.platform?.arch, "x64", "architecture");
1327
+ assertEqual(value.platform?.package_node_engine, ">=22.5.0", "package node engine");
1328
+ if (!/^24\.15\.0$/u.test(String(value.platform?.authorization_builder_node || "")) || value.platform?.authorization_builder_npm !== "11.12.1") {
1329
+ throw new Error("runtime authorization builder toolchain mismatch");
1330
+ }
1331
+ for (const [key, expected] of Object.entries({
1332
+ ignore_scripts: true,
1333
+ bin_links: false,
1334
+ include_optional: true,
1335
+ omit_dev: true,
1336
+ audit: false,
1337
+ fund: false,
1338
+ reject_links_reparse_points: true,
1339
+ reject_hardlinks: true,
1340
+ remove_generated_node_modules_package_lock_before_tree_validation: true
1341
+ })) assertEqual(value.install_contract?.[key], expected, `install contract ${key}`);
1342
+ assertEqual(value.install_contract?.allowed_registry_prefix, EXPECTED.registry, "registry prefix");
1343
+ const lock = value.dependency_lock;
1344
+ if (!lock?.packages || typeof lock.packages !== "object" || Array.isArray(lock.packages)) {
1345
+ throw new Error("runtime authorization dependency set missing");
1346
+ }
1347
+ assertEqual(lock.source_lockfile_version, 3, "lockfile version");
1348
+ assertEqual(lock.source_lock_sha256, EXPECTED.sourceLockSha256, "source lock sha256");
1349
+ assertEqual(lock.canonical_entries_sha256, EXPECTED.canonicalEntriesSha256, "entry-set sha256");
1350
+ assertEqual(lock.entry_count, EXPECTED.entryCount, "entry count");
1351
+ assertEqual(lock.integrity_entry_count, EXPECTED.entryCount, "integrity count");
1352
+ assertEqual(lock.optional_entry_count, EXPECTED.optionalEntryCount, "optional count");
1353
+ assertEqual(lock.windows_x64_installed_entry_count, EXPECTED.installedEntryCount, "installed count");
1354
+ const entries = Object.entries(lock.packages);
1355
+ assertEqual(entries.length, EXPECTED.entryCount, "package map count");
1356
+ for (const [key, entry] of entries) {
1357
+ if (!key.startsWith("node_modules/") || key.includes("\\") || posix.normalize(key) !== key || key.split("/").includes("..")) {
1358
+ throw new Error(`runtime authorization has unsafe package path: ${key}`);
1359
+ }
1360
+ if (!entry || typeof entry !== "object" || entry.link === true) {
1361
+ throw new Error(`runtime authorization contains a linked package: ${key}`);
1362
+ }
1363
+ if (!SRI_RE.test(String(entry.integrity || ""))) {
1364
+ throw new Error(`runtime authorization package lacks sha512 integrity: ${key}`);
1365
+ }
1366
+ if (!String(entry.resolved || "").startsWith(EXPECTED.registry)) {
1367
+ throw new Error(`runtime authorization package is outside the public registry: ${key}`);
1368
+ }
1369
+ if (entry.hasInstallScript === true) {
1370
+ throw new Error(`runtime authorization package declares an install script: ${key}`);
1371
+ }
1372
+ }
1373
+ const runner = lock.packages["node_modules/@algosuite/vo-mcp"];
1374
+ assertEqual(runner?.version, EXPECTED.version, "runner dependency version");
1375
+ assertEqual(runner?.integrity, EXPECTED.integrity, "runner dependency integrity");
1376
+ assertEqual(sha256Canonical(lock.packages), EXPECTED.canonicalEntriesSha256, "computed entry-set sha256");
1377
+ const omitted = entries.filter(([, entry]) => isOmittedOnWindowsX64(entry)).map(([key]) => key).sort();
1378
+ const declaredOmitted = [...lock.windows_x64_omitted_optional_entries || []].sort();
1379
+ assertEqual(JSON.stringify(declaredOmitted), JSON.stringify(omitted), "omitted optional entries");
1380
+ assertEqual(entries.length - omitted.length, EXPECTED.installedEntryCount, "derived installed count");
1381
+ const tree = value.installed_tree;
1382
+ assertEqual(tree?.algorithm, EXPECTED.treeAlgorithm, "tree algorithm");
1383
+ if (!SHA256_RE.test(String(tree?.sha256 || ""))) throw new Error("runtime authorization tree hash invalid");
1384
+ assertEqual(tree.sha256, EXPECTED.treeSha256, "tree sha256");
1385
+ assertEqual(tree.file_count, EXPECTED.treeFileCount, "tree file count");
1386
+ assertEqual(tree.directory_count, EXPECTED.treeDirectoryCount, "tree directory count");
1387
+ assertEqual(tree.byte_count, EXPECTED.treeByteCount, "tree byte count");
1388
+ assertEqual(tree.canonical_manifest_byte_count, EXPECTED.treeManifestByteCount, "tree manifest byte count");
1389
+ assertEqual(tree.reparse_point_count, 0, "tree reparse count");
1390
+ assertEqual(tree.hardlinked_file_count, 0, "tree hardlink count");
1391
+ return value;
1392
+ }
1393
+ function readRuntimeAuthorization(file = BETA13_WINDOWS_X64_AUTHORIZATION) {
1394
+ return validateRuntimeAuthorization(JSON.parse(readFileSync2(file, "utf8")));
1395
+ }
1396
+ function validateStagedRuntimeAuthorization(options) {
1397
+ const authorization = validateRuntimeAuthorization(options?.authorization || readRuntimeAuthorization());
1398
+ return compareStagedRuntime({ ...options, authorization });
1399
+ }
1400
+ if (process.argv[1] && resolve2(process.argv[1]) === fileURLToPath(import.meta.url)) {
1401
+ readRuntimeAuthorization(process.argv[2] ? resolve2(process.argv[2]) : void 0);
1402
+ process.stdout.write("AlgoHQ runtime authorization valid\n");
1403
+ }
1404
+
1405
+ // src/runner/bundled-runtime-store.mjs
1406
+ import { createHash as createHash3, randomUUID } from "node:crypto";
1407
+ import {
1408
+ closeSync,
1409
+ existsSync as existsSync3,
1410
+ fsyncSync,
1411
+ lstatSync as lstatSync2,
1412
+ mkdirSync,
1413
+ openSync,
1414
+ readFileSync as readFileSync3,
1415
+ readdirSync as readdirSync2,
1416
+ realpathSync,
1417
+ renameSync,
1418
+ rmSync,
1419
+ writeFileSync
1420
+ } from "node:fs";
1421
+ import { homedir } from "node:os";
1422
+ import { dirname as dirname2, isAbsolute as isAbsolute2, join as join2, relative as relative2, resolve as resolve3 } from "node:path";
1423
+ var SLOT_ID_RE = /^vo-mcp-[0-9A-Za-z._-]{1,96}$/u;
1424
+ var ACTION_ID_RE = /^[0-9A-Za-z._-]{1,128}$/u;
1425
+ var INTEGRITY_RE = /^sha512-[A-Za-z0-9+/]{86}==$/u;
1426
+ var VERSION_RE = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/u;
1427
+ 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;
1428
+ var ENTRY_REL = join2("node_modules", "@algosuite", "vo-mcp", "bin", "vo-mcp");
1429
+ var SUPERVISOR_REL = join2("node_modules", "@algosuite", "vo-mcp", "dist", "runner-supervisor.js");
1430
+ var PACKAGE_REL = join2("node_modules", "@algosuite", "vo-mcp", "package.json");
1431
+ var CREDENTIAL_HELPER_REL = join2("node_modules", "@algosuite", "vo-mcp", "dist", "supervisor-credential-helper.js");
1432
+ var MANIFEST_FILE = "runtime-manifest.json";
1433
+ function within(parent, candidate) {
1434
+ const rel = relative2(resolve3(parent), resolve3(candidate));
1435
+ return rel === "" || !rel.startsWith("..") && !isAbsolute2(rel);
1436
+ }
1437
+ var APP_IDENTIFIER = "ai.algosuite.vo-runner";
1438
+ var RUNNER_RUNTIME_DIR = "runner-runtime";
1439
+ function defaultRuntimeRoot({ platform = process.platform, env = process.env, home = homedir() } = {}) {
1440
+ if (platform === "win32") {
1441
+ const appData = String(env.APPDATA || "").trim();
1442
+ return appData && isAbsolute2(appData) ? join2(appData, APP_IDENTIFIER, RUNNER_RUNTIME_DIR) : null;
1443
+ }
1444
+ if (!home) return null;
1445
+ if (platform === "darwin") {
1446
+ return join2(home, "Library", "Application Support", APP_IDENTIFIER, RUNNER_RUNTIME_DIR);
1447
+ }
1448
+ const xdg = String(env.XDG_CONFIG_HOME || "").trim();
1449
+ const base = xdg && isAbsolute2(xdg) ? xdg : join2(home, ".config");
1450
+ return join2(base, APP_IDENTIFIER, RUNNER_RUNTIME_DIR);
1451
+ }
1452
+ function runtimeRootFromEnv(env = process.env, { platform, home } = {}) {
1453
+ const value = String(env.VO_RUNNER_RUNTIME_ROOT || "").trim();
1454
+ if (value) return isAbsolute2(value) ? resolve3(value) : null;
1455
+ const derived = defaultRuntimeRoot({ platform, env, home });
1456
+ return derived ? resolve3(derived) : null;
1457
+ }
1458
+ function hashFileSha512(file) {
1459
+ return `sha512-${createHash3("sha512").update(readFileSync3(file)).digest("base64")}`;
1460
+ }
1461
+ function hashRuntimeTree(root) {
1462
+ const hasher = createHash3("sha512");
1463
+ const files = [];
1464
+ const visit = (directory, prefix = "") => {
1465
+ const rootStat = lstatSync2(directory);
1466
+ if (rootStat.isSymbolicLink()) throw new Error("runtime tree contains a link/reparse point");
1467
+ if (!rootStat.isDirectory()) throw new Error("runtime tree root is not a directory");
1468
+ for (const name of readdirSync2(directory).sort((a, b) => Buffer.compare(Buffer.from(a), Buffer.from(b)))) {
1469
+ const absolute = join2(directory, name);
1470
+ const relativePath = prefix ? `${prefix}/${name}` : name;
1471
+ const stat = lstatSync2(absolute);
1472
+ if (stat.isSymbolicLink()) throw new Error("runtime tree contains a link/reparse point");
1473
+ if (stat.isDirectory()) visit(absolute, relativePath);
1474
+ else if (stat.isFile() && relativePath !== MANIFEST_FILE) files.push({ absolute, relativePath, size: stat.size });
1475
+ else if (!stat.isFile()) throw new Error("runtime tree contains a non-regular file");
1476
+ }
1477
+ };
1478
+ visit(root);
1479
+ files.sort((a, b) => Buffer.compare(Buffer.from(a.relativePath), Buffer.from(b.relativePath)));
1480
+ for (const file of files) {
1481
+ const pathBytes = Buffer.from(file.relativePath, "utf8");
1482
+ hasher.update(`${pathBytes.length}:`);
1483
+ hasher.update(pathBytes);
1484
+ hasher.update(`:${file.size}:`);
1485
+ hasher.update(readFileSync3(file.absolute));
1486
+ hasher.update("\n");
1487
+ }
1488
+ return `sha512-${hasher.digest("base64")}`;
1489
+ }
1490
+ function atomicWriteJson(file, value) {
1491
+ mkdirSync(dirname2(file), { recursive: true });
1492
+ const temp = join2(dirname2(file), `.${randomUUID()}.tmp`);
1493
+ const fd = openSync(temp, "wx", 384);
1494
+ try {
1495
+ writeFileSync(fd, `${JSON.stringify(value, null, 2)}
1496
+ `, "utf8");
1497
+ fsyncSync(fd);
1498
+ } finally {
1499
+ closeSync(fd);
1500
+ }
1501
+ try {
1502
+ renameSync(temp, file);
1503
+ if (process.platform !== "win32") {
1504
+ try {
1505
+ const parentFd = openSync(dirname2(file), "r");
1506
+ try {
1507
+ fsyncSync(parentFd);
1508
+ } finally {
1509
+ closeSync(parentFd);
1510
+ }
1511
+ } catch {
1512
+ }
1513
+ }
1514
+ } finally {
1515
+ rmSync(temp, { force: true });
1516
+ }
1517
+ }
1518
+ function readActivation(runtimeRoot) {
1519
+ const file = join2(runtimeRoot, "current.json");
1520
+ if (!existsSync3(file)) return null;
1521
+ try {
1522
+ const value = JSON.parse(readFileSync3(file, "utf8"));
1523
+ return value?.schema_version === 1 ? value : null;
1524
+ } catch {
1525
+ return null;
1526
+ }
1527
+ }
1528
+ function slotPaths(runtimeRoot, slotId) {
1529
+ if (!SLOT_ID_RE.test(slotId)) throw new Error("invalid runtime slot id");
1530
+ const slotRoot = join2(runtimeRoot, "slots", slotId);
1531
+ return {
1532
+ slotRoot,
1533
+ entry: join2(slotRoot, ENTRY_REL),
1534
+ supervisor: join2(slotRoot, SUPERVISOR_REL),
1535
+ packageJson: join2(slotRoot, PACKAGE_REL),
1536
+ credentialHelper: join2(slotRoot, CREDENTIAL_HELPER_REL),
1537
+ manifest: join2(slotRoot, MANIFEST_FILE)
1538
+ };
1539
+ }
1540
+ function validActive(active) {
1541
+ 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);
1542
+ }
1543
+ function validateSlot(runtimeRoot, active) {
1544
+ if (!validActive(active)) return { ok: false, detail: "invalid activation metadata" };
1545
+ const paths = slotPaths(runtimeRoot, active.slot_id);
1546
+ try {
1547
+ const manifest = JSON.parse(readFileSync3(paths.manifest, "utf8"));
1548
+ const pkg = JSON.parse(readFileSync3(paths.packageJson, "utf8"));
1549
+ const expected = {
1550
+ slot_id: active.slot_id,
1551
+ version: active.version,
1552
+ integrity: active.integrity,
1553
+ entry_sha512: active.entry_sha512,
1554
+ supervisor_sha512: active.supervisor_sha512,
1555
+ tree_sha512: active.tree_sha512
1556
+ };
1557
+ for (const [key, value] of Object.entries(expected)) {
1558
+ if (manifest?.[key] !== value) return { ok: false, detail: `manifest ${key} mismatch` };
1559
+ }
1560
+ if (manifest?.schema_version !== 1 || pkg?.name !== "@algosuite/vo-mcp" || pkg?.version !== active.version) {
1561
+ return { ok: false, detail: "package identity mismatch" };
1562
+ }
1563
+ if (lstatSync2(paths.entry).isSymbolicLink() || lstatSync2(paths.supervisor).isSymbolicLink() || lstatSync2(paths.credentialHelper).isSymbolicLink()) {
1564
+ return { ok: false, detail: "runtime entry cannot be a link" };
1565
+ }
1566
+ if (!within(paths.slotRoot, realpathSync(paths.entry)) || !within(paths.slotRoot, realpathSync(paths.supervisor)) || !within(paths.slotRoot, realpathSync(paths.credentialHelper))) {
1567
+ return { ok: false, detail: "runtime entry escaped its slot" };
1568
+ }
1569
+ if (hashFileSha512(paths.entry) !== active.entry_sha512) return { ok: false, detail: "entry hash mismatch" };
1570
+ if (hashFileSha512(paths.supervisor) !== active.supervisor_sha512) return { ok: false, detail: "supervisor hash mismatch" };
1571
+ if (hashRuntimeTree(paths.slotRoot) !== active.tree_sha512) return { ok: false, detail: "runtime tree hash mismatch" };
1572
+ return { ok: true, paths, manifest };
1573
+ } catch (error) {
1574
+ return { ok: false, detail: error instanceof Error ? error.message : String(error) };
1575
+ }
1576
+ }
1577
+ function journalActivation(runtimeRoot, actionId, state, detail = "") {
1578
+ if (!ACTION_ID_RE.test(actionId)) throw new Error("invalid runner action id");
1579
+ atomicWriteJson(join2(runtimeRoot, "transactions", `${actionId}.json`), {
1580
+ schema_version: 1,
1581
+ action_id: actionId,
1582
+ state,
1583
+ detail: String(detail).slice(0, 400),
1584
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
1585
+ });
1586
+ }
1587
+ function activateSlot(runtimeRoot, active, action) {
1588
+ if (!validActive(active)) throw new Error("cannot activate invalid runtime metadata");
1589
+ if (!ACTION_ID_RE.test(action.actionId)) throw new Error("invalid runner action id");
1590
+ if (!UUID_RE.test(String(action.supervisorInstanceId || ""))) throw new Error("invalid claiming supervisor instance id");
1591
+ const validated = validateSlot(runtimeRoot, active);
1592
+ if (!validated.ok) throw new Error(`cannot activate invalid runtime slot: ${validated.detail}`);
1593
+ const current = readActivation(runtimeRoot);
1594
+ if (current?.pending) throw new Error("another runtime activation is still pending");
1595
+ const pointer = {
1596
+ schema_version: 1,
1597
+ generation: randomUUID(),
1598
+ active,
1599
+ previous: validActive(current?.active) ? current.active : null,
1600
+ pending: {
1601
+ action_id: action.actionId,
1602
+ runner_id: String(action.runnerId || ""),
1603
+ operator_id: String(action.operatorId || ""),
1604
+ supervisor_instance_id: action.supervisorInstanceId,
1605
+ activated_at: (/* @__PURE__ */ new Date()).toISOString(),
1606
+ ack_attempts: 0
1607
+ }
1608
+ };
1609
+ journalActivation(runtimeRoot, action.actionId, "prepared", `${active.version} ${active.integrity}`);
1610
+ atomicWriteJson(join2(runtimeRoot, "current.json"), pointer);
1611
+ return pointer;
1612
+ }
1613
+ function activationSupervisorInstanceId(runtimeRoot, fallback) {
1614
+ const current = runtimeRoot ? readActivation(runtimeRoot) : null;
1615
+ const pending = String(current?.pending?.supervisor_instance_id || "");
1616
+ if (UUID_RE.test(pending)) return pending;
1617
+ return UUID_RE.test(String(fallback || "")) ? fallback : null;
1618
+ }
1619
+ function recordActivationRetry(runtimeRoot, pointer, detail) {
1620
+ const current = readActivation(runtimeRoot);
1621
+ if (current?.generation !== pointer?.generation || current?.pending?.action_id !== pointer?.pending?.action_id) {
1622
+ throw new Error("runtime activation generation changed before retry");
1623
+ }
1624
+ const attempts = Math.max(0, Number(current.pending.ack_attempts || 0)) + 1;
1625
+ const updated = {
1626
+ ...current,
1627
+ pending: { ...current.pending, ack_attempts: attempts, last_error: String(detail).slice(0, 240) }
1628
+ };
1629
+ atomicWriteJson(join2(runtimeRoot, "current.json"), updated);
1630
+ journalActivation(runtimeRoot, current.pending.action_id, "ack-retry", `attempt ${attempts}: ${detail}`);
1631
+ return updated;
1632
+ }
1633
+ function attestCurrentSupervisor({ runtimeRoot, selfPath: selfPath2, version }) {
1634
+ const pointer = readActivation(runtimeRoot);
1635
+ if (!pointer?.pending || !validActive(pointer.active)) return { ok: false, detail: "no pending activation" };
1636
+ const validated = validateSlot(runtimeRoot, pointer.active);
1637
+ if (!validated.ok) return validated;
1638
+ try {
1639
+ if (realpathSync(selfPath2) !== realpathSync(validated.paths.supervisor)) {
1640
+ return { ok: false, detail: "running supervisor is not the activated supervisor" };
1641
+ }
1642
+ } catch {
1643
+ return { ok: false, detail: "could not resolve running supervisor path" };
1644
+ }
1645
+ if (version !== pointer.active.version) return { ok: false, detail: "running supervisor version mismatch" };
1646
+ return { ok: true, pointer, active: pointer.active, paths: validated.paths };
1647
+ }
1648
+ function finalizeActivation(runtimeRoot, pointer) {
1649
+ const current = readActivation(runtimeRoot);
1650
+ if (current?.generation !== pointer?.generation || current?.pending?.action_id !== pointer?.pending?.action_id) {
1651
+ throw new Error("runtime activation generation changed before finalization");
1652
+ }
1653
+ journalActivation(runtimeRoot, pointer.pending.action_id, "attesting", `${pointer.active.version} verified`);
1654
+ atomicWriteJson(join2(runtimeRoot, "current.json"), {
1655
+ schema_version: 1,
1656
+ generation: pointer.generation,
1657
+ active: pointer.active,
1658
+ previous: pointer.previous || null,
1659
+ pending: null
1660
+ });
1661
+ try {
1662
+ journalActivation(runtimeRoot, pointer.pending.action_id, "attested", `${pointer.active.version} active`);
1663
+ } catch {
1664
+ }
1665
+ }
1666
+ function rollbackActivation(runtimeRoot, pointer, detail) {
1667
+ const current = readActivation(runtimeRoot);
1668
+ if (current?.generation !== pointer?.generation || current?.pending?.action_id !== pointer?.pending?.action_id) {
1669
+ throw new Error("runtime activation generation changed before rollback");
1670
+ }
1671
+ journalActivation(runtimeRoot, pointer.pending.action_id, "rolling-back", detail);
1672
+ const rolledBack = {
1673
+ schema_version: 1,
1674
+ generation: randomUUID(),
1675
+ active: validActive(pointer?.previous) ? pointer.previous : null,
1676
+ previous: null,
1677
+ pending: {
1678
+ ...pointer.pending,
1679
+ terminal_status: "failed",
1680
+ terminal_detail: String(detail).slice(0, 400),
1681
+ rolled_back_at: (/* @__PURE__ */ new Date()).toISOString()
1682
+ }
1683
+ };
1684
+ atomicWriteJson(join2(runtimeRoot, "current.json"), rolledBack);
1685
+ try {
1686
+ journalActivation(runtimeRoot, pointer.pending.action_id, "rolled-back", detail);
1687
+ } catch {
1688
+ }
1689
+ return rolledBack;
1690
+ }
1691
+ function acknowledgeActivationFailure(runtimeRoot, pointer) {
1692
+ const current = readActivation(runtimeRoot);
1693
+ if (current?.generation !== pointer?.generation || current?.pending?.action_id !== pointer?.pending?.action_id || current?.pending?.terminal_status !== "failed") {
1694
+ throw new Error("runtime rollback acknowledgement obligation changed");
1695
+ }
1696
+ atomicWriteJson(join2(runtimeRoot, "current.json"), { ...current, pending: null });
1697
+ try {
1698
+ journalActivation(runtimeRoot, pointer.pending.action_id, "failure-acknowledged", pointer.pending.terminal_detail);
1699
+ } catch {
1700
+ }
1701
+ }
1702
+
1703
+ // src/runner/bundled-runtime-updater.mjs
1704
+ var PACKAGE_NAME = "@algosuite/vo-mcp";
1705
+ var PACKAGE_SPEC_RE2 = /^@algosuite\/vo-mcp@\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/u;
1706
+ var INTEGRITY_RE2 = /^sha512-[A-Za-z0-9+/]{86}==$/u;
1707
+ var MAX_TARBALL_BYTES = 100 * 1024 * 1024;
1708
+ var PUBLIC_REGISTRY = "https://registry.npmjs.org/";
1709
+ function buildMinimalMaintenanceEnv(env = process.env, runtimeRoot = "") {
1710
+ const allowed = /* @__PURE__ */ new Set([
1711
+ "PATH",
1712
+ "Path",
1713
+ "path",
1714
+ "PATHEXT",
1715
+ "SystemRoot",
1716
+ "SYSTEMROOT",
1717
+ "WINDIR",
1718
+ "COMSPEC",
1719
+ "TEMP",
1720
+ "TMP",
1721
+ "TMPDIR",
1722
+ "HOME",
1723
+ "USERPROFILE",
1724
+ "APPDATA",
1725
+ "LOCALAPPDATA",
1726
+ "ProgramFiles",
1727
+ "ProgramFiles(x86)",
1728
+ "ProgramW6432",
1729
+ "LANG",
1730
+ "LC_ALL"
1731
+ ]);
1732
+ const clean = {};
1733
+ for (const [key, value] of Object.entries(env)) {
1734
+ if (allowed.has(key) && typeof value === "string") clean[key] = value;
1735
+ }
1736
+ return {
1737
+ ...clean,
1738
+ npm_config_ignore_scripts: "true",
1739
+ npm_config_bin_links: "false",
1740
+ npm_config_audit: "false",
1741
+ npm_config_fund: "false",
1742
+ npm_config_update_notifier: "false",
1743
+ npm_config_registry: PUBLIC_REGISTRY,
1744
+ ...runtimeRoot ? {
1745
+ npm_config_userconfig: join3(runtimeRoot, "maintenance", "user.npmrc"),
1746
+ npm_config_globalconfig: join3(runtimeRoot, "maintenance", "global.npmrc"),
1747
+ npm_config_cache: join3(runtimeRoot, "maintenance", "npm-cache")
1748
+ } : {}
1749
+ };
1750
+ }
1751
+ function defaultRun(command, args, options) {
1752
+ return spawnSync2(command, args, {
1753
+ cwd: options.cwd,
1754
+ encoding: "utf8",
1755
+ env: options.env,
1756
+ shell: false,
1757
+ windowsHide: true,
1758
+ stdio: ["ignore", "pipe", "pipe"],
1759
+ timeout: options.timeout ?? 12e4
1760
+ });
1761
+ }
1762
+ function commandRunner({ platform, execPath, env, fileExists, run }) {
1763
+ const npmCli = platform === "win32" ? resolveNpmCli({ env, execPath, fileExists }) : null;
1764
+ if (platform === "win32" && !npmCli) throw new Error("trusted npm CLI not found");
1765
+ const npmCommand = platform === "win32" ? execPath : "npm";
1766
+ const prefix = npmCli ? [npmCli] : [];
1767
+ return {
1768
+ npm(args, options) {
1769
+ return run(npmCommand, [...prefix, ...args], options);
1770
+ },
1771
+ node(args, options) {
1772
+ return run(execPath, args, options);
1773
+ }
1774
+ };
1775
+ }
1776
+ function parseJsonOutput(result, operation) {
1777
+ if (result?.status !== 0) {
1778
+ throw new Error(`${operation} failed: ${String(result?.stderr || result?.error?.message || `exit ${result?.status ?? 1}`)}`);
1779
+ }
1780
+ try {
1781
+ return JSON.parse(String(result.stdout || ""));
1782
+ } catch {
1783
+ throw new Error(`${operation} returned invalid JSON`);
1784
+ }
1785
+ }
1786
+ function tarballIntegrity(file) {
1787
+ return `sha512-${createHash4("sha512").update(readFileSync4(file)).digest("base64")}`;
1788
+ }
1789
+ function assertNoLinks(root) {
1790
+ const pending = [root];
1791
+ while (pending.length) {
1792
+ const current = pending.pop();
1793
+ const stat = lstatSync3(current);
1794
+ if (stat.isSymbolicLink()) throw new Error("installed runtime contains a link/reparse point");
1795
+ if (!stat.isDirectory()) continue;
1796
+ for (const entry of readdirSync3(current)) pending.push(join3(current, entry));
1797
+ }
1798
+ }
1799
+ function validateDependencyLock(payloadRoot, expected) {
1800
+ const lock = JSON.parse(readFileSync4(join3(payloadRoot, "package-lock.json"), "utf8"));
1801
+ if (Number(lock.lockfileVersion) < 3 || !lock.packages || typeof lock.packages !== "object") {
1802
+ throw new Error("runtime dependency lock is missing or unsupported");
1803
+ }
1804
+ let foundPackage = false;
1805
+ for (const [key, item] of Object.entries(lock.packages)) {
1806
+ if (!key) continue;
1807
+ if (item?.link === true) throw new Error(`runtime dependency lock contains link: ${key}`);
1808
+ const isRunner = key.replaceAll("\\", "/").endsWith("node_modules/@algosuite/vo-mcp");
1809
+ if (isRunner) {
1810
+ foundPackage = item.version === expected.version && item.integrity === expected.integrity;
1811
+ continue;
1812
+ }
1813
+ if (!INTEGRITY_RE2.test(String(item?.integrity || ""))) throw new Error(`dependency lacks sha512 integrity: ${key}`);
1814
+ if (!String(item?.resolved || "").startsWith("https://registry.npmjs.org/")) {
1815
+ throw new Error(`dependency is not registry-pinned: ${key}`);
1816
+ }
1817
+ }
1818
+ if (!foundPackage) throw new Error("installed runner package does not match registry integrity");
1819
+ }
1820
+ function writeAuthorizedInstallSeed(payloadRoot, tarball, runtimeAuthorization) {
1821
+ const authorization = validateRuntimeAuthorization(runtimeAuthorization);
1822
+ const stagingRoot = resolve4(payloadRoot, "..", "..");
1823
+ const tarballFromStaging = relative3(stagingRoot, resolve4(tarball));
1824
+ if (!tarballFromStaging || tarballFromStaging === ".." || tarballFromStaging.startsWith(`..${sep2}`) || isAbsolute3(tarballFromStaging)) {
1825
+ throw new Error("authorized runtime tarball escaped staging root");
1826
+ }
1827
+ const relativeTarball = relative3(payloadRoot, resolve4(tarball)).replaceAll("\\", "/");
1828
+ if (!relativeTarball || isAbsolute3(relativeTarball) || relativeTarball.includes("\n") || relativeTarball.includes("\r")) {
1829
+ throw new Error("authorized runtime tarball path invalid");
1830
+ }
1831
+ const fileSpec = `file:${relativeTarball}`;
1832
+ const packageRecord = {
1833
+ name: "algohq-runner-runtime",
1834
+ version: "0.0.0",
1835
+ private: true,
1836
+ dependencies: { [PACKAGE_NAME]: fileSpec }
1837
+ };
1838
+ const packages = structuredClone(authorization.dependency_lock.packages);
1839
+ packages[`node_modules/${PACKAGE_NAME}`].resolved = fileSpec;
1840
+ const lock = {
1841
+ name: packageRecord.name,
1842
+ version: packageRecord.version,
1843
+ lockfileVersion: authorization.dependency_lock.source_lockfile_version,
1844
+ requires: true,
1845
+ packages: { "": packageRecord, ...packages }
1846
+ };
1847
+ writeFileSync2(join3(payloadRoot, "package.json"), `${JSON.stringify(packageRecord)}
1848
+ `, { mode: 384 });
1849
+ writeFileSync2(join3(payloadRoot, "package-lock.json"), `${JSON.stringify(lock)}
1850
+ `, { mode: 384 });
1851
+ return { fileSpec, lock };
1852
+ }
1853
+ function buildActive(slotId, metadata, paths) {
1854
+ return {
1855
+ slot_id: slotId,
1856
+ version: metadata.version,
1857
+ integrity: metadata.integrity,
1858
+ entry_sha512: hashFileSha512(paths.entry),
1859
+ supervisor_sha512: hashFileSha512(paths.supervisor),
1860
+ tree_sha512: hashRuntimeTree(paths.slotRoot ?? paths.payloadRoot)
1861
+ };
1862
+ }
1863
+ function installSlot({
1864
+ runtimeRoot,
1865
+ metadata,
1866
+ tarball,
1867
+ runner,
1868
+ npmEnv,
1869
+ runOptions,
1870
+ force,
1871
+ runtimeAuthorization
1872
+ }) {
1873
+ const digest = createHash4("sha256").update(metadata.integrity).digest("hex").slice(0, 16);
1874
+ const suffix = force ? `${digest}-${randomUUID2().slice(0, 8)}` : digest;
1875
+ const slotId = `vo-mcp-${metadata.version}-${suffix}`;
1876
+ const finalPaths = slotPaths(runtimeRoot, slotId);
1877
+ if (!force && existsSync4(finalPaths.slotRoot)) {
1878
+ const manifest = JSON.parse(readFileSync4(finalPaths.manifest, "utf8"));
1879
+ const active = buildActive(slotId, metadata, finalPaths);
1880
+ const validated = validateSlot(runtimeRoot, active);
1881
+ if (validated.ok && manifest.integrity === metadata.integrity) {
1882
+ if (runtimeAuthorization) {
1883
+ validateStagedRuntimeAuthorization({
1884
+ payloadRoot: finalPaths.slotRoot,
1885
+ authorization: runtimeAuthorization
1886
+ });
1887
+ }
1888
+ return { active, created: false };
1889
+ }
1890
+ }
1891
+ const staging = join3(runtimeRoot, "staging", randomUUID2());
1892
+ const payload = join3(staging, "payload");
1893
+ let installedSlot = false;
1894
+ try {
1895
+ mkdirSync2(payload, { recursive: true });
1896
+ let installArgs;
1897
+ if (runtimeAuthorization) {
1898
+ writeAuthorizedInstallSeed(payload, tarball, runtimeAuthorization);
1899
+ installArgs = [
1900
+ "ci",
1901
+ "--ignore-scripts",
1902
+ "--no-bin-links",
1903
+ "--no-audit",
1904
+ "--no-fund",
1905
+ `--registry=${PUBLIC_REGISTRY}`
1906
+ ];
1907
+ } else {
1908
+ writeFileSync2(join3(payload, "package.json"), `${JSON.stringify({
1909
+ name: "algohq-runner-runtime",
1910
+ version: "0.0.0",
1911
+ private: true
1912
+ })}
1913
+ `);
1914
+ installArgs = [
1915
+ "install",
1916
+ "--ignore-scripts",
1917
+ "--no-bin-links",
1918
+ "--no-audit",
1919
+ "--no-fund",
1920
+ "--package-lock=true",
1921
+ "--save-exact",
1922
+ `--registry=${PUBLIC_REGISTRY}`,
1923
+ tarball
1924
+ ];
1925
+ }
1926
+ const install = runner.npm(
1927
+ installArgs,
1928
+ { ...runOptions, cwd: payload, env: npmEnv, timeout: 18e4 }
1929
+ );
1930
+ if (install.status !== 0) throw new Error(`npm install failed: ${install.stderr || install.error?.message || install.status}`);
1931
+ assertNoLinks(payload);
1932
+ validateDependencyLock(payload, metadata);
1933
+ if (runtimeAuthorization) {
1934
+ validateStagedRuntimeAuthorization({ payloadRoot: payload, authorization: runtimeAuthorization });
1935
+ }
1936
+ const stagedPaths = {
1937
+ entry: join3(payload, "node_modules", "@algosuite", "vo-mcp", "bin", "vo-mcp"),
1938
+ supervisor: join3(payload, "node_modules", "@algosuite", "vo-mcp", "dist", "runner-supervisor.js"),
1939
+ packageJson: join3(payload, "node_modules", "@algosuite", "vo-mcp", "package.json"),
1940
+ credentialHelper: join3(payload, "node_modules", "@algosuite", "vo-mcp", "dist", "supervisor-credential-helper.js"),
1941
+ slotRoot: payload
1942
+ };
1943
+ const pkg = JSON.parse(readFileSync4(stagedPaths.packageJson, "utf8"));
1944
+ if (pkg.name !== PACKAGE_NAME || pkg.version !== metadata.version) throw new Error("installed package identity mismatch");
1945
+ if (!lstatSync3(stagedPaths.credentialHelper).isFile()) throw new Error("installed credential helper is missing");
1946
+ const smoke = runner.node([stagedPaths.entry, "runner", "--version"], { ...runOptions, cwd: payload, env: npmEnv, timeout: 3e4 });
1947
+ if (smoke.status !== 0 || String(smoke.stdout || "").trim() !== `vo-mcp runner ${metadata.version}`) {
1948
+ throw new Error("bundled runtime smoke check failed");
1949
+ }
1950
+ const active = buildActive(slotId, metadata, stagedPaths);
1951
+ atomicWriteJson(join3(payload, "runtime-manifest.json"), { schema_version: 1, ...active });
1952
+ mkdirSync2(join3(runtimeRoot, "slots"), { recursive: true });
1953
+ if (existsSync4(finalPaths.slotRoot)) throw new Error("immutable runtime slot already exists");
1954
+ renameSync2(payload, finalPaths.slotRoot);
1955
+ installedSlot = true;
1956
+ const validated = validateSlot(runtimeRoot, active);
1957
+ if (!validated.ok) throw new Error(`staged runtime validation failed: ${validated.detail}`);
1958
+ return { active, created: true };
1959
+ } catch (error) {
1960
+ if (installedSlot) rmSync2(finalPaths.slotRoot, { recursive: true, force: true });
1961
+ throw error;
1962
+ } finally {
1963
+ rmSync2(staging, { recursive: true, force: true });
1964
+ }
1965
+ }
1966
+ function stageBundledRuntimeSlot(options) {
1967
+ const {
1968
+ runtimeRoot,
1969
+ packageSpec,
1970
+ expectedVersion,
1971
+ expectedIntegrity,
1972
+ platform = process.platform,
1973
+ execPath = process.execPath,
1974
+ env = process.env,
1975
+ fileExists = existsSync4,
1976
+ run = defaultRun,
1977
+ force = false,
1978
+ runtimeAuthorization = null
1979
+ } = options;
1980
+ if (!runtimeRoot || !isAbsolute3(runtimeRoot)) return { ok: false, status: 2, detail: "bundled runtime root unavailable" };
1981
+ if (!expectedVersion || !PACKAGE_SPEC_RE2.test(`${PACKAGE_NAME}@${expectedVersion}`)) {
1982
+ return { ok: false, status: 2, detail: "invalid expected runner version" };
1983
+ }
1984
+ if (!expectedIntegrity || !INTEGRITY_RE2.test(expectedIntegrity)) {
1985
+ return { ok: false, status: 2, detail: "invalid expected runner integrity" };
1986
+ }
1987
+ const exactSpec = `${PACKAGE_NAME}@${expectedVersion}`;
1988
+ if (packageSpec !== exactSpec) return { ok: false, status: 2, detail: "runner package spec does not match authorized version" };
1989
+ const resolvedRoot = resolve4(runtimeRoot);
1990
+ const npmEnv = buildMinimalMaintenanceEnv(env, resolvedRoot);
1991
+ const runOptions = { env: npmEnv, cwd: resolvedRoot };
1992
+ let tarDir = null;
1993
+ try {
1994
+ mkdirSync2(resolvedRoot, { recursive: true });
1995
+ mkdirSync2(join3(resolvedRoot, "maintenance"), { recursive: true });
1996
+ writeFileSync2(npmEnv.npm_config_userconfig, "", { mode: 384 });
1997
+ writeFileSync2(npmEnv.npm_config_globalconfig, "", { mode: 384 });
1998
+ const runner = commandRunner({ platform, execPath, env: npmEnv, fileExists, run });
1999
+ const metadata = { version: expectedVersion, integrity: expectedIntegrity };
2000
+ tarDir = join3(resolvedRoot, "staging", randomUUID2());
2001
+ mkdirSync2(tarDir, { recursive: true });
2002
+ const packed = parseJsonOutput(runner.npm([
2003
+ "pack",
2004
+ exactSpec,
2005
+ "--ignore-scripts",
2006
+ "--json",
2007
+ "--pack-destination",
2008
+ tarDir,
2009
+ `--registry=${PUBLIC_REGISTRY}`
2010
+ ], runOptions), "npm pack");
2011
+ const record = Array.isArray(packed) ? packed[0] : packed;
2012
+ const tarball = join3(tarDir, basename(String(record?.filename || "")));
2013
+ if (!existsSync4(tarball) || !basename(tarball).endsWith(".tgz")) throw new Error("npm pack returned no tarball");
2014
+ if (lstatSync3(tarball).size > MAX_TARBALL_BYTES) throw new Error("runner package tarball exceeds size limit");
2015
+ if (record.integrity !== metadata.integrity || tarballIntegrity(tarball) !== metadata.integrity) {
2016
+ throw new Error("runner package sha512 integrity mismatch");
2017
+ }
2018
+ const installed = installSlot({
2019
+ runtimeRoot: resolvedRoot,
2020
+ metadata,
2021
+ tarball,
2022
+ runner,
2023
+ npmEnv,
2024
+ runOptions,
2025
+ force,
2026
+ runtimeAuthorization
2027
+ });
2028
+ return { ok: true, status: 0, ...installed };
2029
+ } catch (error) {
2030
+ return { ok: false, status: 1, detail: sanitizeMaintenanceDiagnostic(error instanceof Error ? error.message : String(error), env) };
2031
+ } finally {
2032
+ if (tarDir) rmSync2(tarDir, { recursive: true, force: true });
2033
+ }
2034
+ }
2035
+ function stageAndActivateBundledUpdate(options) {
2036
+ const staged = stageBundledRuntimeSlot(options);
2037
+ if (!staged.ok) return staged;
2038
+ try {
2039
+ activateSlot(resolve4(options.runtimeRoot), staged.active, options.action);
2040
+ return { ...staged, handoff: true };
2041
+ } catch (error) {
2042
+ return {
2043
+ ok: false,
2044
+ status: 1,
2045
+ detail: sanitizeMaintenanceDiagnostic(error instanceof Error ? error.message : String(error), options.env ?? process.env)
2046
+ };
2047
+ }
2048
+ }
2049
+
2050
+ // ../../scripts/virtual-office/code-runner/legacy-orphan-sweep.mjs
2051
+ import { spawnSync as spawnSync4 } from "node:child_process";
2052
+
2053
+ // ../../scripts/virtual-office/code-runner/orphan-agent-reaper.mjs
2054
+ import { spawnSync as spawnSync3 } from "node:child_process";
2055
+ import { existsSync as existsSync5, mkdirSync as mkdirSync3, readdirSync as readdirSync4, readFileSync as readFileSync5, rmSync as rmSync3, writeFileSync as writeFileSync3 } from "node:fs";
2056
+ import os from "node:os";
2057
+ import path from "node:path";
2058
+ function windowsSystemRoot(env = process.env) {
2059
+ return env.SystemRoot || env.WINDIR || "C:\\Windows";
2060
+ }
2061
+ function windowsPowershellExe(env = process.env) {
2062
+ return path.join(windowsSystemRoot(env), "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
2063
+ }
2064
+ function killProcessTree(pid, { platform = process.platform, spawn: spawn2 = spawnSync3, env = process.env } = {}) {
2065
+ if (!Number.isInteger(pid) || pid <= 0) return false;
2066
+ if (platform === "win32") {
2067
+ const taskkill = path.join(windowsSystemRoot(env), "System32", "taskkill.exe");
2068
+ const r = spawn2(taskkill, ["/PID", String(pid), "/T", "/F"], { windowsHide: true, stdio: "ignore", timeout: 15e3 });
2069
+ return !r.error && r.status === 0;
2070
+ }
2071
+ try {
2072
+ process.kill(-pid, "SIGKILL");
2073
+ return true;
2074
+ } catch {
2075
+ try {
2076
+ process.kill(pid, "SIGKILL");
2077
+ return true;
2078
+ } catch {
2079
+ return false;
2080
+ }
2081
+ }
2082
+ }
2083
+
2084
+ // ../../scripts/virtual-office/code-runner/legacy-orphan-sweep.mjs
2085
+ var MAX_LEGACY_KILLS = 50;
2086
+ var SWEEP_RECENCY_BUFFER_MS = 5e3;
2087
+ var SIGNATURES = [
2088
+ {
2089
+ signature: "claude-headless",
2090
+ // claude-args.mjs always emits `-p --output-format stream-json --verbose`.
2091
+ test: (cl) => /(?:^|[\\/"\s])claude(?:\.exe|\.cmd|\.ps1)?(?:"|\s)/iu.test(cl) && /--output-format[\s"=]+stream-json/iu.test(cl)
2092
+ },
2093
+ {
2094
+ signature: "codex-headless",
2095
+ // openai-compatible-runner always emits `exec --json`.
2096
+ test: (cl) => /(?:^|[\\/"\s])codex(?:\.exe|\.cmd|\.ps1)?(?:"|\s)/iu.test(cl) && /\bexec\b/u.test(cl) && /--json\b/u.test(cl)
2097
+ }
2098
+ ];
2099
+ function matchAgentSignature(commandLine) {
2100
+ if (typeof commandLine !== "string" || !commandLine) return null;
2101
+ for (const { signature, test } of SIGNATURES) {
2102
+ if (test(commandLine)) return signature;
2103
+ }
2104
+ return null;
2105
+ }
2106
+ function selectLegacyOrphans({ processes, cutoffMs, protectedPids = /* @__PURE__ */ new Set() }) {
2107
+ const byPid = /* @__PURE__ */ new Map();
2108
+ for (const proc of processes) {
2109
+ if (Number.isInteger(proc?.pid) && proc.pid > 0) byPid.set(proc.pid, proc);
2110
+ }
2111
+ const kills = [];
2112
+ for (const proc of byPid.values()) {
2113
+ if (protectedPids.has(proc.pid)) continue;
2114
+ if (!(Number.isFinite(proc.creationMs) && proc.creationMs < cutoffMs)) continue;
2115
+ const signature = matchAgentSignature(proc.commandLine);
2116
+ if (!signature) continue;
2117
+ const parent = Number.isInteger(proc.ppid) && proc.ppid > 0 ? byPid.get(proc.ppid) : void 0;
2118
+ const parentDead = !parent || Number.isFinite(parent.creationMs) && parent.creationMs > proc.creationMs;
2119
+ if (!parentDead) continue;
2120
+ kills.push({ pid: proc.pid, creationMs: proc.creationMs, signature, commandLine: proc.commandLine });
2121
+ }
2122
+ kills.sort((a, b) => a.creationMs - b.creationMs);
2123
+ return { kills: kills.slice(0, MAX_LEGACY_KILLS) };
2124
+ }
2125
+ function parsePosixSweepLine(line, nowMs) {
2126
+ const match = /^\s*(\d+)\s+(\d+)\s+(\d+)\s+(.+)$/u.exec(line ?? "");
2127
+ if (!match) return null;
2128
+ const pid = Number(match[1]);
2129
+ if (!Number.isInteger(pid) || pid <= 0) return null;
2130
+ return {
2131
+ pid,
2132
+ ppid: Number(match[2]),
2133
+ creationMs: nowMs - Number(match[3]) * 1e3,
2134
+ commandLine: match[4]
2135
+ };
2136
+ }
2137
+ function listProcessesForSweep({ platform = process.platform, spawn: spawn2 = spawnSync4, nowMs = Date.now(), env = process.env, warn = console.warn } = {}) {
2138
+ const rows = [];
2139
+ if (platform === "win32") {
2140
+ const ps = "Get-CimInstance Win32_Process | Where-Object { $_.CreationDate } | ForEach-Object { @{ p = $_.ProcessId; pp = $_.ParentProcessId; c = (([DateTimeOffset]$_.CreationDate.ToUniversalTime()).ToUnixTimeMilliseconds()); cl = [string]$_.CommandLine } | ConvertTo-Json -Compress }";
2141
+ const result2 = spawn2(windowsPowershellExe(env), ["-NoProfile", "-NonInteractive", "-Command", ps], {
2142
+ windowsHide: true,
2143
+ encoding: "utf8",
2144
+ timeout: 3e4,
2145
+ maxBuffer: 64 * 1024 * 1024
2146
+ });
2147
+ if (result2.error || result2.status !== 0) {
2148
+ const cause = result2.error ? result2.error.message : `powershell exit ${result2.status}`;
2149
+ warn(`[orphan-sweep] process enumeration failed (${cause}); sweeping nothing this cycle`);
2150
+ throw new Error(`process enumeration failed (${cause}); swept nothing`);
2151
+ }
2152
+ for (const line of String(result2.stdout ?? "").split(/\r?\n/u)) {
2153
+ if (!line.trim()) continue;
2154
+ try {
2155
+ const parsed = JSON.parse(line);
2156
+ const pid = Number(parsed?.p);
2157
+ if (!Number.isInteger(pid) || pid <= 0) continue;
2158
+ rows.push({
2159
+ pid,
2160
+ ppid: Number(parsed.pp),
2161
+ creationMs: Number(parsed.c),
2162
+ commandLine: typeof parsed.cl === "string" ? parsed.cl : ""
2163
+ });
2164
+ } catch {
2165
+ }
2166
+ }
2167
+ return rows;
2168
+ }
2169
+ const result = spawn2("ps", ["-eo", "pid=,ppid=,etimes=,args="], { encoding: "utf8", timeout: 3e4, maxBuffer: 64 * 1024 * 1024 });
2170
+ if (result.error || result.status !== 0) {
2171
+ const cause = result.error ? result.error.message : `ps exit ${result.status}`;
2172
+ warn(`[orphan-sweep] process enumeration failed (${cause}); sweeping nothing this cycle`);
2173
+ throw new Error(`process enumeration failed (${cause}); swept nothing`);
2174
+ }
2175
+ for (const line of String(result.stdout ?? "").split("\n")) {
2176
+ const row = parsePosixSweepLine(line, nowMs);
2177
+ if (row) rows.push(row);
2178
+ }
2179
+ return rows;
2180
+ }
2181
+ function runLegacyOrphanSweep({
2182
+ nowMs = Date.now(),
2183
+ protectedPids = [process.pid],
2184
+ listProcesses = listProcessesForSweep,
2185
+ killTree = killProcessTree,
2186
+ log = () => {
2187
+ }
2188
+ } = {}) {
2189
+ try {
2190
+ const processes = listProcesses({ nowMs });
2191
+ const { kills } = selectLegacyOrphans({
2192
+ processes,
2193
+ cutoffMs: nowMs - SWEEP_RECENCY_BUFFER_MS,
2194
+ protectedPids: new Set(protectedPids)
2195
+ });
2196
+ const killed = [];
2197
+ for (const kill of kills) {
2198
+ const done = killTree(kill.pid);
2199
+ log(`legacy-orphan-sweep ${done ? "killed" : "FAILED to kill"} pid=${kill.pid} sig=${kill.signature} cmd=${String(kill.commandLine).slice(0, 200)}`);
2200
+ if (done) killed.push({ pid: kill.pid, signature: kill.signature });
2201
+ }
2202
+ const failed2 = kills.length - killed.length;
2203
+ const detail = kills.length === 0 ? `no orphaned agent processes matched the sweep criteria (${processes.length} scanned)` : `purged ${killed.length} of ${kills.length} orphaned agent process tree(s)${failed2 > 0 ? ` (${failed2} kill(s) failed)` : ""}: ${killed.map((k) => `${k.pid}:${k.signature}`).join(", ").slice(0, 700)}`;
2204
+ return { ok: true, status: 0, detail, killed };
2205
+ } catch (error) {
2206
+ return { ok: false, status: 1, detail: `legacy sweep error: ${error instanceof Error ? error.message : String(error)}`.slice(0, 500), killed: [] };
2207
+ }
2208
+ }
2209
+
2210
+ // src/runner/supervisor-activation.mjs
2211
+ var MAX_ACK_ATTEMPTS = 3;
2212
+ var delay = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms));
2213
+ async function waitForAuthoritativeRunnerHeartbeat({
2214
+ client,
2215
+ runnerId: runnerId2,
2216
+ operatorId,
2217
+ runnerInstanceId,
2218
+ excludeRunnerInstanceId,
2219
+ daemonVersion,
2220
+ supervisorIdentity,
2221
+ timeoutMs = 6e4,
2222
+ pollMs = 1e3
2223
+ }) {
2224
+ if (!runnerInstanceId && !excludeRunnerInstanceId) throw new Error("runner instance identity proof unavailable");
2225
+ const deadline = Date.now() + timeoutMs;
2226
+ while (Date.now() < deadline) {
2227
+ try {
2228
+ const runners = await client.getRunnerStatus({ ...operatorId ? { operatorId } : {} });
2229
+ 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));
2230
+ if (runner) return runner;
2231
+ } catch {
2232
+ }
2233
+ await delay(pollMs);
2234
+ }
2235
+ throw new Error("authoritative child heartbeat attestation timed out");
2236
+ }
2237
+ async function finishPendingActivation({
2238
+ client,
2239
+ child,
2240
+ runtimeRoot,
2241
+ operatorId,
2242
+ runnerId: runnerId2,
2243
+ selfPath: selfPath2,
2244
+ packageVersion: packageVersion2,
2245
+ supervisorIdentity,
2246
+ waitForLocalRunner: waitForLocalRunner2,
2247
+ isReadinessDeferred = () => false,
2248
+ localStatus: localStatus2,
2249
+ waitForCloudRunner = waitForAuthoritativeRunnerHeartbeat,
2250
+ cloudTimeoutMs,
2251
+ cloudPollMs,
2252
+ stopChild: stopChild2,
2253
+ launchPreviousChild,
2254
+ log = () => {
2255
+ }
2256
+ }) {
2257
+ const pointer = runtimeRoot ? readActivation(runtimeRoot) : null;
2258
+ if (!pointer?.pending) return true;
2259
+ const runnerIdForAction = pointer.pending.runner_id || runnerId2;
2260
+ const operatorIdForAction = pointer.pending.operator_id || operatorId;
2261
+ if (pointer.pending.terminal_status === "failed") {
2262
+ try {
2263
+ await client.completeRunnerControl(pointer.pending.action_id, {
2264
+ runnerId: runnerIdForAction,
2265
+ ...operatorIdForAction ? { operatorId: operatorIdForAction } : {},
2266
+ ...supervisorIdentity,
2267
+ status: "failed",
2268
+ detail: pointer.pending.terminal_detail
2269
+ });
2270
+ acknowledgeActivationFailure(runtimeRoot, pointer);
2271
+ return true;
2272
+ } catch (error) {
2273
+ log(`activation failure acknowledgement remains pending: ${error instanceof Error ? error.message : String(error)}`);
2274
+ await stopChild2(child);
2275
+ return false;
2276
+ }
2277
+ }
2278
+ let attestation;
2279
+ try {
2280
+ attestation = attestCurrentSupervisor({ runtimeRoot, selfPath: selfPath2, version: packageVersion2 });
2281
+ if (!attestation.ok) throw new Error(attestation.detail);
2282
+ if (!await waitForLocalRunner2(child)) {
2283
+ if (isReadinessDeferred(child)) return false;
2284
+ throw new Error("activated runner did not become locally ready");
2285
+ }
2286
+ } catch (error) {
2287
+ let detail = `activation attestation failed: ${error instanceof Error ? error.message : String(error)}`.slice(0, 400);
2288
+ let rolledBack = null;
2289
+ try {
2290
+ rolledBack = rollbackActivation(runtimeRoot, pointer, detail);
2291
+ } catch (rollbackError) {
2292
+ detail = `${detail}; rollback pending: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`.slice(0, 400);
2293
+ }
2294
+ try {
2295
+ await client.completeRunnerControl(pointer.pending.action_id, {
2296
+ runnerId: runnerIdForAction,
2297
+ ...operatorIdForAction ? { operatorId: operatorIdForAction } : {},
2298
+ ...supervisorIdentity,
2299
+ status: "failed",
2300
+ detail
2301
+ });
2302
+ if (rolledBack) acknowledgeActivationFailure(runtimeRoot, rolledBack);
2303
+ } catch {
2304
+ } finally {
2305
+ await stopChild2(child);
2306
+ }
2307
+ return false;
2308
+ }
2309
+ let activatedRunnerInstanceId = null;
2310
+ try {
2311
+ const status = await localStatus2();
2312
+ activatedRunnerInstanceId = status?.runnerInstanceId || null;
2313
+ await waitForCloudRunner({
2314
+ client,
2315
+ runnerId: runnerIdForAction,
2316
+ operatorId: operatorIdForAction,
2317
+ runnerInstanceId: status?.runnerInstanceId,
2318
+ daemonVersion: `vo-mcp/${attestation.active.version}`,
2319
+ supervisorIdentity,
2320
+ timeoutMs: cloudTimeoutMs,
2321
+ pollMs: cloudPollMs
2322
+ });
2323
+ await client.completeRunnerControl(pointer.pending.action_id, {
2324
+ runnerId: runnerIdForAction,
2325
+ ...operatorIdForAction ? { operatorId: operatorIdForAction } : {},
2326
+ ...supervisorIdentity,
2327
+ status: "succeeded",
2328
+ detail: `attested new supervisor ${attestation.active.version} ${attestation.active.integrity}`
2329
+ });
2330
+ finalizeActivation(runtimeRoot, attestation.pointer);
2331
+ return true;
2332
+ } catch (error) {
2333
+ const failure = `activation acknowledgement failed: ${error instanceof Error ? error.message : String(error)}`.slice(0, 400);
2334
+ let retry;
2335
+ try {
2336
+ retry = recordActivationRetry(runtimeRoot, attestation.pointer, failure);
2337
+ } catch (retryError) {
2338
+ log(`activation retry could not be recorded: ${retryError instanceof Error ? retryError.message : String(retryError)}`);
2339
+ await stopChild2(child);
2340
+ return false;
2341
+ }
2342
+ if (retry.pending.ack_attempts < MAX_ACK_ATTEMPTS) {
2343
+ log(`activation acknowledgement pending (${retry.pending.ack_attempts}/${MAX_ACK_ATTEMPTS})`);
2344
+ await stopChild2(child);
2345
+ return false;
2346
+ }
2347
+ const previous = validateSlot(runtimeRoot, retry.previous);
2348
+ let detail = `${failure}; retry limit reached; previous runtime restored`.slice(0, 400);
2349
+ let rollbackChild = null;
2350
+ let rolledBack = null;
2351
+ try {
2352
+ if (!previous.ok) throw new Error(`previous runtime invalid: ${previous.detail}`, { cause: error });
2353
+ rolledBack = rollbackActivation(runtimeRoot, retry, detail);
2354
+ await stopChild2(child);
2355
+ rollbackChild = launchPreviousChild(previous.paths.entry);
2356
+ if (!await waitForLocalRunner2(rollbackChild)) throw new Error("previous runner did not become locally ready", { cause: error });
2357
+ const status = await localStatus2();
2358
+ await waitForCloudRunner({
2359
+ client,
2360
+ runnerId: runnerIdForAction,
2361
+ operatorId: operatorIdForAction,
2362
+ runnerInstanceId: status?.runnerInstanceId,
2363
+ excludeRunnerInstanceId: status?.runnerInstanceId ? void 0 : activatedRunnerInstanceId,
2364
+ daemonVersion: `vo-mcp/${retry.previous.version}`,
2365
+ supervisorIdentity,
2366
+ timeoutMs: cloudTimeoutMs,
2367
+ pollMs: cloudPollMs
2368
+ });
2369
+ } catch (rollbackError) {
2370
+ detail = `${detail}; rollback proof failed: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`.slice(0, 400);
2371
+ }
2372
+ try {
2373
+ await client.completeRunnerControl(retry.pending.action_id, {
2374
+ runnerId: runnerIdForAction,
2375
+ ...operatorIdForAction ? { operatorId: operatorIdForAction } : {},
2376
+ ...supervisorIdentity,
2377
+ status: "failed",
2378
+ detail
2379
+ });
2380
+ if (rolledBack) acknowledgeActivationFailure(runtimeRoot, rolledBack);
2381
+ } catch {
2382
+ }
2383
+ if (rollbackChild) await stopChild2(rollbackChild);
2384
+ else await stopChild2(child);
2385
+ return false;
2386
+ }
2387
+ }
2388
+
2389
+ // src/runner/supervisor-child-entry.mjs
2390
+ var VERSION_RE2 = /^(?:[\w.-]+\/)?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/u;
2391
+ function parseRuntimeVersion(value) {
2392
+ if (typeof value !== "string") return null;
2393
+ const match = VERSION_RE2.exec(value.trim());
2394
+ if (!match) return null;
2395
+ const prerelease = match[4] ? match[4].split(".").map((part) => /^\d+$/u.test(part) ? Number(part) : part) : null;
2396
+ return { release: [Number(match[1]), Number(match[2]), Number(match[3])], prerelease };
2397
+ }
2398
+ function compareRuntimeVersions(a, b) {
2399
+ for (let i = 0; i < 3; i += 1) {
2400
+ const av = a.release[i] ?? 0;
2401
+ const bv = b.release[i] ?? 0;
2402
+ if (av !== bv) return av < bv ? -1 : 1;
2403
+ }
2404
+ if (!a.prerelease && !b.prerelease) return 0;
2405
+ if (!a.prerelease) return 1;
2406
+ if (!b.prerelease) return -1;
2407
+ const len = Math.max(a.prerelease.length, b.prerelease.length);
2408
+ for (let i = 0; i < len; i += 1) {
2409
+ const av = a.prerelease[i];
2410
+ const bv = b.prerelease[i];
2411
+ if (av === void 0) return -1;
2412
+ if (bv === void 0) return 1;
2413
+ if (av === bv) continue;
2414
+ const aNum = typeof av === "number";
2415
+ const bNum = typeof bv === "number";
2416
+ if (aNum && bNum) return av < bv ? -1 : 1;
2417
+ if (aNum !== bNum) return aNum ? -1 : 1;
2418
+ return String(av) < String(bv) ? -1 : 1;
2419
+ }
2420
+ return 0;
2421
+ }
2422
+ function resolveSupervisorChildEntry({
2423
+ runtimeRoot,
2424
+ bundledEntry,
2425
+ bundledVersion,
2426
+ readPointer = readActivation,
2427
+ validate = validateSlot
2428
+ }) {
2429
+ const bundled = (detail) => ({
2430
+ // The bundled entry is `dist/runner-cli.js`, which IS the daemon and takes
2431
+ // no subcommand. The slot entry is `bin/vo-mcp`, the multiplexed CLI, which
2432
+ // needs the `runner` subcommand — the same argv the proven rollback path
2433
+ // (`launchPreviousChild`) uses.
2434
+ args: [bundledEntry],
2435
+ entry: bundledEntry,
2436
+ source: "bundled",
2437
+ version: String(bundledVersion || "unknown"),
2438
+ descriptor: `bundled ${bundledVersion || "unknown"} ${bundledEntry}`,
2439
+ detail
2440
+ });
2441
+ if (!runtimeRoot) return bundled("no runtime root");
2442
+ let pointer;
2443
+ try {
2444
+ pointer = readPointer(runtimeRoot);
2445
+ } catch (error) {
2446
+ return bundled(`activation pointer unreadable: ${error instanceof Error ? error.message : String(error)}`);
2447
+ }
2448
+ if (!pointer?.active) return bundled("no activated runtime slot");
2449
+ if (pointer.pending) return bundled("runtime activation still pending");
2450
+ const activeVersion = parseRuntimeVersion(pointer.active.version);
2451
+ const currentVersion = parseRuntimeVersion(bundledVersion);
2452
+ if (!activeVersion) return bundled(`active slot version unparseable: ${String(pointer.active.version)}`);
2453
+ if (!currentVersion) return bundled(`bundled version unparseable: ${String(bundledVersion)}`);
2454
+ if (compareRuntimeVersions(activeVersion, currentVersion) <= 0) {
2455
+ return bundled(`active slot ${pointer.active.version} is not newer than bundled ${bundledVersion}`);
2456
+ }
2457
+ let validated;
2458
+ try {
2459
+ validated = validate(runtimeRoot, pointer.active);
2460
+ } catch (error) {
2461
+ return bundled(`slot validation threw: ${error instanceof Error ? error.message : String(error)}`);
2462
+ }
2463
+ if (!validated?.ok) return bundled(`active slot invalid: ${validated?.detail || "unknown"}`);
2464
+ const entry = validated.paths.entry;
2465
+ return {
2466
+ args: [entry, "runner"],
2467
+ entry,
2468
+ source: "active-slot",
2469
+ version: pointer.active.version,
2470
+ descriptor: `active-slot ${pointer.active.version} ${entry}`,
2471
+ detail: `activated slot ${pointer.active.slot_id}`
2472
+ };
2473
+ }
2474
+
2475
+ // src/runner/update-drain-gate.mjs
2476
+ var DEFAULT_DRAIN_CAP_MS = 45 * 60 * 1e3;
2477
+ var DEFAULT_DRAIN_CHECK_MS = 3e4;
2478
+ var MAX_DRAIN_CAP_MS = 6 * 60 * 60 * 1e3;
2479
+ var MIN_DRAIN_CHECK_MS = 1e3;
2480
+ var MAX_DRAIN_CHECK_MS = 5 * 60 * 1e3;
2481
+ var sleepMs = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms));
2482
+ function positiveNumber(raw) {
2483
+ if (raw === void 0 || raw === null || String(raw).trim() === "") return null;
2484
+ const value = Number(raw);
2485
+ return Number.isFinite(value) && value >= 0 ? value : null;
2486
+ }
2487
+ function resolveDrainCapMs(env = {}) {
2488
+ const ms = positiveNumber(env.VO_RUNNER_UPDATE_DRAIN_CAP_MS);
2489
+ if (ms !== null) return Math.min(ms, MAX_DRAIN_CAP_MS);
2490
+ const minutes = positiveNumber(env.VO_RUNNER_UPDATE_DRAIN_CAP_MIN);
2491
+ if (minutes !== null) return Math.min(minutes * 6e4, MAX_DRAIN_CAP_MS);
2492
+ return DEFAULT_DRAIN_CAP_MS;
2493
+ }
2494
+ function resolveDrainCheckMs(env = {}) {
2495
+ const ms = positiveNumber(env.VO_RUNNER_UPDATE_DRAIN_CHECK_MS);
2496
+ if (ms === null) return DEFAULT_DRAIN_CHECK_MS;
2497
+ return Math.min(Math.max(ms, MIN_DRAIN_CHECK_MS), MAX_DRAIN_CHECK_MS);
2498
+ }
2499
+ function readActiveTasks(status, { childRunning = true } = {}) {
2500
+ if (!childRunning) {
2501
+ return { count: 0, ids: [], known: true, runnerId: null, runnerInstanceId: null };
2502
+ }
2503
+ if (!status || status.ok === false) {
2504
+ return { count: 0, ids: [], known: false, runnerId: null, runnerInstanceId: null };
2505
+ }
2506
+ const ids = Array.isArray(status.activeTaskIds) ? status.activeTaskIds.map((id) => String(id)).filter(Boolean) : [];
2507
+ const reported = Number(status.activeTasks);
2508
+ const count = Number.isFinite(reported) && reported >= 0 ? reported : ids.length;
2509
+ return {
2510
+ count: Math.max(count, ids.length),
2511
+ ids,
2512
+ known: true,
2513
+ runnerId: status.runnerId ? String(status.runnerId) : null,
2514
+ runnerInstanceId: status.runnerInstanceId ? String(status.runnerInstanceId) : null
2515
+ };
2516
+ }
2517
+ function decideDrainStep({ active, elapsedMs, capMs }) {
2518
+ const busy = active.known ? active.count : "unknown";
2519
+ if (active.known && active.count === 0) return { action: "proceed", busy };
2520
+ if (elapsedMs >= capMs) return { action: "cap", busy };
2521
+ return { action: "wait", busy };
2522
+ }
2523
+ function describeBusy(active) {
2524
+ if (!active.known) return "status unreadable from a running child (assumed busy)";
2525
+ const ids = active.ids.length > 0 ? ` [${active.ids.join(", ")}]` : " [ids unavailable]";
2526
+ return `${active.count} active task(s)${ids}`;
2527
+ }
2528
+ function describeDeferral(active) {
2529
+ if (!active.known) return "status unreadable from a running child (assumed busy)";
2530
+ return `${active.count} active task(s)`;
2531
+ }
2532
+ async function awaitRunnerUpdateDrain({
2533
+ readStatus,
2534
+ isChildRunning = () => true,
2535
+ drainable = false,
2536
+ env = {},
2537
+ now = Date.now,
2538
+ sleep: sleep2 = sleepMs,
2539
+ log = () => {
2540
+ }
2541
+ } = {}) {
2542
+ const capMs = resolveDrainCapMs(env);
2543
+ const checkMs = resolveDrainCheckMs(env);
2544
+ const startedAt = now();
2545
+ let checks = 0;
2546
+ let lastReported = null;
2547
+ const snapshot = async () => {
2548
+ checks += 1;
2549
+ const childRunning = Boolean(isChildRunning());
2550
+ let status;
2551
+ try {
2552
+ status = await readStatus();
2553
+ } catch {
2554
+ status = null;
2555
+ }
2556
+ return readActiveTasks(status, { childRunning });
2557
+ };
2558
+ if (!drainable) {
2559
+ const active = await snapshot();
2560
+ if (active.known && active.count > 0) {
2561
+ return {
2562
+ proceed: false,
2563
+ detail: `deferred safely: ${active.count} active task(s); retry when the runner is idle`,
2564
+ capped: false,
2565
+ waitedMs: 0,
2566
+ checks,
2567
+ markedTaskIds: []
2568
+ };
2569
+ }
2570
+ return { proceed: true, detail: "runner idle", capped: false, waitedMs: 0, checks, markedTaskIds: [] };
2571
+ }
2572
+ for (; ; ) {
2573
+ const active = await snapshot();
2574
+ const elapsedMs = now() - startedAt;
2575
+ const step = decideDrainStep({ active, elapsedMs, capMs });
2576
+ if (step.action === "proceed") {
2577
+ if (lastReported !== null) {
2578
+ log(`update drain complete after ${Math.round(elapsedMs / 1e3)}s \u2014 runner idle, applying staged update restart`);
2579
+ }
2580
+ return { proceed: true, detail: "runner idle", capped: false, waitedMs: elapsedMs, checks, markedTaskIds: [] };
2581
+ }
2582
+ if (step.action === "cap") {
2583
+ log(`update drain cap reached after ${Math.round(elapsedMs / 1e3)}s (cap ${Math.round(capMs / 1e3)}s) \u2014 ${describeBusy(active)}; deferring update until idle`);
2584
+ return {
2585
+ proceed: false,
2586
+ capped: true,
2587
+ // This reaches the runner-control `detail` field; keep it bounded and
2588
+ // reserve task IDs for the local log above.
2589
+ detail: `deferred safely: update drain cap reached with ${describeDeferral(active)}; retry when the runner is idle`,
2590
+ waitedMs: elapsedMs,
2591
+ checks,
2592
+ // Kept for result-shape compatibility; a safe deferral never writes
2593
+ // a terminal task outcome or stops the child.
2594
+ markedTaskIds: []
2595
+ };
2596
+ }
2597
+ const fingerprint = `${step.busy}:${active.ids.join(",")}`;
2598
+ if (fingerprint !== lastReported) {
2599
+ lastReported = fingerprint;
2600
+ log(`deferring staged update restart \u2014 ${describeBusy(active)}; re-checking every ${Math.round(checkMs / 1e3)}s until idle (cap ${Math.round(capMs / 1e3)}s)`);
2601
+ }
2602
+ await sleep2(Math.max(1, Math.min(checkMs, capMs - elapsedMs)));
2603
+ }
2604
+ }
2605
+
2606
+ // src/runner/supervisor-child-env.mjs
2607
+ import { hostname as systemHostname } from "node:os";
2608
+
2609
+ // src/runner-readiness.mjs
2610
+ import { createHash as createHash5, randomUUID as randomUUID3 } from "node:crypto";
2611
+ var RUNNER_GITHUB_READINESS_TIMEOUT_MS = 5e4;
2612
+ var RUNNER_IDENTITY_READINESS_TIMEOUT_MS = 1e4;
2613
+ var RUNNER_READINESS_MAX_REPOSITORIES = 4;
2614
+ var RUNNER_READINESS_TRANSIENT_MAX_RETRY_AFTER_MS = 3e5;
2615
+ var RUNNER_READINESS_DEFERRAL_MESSAGE_TYPE = "vo-runner-readiness-deferred-v1";
2616
+ var RUNNER_READINESS_DEFERRAL_ACK_TYPE = "vo-runner-readiness-deferred-ack-v1";
2617
+ var RUNNER_REPOSITORY = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/u;
2618
+ 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;
2619
+ function runnerRepositoryScopeFromEnv(env = {}) {
2620
+ return String(env.VO_CODE_RUNNER_REPOS || "").split(/[\s,]+/u).map((repo) => repo.trim()).filter(Boolean);
2621
+ }
2622
+ function normalizeRunnerRepositoryScope(repositories) {
2623
+ if (!Array.isArray(repositories) || repositories.length < 1) {
2624
+ throw new Error("VO_CODE_RUNNER_REPOS must name at least one owner/name repository");
2625
+ }
2626
+ if (repositories.length > RUNNER_READINESS_MAX_REPOSITORIES) {
2627
+ throw new Error(`VO_CODE_RUNNER_REPOS supports at most ${RUNNER_READINESS_MAX_REPOSITORIES} repositories`);
2628
+ }
2629
+ const normalized = repositories.map((repo) => {
2630
+ const [owner, name] = typeof repo === "string" ? repo.split("/") : [];
2631
+ if (typeof repo !== "string" || repo.length > 140 || !RUNNER_REPOSITORY.test(repo) || owner === "." || owner === ".." || name === "." || name === "..") {
2632
+ throw new Error("VO_CODE_RUNNER_REPOS entries must be canonical owner/name repositories");
2633
+ }
2634
+ return repo.toLowerCase();
2635
+ });
2636
+ if (new Set(normalized).size !== normalized.length) {
2637
+ throw new Error("VO_CODE_RUNNER_REPOS entries must be unique");
2638
+ }
2639
+ const owners = new Set(normalized.map((repo) => repo.split("/")[0]));
2640
+ if (owners.size !== 1) {
2641
+ throw new Error("VO_CODE_RUNNER_REPOS entries must share one owner");
2642
+ }
2643
+ return Object.freeze(normalized.sort());
2644
+ }
2645
+ function runnerRepositoryScopeDigest(repositories) {
2646
+ const scope = normalizeRunnerRepositoryScope(repositories);
2647
+ return createHash5("sha256").update(JSON.stringify({
2648
+ version: 1,
2649
+ repositories: scope
2650
+ }), "utf8").digest("hex");
2651
+ }
2652
+ function runnerReadinessRetryDelayMs(readiness) {
2653
+ if (readiness?.paired !== true || readiness?.githubReady !== false) return null;
2654
+ if (Number.isFinite(readiness.retryAfterMs) && readiness.retryAfterMs > 0) {
2655
+ return Math.ceil(readiness.retryAfterMs);
2656
+ }
2657
+ return null;
2658
+ }
2659
+ function parseRunnerReadinessDeferralRequest(message) {
2660
+ if (message?.type !== RUNNER_READINESS_DEFERRAL_MESSAGE_TYPE || !UUID_RE2.test(String(message.nonce || "")) || !Number.isFinite(message.retryAfterMs) || message.retryAfterMs <= 0) return null;
2661
+ return Object.freeze({
2662
+ nonce: message.nonce,
2663
+ retryAfterMs: Math.ceil(message.retryAfterMs),
2664
+ error: typeof message.error === "string" ? message.error : null
2665
+ });
2666
+ }
2667
+ function failed({
2668
+ paired = false,
2669
+ operatorId = null,
2670
+ tenantId = null,
2671
+ githubReady = null,
2672
+ retryAfterMs = null,
2673
+ error,
2674
+ message
2675
+ }) {
2676
+ return {
2677
+ ok: false,
2678
+ paired,
2679
+ operatorId,
2680
+ tenantId,
2681
+ githubReady,
2682
+ ...Number.isFinite(retryAfterMs) && retryAfterMs > 0 ? { retryAfterMs } : {},
2683
+ error,
2684
+ message
2685
+ };
2686
+ }
2687
+ function responseRetryAfterMs(response, body) {
2688
+ const transientReadinessFailure = response.status === 503 && body?.error === "github_installation_readiness_retryable";
2689
+ if (response.status !== 429 && !transientReadinessFailure) return null;
2690
+ const bound = (retryAfterMs) => transientReadinessFailure ? Math.min(RUNNER_READINESS_TRANSIENT_MAX_RETRY_AFTER_MS, retryAfterMs) : retryAfterMs;
2691
+ const value = response.headers?.get?.("retry-after")?.trim() ?? "";
2692
+ const seconds = Number(value);
2693
+ if (value && Number.isFinite(seconds) && seconds >= 0) {
2694
+ return bound(Math.max(1e3, Math.ceil(seconds * 1e3)));
2695
+ }
2696
+ const at = value ? Date.parse(value) : Number.NaN;
2697
+ if (Number.isFinite(at)) {
2698
+ return bound(Math.max(1e3, Math.ceil(at - Date.now())));
2699
+ }
2700
+ return bound(6e4);
2701
+ }
2702
+ async function responseBody(response) {
2703
+ try {
2704
+ const value = await response.json();
2705
+ return value && typeof value === "object" ? value : {};
2706
+ } catch {
2707
+ return {};
2708
+ }
2709
+ }
2710
+ function serverMessage(body, fallback) {
2711
+ return typeof body.message === "string" && body.message.trim() ? body.message.trim() : fallback;
2712
+ }
2713
+ async function fetchJsonWithTimeout(fetchImpl, url, init, timeoutMs) {
2714
+ const controller = new AbortController();
2715
+ let timer;
2716
+ const timeout = new Promise((_, reject) => {
2717
+ timer = setTimeout(() => {
2718
+ controller.abort();
2719
+ reject(new Error(`request aborted after ${timeoutMs}ms`));
2720
+ }, timeoutMs);
2721
+ });
2722
+ try {
2723
+ const requestAndBody = (async () => {
2724
+ const response = await fetchImpl(url, { ...init, signal: controller.signal });
2725
+ return { response, body: await responseBody(response) };
2726
+ })();
2727
+ return await Promise.race([requestAndBody, timeout]);
2728
+ } finally {
2729
+ clearTimeout(timer);
2730
+ }
2731
+ }
2732
+ async function probeRunnerReadiness({
2733
+ controlPlaneUrl,
2734
+ token,
2735
+ fetchImpl = fetch,
2736
+ requireGithub = false,
2737
+ repositories = [],
2738
+ timeoutMs = RUNNER_IDENTITY_READINESS_TIMEOUT_MS,
2739
+ // The server performs at most five sequential App-JWT GETs (installation plus
2740
+ // every one of at most four configured repositories), each bounded at 8s.
2741
+ // Keep a transport margin while preventing a stuck proof from hanging startup.
2742
+ githubTimeoutMs = RUNNER_GITHUB_READINESS_TIMEOUT_MS
2743
+ }) {
2744
+ const base = controlPlaneUrl.replace(/\/+$/u, "");
2745
+ const headers = { authorization: `Bearer ${token}` };
2746
+ let identityResponse;
2747
+ let identity;
2748
+ try {
2749
+ ({ response: identityResponse, body: identity } = await fetchJsonWithTimeout(
2750
+ fetchImpl,
2751
+ `${base}/api/v1/auth/me`,
2752
+ { headers },
2753
+ timeoutMs
2754
+ ));
2755
+ } catch (error) {
2756
+ const detail = error instanceof Error ? error.message : String(error);
2757
+ return failed({
2758
+ error: "control_plane_unreachable",
2759
+ message: `AlgoHQ could not be reached: ${detail}`
2760
+ });
2761
+ }
2762
+ if (!identityResponse.ok) {
2763
+ return failed({
2764
+ error: "credential_rejected",
2765
+ message: "The saved pairing is expired or revoked. Pair this computer again."
2766
+ });
2767
+ }
2768
+ if (identity.provisioned !== true || identity.role !== "operator" || typeof identity.operator_id !== "string" || !identity.operator_id.trim() || typeof identity.tenant_id !== "string" || !identity.tenant_id.trim()) {
2769
+ return failed({
2770
+ error: "operator_identity_missing",
2771
+ message: "The pairing credential is valid but has no operator identity. Pair this computer again."
2772
+ });
2773
+ }
2774
+ const operatorId = identity.operator_id.trim();
2775
+ const tenantId = identity.tenant_id.trim();
2776
+ if (!requireGithub) {
2777
+ return {
2778
+ ok: true,
2779
+ paired: true,
2780
+ operatorId,
2781
+ tenantId,
2782
+ githubReady: null,
2783
+ error: null,
2784
+ message: "Paired to AlgoHQ."
2785
+ };
2786
+ }
2787
+ let repositoryScope = null;
2788
+ try {
2789
+ if (!Array.isArray(repositories)) {
2790
+ throw new Error("VO_CODE_RUNNER_REPOS must be a repository array");
2791
+ }
2792
+ if (repositories.length > 0) repositoryScope = normalizeRunnerRepositoryScope(repositories);
2793
+ } catch (error) {
2794
+ return failed({
2795
+ paired: true,
2796
+ operatorId,
2797
+ tenantId,
2798
+ githubReady: false,
2799
+ error: "github_repository_scope_invalid",
2800
+ message: error instanceof Error ? error.message : String(error)
2801
+ });
2802
+ }
2803
+ let githubResponse;
2804
+ let github;
2805
+ try {
2806
+ const readinessUrl = new URL(`${base}/api/v1/github/installation-readiness`);
2807
+ for (const repo of repositoryScope ?? []) readinessUrl.searchParams.append("repo", repo);
2808
+ ({ response: githubResponse, body: github } = await fetchJsonWithTimeout(
2809
+ fetchImpl,
2810
+ readinessUrl.toString(),
2811
+ { headers },
2812
+ githubTimeoutMs
2813
+ ));
2814
+ } catch (error) {
2815
+ const detail = error instanceof Error ? error.message : String(error);
2816
+ return failed({
2817
+ paired: true,
2818
+ operatorId,
2819
+ tenantId,
2820
+ githubReady: false,
2821
+ error: "github_preflight_unreachable",
2822
+ message: `GitHub publication readiness could not be checked: ${detail}`
2823
+ });
2824
+ }
2825
+ const repositorySelection = github.repository_selection;
2826
+ const repositoriesVerified = github.repositories_verified;
2827
+ const returnedScope = github.repository_scope;
2828
+ let normalizedReturnedScope = null;
2829
+ try {
2830
+ normalizedReturnedScope = normalizeRunnerRepositoryScope(returnedScope);
2831
+ } catch {
2832
+ }
2833
+ const effectiveScope = repositoryScope ?? normalizedReturnedScope;
2834
+ const sourceVerified = repositoryScope === null ? github.repository_scope_source === "persisted_installation_singleton" && normalizedReturnedScope?.length === 1 : github.repository_scope_source === "runner_config";
2835
+ const repositoryScopeVerified = effectiveScope !== null && normalizedReturnedScope !== null && Array.isArray(returnedScope) && returnedScope.length === normalizedReturnedScope.length && returnedScope.every((repo, index) => repo === normalizedReturnedScope[index]) && normalizedReturnedScope.length === effectiveScope.length && normalizedReturnedScope.every((repo, index) => repo === effectiveScope[index]) && github.repository_scope_sha256 === runnerRepositoryScopeDigest(effectiveScope) && repositoriesVerified === effectiveScope.length && sourceVerified && (repositorySelection === "all" || repositorySelection === "selected");
2836
+ if (!githubResponse.ok || github.configured !== true || github.verified !== true || github.publication_ready !== true || !repositoryScopeVerified) {
2837
+ const error = typeof github.error === "string" && github.error ? github.error : "github_not_ready";
2838
+ return failed({
2839
+ paired: true,
2840
+ operatorId,
2841
+ tenantId,
2842
+ githubReady: false,
2843
+ retryAfterMs: responseRetryAfterMs(githubResponse, github),
2844
+ error,
2845
+ message: serverMessage(github, `GitHub publication preflight failed (HTTP ${githubResponse.status}).`)
2846
+ });
2847
+ }
2848
+ return {
2849
+ ok: true,
2850
+ paired: true,
2851
+ operatorId,
2852
+ tenantId,
2853
+ githubReady: true,
2854
+ repositoryScope: effectiveScope,
2855
+ repositoryScopeSource: github.repository_scope_source,
2856
+ error: null,
2857
+ message: `Paired with a verified Algosuite GitHub App installation (${repositoriesVerified} repos).`
2858
+ };
2859
+ }
2860
+ function pairedOperatorScope(readiness) {
2861
+ return readiness?.ok === true && readiness?.paired === true && typeof readiness.operatorId === "string" && readiness.operatorId.trim() ? readiness.operatorId.trim() : null;
2862
+ }
2863
+
2864
+ // src/runner/supervisor-child-env.mjs
2865
+ function resolveSupervisorRunnerId(env = {}, hostname = systemHostname) {
2866
+ const explicit = String(env.VO_CODE_RUNNER_ID || "").trim();
2867
+ return explicit || `vo-code-runner-${hostname()}`;
2868
+ }
2869
+ function buildSupervisorChildEnv({
2870
+ baseEnv = {},
2871
+ controlPlaneUrl,
2872
+ explicitAdminToken = null,
2873
+ pairedOperatorId = null
2874
+ } = {}) {
2875
+ const childEnv = {
2876
+ ...baseEnv,
2877
+ VO_CONTROL_PLANE_URL: controlPlaneUrl
2878
+ };
2879
+ const adminToken = typeof explicitAdminToken === "string" ? explicitAdminToken.trim() : "";
2880
+ const operatorId = typeof pairedOperatorId === "string" ? pairedOperatorId.trim() : "";
2881
+ if (adminToken) childEnv.VO_CONTROL_PLANE_ADMIN_TOKEN = adminToken;
2882
+ else delete childEnv.VO_CONTROL_PLANE_ADMIN_TOKEN;
2883
+ if (operatorId) childEnv.VO_CODE_RUNNER_OPERATOR_IDS = operatorId;
2884
+ return childEnv;
2885
+ }
2886
+ async function prepareSupervisorAuth({
2887
+ baseEnv = {},
2888
+ storedCredential = null,
2889
+ controlPlaneUrl,
2890
+ probeReadiness = probeRunnerReadiness
2891
+ } = {}) {
2892
+ const explicitAdminToken = baseEnv.VO_CONTROL_PLANE_ADMIN_TOKEN?.trim();
2893
+ const token = explicitAdminToken || storedCredential?.vo_credential;
2894
+ if (!token) throw new Error("runner is not paired; run `vo-mcp pair` once on this host");
2895
+ let operatorId = String(baseEnv.VO_CODE_RUNNER_OPERATOR_IDS || "").split(/[\s,]+/u).filter(Boolean)[0] || void 0;
2896
+ let repositoryScope = runnerRepositoryScopeFromEnv(baseEnv);
2897
+ let startupRetryAfterMs = null;
2898
+ let githubReady = null;
2899
+ let readinessError = null;
2900
+ if (!explicitAdminToken) {
2901
+ const readiness = await probeReadiness({
2902
+ controlPlaneUrl,
2903
+ token,
2904
+ requireGithub: true,
2905
+ repositories: repositoryScope
2906
+ });
2907
+ const pairedScope = pairedOperatorScope(readiness) || (readiness?.paired === true && typeof readiness.operatorId === "string" ? readiness.operatorId.trim() : "");
2908
+ operatorId = pairedScope || void 0;
2909
+ if (!operatorId) throw new Error("runner readiness failed: paired operator scope is missing");
2910
+ if (readiness.ok && Array.isArray(readiness.repositoryScope)) {
2911
+ repositoryScope = [...readiness.repositoryScope];
2912
+ githubReady = true;
2913
+ } else {
2914
+ githubReady = false;
2915
+ readinessError = typeof readiness?.error === "string" ? readiness.error : "github_not_ready";
2916
+ startupRetryAfterMs = runnerReadinessRetryDelayMs(readiness);
2917
+ }
2918
+ }
2919
+ const effectiveBaseEnv = repositoryScope.length > 0 ? { ...baseEnv, VO_CODE_RUNNER_REPOS: repositoryScope.join(",") } : baseEnv;
2920
+ const childEnv = buildSupervisorChildEnv({
2921
+ baseEnv: effectiveBaseEnv,
2922
+ controlPlaneUrl,
2923
+ explicitAdminToken,
2924
+ pairedOperatorId: explicitAdminToken ? null : operatorId
2925
+ });
2926
+ const clientEnv = {
2927
+ ...effectiveBaseEnv,
2928
+ VO_CONTROL_PLANE_ADMIN_TOKEN: token,
2929
+ VO_CONTROL_PLANE_URL: controlPlaneUrl,
2930
+ ...operatorId ? { VO_CODE_RUNNER_OPERATOR_IDS: operatorId } : {}
2931
+ };
2932
+ return {
2933
+ childEnv,
2934
+ clientEnv,
2935
+ operatorId,
2936
+ repositoryScope,
2937
+ githubReady,
2938
+ readinessError,
2939
+ startupRetryAfterMs
2940
+ };
2941
+ }
2942
+
2943
+ // src/runner/supervisor-credential-reader.mjs
2944
+ import { spawnSync as spawnSync5 } from "node:child_process";
2945
+ import { existsSync as existsSync6 } from "node:fs";
2946
+ import { dirname as dirname3, join as join4 } from "node:path";
2947
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
2948
+ function defaultCredentialHelperPath(metaUrl = import.meta.url) {
2949
+ const moduleDir = dirname3(fileURLToPath2(metaUrl));
2950
+ const bundled = join4(moduleDir, "supervisor-credential-helper.js");
2951
+ const source = join4(moduleDir, "..", "supervisor-credential-helper.mjs");
2952
+ return existsSync6(source) ? source : bundled;
2953
+ }
2954
+ function readStoredCredentialIsolated({
2955
+ spawn: spawn2 = spawnSync5,
2956
+ execPath = process.execPath,
2957
+ helperPath = defaultCredentialHelperPath(),
2958
+ helperArgs = [],
2959
+ env = process.env
2960
+ } = {}) {
2961
+ const result = spawn2(execPath, [helperPath, ...helperArgs], {
2962
+ env,
2963
+ encoding: "utf8",
2964
+ stdio: ["ignore", "pipe", "pipe"],
2965
+ shell: false,
2966
+ windowsHide: true,
2967
+ timeout: 1e4
2968
+ });
2969
+ if (result.status !== 0) {
2970
+ throw new Error("runner credential helper could not read the paired credential");
2971
+ }
2972
+ try {
2973
+ const parsed = JSON.parse(String(result.stdout || ""));
2974
+ return parsed && typeof parsed === "object" ? parsed : null;
2975
+ } catch {
2976
+ throw new Error("runner credential helper returned an invalid credential");
2977
+ }
2978
+ }
2979
+
2980
+ // src/runner/respawn-circuit.mjs
2981
+ var DEFAULT_RESPAWN_CIRCUIT = Object.freeze({
2982
+ baseDelayMs: 2e3,
2983
+ maxDelayMs: 3e4,
2984
+ healthyResetMs: 6e4,
2985
+ maxRapidExits: 5
2986
+ });
2987
+ function createRespawnCircuit(options = {}) {
2988
+ const config = { ...DEFAULT_RESPAWN_CIRCUIT, ...options };
2989
+ for (const [key, value] of Object.entries(config)) {
2990
+ if (!Number.isInteger(value) || value < 1) throw new Error(`${key} must be a positive integer`);
2991
+ }
2992
+ let rapidExits = 0;
2993
+ return Object.freeze({
2994
+ recordExit(startedAt, exitedAt) {
2995
+ if (!Number.isFinite(startedAt) || !Number.isFinite(exitedAt) || exitedAt < startedAt) {
2996
+ throw new Error("respawn circuit timestamps are invalid");
2997
+ }
2998
+ const uptimeMs = exitedAt - startedAt;
2999
+ rapidExits = uptimeMs >= config.healthyResetMs ? 0 : rapidExits + 1;
3000
+ const tripped = rapidExits >= config.maxRapidExits;
3001
+ const delayMs = Math.min(
3002
+ config.maxDelayMs,
3003
+ config.baseDelayMs * 2 ** Math.max(0, rapidExits - 1)
3004
+ );
3005
+ return Object.freeze({ rapidExits, uptimeMs, delayMs, tripped });
3006
+ },
3007
+ snapshot() {
3008
+ return Object.freeze({ rapidExits, tripped: rapidExits >= config.maxRapidExits });
3009
+ }
3010
+ });
3011
+ }
3012
+
3013
+ // src/runner/supervisor-child-health.mjs
3014
+ var terminatedChildren = /* @__PURE__ */ new WeakSet();
3015
+ function attachSupervisorChildTerminationCustody(target, onTerminated, onObservedError = () => {
3016
+ }) {
3017
+ let accounted = false;
3018
+ const account = (kind, { error = null, code = null, signal = null } = {}) => {
3019
+ terminatedChildren.add(target);
3020
+ if (accounted) return false;
3021
+ accounted = true;
3022
+ onTerminated(Object.freeze({ target, kind, error, code, signal }));
3023
+ return true;
3024
+ };
3025
+ target.on("error", (error) => {
3026
+ onObservedError(error);
3027
+ if (target.pid === void 0 || target.pid === null) account("error", { error });
3028
+ });
3029
+ target.on("exit", (code, signal) => {
3030
+ account("exit", { code: code ?? null, signal: signal ?? null });
3031
+ });
3032
+ return Object.freeze({ accounted: () => accounted });
3033
+ }
3034
+ function describeChildTermination(record, runtime = process.version) {
3035
+ if (!record || typeof record !== "object") return `unknown termination on Node ${runtime}`;
3036
+ if (record.kind === "error") {
3037
+ const detail = record.error instanceof Error ? record.error.message : String(record.error ?? "no detail");
3038
+ return `spawn error on Node ${runtime}: ${detail}`;
3039
+ }
3040
+ if (record.signal) return `killed by ${record.signal} on Node ${runtime}`;
3041
+ if (record.code === 0) return `exited cleanly (code 0) on Node ${runtime}`;
3042
+ if (record.code === null) return `exited with no reported code or signal on Node ${runtime}`;
3043
+ return `exited with code ${record.code} on Node ${runtime}`;
3044
+ }
3045
+ function supervisorChildHasExited(target) {
3046
+ return terminatedChildren.has(target) || target.exitCode !== null || (target.signalCode ?? null) !== null;
3047
+ }
3048
+ function supervisorChildIsRunning(target) {
3049
+ return !supervisorChildHasExited(target);
3050
+ }
3051
+ function shouldRespawnSupervisorChild({ stopping, degraded, handling, child }) {
3052
+ return !stopping && !degraded && !handling && (!child || supervisorChildHasExited(child));
3053
+ }
3054
+ function shouldDeferSupervisorReadinessExit({
3055
+ target,
3056
+ currentChild,
3057
+ retryAfterMs
3058
+ }) {
3059
+ return target === currentChild && target?.exitCode === 75 && Number.isFinite(retryAfterMs) && retryAfterMs > 0;
3060
+ }
3061
+ function createSupervisorReadinessDeferralTracker({
3062
+ currentChild,
3063
+ releaseCurrentChild,
3064
+ onCaptured
3065
+ }) {
3066
+ const pending = /* @__PURE__ */ new WeakMap();
3067
+ const terminal = /* @__PURE__ */ new WeakSet();
3068
+ const recoveryGenerations = /* @__PURE__ */ new WeakMap();
3069
+ return Object.freeze({
3070
+ recordPending(target, request) {
3071
+ if (!target || target !== currentChild() || terminal.has(target) || pending.has(target) || typeof request?.nonce !== "string" || !request.nonce || !Number.isFinite(request.retryAfterMs) || request.retryAfterMs <= 0) return false;
3072
+ pending.set(target, Object.freeze({
3073
+ nonce: request.nonce,
3074
+ retryAfterMs: Math.ceil(request.retryAfterMs)
3075
+ }));
3076
+ return true;
3077
+ },
3078
+ recordRecoveryGeneration(target, generation) {
3079
+ if (!target || !Number.isInteger(generation)) return false;
3080
+ recoveryGenerations.set(target, generation);
3081
+ return true;
3082
+ },
3083
+ isDeferred(target) {
3084
+ const request = target ? pending.get(target) : null;
3085
+ return Boolean(target) && (terminal.has(target) || shouldDeferSupervisorReadinessExit({
3086
+ target,
3087
+ currentChild: currentChild(),
3088
+ retryAfterMs: request?.retryAfterMs
3089
+ }));
3090
+ },
3091
+ capture(target) {
3092
+ if (!target) return false;
3093
+ if (terminal.has(target)) return true;
3094
+ const request = pending.get(target);
3095
+ if (!shouldDeferSupervisorReadinessExit({
3096
+ target,
3097
+ currentChild: currentChild(),
3098
+ retryAfterMs: request?.retryAfterMs
3099
+ })) return false;
3100
+ const recoveryGeneration = recoveryGenerations.get(target);
3101
+ terminal.add(target);
3102
+ pending.delete(target);
3103
+ recoveryGenerations.delete(target);
3104
+ releaseCurrentChild(target);
3105
+ onCaptured(Object.freeze({
3106
+ target,
3107
+ retryAfterMs: request.retryAfterMs,
3108
+ recoveryGeneration: Number.isInteger(recoveryGeneration) ? recoveryGeneration : null
3109
+ }));
3110
+ return true;
3111
+ },
3112
+ forget(target) {
3113
+ if (!target) return;
3114
+ pending.delete(target);
3115
+ recoveryGenerations.delete(target);
3116
+ }
3117
+ });
3118
+ }
3119
+ function resolveSupervisorChildStartAuthority({
3120
+ degradationState,
3121
+ deferredRecoveryGeneration
3122
+ }) {
3123
+ const snapshot = degradationState.snapshot();
3124
+ const exactRecovery = Number.isInteger(deferredRecoveryGeneration) && deferredRecoveryGeneration === snapshot.generation;
3125
+ return Object.freeze({
3126
+ allowed: !snapshot.degraded || exactRecovery,
3127
+ recoveryGeneration: exactRecovery ? deferredRecoveryGeneration : null
3128
+ });
3129
+ }
3130
+ function createSupervisorStartupDeferral({
3131
+ retryAfterMs = null,
3132
+ nowMs = () => Date.now()
3133
+ } = {}) {
3134
+ let started = false;
3135
+ let notBefore = null;
3136
+ const defer = (delayMs) => {
3137
+ if (started || !Number.isFinite(delayMs) || delayMs <= 0) return false;
3138
+ const candidate = nowMs() + Math.ceil(delayMs);
3139
+ notBefore = notBefore === null ? candidate : Math.max(notBefore, candidate);
3140
+ return true;
3141
+ };
3142
+ defer(retryAfterMs);
3143
+ return Object.freeze({
3144
+ defer,
3145
+ reopen(delayMs) {
3146
+ if (!Number.isFinite(delayMs) || delayMs <= 0) return false;
3147
+ started = false;
3148
+ notBefore = null;
3149
+ return defer(delayMs);
3150
+ },
3151
+ isPending() {
3152
+ return !started && notBefore !== null && nowMs() < notBefore;
3153
+ },
3154
+ remainingMs() {
3155
+ return started || notBefore === null ? 0 : Math.max(0, notBefore - nowMs());
3156
+ },
3157
+ consumeIfReady() {
3158
+ if (started || notBefore !== null && nowMs() < notBefore) return false;
3159
+ started = true;
3160
+ notBefore = null;
3161
+ return true;
3162
+ },
3163
+ hasStarted() {
3164
+ return started;
3165
+ }
3166
+ });
3167
+ }
3168
+ function createSupervisorDegradationState() {
3169
+ let degraded = false;
3170
+ let generation = 0;
3171
+ return {
3172
+ isDegraded() {
3173
+ return degraded;
3174
+ },
3175
+ markDegraded() {
3176
+ degraded = true;
3177
+ generation += 1;
3178
+ },
3179
+ beginHealthProof({ target, currentChild, allowRecovery, onRecovery }) {
3180
+ const observedGeneration = generation;
3181
+ return () => {
3182
+ if (currentChild() !== target || generation !== observedGeneration) return false;
3183
+ if (degraded && !allowRecovery) return false;
3184
+ const recovered = degraded;
3185
+ degraded = false;
3186
+ if (recovered) onRecovery?.();
3187
+ return true;
3188
+ };
3189
+ },
3190
+ beginDegradationProof({ target, currentChild, onDegraded }) {
3191
+ const observedGeneration = generation;
3192
+ return (message) => {
3193
+ if (currentChild() !== target || generation !== observedGeneration) return false;
3194
+ onDegraded(message);
3195
+ return true;
3196
+ };
3197
+ },
3198
+ snapshot() {
3199
+ return { degraded, generation };
3200
+ }
3201
+ };
3202
+ }
3203
+ function beginSupervisorHealthyStateCommit({
3204
+ degradationState,
3205
+ target,
3206
+ currentChild,
3207
+ allowRecovery,
3208
+ clearStaleExitCode
3209
+ }) {
3210
+ return degradationState.beginHealthProof({
3211
+ target,
3212
+ currentChild,
3213
+ allowRecovery,
3214
+ onRecovery: clearStaleExitCode
3215
+ });
3216
+ }
3217
+ var RUNNER_RECOVERY_ACTIONS = /* @__PURE__ */ new Set(["update", "reinstall", "reconnect"]);
3218
+ function shouldRecoverSupervisorChildAfterAction({
3219
+ stoppedChild,
3220
+ actionKind,
3221
+ actionSucceeded
3222
+ }) {
3223
+ return Boolean(stoppedChild) || actionSucceeded === true && RUNNER_RECOVERY_ACTIONS.has(actionKind);
3224
+ }
3225
+ function createSupervisorControlRecoveryFence() {
3226
+ let targetStoppedForControl = null;
3227
+ const consume = () => {
3228
+ const target = targetStoppedForControl;
3229
+ targetStoppedForControl = null;
3230
+ return target;
3231
+ };
3232
+ return {
3233
+ markBeforeStop(target) {
3234
+ targetStoppedForControl = target && supervisorChildIsRunning(target) ? target : null;
3235
+ return targetStoppedForControl !== null;
3236
+ },
3237
+ consume,
3238
+ async recoverOnce(recover) {
3239
+ const target = consume();
3240
+ if (!target) return false;
3241
+ await recover(target);
3242
+ return true;
3243
+ }
3244
+ };
3245
+ }
3246
+ async function verifySupervisorChildHealth({
3247
+ target,
3248
+ degradeOnExit,
3249
+ context,
3250
+ waitBeforeProbe,
3251
+ waitForLocalRunner: waitForLocalRunner2,
3252
+ stopExpectedly,
3253
+ markHealthy,
3254
+ markDegraded,
3255
+ isReadinessDeferredExit = () => false,
3256
+ isExpectedStop = () => false
3257
+ }) {
3258
+ try {
3259
+ await waitBeforeProbe();
3260
+ const healthy = supervisorChildIsRunning(target) && await waitForLocalRunner2(target);
3261
+ const exited = supervisorChildHasExited(target);
3262
+ if (isExpectedStop(target)) return false;
3263
+ if (exited && isReadinessDeferredExit(target)) return false;
3264
+ if (healthy && !exited) {
3265
+ if (markHealthy() !== false) return true;
3266
+ if (supervisorChildIsRunning(target)) await stopExpectedly(target);
3267
+ return false;
3268
+ }
3269
+ if (!exited || degradeOnExit) {
3270
+ const committed = markDegraded(`${context} did not become healthy; entering degraded mode`) !== false;
3271
+ if (!committed) return false;
3272
+ if (supervisorChildIsRunning(target)) await stopExpectedly(target);
3273
+ }
3274
+ return false;
3275
+ } catch (error) {
3276
+ if (isExpectedStop(target)) return false;
3277
+ const committed = markDegraded(
3278
+ `${context} health proof failed; entering degraded mode: ${error instanceof Error ? error.message : String(error)}`
3279
+ ) !== false;
3280
+ if (!committed) return false;
3281
+ if (supervisorChildIsRunning(target)) await stopExpectedly(target).catch(() => {
3282
+ });
3283
+ return false;
3284
+ }
3285
+ }
3286
+
3287
+ // src/runner-supervisor.mjs
3288
+ var DEFAULT_CONTROL_PLANE_URL = "https://vo-control-plane-bzjphrajaq-uc.a.run.app";
3289
+ var POLL_MS = 5e3;
3290
+ var CHILD_START_MS = 1500;
3291
+ var CHILD_READINESS_TIMEOUT_MS = RUNNER_IDENTITY_READINESS_TIMEOUT_MS + RUNNER_GITHUB_READINESS_TIMEOUT_MS + 1e4;
3292
+ var SUPERVISOR_CAPABILITIES = [
3293
+ "bundled-runtime-slots-v1",
3294
+ "legacy-orphan-purge-v1",
3295
+ "exact-source-sha-v1"
3296
+ ];
3297
+ var UUID_RE3 = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu;
3298
+ var selfPath = fileURLToPath3(import.meta.url);
3299
+ var bundledChildEntry = join5(dirname4(selfPath), "runner-cli.js");
3300
+ var supervisorRuntimeRoot = runtimeRootFromEnv(process.env);
3301
+ var sleep = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms));
3302
+ var runnerId = resolveSupervisorRunnerId(process.env);
3303
+ function packageVersion() {
3304
+ try {
3305
+ return createRequire(import.meta.url)("../package.json").version || "unknown";
3306
+ } catch {
3307
+ return "unknown";
3308
+ }
3309
+ }
3310
+ var requestedSupervisorInstanceId = UUID_RE3.test(String(process.env.VO_RUNNER_SUPERVISOR_INSTANCE_ID || "")) ? process.env.VO_RUNNER_SUPERVISOR_INSTANCE_ID : randomUUID4();
3311
+ var supervisorInstanceId = activationSupervisorInstanceId(
3312
+ runtimeRootFromEnv(process.env),
3313
+ requestedSupervisorInstanceId
3314
+ );
3315
+ var supervisorVersion = packageVersion();
3316
+ var supervisorControlIdentity = {
3317
+ supervisorInstanceId,
3318
+ supervisorVersion,
3319
+ capabilities: SUPERVISOR_CAPABILITIES
3320
+ };
3321
+ var lastResolvedChildDescriptor = null;
3322
+ function spawnChild(childEnv) {
3323
+ const resolved = resolveSupervisorChildEntry({
3324
+ runtimeRoot: supervisorRuntimeRoot,
3325
+ bundledEntry: bundledChildEntry,
3326
+ bundledVersion: supervisorVersion
3327
+ });
3328
+ if (resolved.descriptor !== lastResolvedChildDescriptor) {
3329
+ console.error(`[vo-runner supervisor] child entry -> ${resolved.descriptor} (${resolved.detail})`);
3330
+ lastResolvedChildDescriptor = resolved.descriptor;
3331
+ }
3332
+ return spawn(process.execPath, resolved.args, {
3333
+ env: childEnv,
3334
+ stdio: ["inherit", "inherit", "inherit", "ipc"],
3335
+ windowsHide: true
3336
+ });
3337
+ }
3338
+ function spawnPreviousChild(entry, childEnv) {
3339
+ const previous = spawn(process.execPath, [entry, "runner"], {
3340
+ env: childEnv,
3341
+ stdio: ["inherit", "inherit", "inherit", "ipc"],
3342
+ windowsHide: true
3343
+ });
3344
+ attachSupervisorChildTerminationCustody(
3345
+ previous,
3346
+ () => {
3347
+ },
3348
+ (error) => {
3349
+ console.error(
3350
+ `[vo-runner supervisor] rollback child process error: ${error instanceof Error ? error.message : String(error)}`
3351
+ );
3352
+ }
3353
+ );
3354
+ return previous;
3355
+ }
3356
+ async function localStatus() {
3357
+ try {
3358
+ const port = Number(process.env.VO_CODE_RUNNER_CONTROL_PORT || 7787);
3359
+ const response = await fetch(`http://127.0.0.1:${port}/status`, { signal: AbortSignal.timeout(800) });
3360
+ if (!response.ok) return null;
3361
+ const body = await response.json();
3362
+ return body?.ok === true ? body : null;
3363
+ } catch {
3364
+ return null;
3365
+ }
3366
+ }
3367
+ async function waitForLocalRunner(child) {
3368
+ const deadline = Date.now() + CHILD_READINESS_TIMEOUT_MS;
3369
+ while (Date.now() < deadline) {
3370
+ if (supervisorChildHasExited(child)) return false;
3371
+ const status = await localStatus();
3372
+ if (status?.running === true && Number(status.pid) === Number(child.pid)) return true;
3373
+ await sleep(500);
3374
+ }
3375
+ return false;
3376
+ }
3377
+ async function waitForChildExit(child, timeoutMs) {
3378
+ if (!child || supervisorChildHasExited(child)) return true;
3379
+ return new Promise((resolve5) => {
3380
+ let settled = false;
3381
+ const finish = (exited) => {
3382
+ if (settled) return;
3383
+ settled = true;
3384
+ clearTimeout(timer);
3385
+ child.off("exit", onExit);
3386
+ resolve5(exited);
3387
+ };
3388
+ const onExit = () => finish(true);
3389
+ const timer = setTimeout(() => finish(false), timeoutMs);
3390
+ child.once("exit", onExit);
3391
+ if (supervisorChildHasExited(child)) finish(true);
3392
+ });
3393
+ }
3394
+ async function stopChild(child) {
3395
+ if (!child || supervisorChildHasExited(child)) return;
3396
+ child.kill(process.platform === "win32" ? void 0 : "SIGTERM");
3397
+ if (await waitForChildExit(child, 15e3)) return;
3398
+ if (supervisorChildIsRunning(child)) child.kill("SIGKILL");
3399
+ await waitForChildExit(child, 5e3);
3400
+ }
3401
+ async function main() {
3402
+ const stored = readStoredCredentialIsolated();
3403
+ const controlPlaneUrl = process.env.VO_CONTROL_PLANE_URL || DEFAULT_CONTROL_PLANE_URL;
3404
+ const pairedSupervisor = !String(process.env.VO_CONTROL_PLANE_ADMIN_TOKEN || "").trim();
3405
+ const supervisorAuthInput = {
3406
+ baseEnv: process.env,
3407
+ storedCredential: stored,
3408
+ controlPlaneUrl
3409
+ };
3410
+ const {
3411
+ childEnv,
3412
+ clientEnv,
3413
+ operatorId,
3414
+ startupRetryAfterMs
3415
+ } = await prepareSupervisorAuth(supervisorAuthInput);
3416
+ Object.assign(childEnv, {
3417
+ VO_RUNNER_SUPERVISOR_INSTANCE_ID: supervisorInstanceId,
3418
+ VO_RUNNER_SUPERVISOR_VERSION: supervisorVersion,
3419
+ VO_RUNNER_SUPERVISOR_CAPABILITIES: SUPERVISOR_CAPABILITIES.join(",")
3420
+ });
3421
+ const client = createControlPlaneClient({ baseUrl: controlPlaneUrl, env: clientEnv });
3422
+ const runtimeRoot = runtimeRootFromEnv(process.env);
3423
+ let child = null;
3424
+ let stopping = false;
3425
+ let handling = false;
3426
+ const expectedChildStops = /* @__PURE__ */ new WeakSet();
3427
+ const healthNeutralizedChildren = /* @__PURE__ */ new WeakSet();
3428
+ const degradationState = createSupervisorDegradationState();
3429
+ const markSupervisorDegraded = () => {
3430
+ degradationState.markDegraded();
3431
+ process.exitCode = 1;
3432
+ };
3433
+ const respawnCircuit = createRespawnCircuit();
3434
+ const controlRecoveryFence = createSupervisorControlRecoveryFence();
3435
+ const startupDeferral = createSupervisorStartupDeferral({
3436
+ retryAfterMs: startupRetryAfterMs
3437
+ });
3438
+ let startupNeedsReadinessRefresh = startupRetryAfterMs !== null;
3439
+ let deferredControlRecoveryGeneration = null;
3440
+ const stopChildExpectedly = async (target) => {
3441
+ if (!target) return;
3442
+ expectedChildStops.add(target);
3443
+ healthNeutralizedChildren.add(target);
3444
+ try {
3445
+ await stopChild(target);
3446
+ } finally {
3447
+ if (supervisorChildHasExited(target)) expectedChildStops.delete(target);
3448
+ }
3449
+ };
3450
+ const readinessDeferrals = createSupervisorReadinessDeferralTracker({
3451
+ currentChild: () => child,
3452
+ releaseCurrentChild: (target) => {
3453
+ if (child === target) child = null;
3454
+ },
3455
+ onCaptured: ({ retryAfterMs, recoveryGeneration }) => {
3456
+ startupNeedsReadinessRefresh = true;
3457
+ if (Number.isInteger(recoveryGeneration) && degradationState.snapshot().generation === recoveryGeneration) {
3458
+ deferredControlRecoveryGeneration = recoveryGeneration;
3459
+ }
3460
+ startupDeferral.reopen(retryAfterMs);
3461
+ console.warn(
3462
+ `[vo-runner supervisor] child deferred by GitHub readiness for ${retryAfterMs}ms; preserving control polling without consuming rapid-exit breaker budget`
3463
+ );
3464
+ }
3465
+ });
3466
+ const isChildReadinessDeferred = (target) => readinessDeferrals.isDeferred(target);
3467
+ const captureChildReadinessDeferral = (target) => readinessDeferrals.capture(target);
3468
+ const respawn = () => {
3469
+ if (!startupDeferral.hasStarted()) return;
3470
+ if (!shouldRespawnSupervisorChild({
3471
+ stopping,
3472
+ degraded: degradationState.isDegraded(),
3473
+ handling,
3474
+ child
3475
+ })) return;
3476
+ try {
3477
+ child = launchChild();
3478
+ void verifyChildHealth(child, false, "automatic relaunch");
3479
+ } catch (error) {
3480
+ markSupervisorDegraded();
3481
+ console.error(
3482
+ `[vo-runner supervisor] child relaunch failed; entering degraded mode: ${error instanceof Error ? error.message : String(error)}`
3483
+ );
3484
+ }
3485
+ };
3486
+ const launchChild = () => {
3487
+ const next = spawnChild(childEnv);
3488
+ const startedAt = Date.now();
3489
+ next.on("message", (message) => {
3490
+ const request = parseRunnerReadinessDeferralRequest(message);
3491
+ if (!request || !readinessDeferrals.recordPending(next, request)) return;
3492
+ try {
3493
+ next.send({
3494
+ type: RUNNER_READINESS_DEFERRAL_ACK_TYPE,
3495
+ nonce: request.nonce
3496
+ }, (error) => {
3497
+ if (error) console.warn(`[vo-runner supervisor] readiness deferral ACK failed: ${error.message}`);
3498
+ });
3499
+ } catch (error) {
3500
+ console.warn(
3501
+ `[vo-runner supervisor] readiness deferral ACK failed: ${error instanceof Error ? error.message : String(error)}`
3502
+ );
3503
+ }
3504
+ });
3505
+ attachSupervisorChildTerminationCustody(
3506
+ next,
3507
+ (record) => {
3508
+ if (stopping || expectedChildStops.delete(next)) {
3509
+ readinessDeferrals.forget(next);
3510
+ return;
3511
+ }
3512
+ if (captureChildReadinessDeferral(next)) return;
3513
+ readinessDeferrals.forget(next);
3514
+ console.error(`[vo-runner supervisor] child ${describeChildTermination(record)}`);
3515
+ const decision = respawnCircuit.recordExit(startedAt, Date.now());
3516
+ if (decision.tripped) {
3517
+ markSupervisorDegraded();
3518
+ console.error(
3519
+ `[vo-runner supervisor] child exited rapidly ${decision.rapidExits} times; entering degraded mode to stop startup/token churn while remote repair remains available`
3520
+ );
3521
+ return;
3522
+ }
3523
+ setTimeout(respawn, decision.delayMs);
3524
+ },
3525
+ (error) => {
3526
+ console.error(
3527
+ `[vo-runner supervisor] child process error: ${error instanceof Error ? error.message : String(error)}`
3528
+ );
3529
+ }
3530
+ );
3531
+ return next;
3532
+ };
3533
+ const verifyChildHealth = async (target, degradeOnExit, context) => {
3534
+ const commitSupervisorHealthyState = beginSupervisorHealthyStateCommit({
3535
+ degradationState,
3536
+ target,
3537
+ currentChild: () => child,
3538
+ allowRecovery: degradeOnExit,
3539
+ clearStaleExitCode: () => {
3540
+ process.exitCode = void 0;
3541
+ }
3542
+ });
3543
+ const commitSupervisorDegradedState = degradationState.beginDegradationProof({
3544
+ target,
3545
+ currentChild: () => child,
3546
+ onDegraded: (message) => {
3547
+ markSupervisorDegraded();
3548
+ console.error(`[vo-runner supervisor] ${message}`);
3549
+ }
3550
+ });
3551
+ return verifySupervisorChildHealth({
3552
+ target,
3553
+ degradeOnExit,
3554
+ context,
3555
+ waitBeforeProbe: () => sleep(CHILD_START_MS),
3556
+ waitForLocalRunner,
3557
+ stopExpectedly: stopChildExpectedly,
3558
+ isReadinessDeferredExit: isChildReadinessDeferred,
3559
+ isExpectedStop: (candidate) => healthNeutralizedChildren.has(candidate),
3560
+ markHealthy: commitSupervisorHealthyState,
3561
+ markDegraded: commitSupervisorDegradedState
3562
+ });
3563
+ };
3564
+ const deferChildAdmission = (retryAfterMs) => {
3565
+ startupNeedsReadinessRefresh = true;
3566
+ if (startupDeferral.hasStarted()) startupDeferral.reopen(retryAfterMs);
3567
+ else startupDeferral.defer(retryAfterMs);
3568
+ console.warn(
3569
+ `[vo-runner supervisor] GitHub readiness embargoed child startup for ${retryAfterMs}ms; control polling remains active`
3570
+ );
3571
+ };
3572
+ const refreshSupervisorChildAdmission = async () => {
3573
+ if (!pairedSupervisor) return true;
3574
+ const refreshed = await prepareSupervisorAuth({
3575
+ ...supervisorAuthInput,
3576
+ baseEnv: childEnv
3577
+ });
3578
+ if (refreshed.operatorId !== operatorId) {
3579
+ throw new Error("runner readiness operator identity changed during supervisor admission");
3580
+ }
3581
+ if (refreshed.startupRetryAfterMs !== null) {
3582
+ deferChildAdmission(refreshed.startupRetryAfterMs);
3583
+ return false;
3584
+ }
3585
+ if (refreshed.repositoryScope.length > 0) {
3586
+ const repositoryScope = refreshed.repositoryScope.join(",");
3587
+ childEnv.VO_CODE_RUNNER_REPOS = repositoryScope;
3588
+ clientEnv.VO_CODE_RUNNER_REPOS = repositoryScope;
3589
+ }
3590
+ startupNeedsReadinessRefresh = false;
3591
+ return true;
3592
+ };
3593
+ const ensureHealthyChildAfterControl = async () => {
3594
+ if (!child || supervisorChildHasExited(child)) {
3595
+ const recoveryGeneration = degradationState.snapshot().generation;
3596
+ if (!startupDeferral.hasStarted() && startupDeferral.isPending()) {
3597
+ deferredControlRecoveryGeneration = recoveryGeneration;
3598
+ return false;
3599
+ }
3600
+ try {
3601
+ if (!await refreshSupervisorChildAdmission()) {
3602
+ deferredControlRecoveryGeneration = recoveryGeneration;
3603
+ return false;
3604
+ }
3605
+ if (!startupDeferral.hasStarted() && !startupDeferral.consumeIfReady()) return false;
3606
+ child = launchChild();
3607
+ readinessDeferrals.recordRecoveryGeneration(child, recoveryGeneration);
3608
+ } catch (error) {
3609
+ markSupervisorDegraded();
3610
+ console.error(
3611
+ `[vo-runner supervisor] control recovery relaunch failed; entering degraded mode: ${error instanceof Error ? error.message : String(error)}`
3612
+ );
3613
+ return false;
3614
+ }
3615
+ }
3616
+ return verifyChildHealth(child, true, "control recovery relaunch");
3617
+ };
3618
+ const startInitialChild = async (recoveryGeneration = null) => {
3619
+ const activationChild = launchChild();
3620
+ child = activationChild;
3621
+ if (Number.isInteger(recoveryGeneration)) {
3622
+ readinessDeferrals.recordRecoveryGeneration(activationChild, recoveryGeneration);
3623
+ }
3624
+ void verifyChildHealth(
3625
+ activationChild,
3626
+ Number.isInteger(recoveryGeneration),
3627
+ Number.isInteger(recoveryGeneration) ? "deferred control recovery" : "initial child"
3628
+ );
3629
+ if (!await finishPendingActivation({
3630
+ client,
3631
+ child: activationChild,
3632
+ runtimeRoot,
3633
+ operatorId,
3634
+ runnerId,
3635
+ selfPath,
3636
+ packageVersion: supervisorVersion,
3637
+ supervisorIdentity: supervisorControlIdentity,
3638
+ waitForLocalRunner,
3639
+ isReadinessDeferred: isChildReadinessDeferred,
3640
+ localStatus,
3641
+ stopChild: stopChildExpectedly,
3642
+ launchPreviousChild: (entry) => spawnPreviousChild(entry, childEnv),
3643
+ log: (message) => console.error(`[vo-runner supervisor] ${message}`)
3644
+ })) {
3645
+ if (captureChildReadinessDeferral(activationChild)) {
3646
+ console.warn("[vo-runner supervisor] activation attestation deferred with GitHub readiness; pending slot remains durable");
3647
+ return false;
3648
+ }
3649
+ markSupervisorDegraded();
3650
+ await stopChildExpectedly(activationChild);
3651
+ if (child === activationChild) child = null;
3652
+ console.error("[vo-runner supervisor] activation FAILED \u2014 entering degraded mode: not serving tasks, still polling for remote control actions");
3653
+ return false;
3654
+ }
3655
+ return true;
3656
+ };
3657
+ const attemptInitialChildStart = async () => {
3658
+ if (startupDeferral.hasStarted() || startupDeferral.isPending()) return false;
3659
+ const beforeRefresh = resolveSupervisorChildStartAuthority({
3660
+ degradationState,
3661
+ deferredRecoveryGeneration: deferredControlRecoveryGeneration
3662
+ });
3663
+ if (!beforeRefresh.allowed) return false;
3664
+ if (startupNeedsReadinessRefresh) {
3665
+ try {
3666
+ if (!await refreshSupervisorChildAdmission()) return false;
3667
+ } catch (error) {
3668
+ markSupervisorDegraded();
3669
+ deferredControlRecoveryGeneration = null;
3670
+ startupDeferral.consumeIfReady();
3671
+ console.error(
3672
+ `[vo-runner supervisor] deferred readiness refresh failed; entering degraded mode: ${error instanceof Error ? error.message : String(error)}`
3673
+ );
3674
+ return false;
3675
+ }
3676
+ }
3677
+ const afterRefresh = resolveSupervisorChildStartAuthority({
3678
+ degradationState,
3679
+ deferredRecoveryGeneration: deferredControlRecoveryGeneration
3680
+ });
3681
+ if (!afterRefresh.allowed) return false;
3682
+ if (!startupDeferral.consumeIfReady()) return false;
3683
+ deferredControlRecoveryGeneration = null;
3684
+ return startInitialChild(afterRefresh.recoveryGeneration);
3685
+ };
3686
+ await attemptInitialChildStart();
3687
+ const shutdown = async () => {
3688
+ if (stopping) return;
3689
+ stopping = true;
3690
+ await stopChild(child);
3691
+ };
3692
+ process.on("SIGINT", () => {
3693
+ void shutdown().finally(() => process.exit(0));
3694
+ });
3695
+ process.on("SIGTERM", () => {
3696
+ void shutdown().finally(() => process.exit(0));
3697
+ });
3698
+ while (!stopping) {
3699
+ try {
3700
+ await attemptInitialChildStart();
3701
+ const action = await client.pollRunnerControl({
3702
+ runnerId,
3703
+ ...operatorId ? { operatorId } : {},
3704
+ ...supervisorControlIdentity
3705
+ });
3706
+ if (!action) {
3707
+ await sleep(POLL_MS);
3708
+ continue;
3709
+ }
3710
+ deferredControlRecoveryGeneration = null;
3711
+ handling = true;
3712
+ const controlIdentity = { runnerId, ...operatorId ? { operatorId } : {}, ...supervisorControlIdentity };
3713
+ const bundledAction = action.kind === "update" || action.kind === "reinstall";
3714
+ const drain = await awaitRunnerUpdateDrain({
3715
+ readStatus: localStatus,
3716
+ drainable: bundledAction,
3717
+ env: process.env,
3718
+ isChildRunning: () => supervisorChildIsRunning(child),
3719
+ log: (message) => console.warn(`[vo-runner supervisor] ${message}`)
3720
+ });
3721
+ if (!drain.proceed) {
3722
+ await client.completeRunnerControl(action.actionId, { ...controlIdentity, status: "failed", detail: drain.detail });
3723
+ handling = false;
3724
+ respawn();
3725
+ continue;
3726
+ }
3727
+ controlRecoveryFence.markBeforeStop(child);
3728
+ await stopChildExpectedly(child);
3729
+ const result = bundledAction ? stageAndActivateBundledUpdate({
3730
+ runtimeRoot,
3731
+ packageSpec: `@algosuite/vo-mcp@${action.desired_package_version}`,
3732
+ expectedVersion: action.desired_package_version,
3733
+ expectedIntegrity: action.desired_package_integrity,
3734
+ action: {
3735
+ actionId: action.actionId,
3736
+ runnerId,
3737
+ operatorId: operatorId || "",
3738
+ supervisorInstanceId
3739
+ },
3740
+ env: process.env,
3741
+ force: action.kind === "reinstall"
3742
+ }) : action.kind === "purge-orphans" ? runLegacyOrphanSweep({
3743
+ protectedPids: [process.pid],
3744
+ log: (message) => console.warn(`[vo-runner supervisor] ${message}`)
3745
+ }) : runHostMaintenance(action.kind, {
3746
+ env: clientEnv,
3747
+ log: (message) => console.warn(`[vo-runner supervisor] ${message}`)
3748
+ });
3749
+ if (result.ok && result.handoff) {
3750
+ if (drain.capped) console.warn(`[vo-runner supervisor] ${drain.detail}`);
3751
+ console.warn(`[vo-runner supervisor] activated ${result.active.version}; exiting for new-process attestation`);
3752
+ return;
3753
+ }
3754
+ const recoveryAuthorized = shouldRecoverSupervisorChildAfterAction({
3755
+ stoppedChild: controlRecoveryFence.consume(),
3756
+ actionKind: action.kind,
3757
+ actionSucceeded: result.ok
3758
+ });
3759
+ const relaunchedHealthy = recoveryAuthorized ? await ensureHealthyChildAfterControl() : false;
3760
+ if (!relaunchedHealthy) result.ok = false;
3761
+ await client.completeRunnerControl(action.actionId, {
3762
+ ...controlIdentity,
3763
+ status: result.ok ? "succeeded" : "failed",
3764
+ detail: result.ok ? result.detail ? `${result.detail}; runner ${packageVersion()} reconnected`.slice(0, 1e3) : `runner ${packageVersion()} reconnected` : `maintenance exited ${result.status}${result.detail ? `: ${result.detail}` : ""}${relaunchedHealthy ? "" : "; runner relaunch did not become healthy"}`
3765
+ });
3766
+ handling = false;
3767
+ respawn();
3768
+ } catch (error) {
3769
+ console.error(`[vo-runner supervisor] ${error instanceof Error ? error.message : String(error)}`);
3770
+ await controlRecoveryFence.recoverOnce(() => ensureHealthyChildAfterControl());
3771
+ handling = false;
3772
+ await sleep(POLL_MS);
3773
+ }
3774
+ }
3775
+ }
3776
+ main().catch((error) => {
3777
+ console.error(`[vo-runner supervisor] fatal: ${error instanceof Error ? error.message : String(error)}`);
3778
+ process.exitCode = 1;
3779
+ });
3780
+ //# sourceMappingURL=runner-supervisor.js.map