@algosuite/vo-mcp 0.2.0-beta.46 → 0.2.0-beta.49

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.
@@ -187,6 +187,911 @@ var init_credential_store = __esm({
187
187
  }
188
188
  });
189
189
 
190
+ // src/cloud/pairing.ts
191
+ import { hostname, platform } from "node:os";
192
+ function formatPairingCode(code) {
193
+ return code.length === 8 ? `${code.slice(0, 4)}-${code.slice(4)}` : code;
194
+ }
195
+ async function readJson(res) {
196
+ const body = await res.json().catch(() => ({}));
197
+ return body && typeof body === "object" ? body : {};
198
+ }
199
+ async function runPairing(deps = {}) {
200
+ const env2 = deps.env ?? process.env;
201
+ const log2 = deps.log ?? ((m) => console.error(m));
202
+ const fetchImpl = deps.fetchImpl ?? fetch;
203
+ const sleep3 = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
204
+ const now = deps.now ?? (() => /* @__PURE__ */ new Date());
205
+ const store = deps.store ?? ((cred, iso) => writeStoredCredential(cred, iso, env2));
206
+ const controlPlaneUrl2 = env2["VO_CONTROL_PLANE_URL"]?.trim() || DEFAULT_CONTROL_PLANE_URL;
207
+ const dashboardUrl = env2["VO_DASHBOARD_URL"]?.trim() || DEFAULT_DASHBOARD_URL;
208
+ const deviceLabel = `${platform()} on ${hostname()}`.slice(0, 120);
209
+ const initRes = await fetchImpl(`${controlPlaneUrl2}/api/v1/pair/initiate`, {
210
+ method: "POST",
211
+ headers: { "content-type": "application/json" },
212
+ body: JSON.stringify({ device_label: deviceLabel })
213
+ });
214
+ if (!initRes.ok) throw new Error(`Could not start pairing (server ${initRes.status}).`);
215
+ const init = await readJson(initRes);
216
+ const code = String(init["code"] ?? "");
217
+ const pollToken = String(init["poll_token"] ?? "");
218
+ if (!code || !pollToken) throw new Error("Pairing service returned an incomplete response.");
219
+ const intervalMs = (Number(init["poll_interval_seconds"]) || 5) * 1e3;
220
+ const expiresAtMs = new Date(String(init["expires_at"] ?? "")).getTime();
221
+ log2("");
222
+ log2(" To connect this runner, open this page in your browser:");
223
+ log2(` ${dashboardUrl}/pair`);
224
+ log2(" and enter this code:");
225
+ log2("");
226
+ log2(` ${formatPairingCode(code)}`);
227
+ log2("");
228
+ log2(" Your Anthropic key never leaves this computer. Waiting for you to authorize\u2026");
229
+ for (; ; ) {
230
+ if (Number.isFinite(expiresAtMs) && now().getTime() >= expiresAtMs) {
231
+ throw new Error("The pairing code expired before it was authorized. Run `vo-mcp pair` again.");
232
+ }
233
+ await sleep3(intervalMs);
234
+ const pollRes = await fetchImpl(`${controlPlaneUrl2}/api/v1/pair/poll`, {
235
+ method: "GET",
236
+ headers: { "x-vo-poll-token": pollToken }
237
+ });
238
+ if (pollRes.status === 404) {
239
+ throw new Error("The pairing expired. Run `vo-mcp pair` again.");
240
+ }
241
+ if (pollRes.status === 410) {
242
+ throw new Error("This code was already used. Run `vo-mcp pair` again.");
243
+ }
244
+ if (!pollRes.ok) {
245
+ continue;
246
+ }
247
+ const body = await readJson(pollRes);
248
+ if (body["status"] === "pending") continue;
249
+ if (body["status"] === "authorized" && typeof body["vo_credential"] === "string") {
250
+ const credentialPath2 = store(
251
+ {
252
+ vo_credential: body["vo_credential"],
253
+ ...typeof body["expires_at"] === "string" ? { vo_credential_expires_at: body["expires_at"] } : {}
254
+ },
255
+ now().toISOString()
256
+ );
257
+ return { credentialPath: credentialPath2, expires_at: String(body["expires_at"] ?? "") };
258
+ }
259
+ throw new Error("Unexpected response from the pairing service.");
260
+ }
261
+ }
262
+ var DEFAULT_CONTROL_PLANE_URL, DEFAULT_DASHBOARD_URL;
263
+ var init_pairing = __esm({
264
+ "src/cloud/pairing.ts"() {
265
+ "use strict";
266
+ init_credential_store();
267
+ DEFAULT_CONTROL_PLANE_URL = "https://vo-control-plane-bzjphrajaq-uc.a.run.app";
268
+ DEFAULT_DASHBOARD_URL = "https://algosuite.ai";
269
+ }
270
+ });
271
+
272
+ // src/config-backup.ts
273
+ import { chmodSync as chmodSync2, copyFileSync, existsSync as existsSync3, readdirSync, readFileSync as readFileSync2, renameSync, statSync, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "node:fs";
274
+ import { basename, dirname as dirname2, join as join2 } from "node:path";
275
+ function sweepStaleTempFiles(configPath) {
276
+ const dir = dirname2(configPath);
277
+ const name = basename(configPath);
278
+ if (!existsSync3(dir)) return 0;
279
+ let removed = 0;
280
+ for (const entry of readdirSync(dir)) {
281
+ if (!entry.startsWith(name) || !TEMP_SUFFIX_RE.test(entry.slice(name.length))) continue;
282
+ try {
283
+ unlinkSync2(join2(dir, entry));
284
+ removed += 1;
285
+ } catch {
286
+ }
287
+ }
288
+ return removed;
289
+ }
290
+ function sleepSync(ms) {
291
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
292
+ }
293
+ function hasIdenticalBackup(configPath, content) {
294
+ const dir = dirname2(configPath);
295
+ const name = basename(configPath);
296
+ if (!existsSync3(dir)) return false;
297
+ const buffer = Buffer.isBuffer(content) ? content : Buffer.from(content, "utf8");
298
+ for (const entry of readdirSync(dir)) {
299
+ if (!entry.startsWith(name) || !BACKUP_SUFFIX_RE.test(entry.slice(name.length))) continue;
300
+ const candidate = join2(dir, entry);
301
+ try {
302
+ if (statSync(candidate).size !== buffer.length) continue;
303
+ if (readFileSync2(candidate).equals(buffer)) return true;
304
+ } catch {
305
+ }
306
+ }
307
+ return false;
308
+ }
309
+ function backupConfigOnce(configPath) {
310
+ if (!existsSync3(configPath)) return null;
311
+ const current = readFileSync2(configPath);
312
+ if (hasIdenticalBackup(configPath, current)) return null;
313
+ const backupPath = `${configPath}.backup-${Date.now()}`;
314
+ copyFileSync(configPath, backupPath);
315
+ return backupPath;
316
+ }
317
+ function writeFileAtomic(path22, content) {
318
+ sweepStaleTempFiles(path22);
319
+ const temp = `${path22}.vo-mcp-tmp-${process.pid}-${Date.now()}`;
320
+ try {
321
+ writeFileSync2(temp, content, { encoding: "utf8", mode: 384 });
322
+ if (existsSync3(path22)) {
323
+ try {
324
+ chmodSync2(temp, statSync(path22).mode & 511);
325
+ } catch {
326
+ }
327
+ }
328
+ let lastErr = null;
329
+ for (let attempt = 0; attempt < RENAME_RETRIES; attempt += 1) {
330
+ try {
331
+ renameSync(temp, path22);
332
+ return;
333
+ } catch (err) {
334
+ lastErr = err;
335
+ const code = err.code;
336
+ if (code !== "EPERM" && code !== "EBUSY" && code !== "EACCES") throw err;
337
+ sleepSync(RENAME_RETRY_MS);
338
+ }
339
+ }
340
+ writeFileSync2(path22, content, "utf8");
341
+ try {
342
+ unlinkSync2(temp);
343
+ } catch {
344
+ }
345
+ void lastErr;
346
+ } catch (err) {
347
+ try {
348
+ unlinkSync2(temp);
349
+ } catch {
350
+ }
351
+ throw err;
352
+ }
353
+ }
354
+ var BACKUP_SUFFIX_RE, TEMP_SUFFIX_RE, RENAME_RETRIES, RENAME_RETRY_MS;
355
+ var init_config_backup = __esm({
356
+ "src/config-backup.ts"() {
357
+ "use strict";
358
+ BACKUP_SUFFIX_RE = /^\.backup-\d+$/u;
359
+ TEMP_SUFFIX_RE = /^\.vo-mcp-tmp-\d+-\d+$/u;
360
+ RENAME_RETRIES = 5;
361
+ RENAME_RETRY_MS = 50;
362
+ }
363
+ });
364
+
365
+ // src/mcp-launcher.ts
366
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "node:fs";
367
+ import { homedir as homedir3, platform as osPlatform } from "node:os";
368
+ import { isAbsolute, join as join3, normalize, resolve, sep, posix as pathPosix, win32 as pathWin32 } from "node:path";
369
+ function defaultRunnerRuntimeRoot(env2 = process.env, { platform: platform4 = osPlatform(), home = homedir3() } = {}) {
370
+ const explicit = String(env2["VO_RUNNER_RUNTIME_ROOT"] || "").trim();
371
+ if (explicit) return isAbsolute(explicit) ? resolve(explicit) : null;
372
+ if (platform4 === "win32") {
373
+ const appData = String(env2["APPDATA"] || "").trim();
374
+ return appData && isAbsolute(appData) ? resolve(join3(appData, APP_IDENTIFIER2, RUNNER_RUNTIME_DIR)) : null;
375
+ }
376
+ if (!home) return null;
377
+ if (platform4 === "darwin") return resolve(join3(home, "Library", "Application Support", APP_IDENTIFIER2, RUNNER_RUNTIME_DIR));
378
+ const xdg = String(env2["XDG_CONFIG_HOME"] || "").trim();
379
+ return resolve(join3(xdg && isAbsolute(xdg) ? xdg : join3(home, ".config"), APP_IDENTIFIER2, RUNNER_RUNTIME_DIR));
380
+ }
381
+ function mcpLauncherPath(runtimeRoot) {
382
+ return join3(runtimeRoot, MCP_LAUNCHER_FILE);
383
+ }
384
+ function renderMcpLauncher() {
385
+ return `#!/usr/bin/env node
386
+ // Written by \`vo-mcp install\` / the runner daemon (F24, 2026-08-16). Claude's and Codex's vo-mcp MCP
387
+ // server entries point here so every MCP spawn runs the ACTIVE runner runtime slot \u2014 the fleet-approved
388
+ // vo-mcp \u2014 instead of whatever copy \`install\` happened to run from. Resolution: this file's directory,
389
+ // then an ABSOLUTE VO_RUNNER_RUNTIME_ROOT -> current.json (schema 1) active slot -> previous slot ->
390
+ // ${MCP_FALLBACK_CLI_ENV} (a non-slot package cli.js) -> exit 2.
391
+ import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
392
+ import { dirname, isAbsolute, join } from 'node:path';
393
+ import { fileURLToPath, pathToFileURL } from 'node:url';
394
+ const SLOT_ID_RE = /^vo-mcp-[0-9A-Za-z._-]{1,96}$/u;
395
+ const here = dirname(fileURLToPath(import.meta.url));
396
+ const envRoot = String(process.env.VO_RUNNER_RUNTIME_ROOT || '').trim();
397
+ const ROOTS = [here, ...(envRoot && isAbsolute(envRoot) && envRoot !== here ? [envRoot] : [])];
398
+ const FALLBACK_CLI = process.env.${MCP_FALLBACK_CLI_ENV} || '';
399
+ function slotCli(root, slotId) {
400
+ if (typeof slotId !== 'string' || !SLOT_ID_RE.test(slotId)) return null;
401
+ const cli = join(root, 'slots', slotId, 'node_modules', '@algosuite', 'vo-mcp', 'dist', 'cli.js');
402
+ return existsSync(cli) ? cli : null;
403
+ }
404
+ function rootCli(root) {
405
+ try {
406
+ const cur = JSON.parse(readFileSync(join(root, 'current.json'), 'utf8'));
407
+ if (!cur || cur.schema_version !== 1) return null;
408
+ return slotCli(root, cur.active && cur.active.slot_id) || slotCli(root, cur.previous && cur.previous.slot_id);
409
+ } catch { return null; }
410
+ }
411
+ // Floor: no usable pointer (quarantined by a bootstrap rollback, corrupt, or a
412
+ // future schema) must not leave every agent without MCP \u2014 run the NEWEST slot
413
+ // on disk that carries a cli.js, and say so on stderr.
414
+ function newestSlotCli(root) {
415
+ try {
416
+ const dir = join(root, 'slots');
417
+ const found = readdirSync(dir).filter((id) => SLOT_ID_RE.test(id) && slotCli(root, id))
418
+ .map((id) => ({ id, mtime: statSync(join(dir, id)).mtimeMs }))
419
+ .sort((a, b) => b.mtime - a.mtime);
420
+ return found.length ? slotCli(root, found[0].id) : null;
421
+ } catch { return null; }
422
+ }
423
+ let target = null;
424
+ for (const root of ROOTS) { target = rootCli(root); if (target) break; }
425
+ if (!target) {
426
+ for (const root of ROOTS) { target = newestSlotCli(root); if (target) { process.stderr.write('[vo-mcp-launcher] no usable runtime pointer \u2014 using the newest slot on disk\\n'); break; } }
427
+ }
428
+ if (!target && FALLBACK_CLI && existsSync(FALLBACK_CLI)) target = FALLBACK_CLI;
429
+ if (!target) { process.stderr.write('[vo-mcp-launcher] no runner runtime slot and no fallback cli\\n'); process.exit(2); }
430
+ process.stderr.write(\`[vo-mcp-launcher] \${target}\\n\`);
431
+ await import(pathToFileURL(target).href);
432
+ `;
433
+ }
434
+ function writeMcpLauncher(runtimeRoot) {
435
+ mkdirSync3(runtimeRoot, { recursive: true });
436
+ const target = mcpLauncherPath(runtimeRoot);
437
+ const content = renderMcpLauncher();
438
+ if (!existsSync4(target) || readFileSync3(target, "utf8") !== content) writeFileSync3(target, content, "utf8");
439
+ return target;
440
+ }
441
+ function samePath(a, b, platform4 = osPlatform()) {
442
+ const pathModule = platform4 === "win32" ? pathWin32 : pathPosix;
443
+ const norm = (p) => {
444
+ const slashed = pathModule.normalize(p).replace(/\\/g, "/").replace(/\/+$/u, "");
445
+ return platform4 === "win32" || platform4 === "darwin" ? slashed.toLowerCase() : slashed;
446
+ };
447
+ return norm(a) === norm(b);
448
+ }
449
+ function isNotLauncherEntry(entry, launcherPath, platform4) {
450
+ const first = entry?.args && entry.args.length > 0 ? String(entry.args[0]) : "";
451
+ return !first || !samePath(first, launcherPath, platform4);
452
+ }
453
+ function isStaleVoMcpEntry(entry, launcherPath, fallbackCli, platform4) {
454
+ if (!entry || isNotLauncherEntry(entry, launcherPath, platform4)) return true;
455
+ const recorded = String(entry.env?.[MCP_FALLBACK_CLI_ENV] ?? "").trim();
456
+ if (!fallbackCli) return recorded !== "";
457
+ return !recorded || !samePath(recorded, fallbackCli, platform4);
458
+ }
459
+ function isSlotCli(cliPath, runtimeRoot = null) {
460
+ if (runtimeRoot) {
461
+ const prefix = normalize(join3(runtimeRoot, "slots")) + sep;
462
+ const candidate = normalize(cliPath);
463
+ const under = process.platform === "win32" ? candidate.toLowerCase().startsWith(prefix.toLowerCase()) : candidate.startsWith(prefix);
464
+ return under && /^vo-mcp-[0-9A-Za-z._-]{1,96}$/u.test(candidate.slice(prefix.length).split(/[\\/]/u)[0] ?? "");
465
+ }
466
+ return /[\\/]slots[\\/]vo-mcp-[0-9A-Za-z._-]{1,96}[\\/]/iu.test(cliPath);
467
+ }
468
+ function chooseFallbackCli(existing, cliPath, sticky, runtimeRoot = null) {
469
+ const current = String(existing ?? "").trim();
470
+ if (sticky && current && !isSlotCli(current, runtimeRoot) && existsSync4(current)) return current;
471
+ return isSlotCli(cliPath, runtimeRoot) ? null : cliPath;
472
+ }
473
+ var MCP_LAUNCHER_FILE, MCP_FALLBACK_CLI_ENV, APP_IDENTIFIER2, RUNNER_RUNTIME_DIR;
474
+ var init_mcp_launcher = __esm({
475
+ "src/mcp-launcher.ts"() {
476
+ "use strict";
477
+ MCP_LAUNCHER_FILE = "vo-mcp-launcher.mjs";
478
+ MCP_FALLBACK_CLI_ENV = "VO_MCP_FALLBACK_CLI";
479
+ APP_IDENTIFIER2 = "ai.algosuite.vo-runner";
480
+ RUNNER_RUNTIME_DIR = "runner-runtime";
481
+ }
482
+ });
483
+
484
+ // src/codex-mcp-config.ts
485
+ import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync4, statSync as statSync2 } from "node:fs";
486
+ import { dirname as dirname3, join as join4 } from "node:path";
487
+ function resolveCodexConfigPath(home) {
488
+ return join4(home, ".codex", "config.toml");
489
+ }
490
+ function normalizeKey(value) {
491
+ const trimmed = value.trim();
492
+ if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) {
493
+ return trimmed.slice(1, -1);
494
+ }
495
+ return trimmed;
496
+ }
497
+ function tablePath(line) {
498
+ const match = /^\s*\[([^\r\n]+)\]\s*(?:#.*)?$/.exec(line);
499
+ const rawPath = match?.[1];
500
+ if (!rawPath || rawPath.includes("[") || rawPath.includes("]")) return null;
501
+ return rawPath.split(".").map(normalizeKey);
502
+ }
503
+ function tableSections(lines) {
504
+ const starts = [];
505
+ for (let index = 0; index < lines.length; index += 1) {
506
+ const path22 = tablePath(lines[index] ?? "");
507
+ if (path22) starts.push({ path: path22, start: index });
508
+ }
509
+ return starts.map((section, index) => ({
510
+ ...section,
511
+ end: starts[index + 1]?.start ?? lines.length
512
+ }));
513
+ }
514
+ function isManagedSection(section) {
515
+ return section.path[0] === "mcp_servers" && MANAGED_SERVER_NAMES.has(section.path[1] ?? "");
516
+ }
517
+ function assignmentKey(line) {
518
+ const match = /^\s*((?:[A-Za-z0-9_-]+)|(?:"[^"]+")|(?:'[^']+'))\s*=/.exec(line);
519
+ return match?.[1] ? normalizeKey(match[1]) : null;
520
+ }
521
+ function isStructurallySafeToml(raw) {
522
+ const lines = raw.split(/\r?\n/);
523
+ const beginIndexes = lines.flatMap((line, index) => line.trim() === MANAGED_BEGIN ? [index] : []);
524
+ const endIndexes = lines.flatMap((line, index) => line.trim() === MANAGED_END ? [index] : []);
525
+ if (beginIndexes.length !== endIndexes.length || beginIndexes.length > 1) return false;
526
+ if (beginIndexes[0] !== void 0 && (endIndexes[0] ?? -1) <= beginIndexes[0]) return false;
527
+ for (const line of lines) {
528
+ const trimmed = line.trim();
529
+ if (/^\[\[?mcp_servers(?:\.|\s|$)/.test(trimmed) && tablePath(line) === null) return false;
530
+ }
531
+ return true;
532
+ }
533
+ function tomlString(value) {
534
+ return JSON.stringify(value);
535
+ }
536
+ function preservedSectionLines(lines, section, managedKeys) {
537
+ if (!section) return [];
538
+ return lines.slice(section.start + 1, section.end).filter((line) => {
539
+ if (line.trim() === MANAGED_BEGIN || line.trim() === MANAGED_END) return false;
540
+ const key = assignmentKey(line);
541
+ return key === null || !managedKeys.has(key);
542
+ }).filter((line, index, all) => line.trim() !== "" || index > 0 && index < all.length - 1);
543
+ }
544
+ function readCodexManagedEnv(configPath, key) {
545
+ if (!existsSync5(configPath)) return void 0;
546
+ const raw = readFileSync4(configPath, "utf8");
547
+ const lines = raw.split(/\r?\n/);
548
+ const sections = tableSections(lines);
549
+ const envSection = sections.find((section) => isManagedSection(section) && section.path[2] === "env");
550
+ if (!envSection) return void 0;
551
+ for (const line of lines.slice(envSection.start + 1, envSection.end)) {
552
+ if (assignmentKey(line) !== key) continue;
553
+ const match = /=\s*("(?:[^"\\]|\\.)*")\s*(?:#.*)?$/.exec(line);
554
+ if (!match?.[1]) return void 0;
555
+ try {
556
+ const value = JSON.parse(match[1]);
557
+ return typeof value === "string" ? value : void 0;
558
+ } catch {
559
+ return void 0;
560
+ }
561
+ }
562
+ return void 0;
563
+ }
564
+ function renderCodexMcpConfig(raw, cliPath, controlPlaneUrl2, managedEnv = {}) {
565
+ if (!isStructurallySafeToml(raw)) {
566
+ throw new Error("Codex config is malformed; refusing to overwrite it");
567
+ }
568
+ const eol = raw.includes("\r\n") ? "\r\n" : "\n";
569
+ const lines = raw.split(/\r?\n/);
570
+ const sections = tableSections(lines);
571
+ const rootSections = sections.filter((section) => isManagedSection(section) && section.path.length === 2);
572
+ const preferredRoot = rootSections.find((section) => section.path[1] === "algohq") ?? rootSections[0];
573
+ const preferredName = preferredRoot?.path[1];
574
+ const envSection = sections.find((section) => isManagedSection(section) && section.path[1] === preferredName && section.path[2] === "env");
575
+ const rootExtras = preservedSectionLines(lines, preferredRoot, /* @__PURE__ */ new Set(["command", "args", "required"]));
576
+ const envExtras = preservedSectionLines(lines, envSection, /* @__PURE__ */ new Set(["VO_CONTROL_PLANE_URL", MCP_FALLBACK_CLI_ENV, ...Object.keys(managedEnv)]));
577
+ const removed = /* @__PURE__ */ new Set();
578
+ for (const [index, line] of lines.entries()) {
579
+ if (line.trim() === MANAGED_BEGIN || line.trim() === MANAGED_END) removed.add(index);
580
+ }
581
+ for (const section of sections.filter(isManagedSection)) {
582
+ for (let index = section.start; index < section.end; index += 1) removed.add(index);
583
+ }
584
+ const base = lines.filter((_line, index) => !removed.has(index)).join(eol).trimEnd();
585
+ const block = [
586
+ MANAGED_BEGIN,
587
+ "[mcp_servers.algohq]",
588
+ 'command = "node"',
589
+ `args = [${tomlString(cliPath)}]`,
590
+ "required = true",
591
+ ...rootExtras,
592
+ "",
593
+ "[mcp_servers.algohq.env]",
594
+ `VO_CONTROL_PLANE_URL = ${tomlString(controlPlaneUrl2)}`,
595
+ ...Object.entries(managedEnv).map(([key, value]) => `${key} = ${tomlString(value)}`),
596
+ ...envExtras,
597
+ MANAGED_END
598
+ ].join(eol);
599
+ return `${base}${base ? `${eol}${eol}` : ""}${block}${eol}`;
600
+ }
601
+ function installCodexMcpConfigAt(configPath, cliPath, controlPlaneUrl2, log2, managedEnv = {}) {
602
+ const exists = existsSync5(configPath);
603
+ const readMtime = exists ? statSync2(configPath).mtimeMs : null;
604
+ const raw = exists ? readFileSync4(configPath, "utf8") : "";
605
+ let rendered;
606
+ try {
607
+ rendered = renderCodexMcpConfig(raw, cliPath, controlPlaneUrl2, managedEnv);
608
+ } catch (error) {
609
+ const backupPath2 = backupConfigOnce(configPath);
610
+ if (backupPath2) log2(` Backed up malformed Codex config \u2192 ${backupPath2}`);
611
+ throw error;
612
+ }
613
+ if (rendered === raw) {
614
+ log2(` Codex already current: ${configPath}`);
615
+ return;
616
+ }
617
+ if (readMtime !== null && (!existsSync5(configPath) || statSync2(configPath).mtimeMs !== readMtime)) {
618
+ log2(` \u26A0 Codex config changed while updating \u2014 left untouched this time: ${configPath}`);
619
+ return;
620
+ }
621
+ const backupPath = backupConfigOnce(configPath);
622
+ if (backupPath) log2(` Backed up Codex config \u2192 ${backupPath}`);
623
+ mkdirSync4(dirname3(configPath), { recursive: true });
624
+ writeFileAtomic(configPath, rendered);
625
+ log2(`\u2713 Wrote AlgoHQ MCP to Codex: ${configPath}`);
626
+ }
627
+ var MANAGED_BEGIN, MANAGED_END, MANAGED_SERVER_NAMES;
628
+ var init_codex_mcp_config = __esm({
629
+ "src/codex-mcp-config.ts"() {
630
+ "use strict";
631
+ init_config_backup();
632
+ init_mcp_launcher();
633
+ MANAGED_BEGIN = "# BEGIN AlgoHQ MCP (managed by vo-mcp install)";
634
+ MANAGED_END = "# END AlgoHQ MCP (managed by vo-mcp install)";
635
+ MANAGED_SERVER_NAMES = /* @__PURE__ */ new Set(["algohq", "vo", "vo-mcp", "vo_mcp"]);
636
+ }
637
+ });
638
+
639
+ // src/autostart.ts
640
+ import { homedir as homedir4, platform as platform2 } from "node:os";
641
+ import { isAbsolute as isAbsolute2, join as join5 } from "node:path";
642
+ import { existsSync as existsSync6, mkdirSync as mkdirSync5, writeFileSync as writeFileSync4, readFileSync as readFileSync5, unlinkSync as unlinkSync3, copyFileSync as copyFileSync2 } from "node:fs";
643
+ function resolveRunnerCommand(override) {
644
+ return override ?? "vo-mcp runner";
645
+ }
646
+ function quotePosixShellArgument(value) {
647
+ if (value.includes("\0") || value.includes("\r") || value.includes("\n")) {
648
+ throw new Error("Runner command must not contain NUL, carriage return, or newline characters.");
649
+ }
650
+ return `'${value.replace(/'/gu, `'"'"'`)}'`;
651
+ }
652
+ function resolveLinuxConfigHome(home, env2) {
653
+ const configured = env2["XDG_CONFIG_HOME"]?.trim();
654
+ return configured && isAbsolute2(configured) ? configured : join5(home, ".config");
655
+ }
656
+ function launcherIsCurrent(path22, desiredContent, label, log2) {
657
+ if (!existsSync6(path22)) return false;
658
+ if (readFileSync5(path22, "utf8") === desiredContent) return true;
659
+ const backupPath = `${path22}.backup-${Date.now()}`;
660
+ copyFileSync2(path22, backupPath);
661
+ log2(` Backed up existing ${label} to: ${backupPath}`);
662
+ return false;
663
+ }
664
+ function installWindowsAutostart(runnerCommand, log2, env2) {
665
+ const appData = env2["APPDATA"] ?? join5(homedir4(), "AppData", "Roaming");
666
+ const startupDir = join5(appData, "Microsoft", "Windows", "Start Menu", "Programs", "Startup");
667
+ mkdirSync5(startupDir, { recursive: true });
668
+ const launcherPath = join5(startupDir, "vo-runner.vbs");
669
+ const legacyCmdPath = join5(startupDir, "vo-runner.cmd");
670
+ if (existsSync6(legacyCmdPath)) {
671
+ try {
672
+ unlinkSync3(legacyCmdPath);
673
+ log2(` Removed legacy minimized .cmd launcher: ${legacyCmdPath}`);
674
+ } catch (error) {
675
+ log2(`\u26A0 Could not remove legacy launcher ${legacyCmdPath} (${error instanceof Error ? error.message : String(error)}); remove it manually to avoid a double start`);
676
+ }
677
+ }
678
+ const hiddenCommand = `cmd /c ${runnerCommand} >> "%USERPROFILE%\\.claude\\vo-runner.log" 2>&1`.replace(/"/g, '""');
679
+ const launcherContent = `' Auto-start launcher for vo-mcp runner
680
+ ' Created by vo-mcp autostart installer
681
+ ' Keepalive supervisor: restarts the runner if it exits (parity with launchd
682
+ ' KeepAlive on macOS and systemd Restart=on-failure on Linux).
683
+ ' To stop: create %USERPROFILE%\\.claude\\vo-runner.stop, or end wscript.exe.
684
+ Dim sh, fso, stopFile, backoff, startedAt, ranMs
685
+ Set sh = CreateObject("WScript.Shell")
686
+ Set fso = CreateObject("Scripting.FileSystemObject")
687
+ sh.CurrentDirectory = sh.ExpandEnvironmentStrings("%USERPROFILE%")
688
+ sh.Environment("Process")("VO_CODE_RUNNER_CLONES_ROOT") = sh.ExpandEnvironmentStrings("%APPDATA%\\ai.algosuite.vo-runner\\clones")
689
+ stopFile = sh.ExpandEnvironmentStrings("%USERPROFILE%\\.claude\\vo-runner.stop")
690
+ backoff = ${WINDOWS_RESTART_BACKOFF_MS}
691
+ Do
692
+ If fso.FileExists(stopFile) Then
693
+ fso.DeleteFile stopFile
694
+ WScript.Quit 0
695
+ End If
696
+ startedAt = Timer
697
+ sh.Run "${hiddenCommand}", 0, True
698
+ ranMs = (Timer - startedAt) * 1000
699
+ If ranMs < 0 Then ranMs = ${WINDOWS_HEALTHY_RUN_MS}
700
+ If ranMs >= ${WINDOWS_HEALTHY_RUN_MS} Then
701
+ backoff = ${WINDOWS_RESTART_BACKOFF_MS}
702
+ ElseIf backoff < ${WINDOWS_MAX_BACKOFF_MS} Then
703
+ backoff = backoff * 2
704
+ End If
705
+ WScript.Sleep backoff
706
+ Loop
707
+ `;
708
+ if (launcherIsCurrent(launcherPath, launcherContent, "launcher", log2)) {
709
+ log2(`\u2713 Auto-start is already configured (Windows Startup folder)`);
710
+ log2(` Path: ${launcherPath}`);
711
+ return;
712
+ }
713
+ writeFileSync4(launcherPath, launcherContent, "utf8");
714
+ log2(`\u2713 Installed Windows auto-start launcher`);
715
+ log2(` Path: ${launcherPath}`);
716
+ log2(` The runner will start hidden at next login.`);
717
+ }
718
+ async function installMacAutostart(runnerCommand, log2, env2) {
719
+ const home = env2["HOME"]?.trim() || homedir4();
720
+ const launchAgentsDir = join5(home, "Library", "LaunchAgents");
721
+ mkdirSync5(launchAgentsDir, { recursive: true });
722
+ const plistPath = join5(launchAgentsDir, "ai.algosuite.vo-runner.plist");
723
+ const parts = runnerCommand.split(/\s+/);
724
+ const program = parts[0] ?? "vo-mcp";
725
+ const args = parts.length > 1 ? parts.slice(1) : ["runner"];
726
+ const plistContent = `<?xml version="1.0" encoding="UTF-8"?>
727
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
728
+ <plist version="1.0">
729
+ <dict>
730
+ <key>Label</key>
731
+ <string>ai.algosuite.vo-runner</string>
732
+ <key>ProgramArguments</key>
733
+ <array>
734
+ <string>${program}</string>
735
+ ${args.map((a) => ` <string>${a}</string>`).join("\n")}
736
+ </array>
737
+ <key>RunAtLoad</key>
738
+ <true/>
739
+ <key>KeepAlive</key>
740
+ <true/>
741
+ <key>WorkingDirectory</key>
742
+ <string>${home}</string>
743
+ <key>EnvironmentVariables</key>
744
+ <dict>
745
+ <key>VO_CODE_RUNNER_CLONES_ROOT</key>
746
+ <string>${join5(home, "Library", "Application Support", "ai.algosuite.vo-runner", "clones")}</string>
747
+ </dict>
748
+ <key>StandardOutPath</key>
749
+ <string>${join5(home, ".claude", "vo-runner.log")}</string>
750
+ <key>StandardErrorPath</key>
751
+ <string>${join5(home, ".claude", "vo-runner-error.log")}</string>
752
+ </dict>
753
+ </plist>
754
+ `;
755
+ if (launcherIsCurrent(plistPath, plistContent, "plist", log2)) {
756
+ log2(`\u2713 Auto-start is already configured (launchd)`);
757
+ log2(` Path: ${plistPath}`);
758
+ return;
759
+ }
760
+ writeFileSync4(plistPath, plistContent, "utf8");
761
+ log2(`\u2713 Installed launchd plist`);
762
+ log2(` Path: ${plistPath}`);
763
+ try {
764
+ const { execSync } = await import("node:child_process");
765
+ execSync(`launchctl load "${plistPath}"`, { stdio: "ignore" });
766
+ log2(`\u2713 Loaded plist with launchctl (runner will start at next login)`);
767
+ log2(` Logs: ${join5(home, ".claude", "vo-runner.log")}`);
768
+ } catch {
769
+ log2(`\u26A0 Failed to load plist with launchctl (you may need to load it manually)`);
770
+ log2(` Run: launchctl load "${plistPath}"`);
771
+ }
772
+ }
773
+ async function installLinuxAutostart(runnerCommand, log2, env2) {
774
+ const home = env2["HOME"]?.trim() || homedir4();
775
+ const configHome = resolveLinuxConfigHome(home, env2);
776
+ const unitDir = join5(configHome, "systemd", "user");
777
+ mkdirSync5(unitDir, { recursive: true });
778
+ const unitPath = join5(unitDir, "vo-runner.service");
779
+ const logFile = join5(home, ".claude", "vo-runner.log");
780
+ const errFile = join5(home, ".claude", "vo-runner-error.log");
781
+ const quotedRunnerCommand = quotePosixShellArgument(runnerCommand);
782
+ mkdirSync5(join5(home, ".claude"), { recursive: true });
783
+ const unit = `[Unit]
784
+ Description=AlgoHQ Code Runner (vo-mcp)
785
+ After=network-online.target
786
+ Wants=network-online.target
787
+
788
+ [Service]
789
+ Type=simple
790
+ WorkingDirectory=${home}
791
+ Environment="VO_CODE_RUNNER_CLONES_ROOT=${join5(configHome, "ai.algosuite.vo-runner", "clones")}"
792
+ ExecStart=/bin/sh -lc ${quotedRunnerCommand}
793
+ Restart=on-failure
794
+ RestartSec=10
795
+ StandardOutput=append:${logFile}
796
+ StandardError=append:${errFile}
797
+
798
+ [Install]
799
+ WantedBy=default.target
800
+ `;
801
+ if (launcherIsCurrent(unitPath, unit, "unit", log2)) {
802
+ log2(`\u2713 Auto-start is already configured (systemd user unit)`);
803
+ log2(` Path: ${unitPath}`);
804
+ return;
805
+ }
806
+ writeFileSync4(unitPath, unit, "utf8");
807
+ log2(`\u2713 Installed systemd user unit`);
808
+ log2(` Path: ${unitPath}`);
809
+ if (process.env["VITEST"]) {
810
+ log2(` (test mode: skipping systemctl enable)`);
811
+ return;
812
+ }
813
+ try {
814
+ const { execSync } = await import("node:child_process");
815
+ execSync("systemctl --user daemon-reload", { stdio: "ignore" });
816
+ execSync("systemctl --user enable --now vo-runner.service", { stdio: "ignore" });
817
+ log2(`\u2713 Enabled + started vo-runner.service (starts at login)`);
818
+ log2(` Logs: ${logFile}`);
819
+ } catch {
820
+ log2(`\u26A0 Could not enable via systemctl (enable it manually):`);
821
+ log2(` systemctl --user daemon-reload && systemctl --user enable --now vo-runner.service`);
822
+ }
823
+ }
824
+ async function installAutostart(opts = {}) {
825
+ const log2 = opts.log ?? ((m) => console.error(m));
826
+ const env2 = opts.env ?? process.env;
827
+ const runnerCommand = resolveRunnerCommand(opts.runnerCommand);
828
+ const plat = opts.platform ?? platform2();
829
+ if (plat === "win32") {
830
+ installWindowsAutostart(runnerCommand, log2, env2);
831
+ } else if (plat === "darwin") {
832
+ await installMacAutostart(runnerCommand, log2, env2);
833
+ } else if (plat === "linux") {
834
+ await installLinuxAutostart(runnerCommand, log2, env2);
835
+ } else {
836
+ log2(`\u2717 Auto-start is not supported on platform: ${plat}`);
837
+ log2(` Supported platforms: win32 (Windows), darwin (macOS), linux (Linux)`);
838
+ }
839
+ }
840
+ var WINDOWS_RESTART_BACKOFF_MS, WINDOWS_HEALTHY_RUN_MS, WINDOWS_MAX_BACKOFF_MS;
841
+ var init_autostart = __esm({
842
+ "src/autostart.ts"() {
843
+ "use strict";
844
+ WINDOWS_RESTART_BACKOFF_MS = 1e4;
845
+ WINDOWS_HEALTHY_RUN_MS = 6e4;
846
+ WINDOWS_MAX_BACKOFF_MS = 3e5;
847
+ }
848
+ });
849
+
850
+ // src/install.ts
851
+ var install_exports = {};
852
+ __export(install_exports, {
853
+ healMcpRegistration: () => healMcpRegistration,
854
+ install: () => install,
855
+ installOptionsFromArgs: () => installOptionsFromArgs
856
+ });
857
+ import { homedir as homedir5, platform as platform3 } from "node:os";
858
+ import { join as join6, dirname as dirname4 } from "node:path";
859
+ import { existsSync as existsSync7, readFileSync as readFileSync6, mkdirSync as mkdirSync6, statSync as statSync3 } from "node:fs";
860
+ import { fileURLToPath } from "node:url";
861
+ function resolveCodeConfigPath(home) {
862
+ return join6(home, ".claude.json");
863
+ }
864
+ function resolveDesktopConfigPath(home, plat, appData) {
865
+ if (plat === "win32") {
866
+ return join6(appData ?? join6(home, "AppData", "Roaming"), "Claude", "claude_desktop_config.json");
867
+ }
868
+ if (plat === "darwin") {
869
+ return join6(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
870
+ }
871
+ return join6(home, ".config", "Claude", "claude_desktop_config.json");
872
+ }
873
+ function readClaudeConfig(path22) {
874
+ if (!existsSync7(path22)) return { kind: "absent", config: {}, mtimeMs: null };
875
+ for (let attempt = 0; attempt < 3; attempt += 1) {
876
+ const before = statSync3(path22).mtimeMs;
877
+ let raw;
878
+ try {
879
+ raw = readFileSync6(path22, "utf8");
880
+ } catch {
881
+ return { kind: "invalid", config: {}, mtimeMs: before };
882
+ }
883
+ if (!existsSync7(path22) || statSync3(path22).mtimeMs !== before) continue;
884
+ const text = raw.replace(/^\uFEFF/u, "");
885
+ if (!text.trim()) return { kind: "empty", config: {}, mtimeMs: before };
886
+ try {
887
+ const parsed = JSON.parse(text);
888
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? { kind: "ok", config: parsed, mtimeMs: before } : { kind: "invalid", config: {}, mtimeMs: before };
889
+ } catch {
890
+ return { kind: "invalid", config: {}, mtimeMs: before };
891
+ }
892
+ }
893
+ return { kind: "invalid", config: {}, mtimeMs: null };
894
+ }
895
+ function writeClaudeConfig(path22, config) {
896
+ mkdirSync6(dirname4(path22), { recursive: true });
897
+ writeFileAtomic(path22, `${JSON.stringify(config, null, 2)}
898
+ `);
899
+ }
900
+ function carriedEntryKeys(entry) {
901
+ if (!entry) return {};
902
+ const { command: _c, args: _a, env: _e, type, url: _u, headers: _h, ...rest } = entry;
903
+ const stdio = type === void 0 || type === "stdio";
904
+ return { ...stdio ? rest : {}, ...type === "stdio" ? { type } : {} };
905
+ }
906
+ function preferredNodeCommand(existing) {
907
+ const current = String(existing ?? "").trim();
908
+ return current && /(^|[\\/])node(\.exe)?$/iu.test(current) ? current : "node";
909
+ }
910
+ function resolveVoMcpCliPath() {
911
+ return join6(dirname4(fileURLToPath(import.meta.url)), "cli.js");
912
+ }
913
+ function installMcpConfigAt(configPath, cliPath, controlPlaneUrl2, log2, label, launcher = { launcherPath: null, ...INSTALL_LAUNCHER }) {
914
+ if (launcher.onlyExisting && !existsSync7(configPath)) return;
915
+ const read = readClaudeConfig(configPath);
916
+ if (read.kind === "invalid" || read.kind === "empty" && launcher.onlyExisting) {
917
+ log2(` \u26A0 ${label} config is ${read.kind === "empty" ? "empty" : "not valid JSON"} \u2014 left untouched: ${configPath}${read.kind === "empty" ? "" : " (fix or remove it, then re-run vo-mcp install)"}`);
918
+ return;
919
+ }
920
+ const existing = read.config;
921
+ const readMtime = read.mtimeMs;
922
+ const mcpServers = existing["mcpServers"] && typeof existing["mcpServers"] === "object" ? existing["mcpServers"] : {};
923
+ const managedEntry = mcpServers["vo-mcp"];
924
+ const voEntry = managedEntry ?? mcpServers["vo"];
925
+ const { launcherPath } = launcher;
926
+ const fallbackCli = chooseFallbackCli(voEntry?.env?.[MCP_FALLBACK_CLI_ENV], cliPath, launcher.sticky, launcherPath ? dirname4(launcherPath) : null);
927
+ const current = launcherPath ? !isStaleVoMcpEntry(voEntry, launcherPath, fallbackCli) : Boolean(voEntry?.args?.some((a) => a.includes(cliPath)));
928
+ if (current) {
929
+ log2(` ${label} already current: ${configPath}`);
930
+ return;
931
+ }
932
+ const backupPath = backupConfigOnce(configPath);
933
+ if (backupPath) log2(` Backed up ${label} config \u2192 ${backupPath}`);
934
+ const { [MCP_FALLBACK_CLI_ENV]: _previousFallback, ...preservedEnv } = voEntry?.env ?? {};
935
+ const merged = {
936
+ ...existing,
937
+ mcpServers: {
938
+ ...mcpServers,
939
+ "vo-mcp": {
940
+ ...carriedEntryKeys(managedEntry),
941
+ command: preferredNodeCommand(managedEntry?.command),
942
+ args: [launcherPath ?? cliPath],
943
+ env: {
944
+ VO_CONTROL_PLANE_URL: controlPlaneUrl2,
945
+ ...preservedEnv,
946
+ ...launcherPath && fallbackCli ? { [MCP_FALLBACK_CLI_ENV]: fallbackCli } : {}
947
+ }
948
+ }
949
+ }
950
+ };
951
+ if (readMtime !== null && (!existsSync7(configPath) || statSync3(configPath).mtimeMs !== readMtime)) {
952
+ log2(` \u26A0 ${label} config changed while updating \u2014 left untouched this time: ${configPath}`);
953
+ return;
954
+ }
955
+ writeClaudeConfig(configPath, merged);
956
+ log2(`\u2713 Wrote vo-mcp to ${label}: ${configPath}`);
957
+ }
958
+ function installMcpConfig(log2, env2, mode = "install") {
959
+ const home = env2["HOME"]?.trim() || env2["USERPROFILE"]?.trim() || homedir5();
960
+ const appData = env2["APPDATA"]?.trim();
961
+ const plat = platform3();
962
+ const cliPath = resolveVoMcpCliPath();
963
+ const controlPlaneUrl2 = env2["VO_CONTROL_PLANE_URL"]?.trim() || DEFAULT_CONTROL_PLANE_URL2;
964
+ const launcherPath = writeMcpLauncherForEnv(env2, log2, { platform: plat, home });
965
+ const launcher = { launcherPath, ...mode === "heal" ? HEAL_LAUNCHER : INSTALL_LAUNCHER };
966
+ const leg = (label, run) => {
967
+ try {
968
+ run();
969
+ } catch (err) {
970
+ if (mode !== "heal") throw err;
971
+ log2(` \u26A0 ${label}: could not update the MCP registration \u2014 ${err instanceof Error ? err.message : String(err)}`);
972
+ }
973
+ };
974
+ leg("Claude Code CLI", () => installMcpConfigAt(resolveCodeConfigPath(home), cliPath, controlPlaneUrl2, log2, "Claude Code CLI", launcher));
975
+ leg("Claude Desktop", () => installMcpConfigAt(resolveDesktopConfigPath(home, plat, appData), cliPath, controlPlaneUrl2, log2, "Claude Desktop", launcher));
976
+ const codexPath = resolveCodexConfigPath(home);
977
+ if (launcher.onlyExisting && !existsSync7(codexPath)) return;
978
+ leg("Codex", () => {
979
+ if (launcherPath) {
980
+ const fallbackCli = chooseFallbackCli(readCodexManagedEnv(codexPath, MCP_FALLBACK_CLI_ENV), cliPath, launcher.sticky, dirname4(launcherPath));
981
+ installCodexMcpConfigAt(codexPath, launcherPath, controlPlaneUrl2, log2, fallbackCli ? { [MCP_FALLBACK_CLI_ENV]: fallbackCli } : {});
982
+ } else {
983
+ installCodexMcpConfigAt(codexPath, cliPath, controlPlaneUrl2, log2);
984
+ }
985
+ });
986
+ }
987
+ function writeMcpLauncherForEnv(env2, log2, hint) {
988
+ try {
989
+ const root = defaultRunnerRuntimeRoot(env2, hint);
990
+ if (!root) return null;
991
+ return writeMcpLauncher(root);
992
+ } catch (err) {
993
+ log2(` \u26A0 could not write the vo-mcp launcher (registering cli.js directly): ${err instanceof Error ? err.message : String(err)}`);
994
+ return null;
995
+ }
996
+ }
997
+ function healMcpRegistration(log2 = () => {
998
+ }, env2 = process.env) {
999
+ try {
1000
+ installMcpConfig(log2, env2, "heal");
1001
+ } catch (err) {
1002
+ log2(`vo-mcp: MCP registration heal skipped: ${err instanceof Error ? err.message : String(err)}`);
1003
+ }
1004
+ }
1005
+ async function runPairFlow(log2, env2) {
1006
+ log2("\n\u2501\u2501\u2501 Step 2: Link your AlgoHQ account (device code) \u2501\u2501\u2501");
1007
+ log2("A short code appears below \u2014 open the URL it prints and enter the code.");
1008
+ log2("Your raw token never persists; a scoped credential is stored in your OS keychain.\n");
1009
+ try {
1010
+ const result = await runPairing({ env: env2, log: log2 });
1011
+ log2(`
1012
+ \u2713 Paired \u2014 scoped credential stored at: ${result.credentialPath}`);
1013
+ } catch (err) {
1014
+ log2(`
1015
+ \u26A0 Pairing didn't complete: ${err instanceof Error ? err.message : String(err)}`);
1016
+ log2(" No problem \u2014 the rest will finish; pair anytime with: vo-mcp pair");
1017
+ }
1018
+ }
1019
+ function printNextSteps(log2, autostartInstalled, configOnly) {
1020
+ log2("\n\u2501\u2501\u2501 Installation complete! \u2501\u2501\u2501\n");
1021
+ log2("What's configured:");
1022
+ log2(" \u2713 Claude Desktop, Claude Code, and Codex will load AlgoHQ MCP on next restart");
1023
+ if (configOnly) {
1024
+ log2(" \u2713 Existing pairing and runner auto-start settings were left unchanged");
1025
+ } else {
1026
+ log2(" \u2713 Your scoped credential is stored (revocable via the dashboard)");
1027
+ }
1028
+ if (autostartInstalled) {
1029
+ log2(" \u2713 Runner daemon will start automatically at login\n");
1030
+ } else {
1031
+ log2("\n");
1032
+ }
1033
+ log2("Next steps:");
1034
+ log2(" 1. Restart Claude Desktop / Claude Code / Codex (if running).");
1035
+ if (configOnly) {
1036
+ log2(" 2. Restart the existing AlgoHQ runner service or runner terminal.");
1037
+ } else if (autostartInstalled) {
1038
+ log2(" 2. Log out and back in (or start the runner manually now: vo-mcp runner)");
1039
+ } else {
1040
+ log2(" 2. Start the agent runner in a terminal (keep it running):");
1041
+ log2(" vo-mcp runner");
1042
+ log2(" (To set up auto-start at login: vo-mcp runner --install-autostart)");
1043
+ }
1044
+ log2(" 3. Visit AlgoHQ to dispatch your first agent:");
1045
+ log2(" https://algosuite.ai/algohq\n");
1046
+ log2("The runner watches for tasks you dispatch and spins up agents in fresh worktrees.");
1047
+ log2("Agents only run while the runner is connected. Ctrl+C to stop it anytime.\n");
1048
+ }
1049
+ async function install(opts = {}) {
1050
+ const log2 = opts.log ?? ((m) => console.error(m));
1051
+ const env2 = opts.env ?? process.env;
1052
+ log2("\u2501\u2501\u2501 vo-mcp installer \u2501\u2501\u2501");
1053
+ log2("This will set up your machine to dispatch AlgoHQ agents from anywhere.\n");
1054
+ log2("\u2501\u2501\u2501 Step 1: Configure Claude Desktop / Claude Code / Codex \u2501\u2501\u2501");
1055
+ installMcpConfig(log2, env2);
1056
+ if (!opts.skipLogin) {
1057
+ await runPairFlow(log2, env2);
1058
+ } else if (opts.configOnly) {
1059
+ log2("\n(Config-only refresh \u2014 existing pairing left unchanged.)");
1060
+ } else {
1061
+ log2("\n(Pairing skipped \u2014 run `vo-mcp pair` when ready.)");
1062
+ }
1063
+ let autostartInstalled = false;
1064
+ if (!opts.skipAutostart) {
1065
+ const plat = platform3();
1066
+ if (plat === "win32" || plat === "darwin" || plat === "linux") {
1067
+ log2("\n\u2501\u2501\u2501 Step 3: Set up auto-start \u2501\u2501\u2501");
1068
+ log2("Would you like the runner daemon to start automatically at login?");
1069
+ log2("(You can skip this and set it up later with: vo-mcp runner --install-autostart)\n");
1070
+ await installAutostart({ log: log2, env: env2 });
1071
+ autostartInstalled = true;
1072
+ }
1073
+ }
1074
+ printNextSteps(log2, autostartInstalled, opts.configOnly === true);
1075
+ }
1076
+ function installOptionsFromArgs(args) {
1077
+ const configOnly = args.includes("--config-only");
1078
+ return configOnly ? { configOnly: true, skipLogin: true, skipAutostart: true } : {};
1079
+ }
1080
+ var DEFAULT_CONTROL_PLANE_URL2, INSTALL_LAUNCHER, HEAL_LAUNCHER;
1081
+ var init_install = __esm({
1082
+ "src/install.ts"() {
1083
+ "use strict";
1084
+ init_pairing();
1085
+ init_codex_mcp_config();
1086
+ init_mcp_launcher();
1087
+ init_config_backup();
1088
+ init_autostart();
1089
+ DEFAULT_CONTROL_PLANE_URL2 = "https://vo-control-plane-bzjphrajaq-uc.a.run.app";
1090
+ INSTALL_LAUNCHER = { sticky: false, onlyExisting: false };
1091
+ HEAL_LAUNCHER = { sticky: true, onlyExisting: true };
1092
+ }
1093
+ });
1094
+
190
1095
  // src/runner/pnpm-link-detach.mjs
191
1096
  import fsp from "node:fs/promises";
192
1097
  import path from "node:path";
@@ -369,15 +1274,15 @@ var init_pnpm_link_detach = __esm({
369
1274
  import { spawn } from "node:child_process";
370
1275
  function sleepMs(ms) {
371
1276
  if (ms <= 0) return Promise.resolve();
372
- return new Promise((resolve2) => setTimeout(resolve2, ms));
1277
+ return new Promise((resolve3) => setTimeout(resolve3, ms));
373
1278
  }
374
1279
  async function spawnUtility(command, args, timeoutMs) {
375
- return await new Promise((resolve2) => {
1280
+ return await new Promise((resolve3) => {
376
1281
  let settled = false;
377
1282
  const finish = (result) => {
378
1283
  if (settled) return;
379
1284
  settled = true;
380
- resolve2(result);
1285
+ resolve3(result);
381
1286
  };
382
1287
  const child = spawn(command, args, {
383
1288
  stdio: "ignore",
@@ -431,7 +1336,7 @@ async function runProcess(command, args = [], options = {}) {
431
1336
  stdio: ["ignore", "pipe", "pipe"],
432
1337
  windowsHide: true
433
1338
  };
434
- return await new Promise((resolve2) => {
1339
+ return await new Promise((resolve3) => {
435
1340
  let settled = false;
436
1341
  let stdout = "";
437
1342
  let stderr = "";
@@ -449,7 +1354,7 @@ async function runProcess(command, args = [], options = {}) {
449
1354
  if (settled) return;
450
1355
  settled = true;
451
1356
  clearTimers();
452
- resolve2({ stdout, stderr, timedOut, ...result });
1357
+ resolve3({ stdout, stderr, timedOut, ...result });
453
1358
  };
454
1359
  const beginForceKill = () => {
455
1360
  if (settled || !child?.pid) return;
@@ -505,8 +1410,8 @@ async function runProcess(command, args = [], options = {}) {
505
1410
  });
506
1411
  }
507
1412
  async function commandExists(command, options = {}) {
508
- const platform = options.platform || process.platform;
509
- const checker = platform === "win32" ? "where" : "which";
1413
+ const platform4 = options.platform || process.platform;
1414
+ const checker = platform4 === "win32" ? "where" : "which";
510
1415
  const result = await (options.runner || runProcess)(checker, [command], {
511
1416
  timeoutMs: 1e4
512
1417
  });
@@ -904,22 +1809,22 @@ function trustedCorepackCandidates(options) {
904
1809
  return [...new Set(roots)].map((root) => portableJoin(root, "node_modules", "corepack", "dist", "corepack.js"));
905
1810
  }
906
1811
  function resolveTrustedCorepackJs(options) {
907
- const existsSync7 = options.existsSync || fs2.existsSync;
908
- return trustedCorepackCandidates(options).find((candidate) => existsSync7(candidate)) || "";
1812
+ const existsSync12 = options.existsSync || fs2.existsSync;
1813
+ return trustedCorepackCandidates(options).find((candidate) => existsSync12(candidate)) || "";
909
1814
  }
910
1815
  async function resolvePnpmInstallCommand(root, options = {}) {
911
1816
  const runner = options.runner || runProcess;
912
- const platform = options.platform || process.platform;
1817
+ const platform4 = options.platform || process.platform;
913
1818
  const selector = pnpmSelector(root);
914
- if (platform !== "win32") {
915
- if (await commandExists("pnpm", { runner, platform })) {
1819
+ if (platform4 !== "win32") {
1820
+ if (await commandExists("pnpm", { runner, platform: platform4 })) {
916
1821
  return {
917
1822
  command: "pnpm",
918
1823
  args: [...DEFAULT_PNPM_INSTALL_ARGS],
919
1824
  displayCommand: ["pnpm", ...DEFAULT_PNPM_INSTALL_ARGS]
920
1825
  };
921
1826
  }
922
- if (await commandExists("corepack", { runner, platform })) {
1827
+ if (await commandExists("corepack", { runner, platform: platform4 })) {
923
1828
  return {
924
1829
  command: "corepack",
925
1830
  args: [selector, ...DEFAULT_PNPM_INSTALL_ARGS],
@@ -1411,7 +2316,7 @@ var init_pnpm_hydration = __esm({
1411
2316
  // src/runner/worktree-paths.mjs
1412
2317
  import { createHash as createHash2 } from "node:crypto";
1413
2318
  import path6 from "node:path";
1414
- function samePath(left, right) {
2319
+ function samePath2(left, right) {
1415
2320
  const a = path6.resolve(left);
1416
2321
  const b = path6.resolve(right);
1417
2322
  return process.platform === "win32" ? a.toLowerCase() === b.toLowerCase() : a === b;
@@ -1420,7 +2325,7 @@ function worktreePoolForRoot(root, { clonesRootDir = process.env.VO_CODE_RUNNER_
1420
2325
  const canonicalRoot = path6.resolve(root);
1421
2326
  if (clonesRootDir) {
1422
2327
  const clonePool = path6.resolve(clonesRootDir);
1423
- if (samePath(path6.dirname(canonicalRoot), clonePool)) {
2328
+ if (samePath2(path6.dirname(canonicalRoot), clonePool)) {
1424
2329
  return path6.join(clonePool, ".agent-worktrees", path6.basename(canonicalRoot));
1425
2330
  }
1426
2331
  }
@@ -3012,7 +3917,7 @@ var init_control_plane_client = __esm({
3012
3917
  });
3013
3918
 
3014
3919
  // ../../scripts/virtual-office/code-runner/windows-claude-launch.mjs
3015
- import { existsSync as existsSync3, realpathSync } from "node:fs";
3920
+ import { existsSync as existsSync8, realpathSync } from "node:fs";
3016
3921
  import { win32 as path11 } from "node:path";
3017
3922
  import { spawnSync } from "node:child_process";
3018
3923
  function pathValue(env2) {
@@ -3086,7 +3991,7 @@ function canonicalExistingPath(candidate, exists, canonicalize) {
3086
3991
  function resolveWindowsClaudeExecutable({
3087
3992
  bin = "claude",
3088
3993
  env: env2 = process.env,
3089
- exists = existsSync3,
3994
+ exists = existsSync8,
3090
3995
  canonicalize = realpathSync
3091
3996
  } = {}) {
3092
3997
  const requested = String(bin || "").trim();
@@ -3264,10 +4169,10 @@ function augmentAuthError(summary) {
3264
4169
  function probeClaudeLoginState({
3265
4170
  spawn: spawn5 = spawnSync2,
3266
4171
  buildWindowsLaunch = buildWindowsClaudeLaunch,
3267
- platform = process.platform
4172
+ platform: platform4 = process.platform
3268
4173
  } = {}) {
3269
4174
  try {
3270
- const launch = platform === "win32" ? buildWindowsLaunch({ bin: "claude", args: ["auth", "status"] }) : { bin: "claude", args: ["auth", "status"], spawnOptions: { windowsHide: true } };
4175
+ const launch = platform4 === "win32" ? buildWindowsLaunch({ bin: "claude", args: ["auth", "status"] }) : { bin: "claude", args: ["auth", "status"], spawnOptions: { windowsHide: true } };
3271
4176
  const st = spawn5(launch.bin, launch.args, { ...launch.spawnOptions, timeout: 5e3, encoding: "utf8" });
3272
4177
  const parsed = JSON.parse(String(st.stdout || "").trim() || "{}");
3273
4178
  return typeof parsed.loggedIn === "boolean" ? parsed.loggedIn : null;
@@ -3554,11 +4459,11 @@ var init_claude_args = __esm({
3554
4459
  import { spawnSync as spawnSync4 } from "node:child_process";
3555
4460
  function terminateAgentProcessTree({
3556
4461
  child,
3557
- platform = process.platform,
4462
+ platform: platform4 = process.platform,
3558
4463
  spawn: spawn5 = spawnSync4
3559
4464
  } = {}) {
3560
4465
  if (!child || !Number.isInteger(child.pid) || child.pid <= 0) return false;
3561
- if (platform === "win32") {
4466
+ if (platform4 === "win32") {
3562
4467
  const result = spawn5("taskkill", ["/PID", String(child.pid), "/T", "/F"], {
3563
4468
  windowsHide: true,
3564
4469
  stdio: "ignore",
@@ -3592,7 +4497,7 @@ var init_terminal_process_cleanup = __esm({
3592
4497
 
3593
4498
  // ../../scripts/virtual-office/code-runner/orphan-agent-reaper.mjs
3594
4499
  import { spawnSync as spawnSync5 } from "node:child_process";
3595
- import { existsSync as existsSync4, mkdirSync as mkdirSync3, readdirSync, readFileSync as readFileSync2, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "node:fs";
4500
+ import { existsSync as existsSync9, mkdirSync as mkdirSync7, readdirSync as readdirSync2, readFileSync as readFileSync7, rmSync as rmSync2, writeFileSync as writeFileSync5 } from "node:fs";
3596
4501
  import os from "node:os";
3597
4502
  import path12 from "node:path";
3598
4503
  function registryRoot(tmp = os.tmpdir()) {
@@ -3609,9 +4514,9 @@ function registerDaemonInstance({
3609
4514
  } = {}) {
3610
4515
  if (!instanceId) return null;
3611
4516
  const dir = instanceDir(root, instanceId);
3612
- mkdirSync3(dir, { recursive: true });
4517
+ mkdirSync7(dir, { recursive: true });
3613
4518
  const file = path12.join(dir, DAEMON_RECORD);
3614
- writeFileSync2(file, JSON.stringify({ daemonPid, daemonStartedAtMs, instanceId }), {
4519
+ writeFileSync5(file, JSON.stringify({ daemonPid, daemonStartedAtMs, instanceId }), {
3615
4520
  encoding: "utf8",
3616
4521
  mode: 384
3617
4522
  });
@@ -3627,8 +4532,8 @@ function recordAgentPid({
3627
4532
  if (!instanceId || !Number.isInteger(pid) || pid <= 0) return false;
3628
4533
  try {
3629
4534
  const dir = instanceDir(root, instanceId);
3630
- mkdirSync3(dir, { recursive: true });
3631
- writeFileSync2(
4535
+ mkdirSync7(dir, { recursive: true });
4536
+ writeFileSync5(
3632
4537
  path12.join(dir, `${pid}.json`),
3633
4538
  JSON.stringify({ pid, agentId, startedAtMs, instanceId }),
3634
4539
  { encoding: "utf8", mode: 384 }
@@ -3663,10 +4568,10 @@ function bootstrapOrphanReaper({ instanceId, log: log2 = () => {
3663
4568
  }
3664
4569
  function readRegistry({ root = registryRoot(), currentInstanceId } = {}) {
3665
4570
  const instances = [];
3666
- if (!existsSync4(root)) return instances;
4571
+ if (!existsSync9(root)) return instances;
3667
4572
  let dirents;
3668
4573
  try {
3669
- dirents = readdirSync(root, { withFileTypes: true });
4574
+ dirents = readdirSync2(root, { withFileTypes: true });
3670
4575
  } catch {
3671
4576
  return instances;
3672
4577
  }
@@ -3678,14 +4583,14 @@ function readRegistry({ root = registryRoot(), currentInstanceId } = {}) {
3678
4583
  const agents = [];
3679
4584
  let files;
3680
4585
  try {
3681
- files = readdirSync(dir);
4586
+ files = readdirSync2(dir);
3682
4587
  } catch {
3683
4588
  continue;
3684
4589
  }
3685
4590
  for (const name of files) {
3686
4591
  let parsed;
3687
4592
  try {
3688
- parsed = JSON.parse(readFileSync2(path12.join(dir, name), "utf8"));
4593
+ parsed = JSON.parse(readFileSync7(path12.join(dir, name), "utf8"));
3689
4594
  } catch {
3690
4595
  continue;
3691
4596
  }
@@ -3735,9 +4640,9 @@ function windowsSystemRoot(env2 = process.env) {
3735
4640
  function windowsPowershellExe(env2 = process.env) {
3736
4641
  return path12.join(windowsSystemRoot(env2), "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
3737
4642
  }
3738
- function listProcessCreationTimes({ platform = process.platform, spawn: spawn5 = spawnSync5, env: env2 = process.env, warn = console.warn } = {}) {
4643
+ function listProcessCreationTimes({ platform: platform4 = process.platform, spawn: spawn5 = spawnSync5, env: env2 = process.env, warn = console.warn } = {}) {
3739
4644
  const map = /* @__PURE__ */ new Map();
3740
- if (platform === "win32") {
4645
+ if (platform4 === "win32") {
3741
4646
  const ps = "Get-CimInstance Win32_Process | ForEach-Object { '{0} {1}' -f $_.ProcessId, (([DateTimeOffset]$_.CreationDate.ToUniversalTime()).ToUnixTimeMilliseconds()) }";
3742
4647
  const result2 = spawn5(windowsPowershellExe(env2), ["-NoProfile", "-NonInteractive", "-Command", ps], {
3743
4648
  windowsHide: true,
@@ -3775,9 +4680,9 @@ function parsePosixPsLine(line) {
3775
4680
  if (!Number.isInteger(pid) || pid <= 0 || !Number.isFinite(when)) return null;
3776
4681
  return { pid, creationMs: when };
3777
4682
  }
3778
- function killProcessTree(pid, { platform = process.platform, spawn: spawn5 = spawnSync5, env: env2 = process.env } = {}) {
4683
+ function killProcessTree(pid, { platform: platform4 = process.platform, spawn: spawn5 = spawnSync5, env: env2 = process.env } = {}) {
3779
4684
  if (!Number.isInteger(pid) || pid <= 0) return false;
3780
- if (platform === "win32") {
4685
+ if (platform4 === "win32") {
3781
4686
  const taskkill = path12.join(windowsSystemRoot(env2), "System32", "taskkill.exe");
3782
4687
  const r = spawn5(taskkill, ["/PID", String(pid), "/T", "/F"], { windowsHide: true, stdio: "ignore", timeout: 15e3 });
3783
4688
  return !r.error && r.status === 0;
@@ -4223,7 +5128,7 @@ function runAgentTask({
4223
5128
  spawnImpl = spawn2,
4224
5129
  sandbox = null
4225
5130
  }) {
4226
- return new Promise((resolve2) => {
5131
+ return new Promise((resolve3) => {
4227
5132
  const args = runner.buildArgs({ permissionMode, maxTurns, model, effort, maxBudgetUsd, researchHarness, prompt });
4228
5133
  const spawnEnv = typeof runner.applyAuthEnv === "function" ? runner.applyAuthEnv(env2) : env2;
4229
5134
  const costBasis = typeof runner.costBasis === "function" ? runner.costBasis(spawnEnv) : "unknown";
@@ -4294,7 +5199,7 @@ function runAgentTask({
4294
5199
  if (settled) return;
4295
5200
  settled = true;
4296
5201
  clearLifecycleHandles();
4297
- resolve2(value);
5202
+ resolve3(value);
4298
5203
  };
4299
5204
  const recordResultEvent = (evt) => {
4300
5205
  const summary = !evt.isError && /^completed$/i.test(String(evt.summary || "").trim()) && lastProgress ? lastProgress : evt.summary;
@@ -4614,17 +5519,17 @@ var init_flat_token_usage = __esm({
4614
5519
 
4615
5520
  // ../../scripts/virtual-office/code-runner/codex-runner.mjs
4616
5521
  import { spawnSync as spawnSync6 } from "node:child_process";
4617
- import { existsSync as existsSync5 } from "node:fs";
5522
+ import { existsSync as existsSync10 } from "node:fs";
4618
5523
  import { win32 as win322 } from "node:path";
4619
5524
  function isTruthyFlag2(value) {
4620
5525
  return ["1", "true", "yes", "on"].includes(String(value ?? "").trim().toLowerCase());
4621
5526
  }
4622
5527
  function resolveCodexBinary({
4623
5528
  env: env2 = process.env,
4624
- platform = process.platform,
4625
- exists = existsSync5
5529
+ platform: platform4 = process.platform,
5530
+ exists = existsSync10
4626
5531
  } = {}) {
4627
- if (platform !== "win32") return "codex";
5532
+ if (platform4 !== "win32") return "codex";
4628
5533
  const appData = String(env2.APPDATA || "").trim();
4629
5534
  const userProfile = String(env2.USERPROFILE || "").trim();
4630
5535
  const localAppData = String(env2.LOCALAPPDATA || "").trim();
@@ -5108,8 +6013,8 @@ var init_ollama_agent_core = __esm({
5108
6013
  });
5109
6014
 
5110
6015
  // ../../scripts/virtual-office/code-runner/ollama-native-transport.mjs
5111
- import { fileURLToPath } from "node:url";
5112
- import { dirname as dirname2, join as join2 } from "node:path";
6016
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
6017
+ import { dirname as dirname5, join as join7 } from "node:path";
5113
6018
  function resolveLocalNativeProfile(env2 = process.env) {
5114
6019
  const profile = String(env2.VO_CODE_RUNNER_LOCAL_PROFILE || "").trim().toLowerCase() || DEFAULT_LOCAL_NATIVE_PROFILE;
5115
6020
  if (!LOCAL_NATIVE_PROFILES.includes(profile)) {
@@ -5121,7 +6026,7 @@ function resolveLocalTransport(env2 = process.env) {
5121
6026
  return String(env2.VO_CODE_RUNNER_LOCAL_TRANSPORT || "").trim().toLowerCase() === "native" ? "native" : DEFAULT_LOCAL_TRANSPORT;
5122
6027
  }
5123
6028
  function ollamaAgentScriptPath() {
5124
- return join2(dirname2(fileURLToPath(import.meta.url)), "ollama-agent.mjs");
6029
+ return join7(dirname5(fileURLToPath2(import.meta.url)), "ollama-agent.mjs");
5125
6030
  }
5126
6031
  function posIntOr(raw, fallback) {
5127
6032
  const n = Number(String(raw ?? "").trim());
@@ -5816,15 +6721,15 @@ var init_rate_limit_resume_state = __esm({
5816
6721
  LOCK_STALE_MS = 10 * 60 * 1e3;
5817
6722
  LOCK_INIT_GRACE_MS = 5e3;
5818
6723
  LOCK_WAIT_MS = 1e4;
5819
- delay = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
6724
+ delay = (ms) => new Promise((resolve3) => setTimeout(resolve3, ms));
5820
6725
  }
5821
6726
  });
5822
6727
 
5823
6728
  // ../../scripts/virtual-office/code-runner/rate-limit-resume.mjs
5824
- import { homedir as homedir3 } from "node:os";
5825
- import { join as join3 } from "node:path";
6729
+ import { homedir as homedir6 } from "node:os";
6730
+ import { join as join8 } from "node:path";
5826
6731
  function resumeQueuePath() {
5827
- return join3(homedir3(), ".claude", "resume-queue.jsonl");
6732
+ return join8(homedir6(), ".claude", "resume-queue.jsonl");
5828
6733
  }
5829
6734
  function buildResumeEntry({ task = {}, resumeAfter = null, summary = "", at } = {}) {
5830
6735
  return {
@@ -6040,8 +6945,9 @@ var init_auto_merge = __esm({
6040
6945
 
6041
6946
  // ../../scripts/virtual-office/code-runner/pr-overlap-gate.mjs
6042
6947
  import { spawnSync as spawnSync8 } from "node:child_process";
6043
- import { existsSync as existsSync6 } from "node:fs";
6044
- import { fileURLToPath as fileURLToPath2 } from "node:url";
6948
+ import { existsSync as existsSync11 } from "node:fs";
6949
+ import { dirname as dirname6, join as join9 } from "node:path";
6950
+ import { fileURLToPath as fileURLToPath3 } from "node:url";
6045
6951
  function stripCredentials(env2 = process.env) {
6046
6952
  const safe = { ...env2 };
6047
6953
  for (const key of CREDENTIAL_ENV_KEYS) delete safe[key];
@@ -6051,7 +6957,7 @@ function resolveOverlapScript({
6051
6957
  worktreeDir,
6052
6958
  trustedPath = null,
6053
6959
  trustedPaths = TRUSTED_OVERLAP_CANDIDATES,
6054
- existsFn = existsSync6,
6960
+ existsFn = existsSync11,
6055
6961
  joinFn = (dir) => `${dir}/scripts/ci/check-local-pr-overlap.mjs`
6056
6962
  } = {}) {
6057
6963
  const candidates = trustedPath ? [trustedPath] : trustedPaths;
@@ -6099,7 +7005,7 @@ var init_pr_overlap_gate = __esm({
6099
7005
  TRUSTED_OVERLAP_CANDIDATES = [
6100
7006
  new URL("../../ci/check-local-pr-overlap.mjs", import.meta.url),
6101
7007
  new URL("./ci/check-local-pr-overlap.js", import.meta.url)
6102
- ].map((candidate) => fileURLToPath2(candidate));
7008
+ ].map((candidate) => fileURLToPath3(candidate));
6103
7009
  CREDENTIAL_ENV_KEYS = Object.freeze([
6104
7010
  "GH_TOKEN",
6105
7011
  "GITHUB_TOKEN",
@@ -6538,9 +7444,9 @@ function boundedTail(text) {
6538
7444
  return s.length <= COMPLETION_GATE_OUTPUT_CAP ? s : s.slice(s.length - COMPLETION_GATE_OUTPUT_CAP);
6539
7445
  }
6540
7446
  function workspaceFingerprint(worktreeDir, execFileImpl = execFile) {
6541
- return new Promise((resolve2) => {
7447
+ return new Promise((resolve3) => {
6542
7448
  execFileImpl("git", ["rev-parse", "HEAD^{tree}"], { cwd: worktreeDir }, (err, stdout) => {
6543
- resolve2(err ? null : String(stdout).trim() || null);
7449
+ resolve3(err ? null : String(stdout).trim() || null);
6544
7450
  });
6545
7451
  });
6546
7452
  }
@@ -6559,10 +7465,10 @@ function writeState(worktreeDir, state) {
6559
7465
  }
6560
7466
  }
6561
7467
  function runGateCommand({ argv, worktreeDir, timeoutMs = COMPLETION_GATE_TIMEOUT_MS, execFileImpl = execFile }) {
6562
- return new Promise((resolve2) => {
7468
+ return new Promise((resolve3) => {
6563
7469
  const command = ALLOWED_GATE_COMMANDS.get(argv?.[0]);
6564
7470
  if (command === void 0) {
6565
- return resolve2({
7471
+ return resolve3({
6566
7472
  exitCode: 1,
6567
7473
  output: `completion_gate executable is not an allowed gate runner (${[...ALLOWED_GATE_COMMANDS.keys()].join(", ")})`,
6568
7474
  timedOut: false
@@ -6575,10 +7481,10 @@ function runGateCommand({ argv, worktreeDir, timeoutMs = COMPLETION_GATE_TIMEOUT
6575
7481
  (err, stdout, stderr) => {
6576
7482
  const output = boundedTail(`${stdout ?? ""}
6577
7483
  ${stderr ?? ""}`.trim());
6578
- if (!err) return resolve2({ exitCode: 0, output, timedOut: false });
7484
+ if (!err) return resolve3({ exitCode: 0, output, timedOut: false });
6579
7485
  const timedOut = err.killed === true || err.signal === "SIGTERM";
6580
7486
  const exitCode = typeof err.code === "number" ? err.code : 1;
6581
- resolve2({ exitCode, output: output || boundedTail(err.message), timedOut });
7487
+ resolve3({ exitCode, output: output || boundedTail(err.message), timedOut });
6582
7488
  }
6583
7489
  );
6584
7490
  });
@@ -6683,7 +7589,7 @@ function runProcess2(cmd, args, {
6683
7589
  forceSettleAfterMs = 1e3,
6684
7590
  spawnImpl = spawn3
6685
7591
  } = {}) {
6686
- return new Promise((resolve2, reject) => {
7592
+ return new Promise((resolve3, reject) => {
6687
7593
  let settled = false;
6688
7594
  let timedOut = false;
6689
7595
  let stdout = "";
@@ -6733,7 +7639,7 @@ function runProcess2(cmd, args, {
6733
7639
  settle(reject, buildExitError(cmd, args, result));
6734
7640
  return;
6735
7641
  }
6736
- settle(resolve2, result.stdout);
7642
+ settle(resolve3, result.stdout);
6737
7643
  });
6738
7644
  if (timeout > 0) {
6739
7645
  timeoutTimer = setTimeout(() => {
@@ -7133,14 +8039,14 @@ var init_publish_async = __esm({
7133
8039
  init_pr_overlap_gate();
7134
8040
  init_existing_pr_publication();
7135
8041
  init_partial_pr_continuation();
7136
- sleep = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
8042
+ sleep = (ms) => new Promise((resolve3) => setTimeout(resolve3, ms));
7137
8043
  }
7138
8044
  });
7139
8045
 
7140
8046
  // ../../scripts/virtual-office/code-runner/skill-catalog.mjs
7141
- import { readdirSync as readdirSync2, readFileSync as readFileSync3, statSync } from "node:fs";
7142
- import { dirname as dirname3, join as join4 } from "node:path";
7143
- import { fileURLToPath as fileURLToPath3 } from "node:url";
8047
+ import { readdirSync as readdirSync3, readFileSync as readFileSync8, statSync as statSync4 } from "node:fs";
8048
+ import { dirname as dirname7, join as join10 } from "node:path";
8049
+ import { fileURLToPath as fileURLToPath4 } from "node:url";
7144
8050
  function parseFrontmatterNameDescription(raw) {
7145
8051
  const text = String(raw).replace(/\r\n/g, "\n");
7146
8052
  if (!text.startsWith("---\n")) return null;
@@ -7159,15 +8065,15 @@ function parseFrontmatterNameDescription(raw) {
7159
8065
  return name && description ? { name, description } : null;
7160
8066
  }
7161
8067
  function resolveDefaultRepoRoot() {
7162
- const starts = [dirname3(fileURLToPath3(import.meta.url)), process.cwd()];
8068
+ const starts = [dirname7(fileURLToPath4(import.meta.url)), process.cwd()];
7163
8069
  for (const start of starts) {
7164
8070
  let dir = start;
7165
8071
  for (let i = 0; i < 8; i += 1) {
7166
8072
  try {
7167
- if (statSync(join4(dir, ".claude", "skills")).isDirectory()) return dir;
8073
+ if (statSync4(join10(dir, ".claude", "skills")).isDirectory()) return dir;
7168
8074
  } catch {
7169
8075
  }
7170
- const parent = dirname3(dir);
8076
+ const parent = dirname7(dir);
7171
8077
  if (parent === dir) break;
7172
8078
  dir = parent;
7173
8079
  }
@@ -7176,14 +8082,14 @@ function resolveDefaultRepoRoot() {
7176
8082
  }
7177
8083
  function loadSkillCatalog({ repoRoot: repoRoot2 = resolveDefaultRepoRoot() } = {}) {
7178
8084
  try {
7179
- const skillsDir = join4(repoRoot2, ".claude", "skills");
8085
+ const skillsDir = join10(repoRoot2, ".claude", "skills");
7180
8086
  const catalog = [];
7181
- for (const entry of readdirSync2(skillsDir)) {
7182
- const dir = join4(skillsDir, entry);
8087
+ for (const entry of readdirSync3(skillsDir)) {
8088
+ const dir = join10(skillsDir, entry);
7183
8089
  try {
7184
- if (!statSync(dir).isDirectory()) continue;
8090
+ if (!statSync4(dir).isDirectory()) continue;
7185
8091
  const parsed = parseFrontmatterNameDescription(
7186
- readFileSync3(join4(dir, "SKILL.md"), "utf8")
8092
+ readFileSync8(join10(dir, "SKILL.md"), "utf8")
7187
8093
  );
7188
8094
  if (parsed) catalog.push(parsed);
7189
8095
  } catch {
@@ -7683,8 +8589,8 @@ var init_task_attachments = __esm({
7683
8589
  });
7684
8590
 
7685
8591
  // ../../scripts/virtual-office/code-runner/session-spool-forwarder.mjs
7686
- import { homedir as homedir4 } from "node:os";
7687
- import { join as join5 } from "node:path";
8592
+ import { homedir as homedir7 } from "node:os";
8593
+ import { join as join11 } from "node:path";
7688
8594
  import { readdir as readdir2, readFile as readFile2, unlink, writeFile as writeFile2 } from "node:fs/promises";
7689
8595
  import { createHash as createHash4 } from "node:crypto";
7690
8596
  function deriveUuid(seed) {
@@ -7716,9 +8622,9 @@ async function readSpool(spoolDir = SPOOL_DIR) {
7716
8622
  for (const f of files) {
7717
8623
  if (!f.endsWith(".json")) continue;
7718
8624
  try {
7719
- const record = JSON.parse(await readFile2(join5(spoolDir, f), "utf8"));
8625
+ const record = JSON.parse(await readFile2(join11(spoolDir, f), "utf8"));
7720
8626
  if (record && typeof record.session_key === "string") {
7721
- out.push({ full: join5(spoolDir, f), record });
8627
+ out.push({ full: join11(spoolDir, f), record });
7722
8628
  }
7723
8629
  } catch {
7724
8630
  }
@@ -7800,8 +8706,8 @@ var SPOOL_DIR, CLOUD_MAP_FILE, STALE_MS, ACTIVE_SILENCE_MS;
7800
8706
  var init_session_spool_forwarder = __esm({
7801
8707
  "../../scripts/virtual-office/code-runner/session-spool-forwarder.mjs"() {
7802
8708
  "use strict";
7803
- SPOOL_DIR = join5(homedir4(), ".vo", "session-spool");
7804
- CLOUD_MAP_FILE = join5(homedir4(), ".vo", "session-cloud-map.json");
8709
+ SPOOL_DIR = join11(homedir7(), ".vo", "session-spool");
8710
+ CLOUD_MAP_FILE = join11(homedir7(), ".vo", "session-cloud-map.json");
7805
8711
  STALE_MS = 60 * 60 * 1e3;
7806
8712
  ACTIVE_SILENCE_MS = 10 * 60 * 1e3;
7807
8713
  }
@@ -7872,7 +8778,7 @@ var init_rate_limit_resume_scheduler_core = __esm({
7872
8778
  });
7873
8779
 
7874
8780
  // ../../scripts/virtual-office/code-runner/rate-limit-resume-scheduler.mjs
7875
- import { dirname as dirname4, join as join6, resolve } from "node:path";
8781
+ import { dirname as dirname8, join as join12, resolve as resolve2 } from "node:path";
7876
8782
  function defaultLog(message) {
7877
8783
  console.log(`[rate-limit-scheduler ${(/* @__PURE__ */ new Date()).toISOString()}] ${message}`);
7878
8784
  }
@@ -7954,7 +8860,7 @@ async function runLockedScheduler({
7954
8860
  async function runScheduler({
7955
8861
  env: env2 = process.env,
7956
8862
  queuePath = resumeQueuePath(),
7957
- attemptsPath = join6(dirname4(queuePath), "resume-attempts.json"),
8863
+ attemptsPath = join12(dirname8(queuePath), "resume-attempts.json"),
7958
8864
  client,
7959
8865
  now,
7960
8866
  log: log2 = defaultLog
@@ -7983,9 +8889,9 @@ var init_rate_limit_resume_scheduler = __esm({
7983
8889
  ATTEMPTS_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
7984
8890
  isMainModule = (() => {
7985
8891
  try {
7986
- const argv1 = process.argv[1] ? resolve(process.argv[1]) : "";
8892
+ const argv1 = process.argv[1] ? resolve2(process.argv[1]) : "";
7987
8893
  const here = new URL(import.meta.url).pathname.replace(/^\/([a-zA-Z]):\//u, "$1:/");
7988
- return resolve(here) === argv1;
8894
+ return resolve2(here) === argv1;
7989
8895
  } catch {
7990
8896
  return false;
7991
8897
  }
@@ -8043,10 +8949,10 @@ function makeLoopTicks({
8043
8949
  state = { running: false, pending: null };
8044
8950
  heartbeatState.set(key, state);
8045
8951
  }
8046
- return new Promise((resolve2) => {
8952
+ return new Promise((resolve3) => {
8047
8953
  if (state.running) {
8048
- if (state.pending) state.pending.waiters.push(resolve2);
8049
- else state.pending = { payload, waiters: [resolve2] };
8954
+ if (state.pending) state.pending.waiters.push(resolve3);
8955
+ else state.pending = { payload, waiters: [resolve3] };
8050
8956
  state.pending.payload = payload;
8051
8957
  return;
8052
8958
  }
@@ -8074,7 +8980,7 @@ function makeLoopTicks({
8074
8980
  }
8075
8981
  });
8076
8982
  };
8077
- launch(payload, [resolve2]);
8983
+ launch(payload, [resolve3]);
8078
8984
  });
8079
8985
  }
8080
8986
  return function tick() {
@@ -8251,7 +9157,7 @@ var init_runner_capacity = __esm({
8251
9157
  });
8252
9158
 
8253
9159
  // ../../scripts/virtual-office/code-runner/agent-auth-probe-process.mjs
8254
- import { fileURLToPath as fileURLToPath4 } from "node:url";
9160
+ import { fileURLToPath as fileURLToPath5 } from "node:url";
8255
9161
  async function probeAgentInChild(agent, timeoutMs) {
8256
9162
  const stdout = await runProcess2(process.execPath, [probeCli, agent], {
8257
9163
  timeout: timeoutMs,
@@ -8264,7 +9170,7 @@ var init_agent_auth_probe_process = __esm({
8264
9170
  "../../scripts/virtual-office/code-runner/agent-auth-probe-process.mjs"() {
8265
9171
  "use strict";
8266
9172
  init_process_runner2();
8267
- probeCli = fileURLToPath4(new URL("./agent-auth-probe-cli.mjs", import.meta.url));
9173
+ probeCli = fileURLToPath5(new URL("./agent-auth-probe-cli.mjs", import.meta.url));
8268
9174
  }
8269
9175
  });
8270
9176
 
@@ -8283,7 +9189,7 @@ async function collectAgentAvailability({
8283
9189
  try {
8284
9190
  const r = await Promise.race([
8285
9191
  runnerFor ? Promise.resolve().then(() => runnerFor(agent).checkAuth()) : probeAgentInChild(agent, probeTimeoutMs),
8286
- new Promise((resolve2) => setTimeout(() => resolve2(null), probeTimeoutMs))
9192
+ new Promise((resolve3) => setTimeout(() => resolve3(null), probeTimeoutMs))
8287
9193
  ]);
8288
9194
  if (!r) return degraded;
8289
9195
  const installed = Boolean(r?.installed);
@@ -8543,7 +9449,7 @@ function readingAgeMs(row, nowMs = Date.now()) {
8543
9449
  if (!Number.isFinite(ms)) return null;
8544
9450
  return Math.max(0, nowMs - ms);
8545
9451
  }
8546
- var clampPct, readJson, ACCOUNT_KEY_SALT;
9452
+ var clampPct, readJson2, ACCOUNT_KEY_SALT;
8547
9453
  var init_shared = __esm({
8548
9454
  "../../scripts/virtual-office/code-runner/account-usage/shared.mjs"() {
8549
9455
  "use strict";
@@ -8551,7 +9457,7 @@ var init_shared = __esm({
8551
9457
  const n = typeof v === "number" ? v : typeof v === "string" && v.trim() !== "" ? Number(v) : NaN;
8552
9458
  return Number.isFinite(n) ? Math.min(100, Math.max(0, Math.round(n))) : null;
8553
9459
  };
8554
- readJson = (p) => {
9460
+ readJson2 = (p) => {
8555
9461
  try {
8556
9462
  return JSON.parse(fs9.readFileSync(p, "utf8"));
8557
9463
  } catch {
@@ -8578,7 +9484,7 @@ function usageBaseUrl(env2 = process.env) {
8578
9484
  const raw = env2.ANTHROPIC_BASE_URL || "https://api.anthropic.com";
8579
9485
  return String(raw).replace(/\/+$/, "");
8580
9486
  }
8581
- function readOAuthToken({ homeDir = os3.homedir(), read = readJson, now = Date.now() } = {}) {
9487
+ function readOAuthToken({ homeDir = os3.homedir(), read = readJson2, now = Date.now() } = {}) {
8582
9488
  const creds = read(path17.join(homeDir, ".claude", ".credentials.json"));
8583
9489
  const oauth = creds && typeof creds === "object" ? creds.claudeAiOauth : null;
8584
9490
  if (!oauth || typeof oauth !== "object") return null;
@@ -8588,7 +9494,7 @@ function readOAuthToken({ homeDir = os3.homedir(), read = readJson, now = Date.n
8588
9494
  if (Number.isFinite(expiresAt) && expiresAt > 0 && expiresAt <= now) return null;
8589
9495
  return token2;
8590
9496
  }
8591
- function readAccountId({ homeDir = os3.homedir(), read = readJson } = {}) {
9497
+ function readAccountId({ homeDir = os3.homedir(), read = readJson2 } = {}) {
8592
9498
  const cfg = read(path17.join(homeDir, ".claude.json"));
8593
9499
  const account = cfg && typeof cfg === "object" ? cfg.oauthAccount : null;
8594
9500
  return account && typeof account.accountUuid === "string" ? account.accountUuid : null;
@@ -8641,7 +9547,7 @@ async function readClaudeOAuthUsage({
8641
9547
  env: env2 = process.env,
8642
9548
  timeoutMs = DEFAULT_TIMEOUT_MS,
8643
9549
  homeDir = os3.homedir(),
8644
- read = readJson,
9550
+ read = readJson2,
8645
9551
  now = () => Date.now()
8646
9552
  } = {}) {
8647
9553
  const token2 = readOAuthToken({ homeDir, read, now: now() });
@@ -8681,7 +9587,7 @@ async function readClaudeOAuthUsage({
8681
9587
  }
8682
9588
  function readClaudeFileUsage({
8683
9589
  homeDir = os3.homedir(),
8684
- read: rawRead = readJson,
9590
+ read: rawRead = readJson2,
8685
9591
  statFn = fs10.statSync,
8686
9592
  now = () => Date.now()
8687
9593
  } = {}) {
@@ -8782,10 +9688,10 @@ function readCodexUsage({
8782
9688
  resolveBinary = resolveCodexBinary,
8783
9689
  timeoutMs = 8e3,
8784
9690
  env: env2 = process.env,
8785
- platform = process.platform,
9691
+ platform: platform4 = process.platform,
8786
9692
  now = () => Date.now()
8787
9693
  } = {}) {
8788
- return new Promise((resolve2) => {
9694
+ return new Promise((resolve3) => {
8789
9695
  let child;
8790
9696
  let settled = false;
8791
9697
  let stdout = "";
@@ -8797,11 +9703,11 @@ function readCodexUsage({
8797
9703
  child?.kill();
8798
9704
  } catch {
8799
9705
  }
8800
- resolve2(value);
9706
+ resolve3(value);
8801
9707
  };
8802
9708
  const timer = setTimeout(() => finish(null), timeoutMs);
8803
9709
  try {
8804
- const binary = resolveBinary({ env: env2, platform });
9710
+ const binary = resolveBinary({ env: env2, platform: platform4 });
8805
9711
  child = spawnImpl(binary, ["app-server", "--stdio"], {
8806
9712
  env: env2,
8807
9713
  windowsHide: true,
@@ -9114,6 +10020,12 @@ function ciFixOccurrenceKey({ repo, prNumber, headSha, repairAttempt }) {
9114
10020
  function coordinationRetryDue(entry, nowMs) {
9115
10021
  return !entry.nextRetryAt || nowMs >= entry.nextRetryAt;
9116
10022
  }
10023
+ function mergeEnqueueActive(entry, nowMs = Date.now()) {
10024
+ if (!entry?.mergeEnqueued) return false;
10025
+ const at = Number(entry.mergeEnqueuedAt || 0);
10026
+ if (!at) return true;
10027
+ return nowMs - at < MERGE_ENQUEUE_TTL_MS;
10028
+ }
9117
10029
  function isTerminalResumeRefusal(error) {
9118
10030
  return typeof error?.code === "string" && TERMINAL_RESUME_REFUSALS.includes(error.code);
9119
10031
  }
@@ -9154,12 +10066,13 @@ async function scheduleCoordinationRetry({
9154
10066
  }
9155
10067
  log2(`watch: pr #${prNumber} ${kind} coordination failed ${entry[errorsKey]}x; retry in ${Math.round(delay2 / 1e3)}s: ${boundedErrorMessage(error)}`);
9156
10068
  }
9157
- var MAX_BACKOFF_MS, TERMINAL_RESUME_REFUSALS;
10069
+ var MAX_BACKOFF_MS, MERGE_ENQUEUE_TTL_MS, TERMINAL_RESUME_REFUSALS;
9158
10070
  var init_watcher_coordination = __esm({
9159
10071
  "../../scripts/virtual-office/code-runner/watcher-coordination.mjs"() {
9160
10072
  "use strict";
9161
10073
  init_error_message();
9162
10074
  MAX_BACKOFF_MS = 60 * 60 * 1e3;
10075
+ MERGE_ENQUEUE_TTL_MS = 2 * 60 * 60 * 1e3;
9163
10076
  TERMINAL_RESUME_REFUSALS = Object.freeze([
9164
10077
  "automatic_continuation_budget_too_small",
9165
10078
  "automatic_continuation_budget_required",
@@ -9177,7 +10090,7 @@ var init_watcher_coordination = __esm({
9177
10090
  // ../../scripts/virtual-office/code-runner/watcher-state.mjs
9178
10091
  import { randomUUID as randomUUID3 } from "node:crypto";
9179
10092
  import { mkdir as mkdir2, open, readFile as readFile3, rename, unlink as unlink2 } from "node:fs/promises";
9180
- import { dirname as dirname5 } from "node:path";
10093
+ import { dirname as dirname9 } from "node:path";
9181
10094
  async function readWatcherState(stateFile) {
9182
10095
  let raw;
9183
10096
  try {
@@ -9193,7 +10106,7 @@ async function readWatcherState(stateFile) {
9193
10106
  return parsed;
9194
10107
  }
9195
10108
  async function writeWatcherState(stateFile, state) {
9196
- const directory = dirname5(stateFile);
10109
+ const directory = dirname9(stateFile);
9197
10110
  await mkdir2(directory, { recursive: true });
9198
10111
  const temp = `${stateFile}.${process.pid}.${randomUUID3()}.tmp`;
9199
10112
  let handle;
@@ -9400,6 +10313,41 @@ function noteCiUnreadable(log2) {
9400
10313
  lastDiagnosticAt = now;
9401
10314
  log2("watch: CI status is UNREADABLE with the GitHub App read token (the installation has not granted checks:read/statuses:read \u2014 accept the App permission update on the installation). Auto-fix and merge stay dormant; untrack/resume keep working on state alone.");
9402
10315
  }
10316
+ function noteCiViaRest(log2) {
10317
+ const now = Date.now();
10318
+ if (now - lastRestDiagnosticAt < DIAGNOSTIC_INTERVAL_MS) return;
10319
+ lastRestDiagnosticAt = now;
10320
+ log2("watch: CI status read via REST check-runs/status (gh's GraphQL rollup needs actions:read for checkSuite.workflowRun, which the read scope does not carry)");
10321
+ }
10322
+ async function readCommitCiViaRest(repo, sha, { run, env: env2 }) {
10323
+ const api = async (path22) => JSON.parse(await run("gh", ["api", path22], { timeout: 3e4, env: env2 }) || "{}");
10324
+ const rollup = [];
10325
+ let total = null;
10326
+ for (let page = 1; page <= REST_MAX_PAGES && (total === null || rollup.length < total); page += 1) {
10327
+ const body = await api(`repos/${repo}/commits/${sha}/check-runs?per_page=${REST_PAGE_SIZE}&page=${page}`);
10328
+ if (!Number.isFinite(Number(body?.total_count))) throw new CiRollupIncompleteError("check-runs response carried no total_count");
10329
+ total = Number(body.total_count);
10330
+ const runs = Array.isArray(body?.check_runs) ? body.check_runs : [];
10331
+ for (const r of runs) {
10332
+ rollup.push({
10333
+ __typename: "CheckRun",
10334
+ name: String(r?.name || ""),
10335
+ status: String(r?.status || ""),
10336
+ conclusion: r?.conclusion == null ? null : String(r.conclusion),
10337
+ ...r?.details_url ? { detailsUrl: String(r.details_url) } : {}
10338
+ });
10339
+ }
10340
+ if (runs.length === 0) break;
10341
+ }
10342
+ if (total === null || rollup.length < total) {
10343
+ throw new CiRollupIncompleteError(`read ${rollup.length} of ${total ?? "?"} check runs (page bound ${REST_MAX_PAGES}\xD7${REST_PAGE_SIZE})`);
10344
+ }
10345
+ const status = await api(`repos/${repo}/commits/${sha}/status?per_page=${REST_PAGE_SIZE}`);
10346
+ for (const s of Array.isArray(status?.statuses) ? status.statuses : []) {
10347
+ rollup.push({ __typename: "StatusContext", context: String(s?.context || ""), state: String(s?.state || "") });
10348
+ }
10349
+ return rollup;
10350
+ }
9403
10351
  async function ghViewPr(prNumber, repo, { githubToken, log: log2 = (m) => console.error(`[vo-runner] ${m}`), run = runProcess2 } = {}) {
9404
10352
  const env2 = githubToken ? { ...process.env, GH_TOKEN: githubToken } : process.env;
9405
10353
  const view = async (fields) => JSON.parse(await run("gh", [
@@ -9415,12 +10363,24 @@ async function ghViewPr(prNumber, repo, { githubToken, log: log2 = (m) => consol
9415
10363
  return await view(VIEW_FIELDS_WITH_CI);
9416
10364
  } catch (err) {
9417
10365
  if (!isCiUnreadableError(err)) throw err;
9418
- noteCiUnreadable(log2);
9419
10366
  const withoutCi = await view(VIEW_FIELDS_WITHOUT_CI);
10367
+ if (typeof withoutCi.headRefOid === "string" && /^[0-9a-f]{40}$/i.test(withoutCi.headRefOid)) {
10368
+ try {
10369
+ const statusCheckRollup = await readCommitCiViaRest(repo, withoutCi.headRefOid, { run, env: env2 });
10370
+ noteCiViaRest(log2);
10371
+ return { ...withoutCi, statusCheckRollup, ciSource: "rest" };
10372
+ } catch (restErr) {
10373
+ const text = `${restErr?.message || ""}
10374
+ ${restErr?.stderr || ""}`;
10375
+ if (!(restErr instanceof CiRollupIncompleteError) && !/Resource not accessible by integration/i.test(text)) throw restErr;
10376
+ if (restErr instanceof CiRollupIncompleteError) log2(`watch: CI rollup incomplete via REST (${restErr.message}) \u2014 reporting ci=unknown`);
10377
+ }
10378
+ }
10379
+ noteCiUnreadable(log2);
9420
10380
  return { ...withoutCi, statusCheckRollup: null, ciUnreadable: true, ciUnreadableReason: CI_UNREADABLE_REASON };
9421
10381
  }
9422
10382
  }
9423
- var VIEW_FIELDS_WITH_CI, VIEW_FIELDS_WITHOUT_CI, CI_UNREADABLE_REASON, DIAGNOSTIC_INTERVAL_MS, lastDiagnosticAt;
10383
+ var VIEW_FIELDS_WITH_CI, VIEW_FIELDS_WITHOUT_CI, CI_UNREADABLE_REASON, DIAGNOSTIC_INTERVAL_MS, lastDiagnosticAt, lastRestDiagnosticAt, REST_PAGE_SIZE, REST_MAX_PAGES, CiRollupIncompleteError;
9424
10384
  var init_pr_watcher_github = __esm({
9425
10385
  "../../scripts/virtual-office/code-runner/pr-watcher-github.mjs"() {
9426
10386
  "use strict";
@@ -9430,6 +10390,15 @@ var init_pr_watcher_github = __esm({
9430
10390
  CI_UNREADABLE_REASON = "app_token_missing_checks_read";
9431
10391
  DIAGNOSTIC_INTERVAL_MS = 30 * 60 * 1e3;
9432
10392
  lastDiagnosticAt = 0;
10393
+ lastRestDiagnosticAt = 0;
10394
+ REST_PAGE_SIZE = 100;
10395
+ REST_MAX_PAGES = 5;
10396
+ CiRollupIncompleteError = class extends Error {
10397
+ constructor(message) {
10398
+ super(message);
10399
+ this.name = "CiRollupIncompleteError";
10400
+ }
10401
+ };
9433
10402
  }
9434
10403
  });
9435
10404
 
@@ -9466,8 +10435,8 @@ var init_enqueue_autonomous_code_task = __esm({
9466
10435
  });
9467
10436
 
9468
10437
  // ../../scripts/virtual-office/code-runner/pr-watcher.mjs
9469
- import { homedir as homedir5 } from "node:os";
9470
- import { join as join7 } from "node:path";
10438
+ import { homedir as homedir8 } from "node:os";
10439
+ import { join as join13 } from "node:path";
9471
10440
  function parsePrCiStatus(view) {
9472
10441
  const state = (view && typeof view.state === "string" ? view.state : "UNKNOWN").toUpperCase();
9473
10442
  const rollup = view && Array.isArray(view.statusCheckRollup) ? view.statusCheckRollup : [];
@@ -9504,14 +10473,14 @@ function parsePrCiStatus(view) {
9504
10473
  mergeState: String(view?.mergeStateStatus || "").toUpperCase()
9505
10474
  };
9506
10475
  }
9507
- function decideWatchAction(pr, fixAttempts, maxFixAttempts, entry = {}, maxResumeAttempts = 1, autoMergeEnabled = false) {
10476
+ function decideWatchAction(pr, fixAttempts, maxFixAttempts, entry = {}, maxResumeAttempts = 1, autoMergeEnabled = false, nowMs = Date.now()) {
9508
10477
  if (pr.state !== "OPEN") return "untrack";
9509
10478
  if (entry && entry.needsContinuation && entry.taskId) {
9510
10479
  if (pr.ci === "pending") return "wait";
9511
10480
  return (entry.resumeAttempts || 0) < maxResumeAttempts ? "resume" : "wait";
9512
10481
  }
9513
10482
  if (pr.ci === "failing" && entry.allowFixDispatch !== false && (fixAttempts || 0) < maxFixAttempts) return "fix";
9514
- if (autoMergeEnabled && pr.ci === "passing" && pr.mergeState === "CLEAN" && !pr.isDraft && !entry.mergeTerminal && !entry.mergeEnqueued && (entry.mergeAttempts || 0) < 3) return "merge";
10483
+ if (autoMergeEnabled && pr.ci === "passing" && pr.mergeState === "CLEAN" && !pr.isDraft && !entry.mergeTerminal && !mergeEnqueueActive(entry, nowMs) && (entry.mergeAttempts || 0) < 3) return "merge";
9515
10484
  return "wait";
9516
10485
  }
9517
10486
  async function trackDispatchedPr({ prNumber, repo, branch, taskId, operatorId, tenantId, needsContinuation = false, continuationExhausted = false, repairChain, allowFixDispatch }, { stateFile = DEFAULT_STATE_FILE, now = () => Date.now() } = {}) {
@@ -9588,7 +10557,8 @@ async function runWatchCycleUnlocked({
9588
10557
  maxFixAttempts,
9589
10558
  entry,
9590
10559
  maxResumeAttempts,
9591
- autoMergeEnabled
10560
+ autoMergeEnabled,
10561
+ now()
9592
10562
  );
9593
10563
  const stableAction = proposedAction === "fix" && !confirmation.confirmed ? "wait" : proposedAction;
9594
10564
  const action = coordinationRetryDue(entry, now()) ? stableAction : "wait";
@@ -9639,6 +10609,7 @@ async function runWatchCycleUnlocked({
9639
10609
  log2(`watch: pr #${prNumber} passed CI + consensus and merged (${outcome.actionReceiptId || "receipt pending"})`);
9640
10610
  } else if (outcome.status === "queued" || outcome.status === "accepted") {
9641
10611
  entry.mergeEnqueued = true;
10612
+ entry.mergeEnqueuedAt = now();
9642
10613
  entry.mergeActionReceiptId = outcome.actionReceiptId || null;
9643
10614
  queued += 1;
9644
10615
  log2(`watch: pr #${prNumber} passed CI + consensus and entered the merge queue (${outcome.actionReceiptId || "receipt pending"})`);
@@ -9811,7 +10782,7 @@ var init_pr_watcher = __esm({
9811
10782
  init_watcher_state();
9812
10783
  init_superseded_pr_source();
9813
10784
  init_ci_fix_prompt();
9814
- DEFAULT_STATE_FILE = join7(homedir5(), ".vo", "dispatched-prs.json");
10785
+ DEFAULT_STATE_FILE = join13(homedir8(), ".vo", "dispatched-prs.json");
9815
10786
  FAIL_CONCLUSIONS = /* @__PURE__ */ new Set([
9816
10787
  "FAILURE",
9817
10788
  "TIMED_OUT",
@@ -10246,7 +11217,7 @@ import { randomUUID as randomUUID5 } from "node:crypto";
10246
11217
  import fs11 from "node:fs";
10247
11218
  import os4 from "node:os";
10248
11219
  import path18 from "node:path";
10249
- import { fileURLToPath as fileURLToPath5 } from "node:url";
11220
+ import { fileURLToPath as fileURLToPath6 } from "node:url";
10250
11221
  function userCacheRoot() {
10251
11222
  try {
10252
11223
  const home = os4.homedir();
@@ -10441,7 +11412,7 @@ var __dirname, DEFAULT_CACHE_DIR, DEFAULT_CACHE_FILE, DEFAULT_TTL_MS3, ANTHROPIC
10441
11412
  var init_model_registry = __esm({
10442
11413
  "../../scripts/virtual-office/model-registry.mjs"() {
10443
11414
  "use strict";
10444
- __dirname = path18.dirname(fileURLToPath5(import.meta.url));
11415
+ __dirname = path18.dirname(fileURLToPath6(import.meta.url));
10445
11416
  DEFAULT_CACHE_DIR = path18.join(
10446
11417
  resolveCacheBaseDir(),
10447
11418
  ".virtual-office-cache",
@@ -11054,9 +12025,9 @@ var init_classify_task = __esm({
11054
12025
  });
11055
12026
 
11056
12027
  // ../../scripts/virtual-office/code-runner/auto-router/effort-policy.mjs
11057
- import { readFileSync as readFileSync4 } from "node:fs";
11058
- import { homedir as homedir6 } from "node:os";
11059
- import { join as join8 } from "node:path";
12028
+ import { readFileSync as readFileSync9 } from "node:fs";
12029
+ import { homedir as homedir9 } from "node:os";
12030
+ import { join as join14 } from "node:path";
11060
12031
  function difficultyToRung(difficulty, thresholds) {
11061
12032
  const b = thresholds.rungBounds;
11062
12033
  if (difficulty >= b.R5) return "R5";
@@ -11081,7 +12052,7 @@ function operatorTierToRung(tier, difficulty, thresholds) {
11081
12052
  if (tier === "best" && difficulty >= thresholds.rungBounds.R5) return "R5";
11082
12053
  return base;
11083
12054
  }
11084
- function readCodexModelsCache({ path: path22 = DEFAULT_CODEX_MODELS_CACHE, read = readFileSync4 } = {}) {
12055
+ function readCodexModelsCache({ path: path22 = DEFAULT_CODEX_MODELS_CACHE, read = readFileSync9 } = {}) {
11085
12056
  try {
11086
12057
  const parsed = JSON.parse(read(path22, "utf8"));
11087
12058
  return Array.isArray(parsed?.models) ? parsed : null;
@@ -11133,7 +12104,7 @@ var init_effort_policy = __esm({
11133
12104
  init_meta_model_catalog();
11134
12105
  RUNG_ORDER = ["R1", "R2", "R3", "R4", "R5"];
11135
12106
  rungIndex = (rung) => RUNG_ORDER.indexOf(rung);
11136
- DEFAULT_CODEX_MODELS_CACHE = join8(homedir6(), ".codex", "models_cache.json");
12107
+ DEFAULT_CODEX_MODELS_CACHE = join14(homedir9(), ".codex", "models_cache.json");
11137
12108
  }
11138
12109
  });
11139
12110
 
@@ -11263,18 +12234,18 @@ var init_role_cost_shadow = __esm({
11263
12234
  });
11264
12235
 
11265
12236
  // ../../scripts/virtual-office/code-runner/auto-router/auto-router.mjs
11266
- import { readFileSync as readFileSync5, appendFileSync, mkdirSync as mkdirSync4 } from "node:fs";
11267
- import { homedir as homedir7 } from "node:os";
11268
- import { join as join9, dirname as dirname6 } from "node:path";
11269
- import { fileURLToPath as fileURLToPath6 } from "node:url";
12237
+ import { readFileSync as readFileSync10, appendFileSync, mkdirSync as mkdirSync8 } from "node:fs";
12238
+ import { homedir as homedir10 } from "node:os";
12239
+ import { join as join15, dirname as dirname10 } from "node:path";
12240
+ import { fileURLToPath as fileURLToPath7 } from "node:url";
11270
12241
  function getAutoRouterMode(env2 = process.env) {
11271
12242
  const raw = String(env2.VO_CODE_RUNNER_AUTO_ROUTER || "").trim().toLowerCase();
11272
12243
  return MODES.has(raw) ? raw : "off";
11273
12244
  }
11274
12245
  function loadThresholds() {
11275
12246
  if (!cachedThresholds) {
11276
- const here = dirname6(fileURLToPath6(import.meta.url));
11277
- cachedThresholds = JSON.parse(readFileSync5(join9(here, "thresholds.json"), "utf8"));
12247
+ const here = dirname10(fileURLToPath7(import.meta.url));
12248
+ cachedThresholds = JSON.parse(readFileSync10(join15(here, "thresholds.json"), "utf8"));
11278
12249
  }
11279
12250
  return cachedThresholds;
11280
12251
  }
@@ -11340,9 +12311,9 @@ function formatDecisionReason(decision, maxLen = 480) {
11340
12311
  const s = `[${decision.routerVersion}] ${decision.taskClass} d=${decision.difficulty} c=${decision.confidence} \u2192 ${decision.rung}/${decision.tier}${decision.effort ? ` effort=${decision.effort}` : ""} turns=${decision.maxTurns} $${decision.maxBudgetUsd}${decision.flags.length ? ` [${decision.flags.join(",")}]` : ""} :: ${decision.reasons.join("; ")}`;
11341
12312
  return s.length > maxLen ? `${s.slice(0, maxLen - 1)}\u2026` : s;
11342
12313
  }
11343
- function appendDecisionFallback(decision, { path: path22 = DECISION_FALLBACK_PATH, append = appendFileSync, mkdir: mkdir4 = mkdirSync4, task, thresholds, roleCostInputs } = {}) {
12314
+ function appendDecisionFallback(decision, { path: path22 = DECISION_FALLBACK_PATH, append = appendFileSync, mkdir: mkdir4 = mkdirSync8, task, thresholds, roleCostInputs } = {}) {
11344
12315
  try {
11345
- mkdir4(dirname6(path22), { recursive: true });
12316
+ mkdir4(dirname10(path22), { recursive: true });
11346
12317
  append(path22, `${JSON.stringify(decision)}
11347
12318
  `, "utf8");
11348
12319
  if (isRouterDecision(decision)) {
@@ -11366,7 +12337,7 @@ var init_auto_router = __esm({
11366
12337
  init_effort_policy();
11367
12338
  init_role_cost_shadow();
11368
12339
  ROUTER_VERSION = "0.1.0";
11369
- DECISION_FALLBACK_PATH = join9(homedir7(), ".claude", "vo-auto-router-decisions.jsonl");
12340
+ DECISION_FALLBACK_PATH = join15(homedir10(), ".claude", "vo-auto-router-decisions.jsonl");
11370
12341
  MODES = /* @__PURE__ */ new Set(["off", "shadow", "on"]);
11371
12342
  cachedThresholds = null;
11372
12343
  isRouterDecision = (d) => Boolean(d && typeof d === "object" && typeof d.taskClass === "string" && typeof d.confidence === "number");
@@ -11668,10 +12639,10 @@ var init_task_helpers = __esm({
11668
12639
  });
11669
12640
 
11670
12641
  // ../../scripts/virtual-office/code-runner/swarm-admission.mjs
11671
- function canLaunchSuccessorAgent(agent, platform = process.platform) {
12642
+ function canLaunchSuccessorAgent(agent, platform4 = process.platform) {
11672
12643
  const name = typeof agent === "string" ? agent.trim() : "";
11673
12644
  if (!Object.prototype.hasOwnProperty.call(SUCCESSOR_LAUNCH_SHAPES, name)) return false;
11674
- if (platform === "win32" && !SUCCESSOR_LAUNCH_SHAPES[name]) return false;
12645
+ if (platform4 === "win32" && !SUCCESSOR_LAUNCH_SHAPES[name]) return false;
11675
12646
  return true;
11676
12647
  }
11677
12648
  function isSubscriptionExhausted(usage) {
@@ -11698,12 +12669,12 @@ function resolveRunnerSwarmBinding({
11698
12669
  accountUsage = [],
11699
12670
  requestedSubagents = MAX_BOUND_SUBAGENTS,
11700
12671
  nowIso,
11701
- platform = process.platform
12672
+ platform: platform4 = process.platform
11702
12673
  } = {}) {
11703
12674
  const id = typeof swarmId === "string" ? swarmId.trim() : "";
11704
12675
  const boundAgent = typeof agent === "string" ? agent.trim() : "";
11705
12676
  if (id.length === 0 || boundAgent.length === 0) return null;
11706
- if (!canLaunchSuccessorAgent(boundAgent, platform)) return null;
12677
+ if (!canLaunchSuccessorAgent(boundAgent, platform4)) return null;
11707
12678
  const budget = clampSubagents(requestedSubagents);
11708
12679
  if (budget === 0) return null;
11709
12680
  const rows = Array.isArray(availableAgents) ? availableAgents : [];
@@ -12363,7 +13334,7 @@ async function canonicalRootForWorktree(worktreeDir, run) {
12363
13334
  "--git-common-dir"
12364
13335
  ])).trim();
12365
13336
  const root = path19.dirname(commonDir);
12366
- return samePath2(root, worktreeDir) ? null : root;
13337
+ return samePath3(root, worktreeDir) ? null : root;
12367
13338
  }
12368
13339
  async function snapshot(root, run) {
12369
13340
  const [head, status] = await Promise.all([
@@ -12471,13 +13442,13 @@ async function assertCanonicalIsolation(baseline, { worktreeDir, taskId, run = d
12471
13442
  `agent attempted ${evidence.tracked.length + evidence.untracked.length} canonical-clone write(s); writes were quarantined and the clone was restored exactly: ${evidence.quarantineDir}`
12472
13443
  );
12473
13444
  }
12474
- var splitZ2, samePath2;
13445
+ var splitZ2, samePath3;
12475
13446
  var init_isolation_audit = __esm({
12476
13447
  "../../scripts/virtual-office/code-runner/isolation-audit.mjs"() {
12477
13448
  "use strict";
12478
13449
  init_process_runner2();
12479
13450
  splitZ2 = (value) => String(value || "").split("\0").map((item) => item.trim()).filter(Boolean);
12480
- samePath2 = (left, right) => {
13451
+ samePath3 = (left, right) => {
12481
13452
  const [a, b] = [left, right].map((value) => path19.resolve(value));
12482
13453
  return process.platform === "win32" ? a.toLowerCase() === b.toLowerCase() : a === b;
12483
13454
  };
@@ -12633,7 +13604,7 @@ var init_outcome_commit = __esm({
12633
13604
  "use strict";
12634
13605
  init_cancelled_run_report();
12635
13606
  init_error_message();
12636
- wait = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
13607
+ wait = (ms) => new Promise((resolve3) => setTimeout(resolve3, ms));
12637
13608
  OutcomeCommitConflictError = class extends Error {
12638
13609
  constructor(message) {
12639
13610
  super(message);
@@ -12675,7 +13646,7 @@ var init_terminal_delivery = __esm({
12675
13646
  "use strict";
12676
13647
  init_cancelled_run_report();
12677
13648
  init_error_message();
12678
- wait2 = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
13649
+ wait2 = (ms) => new Promise((resolve3) => setTimeout(resolve3, ms));
12679
13650
  }
12680
13651
  });
12681
13652
 
@@ -13143,8 +14114,8 @@ async function preservedHeadAlreadyOnBranch(worktreeDir, prBranch, { runGit = de
13143
14114
  }
13144
14115
  async function defaultRunGit(args, cwd) {
13145
14116
  const { execFile: execFile2 } = await import("node:child_process");
13146
- return new Promise((resolve2, reject) => {
13147
- execFile2("git", args, { cwd, encoding: "utf8", timeout: 6e4 }, (err, stdout) => err ? reject(err) : resolve2(stdout));
14117
+ return new Promise((resolve3, reject) => {
14118
+ execFile2("git", args, { cwd, encoding: "utf8", timeout: 6e4 }, (err, stdout) => err ? reject(err) : resolve3(stdout));
13148
14119
  });
13149
14120
  }
13150
14121
  async function recoverPreservedCodeTask({
@@ -13449,8 +14420,8 @@ var init_cancellation_probe = __esm({
13449
14420
  });
13450
14421
 
13451
14422
  // ../../scripts/virtual-office/code-runner/detached-economics-spool.mjs
13452
- import { homedir as homedir8 } from "node:os";
13453
- import { dirname as dirname7, join as join10 } from "node:path";
14423
+ import { homedir as homedir11 } from "node:os";
14424
+ import { dirname as dirname11, join as join16 } from "node:path";
13454
14425
  import { mkdir as mkdir3, readFile as readFile4, rename as rename2, writeFile as writeFile3 } from "node:fs/promises";
13455
14426
  function withLock(operation) {
13456
14427
  const result = serialized.then(operation, operation);
@@ -13468,7 +14439,7 @@ async function readEntries(file) {
13468
14439
  }
13469
14440
  }
13470
14441
  async function writeEntries(file, entries) {
13471
- await mkdir3(dirname7(file), { recursive: true });
14442
+ await mkdir3(dirname11(file), { recursive: true });
13472
14443
  const temp = `${file}.${process.pid}.tmp`;
13473
14444
  await writeFile3(temp, `${JSON.stringify(entries)}
13474
14445
  `, "utf8");
@@ -13514,7 +14485,7 @@ var DEFAULT_FILE, serialized;
13514
14485
  var init_detached_economics_spool = __esm({
13515
14486
  "../../scripts/virtual-office/code-runner/detached-economics-spool.mjs"() {
13516
14487
  "use strict";
13517
- DEFAULT_FILE = join10(homedir8(), ".vo", "detached-run-economics.json");
14488
+ DEFAULT_FILE = join16(homedir11(), ".vo", "detached-run-economics.json");
13518
14489
  serialized = Promise.resolve();
13519
14490
  }
13520
14491
  });
@@ -13855,7 +14826,7 @@ __export(code_runner_daemon_exports, {
13855
14826
  main: () => main
13856
14827
  });
13857
14828
  import { randomUUID as randomUUID7 } from "node:crypto";
13858
- import { fileURLToPath as fileURLToPath7 } from "node:url";
14829
+ import { fileURLToPath as fileURLToPath8 } from "node:url";
13859
14830
  function log(msg) {
13860
14831
  console.log(`[code-runner ${(/* @__PURE__ */ new Date()).toISOString()}] ${msg}`);
13861
14832
  }
@@ -14283,7 +15254,7 @@ var init_code_runner_daemon = __esm({
14283
15254
  RATE_LIMIT_RESUME_ENABLED = process.env.VO_RATE_LIMIT_RESUME !== "0";
14284
15255
  sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
14285
15256
  safeProgress = makeSafeProgress(log);
14286
- invokedDirectly = process.argv[1] && fileURLToPath7(import.meta.url) === process.argv[1] && // Bundle-safe: self-start only when THIS file is the real entry (not inlined into vo-mcp's runner-cli.js ⇒ double-claim).
15257
+ invokedDirectly = process.argv[1] && fileURLToPath8(import.meta.url) === process.argv[1] && // Bundle-safe: self-start only when THIS file is the real entry (not inlined into vo-mcp's runner-cli.js ⇒ double-claim).
14287
15258
  import.meta.url.endsWith("code-runner-daemon.mjs");
14288
15259
  if (invokedDirectly) {
14289
15260
  const once2 = process.argv.includes("--once");
@@ -14428,33 +15399,33 @@ import { posix, win32 } from "node:path";
14428
15399
  import { randomUUID } from "node:crypto";
14429
15400
  var APP_IDENTIFIER = "ai.algosuite.vo-runner";
14430
15401
  var CLONES_DIR = "clones";
14431
- function pathsFor(platform) {
14432
- return platform === "win32" ? win32 : posix;
15402
+ function pathsFor(platform4) {
15403
+ return platform4 === "win32" ? win32 : posix;
14433
15404
  }
14434
15405
  function absoluteOrNull(value, pathApi) {
14435
15406
  const normalized = String(value || "").trim();
14436
15407
  return normalized && pathApi.isAbsolute(normalized) ? pathApi.resolve(normalized) : null;
14437
15408
  }
14438
15409
  function defaultClonesRoot({
14439
- platform = process.platform,
15410
+ platform: platform4 = process.platform,
14440
15411
  env: env2 = process.env,
14441
15412
  home = homedir()
14442
15413
  } = {}) {
14443
- const pathApi = pathsFor(platform);
14444
- if (platform === "win32") {
15414
+ const pathApi = pathsFor(platform4);
15415
+ if (platform4 === "win32") {
14445
15416
  const appData = absoluteOrNull(env2.APPDATA, pathApi);
14446
15417
  return appData ? pathApi.join(appData, APP_IDENTIFIER, CLONES_DIR) : null;
14447
15418
  }
14448
15419
  const absoluteHome = absoluteOrNull(home, pathApi);
14449
15420
  if (!absoluteHome) return null;
14450
- if (platform === "darwin") {
15421
+ if (platform4 === "darwin") {
14451
15422
  return pathApi.join(absoluteHome, "Library", "Application Support", APP_IDENTIFIER, CLONES_DIR);
14452
15423
  }
14453
15424
  const xdg = absoluteOrNull(env2.XDG_CONFIG_HOME, pathApi);
14454
15425
  return pathApi.join(xdg || pathApi.join(absoluteHome, ".config"), APP_IDENTIFIER, CLONES_DIR);
14455
15426
  }
14456
- function findGitRoot(cwd, { platform, exists = existsSync }) {
14457
- const pathApi = pathsFor(platform);
15427
+ function findGitRoot(cwd, { platform: platform4, exists = existsSync }) {
15428
+ const pathApi = pathsFor(platform4);
14458
15429
  let cursor = pathApi.resolve(cwd);
14459
15430
  for (; ; ) {
14460
15431
  if (exists(pathApi.join(cursor, ".git"))) return cursor;
@@ -14466,11 +15437,11 @@ function findGitRoot(cwd, { platform, exists = existsSync }) {
14466
15437
  function resolveRunnerRootConfig({
14467
15438
  env: env2 = process.env,
14468
15439
  cwd = process.cwd(),
14469
- platform = process.platform,
15440
+ platform: platform4 = process.platform,
14470
15441
  home = homedir(),
14471
15442
  exists = existsSync
14472
15443
  } = {}) {
14473
- const pathApi = pathsFor(platform);
15444
+ const pathApi = pathsFor(platform4);
14474
15445
  const explicitRepoValue = String(env2.VO_CODE_RUNNER_REPO || "").trim();
14475
15446
  const explicitClonesValue = String(env2.VO_CODE_RUNNER_CLONES_ROOT || "").trim();
14476
15447
  if (explicitRepoValue && !pathApi.isAbsolute(explicitRepoValue)) {
@@ -14479,8 +15450,8 @@ function resolveRunnerRootConfig({
14479
15450
  if (explicitClonesValue && !pathApi.isAbsolute(explicitClonesValue)) {
14480
15451
  throw new Error(`VO_CODE_RUNNER_CLONES_ROOT must be an absolute path (got '${explicitClonesValue}')`);
14481
15452
  }
14482
- const repoRoot2 = explicitRepoValue ? pathApi.resolve(explicitRepoValue) : findGitRoot(cwd, { platform, exists });
14483
- const clonesRoot2 = explicitClonesValue ? pathApi.resolve(explicitClonesValue) : repoRoot2 ? null : defaultClonesRoot({ platform, env: env2, home });
15453
+ const repoRoot2 = explicitRepoValue ? pathApi.resolve(explicitRepoValue) : findGitRoot(cwd, { platform: platform4, exists });
15454
+ const clonesRoot2 = explicitClonesValue ? pathApi.resolve(explicitClonesValue) : repoRoot2 ? null : defaultClonesRoot({ platform: platform4, env: env2, home });
14484
15455
  if (!repoRoot2 && !clonesRoot2) {
14485
15456
  throw new Error(
14486
15457
  "No safe runner root is available. Set VO_CODE_RUNNER_REPO or VO_CODE_RUNNER_CLONES_ROOT to an absolute path."
@@ -14514,7 +15485,7 @@ function assertWritableRunnerDirectory(root) {
14514
15485
  }
14515
15486
 
14516
15487
  // src/runner-cli.mjs
14517
- var DEFAULT_CONTROL_PLANE_URL = "https://vo-control-plane-bzjphrajaq-uc.a.run.app";
15488
+ var DEFAULT_CONTROL_PLANE_URL3 = "https://vo-control-plane-bzjphrajaq-uc.a.run.app";
14518
15489
  function packageVersion() {
14519
15490
  try {
14520
15491
  return createRequire4(import.meta.url)("../package.json").version || "unknown";
@@ -14582,7 +15553,7 @@ if (statusOnly) {
14582
15553
  process.exit(1);
14583
15554
  }
14584
15555
  const readiness = await probeRunnerReadiness({
14585
- controlPlaneUrl: process.env.VO_CONTROL_PLANE_URL || DEFAULT_CONTROL_PLANE_URL,
15556
+ controlPlaneUrl: process.env.VO_CONTROL_PLANE_URL || DEFAULT_CONTROL_PLANE_URL3,
14586
15557
  token: storedCredential.vo_credential
14587
15558
  });
14588
15559
  if (!readiness.ok) {
@@ -14613,7 +15584,7 @@ if (!token) {
14613
15584
  console.error(" (or set VO_CONTROL_PLANE_ADMIN_TOKEN to a control-plane bearer)");
14614
15585
  process.exit(1);
14615
15586
  }
14616
- var controlPlaneUrl = process.env.VO_CONTROL_PLANE_URL || DEFAULT_CONTROL_PLANE_URL;
15587
+ var controlPlaneUrl = process.env.VO_CONTROL_PLANE_URL || DEFAULT_CONTROL_PLANE_URL3;
14617
15588
  var pairedOperatorId = null;
14618
15589
  if (!explicitAdminToken) {
14619
15590
  const readiness = await probeRunnerReadiness({
@@ -14638,6 +15609,14 @@ try {
14638
15609
  console.error(`[vo-mcp runner] Filesystem readiness failed: ${error instanceof Error ? error.message : String(error)}`);
14639
15610
  process.exit(1);
14640
15611
  }
15612
+ try {
15613
+ const { healMcpRegistration: healMcpRegistration2 } = await Promise.resolve().then(() => (init_install(), install_exports));
15614
+ healMcpRegistration2((message) => {
15615
+ if (/^(✓|\s*⚠|\s*Backed up|vo-mcp: MCP registration heal)/u.test(message)) console.error(`[vo-mcp runner] ${message.trim()}`);
15616
+ });
15617
+ } catch (error) {
15618
+ console.error(`[vo-mcp runner] MCP registration heal skipped: ${error instanceof Error ? error.message : String(error)}`);
15619
+ }
14641
15620
  var env = {
14642
15621
  ...process.env,
14643
15622
  VO_CONTROL_PLANE_ADMIN_TOKEN: token,