@algosuite/vo-mcp 0.2.0-beta.45 → 0.2.0-beta.48
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ci/check-local-pr-overlap.js +1 -1
- package/dist/cli.js +2 -0
- package/dist/cli.js.map +2 -2
- package/dist/install-cli.js +383 -94
- package/dist/install-cli.js.map +4 -4
- package/dist/runner-cli.js +1160 -166
- package/dist/runner-cli.js.map +4 -4
- package/package.json +1 -1
package/dist/runner-cli.js
CHANGED
|
@@ -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((
|
|
1277
|
+
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
373
1278
|
}
|
|
374
1279
|
async function spawnUtility(command, args, timeoutMs) {
|
|
375
|
-
return await new Promise((
|
|
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
|
-
|
|
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((
|
|
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
|
-
|
|
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
|
|
509
|
-
const checker =
|
|
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
|
|
908
|
-
return trustedCorepackCandidates(options).find((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
|
|
1817
|
+
const platform4 = options.platform || process.platform;
|
|
913
1818
|
const selector = pnpmSelector(root);
|
|
914
|
-
if (
|
|
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
|
|
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 (
|
|
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
|
|
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 =
|
|
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 =
|
|
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 (
|
|
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
|
|
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
|
-
|
|
4517
|
+
mkdirSync7(dir, { recursive: true });
|
|
3613
4518
|
const file = path12.join(dir, DAEMON_RECORD);
|
|
3614
|
-
|
|
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
|
-
|
|
3631
|
-
|
|
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 (!
|
|
4571
|
+
if (!existsSync9(root)) return instances;
|
|
3667
4572
|
let dirents;
|
|
3668
4573
|
try {
|
|
3669
|
-
dirents =
|
|
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 =
|
|
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(
|
|
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 (
|
|
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 (
|
|
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((
|
|
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
|
-
|
|
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
|
|
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 =
|
|
5529
|
+
platform: platform4 = process.platform,
|
|
5530
|
+
exists = existsSync10
|
|
4626
5531
|
} = {}) {
|
|
4627
|
-
if (
|
|
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
|
|
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
|
|
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((
|
|
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
|
|
5825
|
-
import { join as
|
|
6729
|
+
import { homedir as homedir6 } from "node:os";
|
|
6730
|
+
import { join as join8 } from "node:path";
|
|
5826
6731
|
function resumeQueuePath() {
|
|
5827
|
-
return
|
|
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,8 @@ 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
|
|
6044
|
-
import { fileURLToPath as
|
|
6948
|
+
import { existsSync as existsSync11 } from "node:fs";
|
|
6949
|
+
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
6045
6950
|
function stripCredentials(env2 = process.env) {
|
|
6046
6951
|
const safe = { ...env2 };
|
|
6047
6952
|
for (const key of CREDENTIAL_ENV_KEYS) delete safe[key];
|
|
@@ -6051,7 +6956,7 @@ function resolveOverlapScript({
|
|
|
6051
6956
|
worktreeDir,
|
|
6052
6957
|
trustedPath = null,
|
|
6053
6958
|
trustedPaths = TRUSTED_OVERLAP_CANDIDATES,
|
|
6054
|
-
existsFn =
|
|
6959
|
+
existsFn = existsSync11,
|
|
6055
6960
|
joinFn = (dir) => `${dir}/scripts/ci/check-local-pr-overlap.mjs`
|
|
6056
6961
|
} = {}) {
|
|
6057
6962
|
const candidates = trustedPath ? [trustedPath] : trustedPaths;
|
|
@@ -6099,7 +7004,7 @@ var init_pr_overlap_gate = __esm({
|
|
|
6099
7004
|
TRUSTED_OVERLAP_CANDIDATES = [
|
|
6100
7005
|
new URL("../../ci/check-local-pr-overlap.mjs", import.meta.url),
|
|
6101
7006
|
new URL("./ci/check-local-pr-overlap.js", import.meta.url)
|
|
6102
|
-
].map((candidate) =>
|
|
7007
|
+
].map((candidate) => fileURLToPath3(candidate));
|
|
6103
7008
|
CREDENTIAL_ENV_KEYS = Object.freeze([
|
|
6104
7009
|
"GH_TOKEN",
|
|
6105
7010
|
"GITHUB_TOKEN",
|
|
@@ -6538,9 +7443,9 @@ function boundedTail(text) {
|
|
|
6538
7443
|
return s.length <= COMPLETION_GATE_OUTPUT_CAP ? s : s.slice(s.length - COMPLETION_GATE_OUTPUT_CAP);
|
|
6539
7444
|
}
|
|
6540
7445
|
function workspaceFingerprint(worktreeDir, execFileImpl = execFile) {
|
|
6541
|
-
return new Promise((
|
|
7446
|
+
return new Promise((resolve3) => {
|
|
6542
7447
|
execFileImpl("git", ["rev-parse", "HEAD^{tree}"], { cwd: worktreeDir }, (err, stdout) => {
|
|
6543
|
-
|
|
7448
|
+
resolve3(err ? null : String(stdout).trim() || null);
|
|
6544
7449
|
});
|
|
6545
7450
|
});
|
|
6546
7451
|
}
|
|
@@ -6559,10 +7464,10 @@ function writeState(worktreeDir, state) {
|
|
|
6559
7464
|
}
|
|
6560
7465
|
}
|
|
6561
7466
|
function runGateCommand({ argv, worktreeDir, timeoutMs = COMPLETION_GATE_TIMEOUT_MS, execFileImpl = execFile }) {
|
|
6562
|
-
return new Promise((
|
|
7467
|
+
return new Promise((resolve3) => {
|
|
6563
7468
|
const command = ALLOWED_GATE_COMMANDS.get(argv?.[0]);
|
|
6564
7469
|
if (command === void 0) {
|
|
6565
|
-
return
|
|
7470
|
+
return resolve3({
|
|
6566
7471
|
exitCode: 1,
|
|
6567
7472
|
output: `completion_gate executable is not an allowed gate runner (${[...ALLOWED_GATE_COMMANDS.keys()].join(", ")})`,
|
|
6568
7473
|
timedOut: false
|
|
@@ -6575,10 +7480,10 @@ function runGateCommand({ argv, worktreeDir, timeoutMs = COMPLETION_GATE_TIMEOUT
|
|
|
6575
7480
|
(err, stdout, stderr) => {
|
|
6576
7481
|
const output = boundedTail(`${stdout ?? ""}
|
|
6577
7482
|
${stderr ?? ""}`.trim());
|
|
6578
|
-
if (!err) return
|
|
7483
|
+
if (!err) return resolve3({ exitCode: 0, output, timedOut: false });
|
|
6579
7484
|
const timedOut = err.killed === true || err.signal === "SIGTERM";
|
|
6580
7485
|
const exitCode = typeof err.code === "number" ? err.code : 1;
|
|
6581
|
-
|
|
7486
|
+
resolve3({ exitCode, output: output || boundedTail(err.message), timedOut });
|
|
6582
7487
|
}
|
|
6583
7488
|
);
|
|
6584
7489
|
});
|
|
@@ -6683,7 +7588,7 @@ function runProcess2(cmd, args, {
|
|
|
6683
7588
|
forceSettleAfterMs = 1e3,
|
|
6684
7589
|
spawnImpl = spawn3
|
|
6685
7590
|
} = {}) {
|
|
6686
|
-
return new Promise((
|
|
7591
|
+
return new Promise((resolve3, reject) => {
|
|
6687
7592
|
let settled = false;
|
|
6688
7593
|
let timedOut = false;
|
|
6689
7594
|
let stdout = "";
|
|
@@ -6733,7 +7638,7 @@ function runProcess2(cmd, args, {
|
|
|
6733
7638
|
settle(reject, buildExitError(cmd, args, result));
|
|
6734
7639
|
return;
|
|
6735
7640
|
}
|
|
6736
|
-
settle(
|
|
7641
|
+
settle(resolve3, result.stdout);
|
|
6737
7642
|
});
|
|
6738
7643
|
if (timeout > 0) {
|
|
6739
7644
|
timeoutTimer = setTimeout(() => {
|
|
@@ -7011,6 +7916,7 @@ async function openCodeTaskPrAsync(worktreeDir, files, {
|
|
|
7011
7916
|
if (!Array.isArray(files) || files.length === 0) throw new Error("openCodeTaskPrAsync: no files to commit");
|
|
7012
7917
|
let branch;
|
|
7013
7918
|
let truncated = false;
|
|
7919
|
+
let newCommit = null;
|
|
7014
7920
|
if (alreadyCommitted) {
|
|
7015
7921
|
branch = await resolveOrCreateBranchAsync(worktreeDir, branchPrefix, runCommand);
|
|
7016
7922
|
} else {
|
|
@@ -7023,6 +7929,7 @@ async function openCodeTaskPrAsync(worktreeDir, files, {
|
|
|
7023
7929
|
});
|
|
7024
7930
|
branch = committed.branch;
|
|
7025
7931
|
truncated = committed.truncated;
|
|
7932
|
+
newCommit = committed.committed;
|
|
7026
7933
|
}
|
|
7027
7934
|
const prBranch = String(targetBranch || branch).trim() || branch;
|
|
7028
7935
|
const overlap = await runOverlapGate(worktreeDir, files.filter((file) => !isAgentScratch(file)), {
|
|
@@ -7077,6 +7984,7 @@ async function openCodeTaskPrAsync(worktreeDir, files, {
|
|
|
7077
7984
|
prNumber: existing.number,
|
|
7078
7985
|
branch: prBranch,
|
|
7079
7986
|
truncated,
|
|
7987
|
+
newCommit,
|
|
7080
7988
|
resumed: true,
|
|
7081
7989
|
markedReady,
|
|
7082
7990
|
overlapDraft,
|
|
@@ -7115,7 +8023,7 @@ async function openCodeTaskPrAsync(worktreeDir, files, {
|
|
|
7115
8023
|
runCommand,
|
|
7116
8024
|
onCleanupWarning
|
|
7117
8025
|
});
|
|
7118
|
-
return { prUrl, prNumber, branch: prBranch, truncated, overlapDraft, overlapBlockedBy, ...autoMerge, ...superseded };
|
|
8026
|
+
return { prUrl, prNumber, branch: prBranch, truncated, newCommit, overlapDraft, overlapBlockedBy, ...autoMerge, ...superseded };
|
|
7119
8027
|
}
|
|
7120
8028
|
var sleep;
|
|
7121
8029
|
var init_publish_async = __esm({
|
|
@@ -7130,14 +8038,14 @@ var init_publish_async = __esm({
|
|
|
7130
8038
|
init_pr_overlap_gate();
|
|
7131
8039
|
init_existing_pr_publication();
|
|
7132
8040
|
init_partial_pr_continuation();
|
|
7133
|
-
sleep = (ms) => new Promise((
|
|
8041
|
+
sleep = (ms) => new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
7134
8042
|
}
|
|
7135
8043
|
});
|
|
7136
8044
|
|
|
7137
8045
|
// ../../scripts/virtual-office/code-runner/skill-catalog.mjs
|
|
7138
|
-
import { readdirSync as
|
|
7139
|
-
import { dirname as
|
|
7140
|
-
import { fileURLToPath as
|
|
8046
|
+
import { readdirSync as readdirSync3, readFileSync as readFileSync8, statSync as statSync4 } from "node:fs";
|
|
8047
|
+
import { dirname as dirname6, join as join9 } from "node:path";
|
|
8048
|
+
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
7141
8049
|
function parseFrontmatterNameDescription(raw) {
|
|
7142
8050
|
const text = String(raw).replace(/\r\n/g, "\n");
|
|
7143
8051
|
if (!text.startsWith("---\n")) return null;
|
|
@@ -7156,15 +8064,15 @@ function parseFrontmatterNameDescription(raw) {
|
|
|
7156
8064
|
return name && description ? { name, description } : null;
|
|
7157
8065
|
}
|
|
7158
8066
|
function resolveDefaultRepoRoot() {
|
|
7159
|
-
const starts = [
|
|
8067
|
+
const starts = [dirname6(fileURLToPath4(import.meta.url)), process.cwd()];
|
|
7160
8068
|
for (const start of starts) {
|
|
7161
8069
|
let dir = start;
|
|
7162
8070
|
for (let i = 0; i < 8; i += 1) {
|
|
7163
8071
|
try {
|
|
7164
|
-
if (
|
|
8072
|
+
if (statSync4(join9(dir, ".claude", "skills")).isDirectory()) return dir;
|
|
7165
8073
|
} catch {
|
|
7166
8074
|
}
|
|
7167
|
-
const parent =
|
|
8075
|
+
const parent = dirname6(dir);
|
|
7168
8076
|
if (parent === dir) break;
|
|
7169
8077
|
dir = parent;
|
|
7170
8078
|
}
|
|
@@ -7173,14 +8081,14 @@ function resolveDefaultRepoRoot() {
|
|
|
7173
8081
|
}
|
|
7174
8082
|
function loadSkillCatalog({ repoRoot: repoRoot2 = resolveDefaultRepoRoot() } = {}) {
|
|
7175
8083
|
try {
|
|
7176
|
-
const skillsDir =
|
|
8084
|
+
const skillsDir = join9(repoRoot2, ".claude", "skills");
|
|
7177
8085
|
const catalog = [];
|
|
7178
|
-
for (const entry of
|
|
7179
|
-
const dir =
|
|
8086
|
+
for (const entry of readdirSync3(skillsDir)) {
|
|
8087
|
+
const dir = join9(skillsDir, entry);
|
|
7180
8088
|
try {
|
|
7181
|
-
if (!
|
|
8089
|
+
if (!statSync4(dir).isDirectory()) continue;
|
|
7182
8090
|
const parsed = parseFrontmatterNameDescription(
|
|
7183
|
-
|
|
8091
|
+
readFileSync8(join9(dir, "SKILL.md"), "utf8")
|
|
7184
8092
|
);
|
|
7185
8093
|
if (parsed) catalog.push(parsed);
|
|
7186
8094
|
} catch {
|
|
@@ -7680,8 +8588,8 @@ var init_task_attachments = __esm({
|
|
|
7680
8588
|
});
|
|
7681
8589
|
|
|
7682
8590
|
// ../../scripts/virtual-office/code-runner/session-spool-forwarder.mjs
|
|
7683
|
-
import { homedir as
|
|
7684
|
-
import { join as
|
|
8591
|
+
import { homedir as homedir7 } from "node:os";
|
|
8592
|
+
import { join as join10 } from "node:path";
|
|
7685
8593
|
import { readdir as readdir2, readFile as readFile2, unlink, writeFile as writeFile2 } from "node:fs/promises";
|
|
7686
8594
|
import { createHash as createHash4 } from "node:crypto";
|
|
7687
8595
|
function deriveUuid(seed) {
|
|
@@ -7713,9 +8621,9 @@ async function readSpool(spoolDir = SPOOL_DIR) {
|
|
|
7713
8621
|
for (const f of files) {
|
|
7714
8622
|
if (!f.endsWith(".json")) continue;
|
|
7715
8623
|
try {
|
|
7716
|
-
const record = JSON.parse(await readFile2(
|
|
8624
|
+
const record = JSON.parse(await readFile2(join10(spoolDir, f), "utf8"));
|
|
7717
8625
|
if (record && typeof record.session_key === "string") {
|
|
7718
|
-
out.push({ full:
|
|
8626
|
+
out.push({ full: join10(spoolDir, f), record });
|
|
7719
8627
|
}
|
|
7720
8628
|
} catch {
|
|
7721
8629
|
}
|
|
@@ -7797,8 +8705,8 @@ var SPOOL_DIR, CLOUD_MAP_FILE, STALE_MS, ACTIVE_SILENCE_MS;
|
|
|
7797
8705
|
var init_session_spool_forwarder = __esm({
|
|
7798
8706
|
"../../scripts/virtual-office/code-runner/session-spool-forwarder.mjs"() {
|
|
7799
8707
|
"use strict";
|
|
7800
|
-
SPOOL_DIR =
|
|
7801
|
-
CLOUD_MAP_FILE =
|
|
8708
|
+
SPOOL_DIR = join10(homedir7(), ".vo", "session-spool");
|
|
8709
|
+
CLOUD_MAP_FILE = join10(homedir7(), ".vo", "session-cloud-map.json");
|
|
7802
8710
|
STALE_MS = 60 * 60 * 1e3;
|
|
7803
8711
|
ACTIVE_SILENCE_MS = 10 * 60 * 1e3;
|
|
7804
8712
|
}
|
|
@@ -7869,7 +8777,7 @@ var init_rate_limit_resume_scheduler_core = __esm({
|
|
|
7869
8777
|
});
|
|
7870
8778
|
|
|
7871
8779
|
// ../../scripts/virtual-office/code-runner/rate-limit-resume-scheduler.mjs
|
|
7872
|
-
import { dirname as
|
|
8780
|
+
import { dirname as dirname7, join as join11, resolve as resolve2 } from "node:path";
|
|
7873
8781
|
function defaultLog(message) {
|
|
7874
8782
|
console.log(`[rate-limit-scheduler ${(/* @__PURE__ */ new Date()).toISOString()}] ${message}`);
|
|
7875
8783
|
}
|
|
@@ -7951,7 +8859,7 @@ async function runLockedScheduler({
|
|
|
7951
8859
|
async function runScheduler({
|
|
7952
8860
|
env: env2 = process.env,
|
|
7953
8861
|
queuePath = resumeQueuePath(),
|
|
7954
|
-
attemptsPath =
|
|
8862
|
+
attemptsPath = join11(dirname7(queuePath), "resume-attempts.json"),
|
|
7955
8863
|
client,
|
|
7956
8864
|
now,
|
|
7957
8865
|
log: log2 = defaultLog
|
|
@@ -7980,9 +8888,9 @@ var init_rate_limit_resume_scheduler = __esm({
|
|
|
7980
8888
|
ATTEMPTS_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
7981
8889
|
isMainModule = (() => {
|
|
7982
8890
|
try {
|
|
7983
|
-
const argv1 = process.argv[1] ?
|
|
8891
|
+
const argv1 = process.argv[1] ? resolve2(process.argv[1]) : "";
|
|
7984
8892
|
const here = new URL(import.meta.url).pathname.replace(/^\/([a-zA-Z]):\//u, "$1:/");
|
|
7985
|
-
return
|
|
8893
|
+
return resolve2(here) === argv1;
|
|
7986
8894
|
} catch {
|
|
7987
8895
|
return false;
|
|
7988
8896
|
}
|
|
@@ -8040,10 +8948,10 @@ function makeLoopTicks({
|
|
|
8040
8948
|
state = { running: false, pending: null };
|
|
8041
8949
|
heartbeatState.set(key, state);
|
|
8042
8950
|
}
|
|
8043
|
-
return new Promise((
|
|
8951
|
+
return new Promise((resolve3) => {
|
|
8044
8952
|
if (state.running) {
|
|
8045
|
-
if (state.pending) state.pending.waiters.push(
|
|
8046
|
-
else state.pending = { payload, waiters: [
|
|
8953
|
+
if (state.pending) state.pending.waiters.push(resolve3);
|
|
8954
|
+
else state.pending = { payload, waiters: [resolve3] };
|
|
8047
8955
|
state.pending.payload = payload;
|
|
8048
8956
|
return;
|
|
8049
8957
|
}
|
|
@@ -8071,7 +8979,7 @@ function makeLoopTicks({
|
|
|
8071
8979
|
}
|
|
8072
8980
|
});
|
|
8073
8981
|
};
|
|
8074
|
-
launch(payload, [
|
|
8982
|
+
launch(payload, [resolve3]);
|
|
8075
8983
|
});
|
|
8076
8984
|
}
|
|
8077
8985
|
return function tick() {
|
|
@@ -8248,7 +9156,7 @@ var init_runner_capacity = __esm({
|
|
|
8248
9156
|
});
|
|
8249
9157
|
|
|
8250
9158
|
// ../../scripts/virtual-office/code-runner/agent-auth-probe-process.mjs
|
|
8251
|
-
import { fileURLToPath as
|
|
9159
|
+
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
8252
9160
|
async function probeAgentInChild(agent, timeoutMs) {
|
|
8253
9161
|
const stdout = await runProcess2(process.execPath, [probeCli, agent], {
|
|
8254
9162
|
timeout: timeoutMs,
|
|
@@ -8261,7 +9169,7 @@ var init_agent_auth_probe_process = __esm({
|
|
|
8261
9169
|
"../../scripts/virtual-office/code-runner/agent-auth-probe-process.mjs"() {
|
|
8262
9170
|
"use strict";
|
|
8263
9171
|
init_process_runner2();
|
|
8264
|
-
probeCli =
|
|
9172
|
+
probeCli = fileURLToPath5(new URL("./agent-auth-probe-cli.mjs", import.meta.url));
|
|
8265
9173
|
}
|
|
8266
9174
|
});
|
|
8267
9175
|
|
|
@@ -8280,7 +9188,7 @@ async function collectAgentAvailability({
|
|
|
8280
9188
|
try {
|
|
8281
9189
|
const r = await Promise.race([
|
|
8282
9190
|
runnerFor ? Promise.resolve().then(() => runnerFor(agent).checkAuth()) : probeAgentInChild(agent, probeTimeoutMs),
|
|
8283
|
-
new Promise((
|
|
9191
|
+
new Promise((resolve3) => setTimeout(() => resolve3(null), probeTimeoutMs))
|
|
8284
9192
|
]);
|
|
8285
9193
|
if (!r) return degraded;
|
|
8286
9194
|
const installed = Boolean(r?.installed);
|
|
@@ -8540,7 +9448,7 @@ function readingAgeMs(row, nowMs = Date.now()) {
|
|
|
8540
9448
|
if (!Number.isFinite(ms)) return null;
|
|
8541
9449
|
return Math.max(0, nowMs - ms);
|
|
8542
9450
|
}
|
|
8543
|
-
var clampPct,
|
|
9451
|
+
var clampPct, readJson2, ACCOUNT_KEY_SALT;
|
|
8544
9452
|
var init_shared = __esm({
|
|
8545
9453
|
"../../scripts/virtual-office/code-runner/account-usage/shared.mjs"() {
|
|
8546
9454
|
"use strict";
|
|
@@ -8548,7 +9456,7 @@ var init_shared = __esm({
|
|
|
8548
9456
|
const n = typeof v === "number" ? v : typeof v === "string" && v.trim() !== "" ? Number(v) : NaN;
|
|
8549
9457
|
return Number.isFinite(n) ? Math.min(100, Math.max(0, Math.round(n))) : null;
|
|
8550
9458
|
};
|
|
8551
|
-
|
|
9459
|
+
readJson2 = (p) => {
|
|
8552
9460
|
try {
|
|
8553
9461
|
return JSON.parse(fs9.readFileSync(p, "utf8"));
|
|
8554
9462
|
} catch {
|
|
@@ -8575,7 +9483,7 @@ function usageBaseUrl(env2 = process.env) {
|
|
|
8575
9483
|
const raw = env2.ANTHROPIC_BASE_URL || "https://api.anthropic.com";
|
|
8576
9484
|
return String(raw).replace(/\/+$/, "");
|
|
8577
9485
|
}
|
|
8578
|
-
function readOAuthToken({ homeDir = os3.homedir(), read =
|
|
9486
|
+
function readOAuthToken({ homeDir = os3.homedir(), read = readJson2, now = Date.now() } = {}) {
|
|
8579
9487
|
const creds = read(path17.join(homeDir, ".claude", ".credentials.json"));
|
|
8580
9488
|
const oauth = creds && typeof creds === "object" ? creds.claudeAiOauth : null;
|
|
8581
9489
|
if (!oauth || typeof oauth !== "object") return null;
|
|
@@ -8585,7 +9493,7 @@ function readOAuthToken({ homeDir = os3.homedir(), read = readJson, now = Date.n
|
|
|
8585
9493
|
if (Number.isFinite(expiresAt) && expiresAt > 0 && expiresAt <= now) return null;
|
|
8586
9494
|
return token2;
|
|
8587
9495
|
}
|
|
8588
|
-
function readAccountId({ homeDir = os3.homedir(), read =
|
|
9496
|
+
function readAccountId({ homeDir = os3.homedir(), read = readJson2 } = {}) {
|
|
8589
9497
|
const cfg = read(path17.join(homeDir, ".claude.json"));
|
|
8590
9498
|
const account = cfg && typeof cfg === "object" ? cfg.oauthAccount : null;
|
|
8591
9499
|
return account && typeof account.accountUuid === "string" ? account.accountUuid : null;
|
|
@@ -8638,7 +9546,7 @@ async function readClaudeOAuthUsage({
|
|
|
8638
9546
|
env: env2 = process.env,
|
|
8639
9547
|
timeoutMs = DEFAULT_TIMEOUT_MS,
|
|
8640
9548
|
homeDir = os3.homedir(),
|
|
8641
|
-
read =
|
|
9549
|
+
read = readJson2,
|
|
8642
9550
|
now = () => Date.now()
|
|
8643
9551
|
} = {}) {
|
|
8644
9552
|
const token2 = readOAuthToken({ homeDir, read, now: now() });
|
|
@@ -8678,7 +9586,7 @@ async function readClaudeOAuthUsage({
|
|
|
8678
9586
|
}
|
|
8679
9587
|
function readClaudeFileUsage({
|
|
8680
9588
|
homeDir = os3.homedir(),
|
|
8681
|
-
read: rawRead =
|
|
9589
|
+
read: rawRead = readJson2,
|
|
8682
9590
|
statFn = fs10.statSync,
|
|
8683
9591
|
now = () => Date.now()
|
|
8684
9592
|
} = {}) {
|
|
@@ -8779,10 +9687,10 @@ function readCodexUsage({
|
|
|
8779
9687
|
resolveBinary = resolveCodexBinary,
|
|
8780
9688
|
timeoutMs = 8e3,
|
|
8781
9689
|
env: env2 = process.env,
|
|
8782
|
-
platform = process.platform,
|
|
9690
|
+
platform: platform4 = process.platform,
|
|
8783
9691
|
now = () => Date.now()
|
|
8784
9692
|
} = {}) {
|
|
8785
|
-
return new Promise((
|
|
9693
|
+
return new Promise((resolve3) => {
|
|
8786
9694
|
let child;
|
|
8787
9695
|
let settled = false;
|
|
8788
9696
|
let stdout = "";
|
|
@@ -8794,11 +9702,11 @@ function readCodexUsage({
|
|
|
8794
9702
|
child?.kill();
|
|
8795
9703
|
} catch {
|
|
8796
9704
|
}
|
|
8797
|
-
|
|
9705
|
+
resolve3(value);
|
|
8798
9706
|
};
|
|
8799
9707
|
const timer = setTimeout(() => finish(null), timeoutMs);
|
|
8800
9708
|
try {
|
|
8801
|
-
const binary = resolveBinary({ env: env2, platform });
|
|
9709
|
+
const binary = resolveBinary({ env: env2, platform: platform4 });
|
|
8802
9710
|
child = spawnImpl(binary, ["app-server", "--stdio"], {
|
|
8803
9711
|
env: env2,
|
|
8804
9712
|
windowsHide: true,
|
|
@@ -9111,6 +10019,12 @@ function ciFixOccurrenceKey({ repo, prNumber, headSha, repairAttempt }) {
|
|
|
9111
10019
|
function coordinationRetryDue(entry, nowMs) {
|
|
9112
10020
|
return !entry.nextRetryAt || nowMs >= entry.nextRetryAt;
|
|
9113
10021
|
}
|
|
10022
|
+
function mergeEnqueueActive(entry, nowMs = Date.now()) {
|
|
10023
|
+
if (!entry?.mergeEnqueued) return false;
|
|
10024
|
+
const at = Number(entry.mergeEnqueuedAt || 0);
|
|
10025
|
+
if (!at) return true;
|
|
10026
|
+
return nowMs - at < MERGE_ENQUEUE_TTL_MS;
|
|
10027
|
+
}
|
|
9114
10028
|
function isTerminalResumeRefusal(error) {
|
|
9115
10029
|
return typeof error?.code === "string" && TERMINAL_RESUME_REFUSALS.includes(error.code);
|
|
9116
10030
|
}
|
|
@@ -9151,12 +10065,13 @@ async function scheduleCoordinationRetry({
|
|
|
9151
10065
|
}
|
|
9152
10066
|
log2(`watch: pr #${prNumber} ${kind} coordination failed ${entry[errorsKey]}x; retry in ${Math.round(delay2 / 1e3)}s: ${boundedErrorMessage(error)}`);
|
|
9153
10067
|
}
|
|
9154
|
-
var MAX_BACKOFF_MS, TERMINAL_RESUME_REFUSALS;
|
|
10068
|
+
var MAX_BACKOFF_MS, MERGE_ENQUEUE_TTL_MS, TERMINAL_RESUME_REFUSALS;
|
|
9155
10069
|
var init_watcher_coordination = __esm({
|
|
9156
10070
|
"../../scripts/virtual-office/code-runner/watcher-coordination.mjs"() {
|
|
9157
10071
|
"use strict";
|
|
9158
10072
|
init_error_message();
|
|
9159
10073
|
MAX_BACKOFF_MS = 60 * 60 * 1e3;
|
|
10074
|
+
MERGE_ENQUEUE_TTL_MS = 2 * 60 * 60 * 1e3;
|
|
9160
10075
|
TERMINAL_RESUME_REFUSALS = Object.freeze([
|
|
9161
10076
|
"automatic_continuation_budget_too_small",
|
|
9162
10077
|
"automatic_continuation_budget_required",
|
|
@@ -9174,7 +10089,7 @@ var init_watcher_coordination = __esm({
|
|
|
9174
10089
|
// ../../scripts/virtual-office/code-runner/watcher-state.mjs
|
|
9175
10090
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
9176
10091
|
import { mkdir as mkdir2, open, readFile as readFile3, rename, unlink as unlink2 } from "node:fs/promises";
|
|
9177
|
-
import { dirname as
|
|
10092
|
+
import { dirname as dirname8 } from "node:path";
|
|
9178
10093
|
async function readWatcherState(stateFile) {
|
|
9179
10094
|
let raw;
|
|
9180
10095
|
try {
|
|
@@ -9190,7 +10105,7 @@ async function readWatcherState(stateFile) {
|
|
|
9190
10105
|
return parsed;
|
|
9191
10106
|
}
|
|
9192
10107
|
async function writeWatcherState(stateFile, state) {
|
|
9193
|
-
const directory =
|
|
10108
|
+
const directory = dirname8(stateFile);
|
|
9194
10109
|
await mkdir2(directory, { recursive: true });
|
|
9195
10110
|
const temp = `${stateFile}.${process.pid}.${randomUUID3()}.tmp`;
|
|
9196
10111
|
let handle;
|
|
@@ -9397,6 +10312,41 @@ function noteCiUnreadable(log2) {
|
|
|
9397
10312
|
lastDiagnosticAt = now;
|
|
9398
10313
|
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.");
|
|
9399
10314
|
}
|
|
10315
|
+
function noteCiViaRest(log2) {
|
|
10316
|
+
const now = Date.now();
|
|
10317
|
+
if (now - lastRestDiagnosticAt < DIAGNOSTIC_INTERVAL_MS) return;
|
|
10318
|
+
lastRestDiagnosticAt = now;
|
|
10319
|
+
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)");
|
|
10320
|
+
}
|
|
10321
|
+
async function readCommitCiViaRest(repo, sha, { run, env: env2 }) {
|
|
10322
|
+
const api = async (path22) => JSON.parse(await run("gh", ["api", path22], { timeout: 3e4, env: env2 }) || "{}");
|
|
10323
|
+
const rollup = [];
|
|
10324
|
+
let total = null;
|
|
10325
|
+
for (let page = 1; page <= REST_MAX_PAGES && (total === null || rollup.length < total); page += 1) {
|
|
10326
|
+
const body = await api(`repos/${repo}/commits/${sha}/check-runs?per_page=${REST_PAGE_SIZE}&page=${page}`);
|
|
10327
|
+
if (!Number.isFinite(Number(body?.total_count))) throw new CiRollupIncompleteError("check-runs response carried no total_count");
|
|
10328
|
+
total = Number(body.total_count);
|
|
10329
|
+
const runs = Array.isArray(body?.check_runs) ? body.check_runs : [];
|
|
10330
|
+
for (const r of runs) {
|
|
10331
|
+
rollup.push({
|
|
10332
|
+
__typename: "CheckRun",
|
|
10333
|
+
name: String(r?.name || ""),
|
|
10334
|
+
status: String(r?.status || ""),
|
|
10335
|
+
conclusion: r?.conclusion == null ? null : String(r.conclusion),
|
|
10336
|
+
...r?.details_url ? { detailsUrl: String(r.details_url) } : {}
|
|
10337
|
+
});
|
|
10338
|
+
}
|
|
10339
|
+
if (runs.length === 0) break;
|
|
10340
|
+
}
|
|
10341
|
+
if (total === null || rollup.length < total) {
|
|
10342
|
+
throw new CiRollupIncompleteError(`read ${rollup.length} of ${total ?? "?"} check runs (page bound ${REST_MAX_PAGES}\xD7${REST_PAGE_SIZE})`);
|
|
10343
|
+
}
|
|
10344
|
+
const status = await api(`repos/${repo}/commits/${sha}/status?per_page=${REST_PAGE_SIZE}`);
|
|
10345
|
+
for (const s of Array.isArray(status?.statuses) ? status.statuses : []) {
|
|
10346
|
+
rollup.push({ __typename: "StatusContext", context: String(s?.context || ""), state: String(s?.state || "") });
|
|
10347
|
+
}
|
|
10348
|
+
return rollup;
|
|
10349
|
+
}
|
|
9400
10350
|
async function ghViewPr(prNumber, repo, { githubToken, log: log2 = (m) => console.error(`[vo-runner] ${m}`), run = runProcess2 } = {}) {
|
|
9401
10351
|
const env2 = githubToken ? { ...process.env, GH_TOKEN: githubToken } : process.env;
|
|
9402
10352
|
const view = async (fields) => JSON.parse(await run("gh", [
|
|
@@ -9412,12 +10362,24 @@ async function ghViewPr(prNumber, repo, { githubToken, log: log2 = (m) => consol
|
|
|
9412
10362
|
return await view(VIEW_FIELDS_WITH_CI);
|
|
9413
10363
|
} catch (err) {
|
|
9414
10364
|
if (!isCiUnreadableError(err)) throw err;
|
|
9415
|
-
noteCiUnreadable(log2);
|
|
9416
10365
|
const withoutCi = await view(VIEW_FIELDS_WITHOUT_CI);
|
|
10366
|
+
if (typeof withoutCi.headRefOid === "string" && /^[0-9a-f]{40}$/i.test(withoutCi.headRefOid)) {
|
|
10367
|
+
try {
|
|
10368
|
+
const statusCheckRollup = await readCommitCiViaRest(repo, withoutCi.headRefOid, { run, env: env2 });
|
|
10369
|
+
noteCiViaRest(log2);
|
|
10370
|
+
return { ...withoutCi, statusCheckRollup, ciSource: "rest" };
|
|
10371
|
+
} catch (restErr) {
|
|
10372
|
+
const text = `${restErr?.message || ""}
|
|
10373
|
+
${restErr?.stderr || ""}`;
|
|
10374
|
+
if (!(restErr instanceof CiRollupIncompleteError) && !/Resource not accessible by integration/i.test(text)) throw restErr;
|
|
10375
|
+
if (restErr instanceof CiRollupIncompleteError) log2(`watch: CI rollup incomplete via REST (${restErr.message}) \u2014 reporting ci=unknown`);
|
|
10376
|
+
}
|
|
10377
|
+
}
|
|
10378
|
+
noteCiUnreadable(log2);
|
|
9417
10379
|
return { ...withoutCi, statusCheckRollup: null, ciUnreadable: true, ciUnreadableReason: CI_UNREADABLE_REASON };
|
|
9418
10380
|
}
|
|
9419
10381
|
}
|
|
9420
|
-
var VIEW_FIELDS_WITH_CI, VIEW_FIELDS_WITHOUT_CI, CI_UNREADABLE_REASON, DIAGNOSTIC_INTERVAL_MS, lastDiagnosticAt;
|
|
10382
|
+
var VIEW_FIELDS_WITH_CI, VIEW_FIELDS_WITHOUT_CI, CI_UNREADABLE_REASON, DIAGNOSTIC_INTERVAL_MS, lastDiagnosticAt, lastRestDiagnosticAt, REST_PAGE_SIZE, REST_MAX_PAGES, CiRollupIncompleteError;
|
|
9421
10383
|
var init_pr_watcher_github = __esm({
|
|
9422
10384
|
"../../scripts/virtual-office/code-runner/pr-watcher-github.mjs"() {
|
|
9423
10385
|
"use strict";
|
|
@@ -9427,6 +10389,15 @@ var init_pr_watcher_github = __esm({
|
|
|
9427
10389
|
CI_UNREADABLE_REASON = "app_token_missing_checks_read";
|
|
9428
10390
|
DIAGNOSTIC_INTERVAL_MS = 30 * 60 * 1e3;
|
|
9429
10391
|
lastDiagnosticAt = 0;
|
|
10392
|
+
lastRestDiagnosticAt = 0;
|
|
10393
|
+
REST_PAGE_SIZE = 100;
|
|
10394
|
+
REST_MAX_PAGES = 5;
|
|
10395
|
+
CiRollupIncompleteError = class extends Error {
|
|
10396
|
+
constructor(message) {
|
|
10397
|
+
super(message);
|
|
10398
|
+
this.name = "CiRollupIncompleteError";
|
|
10399
|
+
}
|
|
10400
|
+
};
|
|
9430
10401
|
}
|
|
9431
10402
|
});
|
|
9432
10403
|
|
|
@@ -9463,8 +10434,8 @@ var init_enqueue_autonomous_code_task = __esm({
|
|
|
9463
10434
|
});
|
|
9464
10435
|
|
|
9465
10436
|
// ../../scripts/virtual-office/code-runner/pr-watcher.mjs
|
|
9466
|
-
import { homedir as
|
|
9467
|
-
import { join as
|
|
10437
|
+
import { homedir as homedir8 } from "node:os";
|
|
10438
|
+
import { join as join12 } from "node:path";
|
|
9468
10439
|
function parsePrCiStatus(view) {
|
|
9469
10440
|
const state = (view && typeof view.state === "string" ? view.state : "UNKNOWN").toUpperCase();
|
|
9470
10441
|
const rollup = view && Array.isArray(view.statusCheckRollup) ? view.statusCheckRollup : [];
|
|
@@ -9501,14 +10472,14 @@ function parsePrCiStatus(view) {
|
|
|
9501
10472
|
mergeState: String(view?.mergeStateStatus || "").toUpperCase()
|
|
9502
10473
|
};
|
|
9503
10474
|
}
|
|
9504
|
-
function decideWatchAction(pr, fixAttempts, maxFixAttempts, entry = {}, maxResumeAttempts = 1, autoMergeEnabled = false) {
|
|
10475
|
+
function decideWatchAction(pr, fixAttempts, maxFixAttempts, entry = {}, maxResumeAttempts = 1, autoMergeEnabled = false, nowMs = Date.now()) {
|
|
9505
10476
|
if (pr.state !== "OPEN") return "untrack";
|
|
9506
10477
|
if (entry && entry.needsContinuation && entry.taskId) {
|
|
9507
10478
|
if (pr.ci === "pending") return "wait";
|
|
9508
10479
|
return (entry.resumeAttempts || 0) < maxResumeAttempts ? "resume" : "wait";
|
|
9509
10480
|
}
|
|
9510
10481
|
if (pr.ci === "failing" && entry.allowFixDispatch !== false && (fixAttempts || 0) < maxFixAttempts) return "fix";
|
|
9511
|
-
if (autoMergeEnabled && pr.ci === "passing" && pr.mergeState === "CLEAN" && !pr.isDraft && !entry.mergeTerminal && !entry
|
|
10482
|
+
if (autoMergeEnabled && pr.ci === "passing" && pr.mergeState === "CLEAN" && !pr.isDraft && !entry.mergeTerminal && !mergeEnqueueActive(entry, nowMs) && (entry.mergeAttempts || 0) < 3) return "merge";
|
|
9512
10483
|
return "wait";
|
|
9513
10484
|
}
|
|
9514
10485
|
async function trackDispatchedPr({ prNumber, repo, branch, taskId, operatorId, tenantId, needsContinuation = false, continuationExhausted = false, repairChain, allowFixDispatch }, { stateFile = DEFAULT_STATE_FILE, now = () => Date.now() } = {}) {
|
|
@@ -9585,7 +10556,8 @@ async function runWatchCycleUnlocked({
|
|
|
9585
10556
|
maxFixAttempts,
|
|
9586
10557
|
entry,
|
|
9587
10558
|
maxResumeAttempts,
|
|
9588
|
-
autoMergeEnabled
|
|
10559
|
+
autoMergeEnabled,
|
|
10560
|
+
now()
|
|
9589
10561
|
);
|
|
9590
10562
|
const stableAction = proposedAction === "fix" && !confirmation.confirmed ? "wait" : proposedAction;
|
|
9591
10563
|
const action = coordinationRetryDue(entry, now()) ? stableAction : "wait";
|
|
@@ -9636,6 +10608,7 @@ async function runWatchCycleUnlocked({
|
|
|
9636
10608
|
log2(`watch: pr #${prNumber} passed CI + consensus and merged (${outcome.actionReceiptId || "receipt pending"})`);
|
|
9637
10609
|
} else if (outcome.status === "queued" || outcome.status === "accepted") {
|
|
9638
10610
|
entry.mergeEnqueued = true;
|
|
10611
|
+
entry.mergeEnqueuedAt = now();
|
|
9639
10612
|
entry.mergeActionReceiptId = outcome.actionReceiptId || null;
|
|
9640
10613
|
queued += 1;
|
|
9641
10614
|
log2(`watch: pr #${prNumber} passed CI + consensus and entered the merge queue (${outcome.actionReceiptId || "receipt pending"})`);
|
|
@@ -9808,7 +10781,7 @@ var init_pr_watcher = __esm({
|
|
|
9808
10781
|
init_watcher_state();
|
|
9809
10782
|
init_superseded_pr_source();
|
|
9810
10783
|
init_ci_fix_prompt();
|
|
9811
|
-
DEFAULT_STATE_FILE =
|
|
10784
|
+
DEFAULT_STATE_FILE = join12(homedir8(), ".vo", "dispatched-prs.json");
|
|
9812
10785
|
FAIL_CONCLUSIONS = /* @__PURE__ */ new Set([
|
|
9813
10786
|
"FAILURE",
|
|
9814
10787
|
"TIMED_OUT",
|
|
@@ -10243,7 +11216,7 @@ import { randomUUID as randomUUID5 } from "node:crypto";
|
|
|
10243
11216
|
import fs11 from "node:fs";
|
|
10244
11217
|
import os4 from "node:os";
|
|
10245
11218
|
import path18 from "node:path";
|
|
10246
|
-
import { fileURLToPath as
|
|
11219
|
+
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
10247
11220
|
function userCacheRoot() {
|
|
10248
11221
|
try {
|
|
10249
11222
|
const home = os4.homedir();
|
|
@@ -10438,7 +11411,7 @@ var __dirname, DEFAULT_CACHE_DIR, DEFAULT_CACHE_FILE, DEFAULT_TTL_MS3, ANTHROPIC
|
|
|
10438
11411
|
var init_model_registry = __esm({
|
|
10439
11412
|
"../../scripts/virtual-office/model-registry.mjs"() {
|
|
10440
11413
|
"use strict";
|
|
10441
|
-
__dirname = path18.dirname(
|
|
11414
|
+
__dirname = path18.dirname(fileURLToPath6(import.meta.url));
|
|
10442
11415
|
DEFAULT_CACHE_DIR = path18.join(
|
|
10443
11416
|
resolveCacheBaseDir(),
|
|
10444
11417
|
".virtual-office-cache",
|
|
@@ -11051,9 +12024,9 @@ var init_classify_task = __esm({
|
|
|
11051
12024
|
});
|
|
11052
12025
|
|
|
11053
12026
|
// ../../scripts/virtual-office/code-runner/auto-router/effort-policy.mjs
|
|
11054
|
-
import { readFileSync as
|
|
11055
|
-
import { homedir as
|
|
11056
|
-
import { join as
|
|
12027
|
+
import { readFileSync as readFileSync9 } from "node:fs";
|
|
12028
|
+
import { homedir as homedir9 } from "node:os";
|
|
12029
|
+
import { join as join13 } from "node:path";
|
|
11057
12030
|
function difficultyToRung(difficulty, thresholds) {
|
|
11058
12031
|
const b = thresholds.rungBounds;
|
|
11059
12032
|
if (difficulty >= b.R5) return "R5";
|
|
@@ -11078,7 +12051,7 @@ function operatorTierToRung(tier, difficulty, thresholds) {
|
|
|
11078
12051
|
if (tier === "best" && difficulty >= thresholds.rungBounds.R5) return "R5";
|
|
11079
12052
|
return base;
|
|
11080
12053
|
}
|
|
11081
|
-
function readCodexModelsCache({ path: path22 = DEFAULT_CODEX_MODELS_CACHE, read =
|
|
12054
|
+
function readCodexModelsCache({ path: path22 = DEFAULT_CODEX_MODELS_CACHE, read = readFileSync9 } = {}) {
|
|
11082
12055
|
try {
|
|
11083
12056
|
const parsed = JSON.parse(read(path22, "utf8"));
|
|
11084
12057
|
return Array.isArray(parsed?.models) ? parsed : null;
|
|
@@ -11130,7 +12103,7 @@ var init_effort_policy = __esm({
|
|
|
11130
12103
|
init_meta_model_catalog();
|
|
11131
12104
|
RUNG_ORDER = ["R1", "R2", "R3", "R4", "R5"];
|
|
11132
12105
|
rungIndex = (rung) => RUNG_ORDER.indexOf(rung);
|
|
11133
|
-
DEFAULT_CODEX_MODELS_CACHE =
|
|
12106
|
+
DEFAULT_CODEX_MODELS_CACHE = join13(homedir9(), ".codex", "models_cache.json");
|
|
11134
12107
|
}
|
|
11135
12108
|
});
|
|
11136
12109
|
|
|
@@ -11260,18 +12233,18 @@ var init_role_cost_shadow = __esm({
|
|
|
11260
12233
|
});
|
|
11261
12234
|
|
|
11262
12235
|
// ../../scripts/virtual-office/code-runner/auto-router/auto-router.mjs
|
|
11263
|
-
import { readFileSync as
|
|
11264
|
-
import { homedir as
|
|
11265
|
-
import { join as
|
|
11266
|
-
import { fileURLToPath as
|
|
12236
|
+
import { readFileSync as readFileSync10, appendFileSync, mkdirSync as mkdirSync8 } from "node:fs";
|
|
12237
|
+
import { homedir as homedir10 } from "node:os";
|
|
12238
|
+
import { join as join14, dirname as dirname9 } from "node:path";
|
|
12239
|
+
import { fileURLToPath as fileURLToPath7 } from "node:url";
|
|
11267
12240
|
function getAutoRouterMode(env2 = process.env) {
|
|
11268
12241
|
const raw = String(env2.VO_CODE_RUNNER_AUTO_ROUTER || "").trim().toLowerCase();
|
|
11269
12242
|
return MODES.has(raw) ? raw : "off";
|
|
11270
12243
|
}
|
|
11271
12244
|
function loadThresholds() {
|
|
11272
12245
|
if (!cachedThresholds) {
|
|
11273
|
-
const here =
|
|
11274
|
-
cachedThresholds = JSON.parse(
|
|
12246
|
+
const here = dirname9(fileURLToPath7(import.meta.url));
|
|
12247
|
+
cachedThresholds = JSON.parse(readFileSync10(join14(here, "thresholds.json"), "utf8"));
|
|
11275
12248
|
}
|
|
11276
12249
|
return cachedThresholds;
|
|
11277
12250
|
}
|
|
@@ -11337,9 +12310,9 @@ function formatDecisionReason(decision, maxLen = 480) {
|
|
|
11337
12310
|
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("; ")}`;
|
|
11338
12311
|
return s.length > maxLen ? `${s.slice(0, maxLen - 1)}\u2026` : s;
|
|
11339
12312
|
}
|
|
11340
|
-
function appendDecisionFallback(decision, { path: path22 = DECISION_FALLBACK_PATH, append = appendFileSync, mkdir: mkdir4 =
|
|
12313
|
+
function appendDecisionFallback(decision, { path: path22 = DECISION_FALLBACK_PATH, append = appendFileSync, mkdir: mkdir4 = mkdirSync8, task, thresholds, roleCostInputs } = {}) {
|
|
11341
12314
|
try {
|
|
11342
|
-
mkdir4(
|
|
12315
|
+
mkdir4(dirname9(path22), { recursive: true });
|
|
11343
12316
|
append(path22, `${JSON.stringify(decision)}
|
|
11344
12317
|
`, "utf8");
|
|
11345
12318
|
if (isRouterDecision(decision)) {
|
|
@@ -11363,7 +12336,7 @@ var init_auto_router = __esm({
|
|
|
11363
12336
|
init_effort_policy();
|
|
11364
12337
|
init_role_cost_shadow();
|
|
11365
12338
|
ROUTER_VERSION = "0.1.0";
|
|
11366
|
-
DECISION_FALLBACK_PATH =
|
|
12339
|
+
DECISION_FALLBACK_PATH = join14(homedir10(), ".claude", "vo-auto-router-decisions.jsonl");
|
|
11367
12340
|
MODES = /* @__PURE__ */ new Set(["off", "shadow", "on"]);
|
|
11368
12341
|
cachedThresholds = null;
|
|
11369
12342
|
isRouterDecision = (d) => Boolean(d && typeof d === "object" && typeof d.taskClass === "string" && typeof d.confidence === "number");
|
|
@@ -11665,10 +12638,10 @@ var init_task_helpers = __esm({
|
|
|
11665
12638
|
});
|
|
11666
12639
|
|
|
11667
12640
|
// ../../scripts/virtual-office/code-runner/swarm-admission.mjs
|
|
11668
|
-
function canLaunchSuccessorAgent(agent,
|
|
12641
|
+
function canLaunchSuccessorAgent(agent, platform4 = process.platform) {
|
|
11669
12642
|
const name = typeof agent === "string" ? agent.trim() : "";
|
|
11670
12643
|
if (!Object.prototype.hasOwnProperty.call(SUCCESSOR_LAUNCH_SHAPES, name)) return false;
|
|
11671
|
-
if (
|
|
12644
|
+
if (platform4 === "win32" && !SUCCESSOR_LAUNCH_SHAPES[name]) return false;
|
|
11672
12645
|
return true;
|
|
11673
12646
|
}
|
|
11674
12647
|
function isSubscriptionExhausted(usage) {
|
|
@@ -11695,12 +12668,12 @@ function resolveRunnerSwarmBinding({
|
|
|
11695
12668
|
accountUsage = [],
|
|
11696
12669
|
requestedSubagents = MAX_BOUND_SUBAGENTS,
|
|
11697
12670
|
nowIso,
|
|
11698
|
-
platform = process.platform
|
|
12671
|
+
platform: platform4 = process.platform
|
|
11699
12672
|
} = {}) {
|
|
11700
12673
|
const id = typeof swarmId === "string" ? swarmId.trim() : "";
|
|
11701
12674
|
const boundAgent = typeof agent === "string" ? agent.trim() : "";
|
|
11702
12675
|
if (id.length === 0 || boundAgent.length === 0) return null;
|
|
11703
|
-
if (!canLaunchSuccessorAgent(boundAgent,
|
|
12676
|
+
if (!canLaunchSuccessorAgent(boundAgent, platform4)) return null;
|
|
11704
12677
|
const budget = clampSubagents(requestedSubagents);
|
|
11705
12678
|
if (budget === 0) return null;
|
|
11706
12679
|
const rows = Array.isArray(availableAgents) ? availableAgents : [];
|
|
@@ -12360,7 +13333,7 @@ async function canonicalRootForWorktree(worktreeDir, run) {
|
|
|
12360
13333
|
"--git-common-dir"
|
|
12361
13334
|
])).trim();
|
|
12362
13335
|
const root = path19.dirname(commonDir);
|
|
12363
|
-
return
|
|
13336
|
+
return samePath3(root, worktreeDir) ? null : root;
|
|
12364
13337
|
}
|
|
12365
13338
|
async function snapshot(root, run) {
|
|
12366
13339
|
const [head, status] = await Promise.all([
|
|
@@ -12468,13 +13441,13 @@ async function assertCanonicalIsolation(baseline, { worktreeDir, taskId, run = d
|
|
|
12468
13441
|
`agent attempted ${evidence.tracked.length + evidence.untracked.length} canonical-clone write(s); writes were quarantined and the clone was restored exactly: ${evidence.quarantineDir}`
|
|
12469
13442
|
);
|
|
12470
13443
|
}
|
|
12471
|
-
var splitZ2,
|
|
13444
|
+
var splitZ2, samePath3;
|
|
12472
13445
|
var init_isolation_audit = __esm({
|
|
12473
13446
|
"../../scripts/virtual-office/code-runner/isolation-audit.mjs"() {
|
|
12474
13447
|
"use strict";
|
|
12475
13448
|
init_process_runner2();
|
|
12476
13449
|
splitZ2 = (value) => String(value || "").split("\0").map((item) => item.trim()).filter(Boolean);
|
|
12477
|
-
|
|
13450
|
+
samePath3 = (left, right) => {
|
|
12478
13451
|
const [a, b] = [left, right].map((value) => path19.resolve(value));
|
|
12479
13452
|
return process.platform === "win32" ? a.toLowerCase() === b.toLowerCase() : a === b;
|
|
12480
13453
|
};
|
|
@@ -12630,7 +13603,7 @@ var init_outcome_commit = __esm({
|
|
|
12630
13603
|
"use strict";
|
|
12631
13604
|
init_cancelled_run_report();
|
|
12632
13605
|
init_error_message();
|
|
12633
|
-
wait = (ms) => new Promise((
|
|
13606
|
+
wait = (ms) => new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
12634
13607
|
OutcomeCommitConflictError = class extends Error {
|
|
12635
13608
|
constructor(message) {
|
|
12636
13609
|
super(message);
|
|
@@ -12672,7 +13645,7 @@ var init_terminal_delivery = __esm({
|
|
|
12672
13645
|
"use strict";
|
|
12673
13646
|
init_cancelled_run_report();
|
|
12674
13647
|
init_error_message();
|
|
12675
|
-
wait2 = (ms) => new Promise((
|
|
13648
|
+
wait2 = (ms) => new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
12676
13649
|
}
|
|
12677
13650
|
});
|
|
12678
13651
|
|
|
@@ -12866,7 +13839,7 @@ async function finalizePublishedPr({
|
|
|
12866
13839
|
log: log2,
|
|
12867
13840
|
patch: {
|
|
12868
13841
|
status: "pr_opened",
|
|
12869
|
-
message: `opened ${pr.prUrl}${overlapNote}`,
|
|
13842
|
+
message: `opened ${pr.prUrl}${overlapNote}${pr.newCommit === false ? " (no new commits \u2014 the branch already held every change)" : ""}`,
|
|
12870
13843
|
pr_url: pr.prUrl,
|
|
12871
13844
|
pr_number: pr.prNumber,
|
|
12872
13845
|
pr_branch: pr.branch,
|
|
@@ -13096,7 +14069,9 @@ async function findPreservedRecovery(task, {
|
|
|
13096
14069
|
clonesRoot: clonesRoot2 = process.env.VO_CODE_RUNNER_CLONES_ROOT || "",
|
|
13097
14070
|
readFile: readFile5 = fsp13.readFile,
|
|
13098
14071
|
exists = fs13.existsSync,
|
|
13099
|
-
alreadyPublished = preservedHeadAlreadyOnBranch
|
|
14072
|
+
alreadyPublished = preservedHeadAlreadyOnBranch,
|
|
14073
|
+
log: log2 = () => {
|
|
14074
|
+
}
|
|
13100
14075
|
} = {}) {
|
|
13101
14076
|
const resumedFrom = /^[0-9a-f-]{36}$/iu.test(String(task.resumed_from || "")) ? String(task.resumed_from).toLowerCase() : null;
|
|
13102
14077
|
const originalTaskId = recoveryTaskId(task.prompt) ?? resumedFrom;
|
|
@@ -13107,7 +14082,10 @@ async function findPreservedRecovery(task, {
|
|
|
13107
14082
|
const resolved = entries.some((entry) => RESOLVED_RECOVERY_TYPES.has(entry.type) && entry.taskId === originalTaskId);
|
|
13108
14083
|
const preserved = [...entries].reverse().find((entry) => entry.taskId === originalTaskId && entry.worktreeDir);
|
|
13109
14084
|
if (!resolved && preserved && exists(preserved.worktreeDir)) {
|
|
13110
|
-
if (task.pr_branch && await alreadyPublished(preserved.worktreeDir, task.pr_branch))
|
|
14085
|
+
if (task.pr_branch && await alreadyPublished(preserved.worktreeDir, task.pr_branch)) {
|
|
14086
|
+
log2(`recovery: preserved worktree of ${originalTaskId} is already on ${task.pr_branch}; running the agent instead`);
|
|
14087
|
+
return null;
|
|
14088
|
+
}
|
|
13111
14089
|
return { originalTaskId, ledgerPath, preserved };
|
|
13112
14090
|
}
|
|
13113
14091
|
}
|
|
@@ -13115,11 +14093,19 @@ async function findPreservedRecovery(task, {
|
|
|
13115
14093
|
}
|
|
13116
14094
|
async function preservedHeadAlreadyOnBranch(worktreeDir, prBranch, { runGit = defaultRunGit } = {}) {
|
|
13117
14095
|
try {
|
|
14096
|
+
const dirty = (await runGit(["status", "--porcelain"], worktreeDir)).trim();
|
|
14097
|
+
if (dirty.length > 0) return false;
|
|
13118
14098
|
const head = (await runGit(["rev-parse", "HEAD"], worktreeDir)).trim();
|
|
13119
|
-
|
|
13120
|
-
|
|
13121
|
-
|
|
13122
|
-
|
|
14099
|
+
if (!/^[0-9a-f]{40}$/u.test(head)) return false;
|
|
14100
|
+
let tip = "";
|
|
14101
|
+
try {
|
|
14102
|
+
tip = (await runGit(["rev-parse", `refs/remotes/origin/${prBranch}`], worktreeDir)).trim();
|
|
14103
|
+
} catch {
|
|
14104
|
+
tip = (await runGit(["ls-remote", "origin", `refs/heads/${prBranch}`], worktreeDir)).trim().split(/\s+/u)[0] || "";
|
|
14105
|
+
}
|
|
14106
|
+
if (!/^[0-9a-f]{40}$/u.test(tip)) return false;
|
|
14107
|
+
if (head === tip) return true;
|
|
14108
|
+
await runGit(["merge-base", "--is-ancestor", head, tip], worktreeDir);
|
|
13123
14109
|
return true;
|
|
13124
14110
|
} catch {
|
|
13125
14111
|
return false;
|
|
@@ -13127,8 +14113,8 @@ async function preservedHeadAlreadyOnBranch(worktreeDir, prBranch, { runGit = de
|
|
|
13127
14113
|
}
|
|
13128
14114
|
async function defaultRunGit(args, cwd) {
|
|
13129
14115
|
const { execFile: execFile2 } = await import("node:child_process");
|
|
13130
|
-
return new Promise((
|
|
13131
|
-
execFile2("git", args, { cwd, encoding: "utf8", timeout: 6e4 }, (err, stdout) => err ? reject(err) :
|
|
14116
|
+
return new Promise((resolve3, reject) => {
|
|
14117
|
+
execFile2("git", args, { cwd, encoding: "utf8", timeout: 6e4 }, (err, stdout) => err ? reject(err) : resolve3(stdout));
|
|
13132
14118
|
});
|
|
13133
14119
|
}
|
|
13134
14120
|
async function recoverPreservedCodeTask({
|
|
@@ -13143,7 +14129,7 @@ async function recoverPreservedCodeTask({
|
|
|
13143
14129
|
finalizePublished = finalizePublishedPr,
|
|
13144
14130
|
track
|
|
13145
14131
|
} = {}) {
|
|
13146
|
-
const recovery = await find(task);
|
|
14132
|
+
const recovery = await find(task, { log: log2 });
|
|
13147
14133
|
if (!recovery) return null;
|
|
13148
14134
|
const cwd = recovery.preserved.worktreeDir;
|
|
13149
14135
|
const { files, alreadyCommitted } = await prepareScope(cwd);
|
|
@@ -13433,8 +14419,8 @@ var init_cancellation_probe = __esm({
|
|
|
13433
14419
|
});
|
|
13434
14420
|
|
|
13435
14421
|
// ../../scripts/virtual-office/code-runner/detached-economics-spool.mjs
|
|
13436
|
-
import { homedir as
|
|
13437
|
-
import { dirname as
|
|
14422
|
+
import { homedir as homedir11 } from "node:os";
|
|
14423
|
+
import { dirname as dirname10, join as join15 } from "node:path";
|
|
13438
14424
|
import { mkdir as mkdir3, readFile as readFile4, rename as rename2, writeFile as writeFile3 } from "node:fs/promises";
|
|
13439
14425
|
function withLock(operation) {
|
|
13440
14426
|
const result = serialized.then(operation, operation);
|
|
@@ -13452,7 +14438,7 @@ async function readEntries(file) {
|
|
|
13452
14438
|
}
|
|
13453
14439
|
}
|
|
13454
14440
|
async function writeEntries(file, entries) {
|
|
13455
|
-
await mkdir3(
|
|
14441
|
+
await mkdir3(dirname10(file), { recursive: true });
|
|
13456
14442
|
const temp = `${file}.${process.pid}.tmp`;
|
|
13457
14443
|
await writeFile3(temp, `${JSON.stringify(entries)}
|
|
13458
14444
|
`, "utf8");
|
|
@@ -13498,7 +14484,7 @@ var DEFAULT_FILE, serialized;
|
|
|
13498
14484
|
var init_detached_economics_spool = __esm({
|
|
13499
14485
|
"../../scripts/virtual-office/code-runner/detached-economics-spool.mjs"() {
|
|
13500
14486
|
"use strict";
|
|
13501
|
-
DEFAULT_FILE =
|
|
14487
|
+
DEFAULT_FILE = join15(homedir11(), ".vo", "detached-run-economics.json");
|
|
13502
14488
|
serialized = Promise.resolve();
|
|
13503
14489
|
}
|
|
13504
14490
|
});
|
|
@@ -13839,7 +14825,7 @@ __export(code_runner_daemon_exports, {
|
|
|
13839
14825
|
main: () => main
|
|
13840
14826
|
});
|
|
13841
14827
|
import { randomUUID as randomUUID7 } from "node:crypto";
|
|
13842
|
-
import { fileURLToPath as
|
|
14828
|
+
import { fileURLToPath as fileURLToPath8 } from "node:url";
|
|
13843
14829
|
function log(msg) {
|
|
13844
14830
|
console.log(`[code-runner ${(/* @__PURE__ */ new Date()).toISOString()}] ${msg}`);
|
|
13845
14831
|
}
|
|
@@ -14267,7 +15253,7 @@ var init_code_runner_daemon = __esm({
|
|
|
14267
15253
|
RATE_LIMIT_RESUME_ENABLED = process.env.VO_RATE_LIMIT_RESUME !== "0";
|
|
14268
15254
|
sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
14269
15255
|
safeProgress = makeSafeProgress(log);
|
|
14270
|
-
invokedDirectly = process.argv[1] &&
|
|
15256
|
+
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).
|
|
14271
15257
|
import.meta.url.endsWith("code-runner-daemon.mjs");
|
|
14272
15258
|
if (invokedDirectly) {
|
|
14273
15259
|
const once2 = process.argv.includes("--once");
|
|
@@ -14412,33 +15398,33 @@ import { posix, win32 } from "node:path";
|
|
|
14412
15398
|
import { randomUUID } from "node:crypto";
|
|
14413
15399
|
var APP_IDENTIFIER = "ai.algosuite.vo-runner";
|
|
14414
15400
|
var CLONES_DIR = "clones";
|
|
14415
|
-
function pathsFor(
|
|
14416
|
-
return
|
|
15401
|
+
function pathsFor(platform4) {
|
|
15402
|
+
return platform4 === "win32" ? win32 : posix;
|
|
14417
15403
|
}
|
|
14418
15404
|
function absoluteOrNull(value, pathApi) {
|
|
14419
15405
|
const normalized = String(value || "").trim();
|
|
14420
15406
|
return normalized && pathApi.isAbsolute(normalized) ? pathApi.resolve(normalized) : null;
|
|
14421
15407
|
}
|
|
14422
15408
|
function defaultClonesRoot({
|
|
14423
|
-
platform = process.platform,
|
|
15409
|
+
platform: platform4 = process.platform,
|
|
14424
15410
|
env: env2 = process.env,
|
|
14425
15411
|
home = homedir()
|
|
14426
15412
|
} = {}) {
|
|
14427
|
-
const pathApi = pathsFor(
|
|
14428
|
-
if (
|
|
15413
|
+
const pathApi = pathsFor(platform4);
|
|
15414
|
+
if (platform4 === "win32") {
|
|
14429
15415
|
const appData = absoluteOrNull(env2.APPDATA, pathApi);
|
|
14430
15416
|
return appData ? pathApi.join(appData, APP_IDENTIFIER, CLONES_DIR) : null;
|
|
14431
15417
|
}
|
|
14432
15418
|
const absoluteHome = absoluteOrNull(home, pathApi);
|
|
14433
15419
|
if (!absoluteHome) return null;
|
|
14434
|
-
if (
|
|
15420
|
+
if (platform4 === "darwin") {
|
|
14435
15421
|
return pathApi.join(absoluteHome, "Library", "Application Support", APP_IDENTIFIER, CLONES_DIR);
|
|
14436
15422
|
}
|
|
14437
15423
|
const xdg = absoluteOrNull(env2.XDG_CONFIG_HOME, pathApi);
|
|
14438
15424
|
return pathApi.join(xdg || pathApi.join(absoluteHome, ".config"), APP_IDENTIFIER, CLONES_DIR);
|
|
14439
15425
|
}
|
|
14440
|
-
function findGitRoot(cwd, { platform, exists = existsSync }) {
|
|
14441
|
-
const pathApi = pathsFor(
|
|
15426
|
+
function findGitRoot(cwd, { platform: platform4, exists = existsSync }) {
|
|
15427
|
+
const pathApi = pathsFor(platform4);
|
|
14442
15428
|
let cursor = pathApi.resolve(cwd);
|
|
14443
15429
|
for (; ; ) {
|
|
14444
15430
|
if (exists(pathApi.join(cursor, ".git"))) return cursor;
|
|
@@ -14450,11 +15436,11 @@ function findGitRoot(cwd, { platform, exists = existsSync }) {
|
|
|
14450
15436
|
function resolveRunnerRootConfig({
|
|
14451
15437
|
env: env2 = process.env,
|
|
14452
15438
|
cwd = process.cwd(),
|
|
14453
|
-
platform = process.platform,
|
|
15439
|
+
platform: platform4 = process.platform,
|
|
14454
15440
|
home = homedir(),
|
|
14455
15441
|
exists = existsSync
|
|
14456
15442
|
} = {}) {
|
|
14457
|
-
const pathApi = pathsFor(
|
|
15443
|
+
const pathApi = pathsFor(platform4);
|
|
14458
15444
|
const explicitRepoValue = String(env2.VO_CODE_RUNNER_REPO || "").trim();
|
|
14459
15445
|
const explicitClonesValue = String(env2.VO_CODE_RUNNER_CLONES_ROOT || "").trim();
|
|
14460
15446
|
if (explicitRepoValue && !pathApi.isAbsolute(explicitRepoValue)) {
|
|
@@ -14463,8 +15449,8 @@ function resolveRunnerRootConfig({
|
|
|
14463
15449
|
if (explicitClonesValue && !pathApi.isAbsolute(explicitClonesValue)) {
|
|
14464
15450
|
throw new Error(`VO_CODE_RUNNER_CLONES_ROOT must be an absolute path (got '${explicitClonesValue}')`);
|
|
14465
15451
|
}
|
|
14466
|
-
const repoRoot2 = explicitRepoValue ? pathApi.resolve(explicitRepoValue) : findGitRoot(cwd, { platform, exists });
|
|
14467
|
-
const clonesRoot2 = explicitClonesValue ? pathApi.resolve(explicitClonesValue) : repoRoot2 ? null : defaultClonesRoot({ platform, env: env2, home });
|
|
15452
|
+
const repoRoot2 = explicitRepoValue ? pathApi.resolve(explicitRepoValue) : findGitRoot(cwd, { platform: platform4, exists });
|
|
15453
|
+
const clonesRoot2 = explicitClonesValue ? pathApi.resolve(explicitClonesValue) : repoRoot2 ? null : defaultClonesRoot({ platform: platform4, env: env2, home });
|
|
14468
15454
|
if (!repoRoot2 && !clonesRoot2) {
|
|
14469
15455
|
throw new Error(
|
|
14470
15456
|
"No safe runner root is available. Set VO_CODE_RUNNER_REPO or VO_CODE_RUNNER_CLONES_ROOT to an absolute path."
|
|
@@ -14498,7 +15484,7 @@ function assertWritableRunnerDirectory(root) {
|
|
|
14498
15484
|
}
|
|
14499
15485
|
|
|
14500
15486
|
// src/runner-cli.mjs
|
|
14501
|
-
var
|
|
15487
|
+
var DEFAULT_CONTROL_PLANE_URL3 = "https://vo-control-plane-bzjphrajaq-uc.a.run.app";
|
|
14502
15488
|
function packageVersion() {
|
|
14503
15489
|
try {
|
|
14504
15490
|
return createRequire4(import.meta.url)("../package.json").version || "unknown";
|
|
@@ -14566,7 +15552,7 @@ if (statusOnly) {
|
|
|
14566
15552
|
process.exit(1);
|
|
14567
15553
|
}
|
|
14568
15554
|
const readiness = await probeRunnerReadiness({
|
|
14569
|
-
controlPlaneUrl: process.env.VO_CONTROL_PLANE_URL ||
|
|
15555
|
+
controlPlaneUrl: process.env.VO_CONTROL_PLANE_URL || DEFAULT_CONTROL_PLANE_URL3,
|
|
14570
15556
|
token: storedCredential.vo_credential
|
|
14571
15557
|
});
|
|
14572
15558
|
if (!readiness.ok) {
|
|
@@ -14597,7 +15583,7 @@ if (!token) {
|
|
|
14597
15583
|
console.error(" (or set VO_CONTROL_PLANE_ADMIN_TOKEN to a control-plane bearer)");
|
|
14598
15584
|
process.exit(1);
|
|
14599
15585
|
}
|
|
14600
|
-
var controlPlaneUrl = process.env.VO_CONTROL_PLANE_URL ||
|
|
15586
|
+
var controlPlaneUrl = process.env.VO_CONTROL_PLANE_URL || DEFAULT_CONTROL_PLANE_URL3;
|
|
14601
15587
|
var pairedOperatorId = null;
|
|
14602
15588
|
if (!explicitAdminToken) {
|
|
14603
15589
|
const readiness = await probeRunnerReadiness({
|
|
@@ -14622,6 +15608,14 @@ try {
|
|
|
14622
15608
|
console.error(`[vo-mcp runner] Filesystem readiness failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
14623
15609
|
process.exit(1);
|
|
14624
15610
|
}
|
|
15611
|
+
try {
|
|
15612
|
+
const { healMcpRegistration: healMcpRegistration2 } = await Promise.resolve().then(() => (init_install(), install_exports));
|
|
15613
|
+
healMcpRegistration2((message) => {
|
|
15614
|
+
if (/^(✓|\s*⚠|\s*Backed up|vo-mcp: MCP registration heal)/u.test(message)) console.error(`[vo-mcp runner] ${message.trim()}`);
|
|
15615
|
+
});
|
|
15616
|
+
} catch (error) {
|
|
15617
|
+
console.error(`[vo-mcp runner] MCP registration heal skipped: ${error instanceof Error ? error.message : String(error)}`);
|
|
15618
|
+
}
|
|
14625
15619
|
var env = {
|
|
14626
15620
|
...process.env,
|
|
14627
15621
|
VO_CONTROL_PLANE_ADMIN_TOKEN: token,
|