@algosuite/vo-mcp 0.2.0-beta.56 → 0.2.0-beta.58

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.
@@ -675,12 +675,15 @@ function installWindowsAutostart(runnerCommand, log2, env2) {
675
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
676
  }
677
677
  }
678
- const hiddenCommand = `cmd /c ${runnerCommand} >> "%USERPROFILE%\\.claude\\vo-runner.log" 2>&1`.replace(/"/g, '""');
678
+ const runnerConsoleCommand = `cmd /c ${runnerCommand} >> "%USERPROFILE%\\.claude\\vo-runner.log" 2>&1`.replace(/"/g, '""');
679
679
  const launcherContent = `' Auto-start launcher for vo-mcp runner
680
680
  ' Created by vo-mcp autostart installer
681
681
  ' Keepalive supervisor: restarts the runner if it exits (parity with launchd
682
682
  ' KeepAlive on macOS and systemd Restart=on-failure on Linux).
683
- ' To stop: create %USERPROFILE%\\.claude\\vo-runner.stop, or end wscript.exe.
683
+ ' Runs the runner in a MINIMIZED (style 7), never a hidden (style 0) window:
684
+ ' hidden script-host exec trips Defender's PowhidSubExec.B heuristic and gets
685
+ ' blocked at logon. To stop: create %USERPROFILE%\\.claude\\vo-runner.stop, or
686
+ ' end wscript.exe.
684
687
  Dim sh, fso, stopFile, backoff, startedAt, ranMs
685
688
  Set sh = CreateObject("WScript.Shell")
686
689
  Set fso = CreateObject("Scripting.FileSystemObject")
@@ -694,7 +697,7 @@ Do
694
697
  WScript.Quit 0
695
698
  End If
696
699
  startedAt = Timer
697
- sh.Run "${hiddenCommand}", 0, True
700
+ sh.Run "${runnerConsoleCommand}", 7, True
698
701
  ranMs = (Timer - startedAt) * 1000
699
702
  If ranMs < 0 Then ranMs = ${WINDOWS_HEALTHY_RUN_MS}
700
703
  If ranMs >= ${WINDOWS_HEALTHY_RUN_MS} Then
@@ -1999,12 +2002,12 @@ var init_pnpm_materialize = __esm({
1999
2002
  });
2000
2003
 
2001
2004
  // src/runner/pnpm-hydration.mjs
2002
- import { createHash } from "node:crypto";
2005
+ import { createHash as createHash2 } from "node:crypto";
2003
2006
  import fs3 from "node:fs";
2004
2007
  import fsp5 from "node:fs/promises";
2005
2008
  import path5 from "node:path";
2006
2009
  function hashText(text) {
2007
- return createHash("sha256").update(String(text)).digest("hex");
2010
+ return createHash2("sha256").update(String(text)).digest("hex");
2008
2011
  }
2009
2012
  function statePath(root) {
2010
2013
  return path5.join(root, ".agent-worktrees", "runner-pnpm-hydration.json");
@@ -2314,7 +2317,7 @@ var init_pnpm_hydration = __esm({
2314
2317
  });
2315
2318
 
2316
2319
  // src/runner/worktree-paths.mjs
2317
- import { createHash as createHash2 } from "node:crypto";
2320
+ import { createHash as createHash3 } from "node:crypto";
2318
2321
  import path6 from "node:path";
2319
2322
  function samePath2(left, right) {
2320
2323
  const a = path6.resolve(left);
@@ -2332,7 +2335,7 @@ function worktreePoolForRoot(root, { clonesRootDir = process.env.VO_CODE_RUNNER_
2332
2335
  return path6.join(canonicalRoot, ".agent-worktrees");
2333
2336
  }
2334
2337
  function worktreeDirForName(root, worktreeName, options = {}) {
2335
- const leaf = createHash2("sha256").update(String(worktreeName)).digest("hex").slice(0, 16);
2338
+ const leaf = createHash3("sha256").update(String(worktreeName)).digest("hex").slice(0, 16);
2336
2339
  return path6.join(worktreePoolForRoot(root, options), leaf);
2337
2340
  }
2338
2341
  function recoveryLedgerPathForRoot(root, options = {}) {
@@ -7646,8 +7649,17 @@ function messageExcerpt(output) {
7646
7649
  const s = String(output ?? "");
7647
7650
  return s.length > 1200 ? `...${s.slice(-1200)}` : s;
7648
7651
  }
7649
- async function enforceCompletionGateOrFail({ client, id, task, worktreeDir, log: log2 = () => {
7650
- }, execFileImpl = execFile } = {}) {
7652
+ async function enforceCompletionGateOrFail({
7653
+ client,
7654
+ id,
7655
+ task,
7656
+ worktreeDir,
7657
+ run = null,
7658
+ truncated = false,
7659
+ log: log2 = () => {
7660
+ },
7661
+ execFileImpl = execFile
7662
+ } = {}) {
7651
7663
  const resolved = resolveCompletionGate(task);
7652
7664
  if (resolved === null) return false;
7653
7665
  if (resolved.invalid) {
@@ -7671,6 +7683,12 @@ ${messageExcerpt(cached2.output)}`, "completion_gate_failed");
7671
7683
  return false;
7672
7684
  }
7673
7685
  const kind = outcome.timedOut ? `timed out after ${COMPLETION_GATE_TIMEOUT_MS}ms` : `exited ${outcome.exitCode}`;
7686
+ if (truncated) {
7687
+ log2(`task ${id}: completion gate FAILED (${kind}) after a budget/turn-truncated run \u2014 salvaging as PARTIAL draft, not terminal-failing`);
7688
+ if (run) run.gateFailureNote = `completion gate '${task.completion_gate}' ${kind} (gate ran against a budget/turn-truncated run):
7689
+ ${messageExcerpt(outcome.output)}`;
7690
+ return false;
7691
+ }
7674
7692
  log2(`task ${id}: completion gate FAILED (${kind}) \u2014 not publishing`);
7675
7693
  await postFailed2(client, id, `completion gate '${task.completion_gate}' ${kind} \u2014 task may not claim completion:
7676
7694
  ${messageExcerpt(outcome.output)}`, "completion_gate_failed");
@@ -8666,7 +8684,7 @@ var init_task_prompt = __esm({
8666
8684
  });
8667
8685
 
8668
8686
  // ../../scripts/virtual-office/code-runner/task-attachments.mjs
8669
- import { createHash as createHash3, randomUUID as randomUUID2 } from "node:crypto";
8687
+ import { createHash as createHash4, randomUUID as randomUUID3 } from "node:crypto";
8670
8688
  import { chmod, mkdir, mkdtemp, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
8671
8689
  import os2 from "node:os";
8672
8690
  import path16 from "node:path";
@@ -8690,7 +8708,7 @@ async function createAttachmentDirectory(taskId, tempRoot) {
8690
8708
  const root = path16.resolve(tempRoot);
8691
8709
  await mkdir(root, { recursive: true });
8692
8710
  const directory = await mkdtemp(path16.join(root, `${DIRECTORY_PREFIX}${safeTaskToken(taskId)}-`));
8693
- const marker = JSON.stringify({ owner: MARKER_OWNER, token: randomUUID2(), directory: path16.basename(directory), created_at: (/* @__PURE__ */ new Date()).toISOString() });
8711
+ const marker = JSON.stringify({ owner: MARKER_OWNER, token: randomUUID3(), directory: path16.basename(directory), created_at: (/* @__PURE__ */ new Date()).toISOString() });
8694
8712
  await writeFile(path16.join(directory, MARKER_FILE), marker, { encoding: "utf8", mode: 384 });
8695
8713
  return { directory, marker, tempRoot: root };
8696
8714
  }
@@ -8766,7 +8784,7 @@ async function materializeTaskAttachments(client, task, { tempRoot = os2.tmpdir(
8766
8784
  const content = await client.downloadTaskAttachment(task.code_task_id, ref.attachment_id);
8767
8785
  if (!Buffer.isBuffer(content)) throw new Error(`attachment ${ref.attachment_id} did not return binary content`);
8768
8786
  if (content.byteLength !== ref.size_bytes) throw new Error(`attachment ${ref.attachment_id} size mismatch`);
8769
- const sha256 = createHash3("sha256").update(content).digest("hex");
8787
+ const sha256 = createHash4("sha256").update(content).digest("hex");
8770
8788
  if (sha256 !== ref.sha256) throw new Error(`attachment ${ref.attachment_id} sha256 mismatch`);
8771
8789
  const name = sanitizeTaskAttachmentName(ref.name, index);
8772
8790
  const filePath = path16.join(state.directory, name);
@@ -8797,9 +8815,9 @@ var init_task_attachments = __esm({
8797
8815
  import { homedir as homedir7 } from "node:os";
8798
8816
  import { join as join11 } from "node:path";
8799
8817
  import { readdir as readdir2, readFile as readFile2, unlink, writeFile as writeFile2 } from "node:fs/promises";
8800
- import { createHash as createHash4 } from "node:crypto";
8818
+ import { createHash as createHash5 } from "node:crypto";
8801
8819
  function deriveUuid(seed) {
8802
- const h = createHash4("sha256").update(seed).digest("hex");
8820
+ const h = createHash5("sha256").update(seed).digest("hex");
8803
8821
  return `${h.slice(0, 8)}-${h.slice(8, 12)}-5${h.slice(13, 16)}-${(parseInt(h.slice(16, 18), 16) & 63 | 128).toString(16)}${h.slice(18, 20)}-${h.slice(20, 32)}`;
8804
8822
  }
8805
8823
  function spoolToCloud(record, ids) {
@@ -10521,7 +10539,7 @@ var init_error_message = __esm({
10521
10539
  });
10522
10540
 
10523
10541
  // ../../scripts/virtual-office/code-runner/watcher-coordination.mjs
10524
- import { createHash as createHash5 } from "node:crypto";
10542
+ import { createHash as createHash6 } from "node:crypto";
10525
10543
  function ciFixOccurrenceKey({ repo, prNumber, headSha, repairAttempt }) {
10526
10544
  const occurrence = JSON.stringify([
10527
10545
  String(repo).toLowerCase(),
@@ -10529,7 +10547,7 @@ function ciFixOccurrenceKey({ repo, prNumber, headSha, repairAttempt }) {
10529
10547
  String(headSha).toLowerCase(),
10530
10548
  Number(repairAttempt)
10531
10549
  ]);
10532
- return `ci-fix:v1:${createHash5("sha256").update(occurrence).digest("hex")}`;
10550
+ return `ci-fix:v1:${createHash6("sha256").update(occurrence).digest("hex")}`;
10533
10551
  }
10534
10552
  function coordinationRetryDue(entry, nowMs) {
10535
10553
  return !entry.nextRetryAt || nowMs >= entry.nextRetryAt;
@@ -10659,7 +10677,7 @@ var init_watcher_coordination = __esm({
10659
10677
  });
10660
10678
 
10661
10679
  // ../../scripts/virtual-office/code-runner/watcher-state.mjs
10662
- import { randomUUID as randomUUID3 } from "node:crypto";
10680
+ import { randomUUID as randomUUID4 } from "node:crypto";
10663
10681
  import { mkdir as mkdir3, open as open2, readFile as readFile4, rename, unlink as unlink2 } from "node:fs/promises";
10664
10682
  import { dirname as dirname10 } from "node:path";
10665
10683
  async function readWatcherState(stateFile) {
@@ -10679,7 +10697,7 @@ async function readWatcherState(stateFile) {
10679
10697
  async function writeWatcherState(stateFile, state) {
10680
10698
  const directory = dirname10(stateFile);
10681
10699
  await mkdir3(directory, { recursive: true });
10682
- const temp = `${stateFile}.${process.pid}.${randomUUID3()}.tmp`;
10700
+ const temp = `${stateFile}.${process.pid}.${randomUUID4()}.tmp`;
10683
10701
  let handle;
10684
10702
  try {
10685
10703
  handle = await open2(temp, "wx");
@@ -11139,8 +11157,8 @@ var init_pr_watcher_github = __esm({
11139
11157
  "../../scripts/virtual-office/code-runner/pr-watcher-github.mjs"() {
11140
11158
  "use strict";
11141
11159
  init_process_runner2();
11142
- VIEW_FIELDS_WITH_CI = "state,statusCheckRollup,headRefName,headRefOid,url,isDraft,mergeStateStatus";
11143
- VIEW_FIELDS_WITHOUT_CI = "state,headRefName,headRefOid,url,isDraft,mergeStateStatus";
11160
+ VIEW_FIELDS_WITH_CI = "state,statusCheckRollup,headRefName,headRefOid,url,isDraft,mergeStateStatus,body";
11161
+ VIEW_FIELDS_WITHOUT_CI = "state,headRefName,headRefOid,url,isDraft,mergeStateStatus,body";
11144
11162
  CI_UNREADABLE_REASON = "app_token_missing_checks_read";
11145
11163
  DIAGNOSTIC_INTERVAL_MS = 30 * 60 * 1e3;
11146
11164
  lastDiagnosticAt = 0;
@@ -11157,7 +11175,7 @@ var init_pr_watcher_github = __esm({
11157
11175
  });
11158
11176
 
11159
11177
  // ../../scripts/virtual-office/code-runner/enqueue-autonomous-code-task.mjs
11160
- import { randomUUID as randomUUID4 } from "node:crypto";
11178
+ import { randomUUID as randomUUID5 } from "node:crypto";
11161
11179
  function isDefiniteRefusal(err) {
11162
11180
  const status = Number(err?.status);
11163
11181
  if (Number.isFinite(status) && status >= 400 && status < 500) return true;
@@ -11176,7 +11194,7 @@ async function enqueueAutonomousCodeTask(client, task, log2 = () => {
11176
11194
  if (typeof client?.reserveAutonomousDispatchBudget !== "function" || typeof client?.releaseAutonomousDispatchBudget !== "function") {
11177
11195
  throw new Error("autonomous dispatch admission client unavailable");
11178
11196
  }
11179
- const reservationId = randomUUID4();
11197
+ const reservationId = randomUUID5();
11180
11198
  const admission = await client.reserveAutonomousDispatchBudget({
11181
11199
  requestedBudgetUsd,
11182
11200
  reservationId,
@@ -11882,7 +11900,7 @@ function startControlServer({ port, getStatus, requestStop, allowedOrigin, log:
11882
11900
  server.unref?.();
11883
11901
  return server;
11884
11902
  }
11885
- function startDaemonControl({ cfg, runnerInstanceId, requestStop, getActiveCount, isRunning, startedAt, log: log2 = () => {
11903
+ function startDaemonControl({ cfg, runnerInstanceId, requestStop, getActiveCount, getActiveTaskIds = () => [], isRunning, startedAt, log: log2 = () => {
11886
11904
  }, onDuplicate = null, getUpdateStatus = () => null, getClaimGate = () => null }) {
11887
11905
  if (!cfg.controlEnabled) return null;
11888
11906
  return startControlServer({
@@ -11899,6 +11917,9 @@ function startDaemonControl({ cfg, runnerInstanceId, requestStop, getActiveCount
11899
11917
  servedOperators: cfg.servedOperators,
11900
11918
  watchEnabled: cfg.watchEnabled,
11901
11919
  activeTasks: getActiveCount(),
11920
+ // The supervisor's update-drain gate reads THESE ids (not a guess) to mark
11921
+ // in-flight work before a capped staged-update restart kills it.
11922
+ activeTaskIds: getActiveTaskIds(),
11902
11923
  startedAt: new Date(startedAt).toISOString(),
11903
11924
  uptimeSec: Math.round((Date.now() - startedAt) / 1e3),
11904
11925
  // Host version awareness — the app + `runner --status` read drift from here.
@@ -12010,7 +12031,7 @@ var init_effort_mode_config = __esm({
12010
12031
  });
12011
12032
 
12012
12033
  // ../../scripts/virtual-office/model-registry.mjs
12013
- import { randomUUID as randomUUID5 } from "node:crypto";
12034
+ import { randomUUID as randomUUID6 } from "node:crypto";
12014
12035
  import fs11 from "node:fs";
12015
12036
  import os4 from "node:os";
12016
12037
  import path18 from "node:path";
@@ -12021,7 +12042,7 @@ function userCacheRoot() {
12021
12042
  if (home) return path18.join(home, ".claude");
12022
12043
  } catch {
12023
12044
  }
12024
- return path18.join(os4.tmpdir(), `vo-model-registry-${randomUUID5()}`);
12045
+ return path18.join(os4.tmpdir(), `vo-model-registry-${randomUUID6()}`);
12025
12046
  }
12026
12047
  function resolveCacheBaseDir(env2 = process.env, moduleDir = __dirname) {
12027
12048
  if (env2.VO_MODEL_REGISTRY_CACHE_DIR) return env2.VO_MODEL_REGISTRY_CACHE_DIR;
@@ -13370,6 +13391,17 @@ var init_redact_tokens = __esm({
13370
13391
  }
13371
13392
  });
13372
13393
 
13394
+ // ../../scripts/ci/check-consensus-receipt-core.mjs
13395
+ var RECEIPT_MARKER_RE, UNAVAILABILITY_RE;
13396
+ var init_check_consensus_receipt_core = __esm({
13397
+ "../../scripts/ci/check-consensus-receipt-core.mjs"() {
13398
+ "use strict";
13399
+ init_methodology_composer();
13400
+ RECEIPT_MARKER_RE = /receipt id:\s*[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/iu;
13401
+ UNAVAILABILITY_RE = /consensus[^.\n]{0,80}(not available|unavailable|not permitted|could not be reached|no receipt_id)/iu;
13402
+ }
13403
+ });
13404
+
13373
13405
  // ../../scripts/virtual-office/code-runner/task-helpers.mjs
13374
13406
  function makeSafeProgress(log2) {
13375
13407
  return async (client, id, patch) => {
@@ -13394,6 +13426,17 @@ ${String(run?.lastAgentMessage || "")}`;
13394
13426
  const missing = found.filter((line) => !String(slicedBodyText || "").includes(line));
13395
13427
  return missing.length ? ["", "### Consensus evidence (preserved past truncation)", "", ...missing.map((line) => redactSecrets(line))] : [];
13396
13428
  }
13429
+ function consensusUnavailabilityLine(stakes) {
13430
+ const signal = String(stakes ?? "").replace(/\s+/gu, " ").trim().slice(0, 80) || "unspecified";
13431
+ return `Consensus judgment was not available in this headless session (no receipt_id) \u2014 automated declaration by the runner; stakes signal: ${signal}.`;
13432
+ }
13433
+ function withConsensusDeclaration(body, stakes) {
13434
+ const text = String(body ?? "");
13435
+ if (RECEIPT_MARKER_RE.test(text) || UNAVAILABILITY_RE.test(text)) return text;
13436
+ return `${text}
13437
+
13438
+ ${consensusUnavailabilityLine(stakes)}`;
13439
+ }
13397
13440
  function buildPrBody(task, run, files, { armAutoMerge = false } = {}) {
13398
13441
  const governedStakes = matchGovernedStakes({ prompt: String(task.prompt || "") });
13399
13442
  const slicedSections = [
@@ -13419,15 +13462,20 @@ function buildPrBody(task, run, files, { armAutoMerge = false } = {}) {
13419
13462
  "",
13420
13463
  // A budget/turn-capped run ends with no assistant text (summary = the bare
13421
13464
  // subtype); its LAST message is the honest report the operator needs.
13422
- ...run.lastAgentMessage ? ["### Last agent message before the cap", "", redactSecrets(String(run.lastAgentMessage)).slice(0, 2e3), ""] : []
13465
+ ...run.lastAgentMessage ? ["### Last agent message before the cap", "", redactSecrets(String(run.lastAgentMessage)).slice(0, 2e3), ""] : [],
13466
+ // Completion-gate failures salvaged against a budget/turn-truncated run
13467
+ // (D4, incident afc90342) — the gate output that would otherwise have
13468
+ // only lived in a terminal-failure message the runner never publishes.
13469
+ ...run.gateFailureNote ? ["### Completion gate output (not yet passing)", "", redactSecrets(String(run.gateFailureNote)).slice(0, 2e3), ""] : []
13423
13470
  ];
13424
- return [
13471
+ const composed = [
13425
13472
  ...slicedSections,
13426
13473
  // Receipt lines the slices dropped — the gate reads only this body.
13427
13474
  ...preservedReceiptLines(run, slicedSections.join("\n")),
13428
13475
  "---",
13429
13476
  armAutoMerge ? "_Opened by the AlgoHQ code-runner daemon. After CI passes, the watcher must obtain a durable consensus receipt and merge the exact verified SHA._" : "_Opened by the AlgoHQ code-runner daemon. This PR awaits the verify-before-act gate / operator review \u2014 it is NOT auto-merged._"
13430
13477
  ].filter((l) => l !== "").join("\n");
13478
+ return governedStakes ? withConsensusDeclaration(composed, governedStakes) : composed;
13431
13479
  }
13432
13480
  async function mintRunnerGithubTokens({ client, taskId, log: log2, repo = null, requirePublish = false }) {
13433
13481
  const publishToken = (await client.getInstallationToken({ required: requirePublish }))?.token ?? null;
@@ -13448,6 +13496,7 @@ var init_task_helpers = __esm({
13448
13496
  "use strict";
13449
13497
  init_redact_tokens();
13450
13498
  init_methodology_composer();
13499
+ init_check_consensus_receipt_core();
13451
13500
  RECEIPT_LINE_RE = /receipt id:\s*[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/giu;
13452
13501
  }
13453
13502
  });
@@ -15313,7 +15362,7 @@ var init_detached_economics_spool = __esm({
15313
15362
  });
15314
15363
 
15315
15364
  // ../../scripts/virtual-office/code-runner/killed-run-outcome.mjs
15316
- import { randomUUID as randomUUID6 } from "node:crypto";
15365
+ import { randomUUID as randomUUID7 } from "node:crypto";
15317
15366
  async function handleKilledRun({
15318
15367
  client,
15319
15368
  id,
@@ -15350,7 +15399,7 @@ async function handleKilledRun({
15350
15399
  };
15351
15400
  }
15352
15401
  if (reason === "claim_authority_changed") {
15353
- const occurrenceId = randomUUID6();
15402
+ const occurrenceId = randomUUID7();
15354
15403
  const economics = {
15355
15404
  occurrence_id: occurrenceId,
15356
15405
  runner_id: runnerId,
@@ -15650,7 +15699,7 @@ var code_runner_daemon_exports = {};
15650
15699
  __export(code_runner_daemon_exports, {
15651
15700
  main: () => main
15652
15701
  });
15653
- import { randomUUID as randomUUID7 } from "node:crypto";
15702
+ import { randomUUID as randomUUID8 } from "node:crypto";
15654
15703
  import { fileURLToPath as fileURLToPath8 } from "node:url";
15655
15704
  function log(msg) {
15656
15705
  console.log(`[code-runner ${(/* @__PURE__ */ new Date()).toISOString()}] ${msg}`);
@@ -15800,7 +15849,7 @@ async function processOneTask(client, task, cfg, runnerInstanceId, swarmAdmissio
15800
15849
  }
15801
15850
  if (await taskWasCancelled({ client, id, run, safeProgress, log })) return;
15802
15851
  if (await gateTestGenTaskOrFail({ client, id, task, files, worktreeDir: wt.worktreeDir, log })) return;
15803
- if (await enforceCompletionGateOrFail({ client, id, task, worktreeDir: wt.worktreeDir, log })) return;
15852
+ if (await enforceCompletionGateOrFail({ client, id, task, worktreeDir: wt.worktreeDir, run, truncated: partial, log })) return;
15804
15853
  const publicationTarget = await resolvePublicationTarget({ task, continuationRestore, worktreeDir: wt.worktreeDir, githubToken, allowAmbientGithubFallback: cfg.allowAmbientGithub });
15805
15854
  const localBranch = await resolveOrCreateBranchAsync(wt.worktreeDir, "vo/code-task");
15806
15855
  const publicationBranch = publicationTarget.targetBranch || localBranch;
@@ -15883,7 +15932,7 @@ Closes #${publicationTarget.supersedesPrNumber}` : "";
15883
15932
  async function main({ env: env2 = process.env, once: once2 = false } = {}) {
15884
15933
  const cfg = loadCodeRunnerConfig(env2, { log });
15885
15934
  await sweepStaleTaskAttachmentDirectories().catch((error) => log(`stale attachment cleanup failed: ${error.message}`));
15886
- const runnerInstanceId = randomUUID7();
15935
+ const runnerInstanceId = randomUUID8();
15887
15936
  const client = createControlPlaneClient({
15888
15937
  env: env2,
15889
15938
  runnerId: cfg.runnerId,
@@ -15893,6 +15942,7 @@ async function main({ env: env2 = process.env, once: once2 = false } = {}) {
15893
15942
  let reconcileStale = true;
15894
15943
  let stopping = false;
15895
15944
  let active = 0;
15945
+ const activeTaskIds = /* @__PURE__ */ new Set();
15896
15946
  const stop = (sig) => {
15897
15947
  if (stopping) return;
15898
15948
  stopping = true;
@@ -15907,6 +15957,7 @@ async function main({ env: env2 = process.env, once: once2 = false } = {}) {
15907
15957
  runnerInstanceId,
15908
15958
  requestStop: () => stop("web-control"),
15909
15959
  getActiveCount: () => active,
15960
+ getActiveTaskIds: () => [...activeTaskIds],
15910
15961
  isRunning: () => !stopping,
15911
15962
  startedAt,
15912
15963
  log,
@@ -15997,6 +16048,7 @@ async function main({ env: env2 = process.env, once: once2 = false } = {}) {
15997
16048
  }
15998
16049
  log(`claimed task ${task.code_task_id} (${task.repo})`);
15999
16050
  active += 1;
16051
+ activeTaskIds.add(task.code_task_id);
16000
16052
  const runTask = task.kind === "inference" ? processInferenceTask(client, task, cfg, { safeProgress, runnerStagePatch, log }) : processOneTask(client, task, cfg, runnerInstanceId, { availableAgents: claimAgents.availableAgents, accountUsage: accountUsage.get() });
16001
16053
  const done = runTask.catch(async (error) => {
16002
16054
  log(`task ${task.code_task_id} unhandled runner error: ${error.message}`);
@@ -16016,6 +16068,7 @@ async function main({ env: env2 = process.env, once: once2 = false } = {}) {
16016
16068
  });
16017
16069
  }).finally(() => {
16018
16070
  active -= 1;
16071
+ activeTaskIds.delete(task.code_task_id);
16019
16072
  });
16020
16073
  if (once2) {
16021
16074
  await done;
@@ -16095,8 +16148,163 @@ var init_code_runner_daemon = __esm({
16095
16148
  import { createRequire as createRequire4 } from "node:module";
16096
16149
 
16097
16150
  // src/runner-readiness.mjs
16098
- function failed({ paired = false, operatorId = null, tenantId = null, githubReady = null, error, message }) {
16099
- return { ok: false, paired, operatorId, tenantId, githubReady, error, message };
16151
+ import { createHash, randomUUID } from "node:crypto";
16152
+ var RUNNER_GITHUB_READINESS_TIMEOUT_MS = 5e4;
16153
+ var RUNNER_IDENTITY_READINESS_TIMEOUT_MS = 1e4;
16154
+ var RUNNER_READINESS_MAX_REPOSITORIES = 4;
16155
+ var RUNNER_READINESS_MAX_TIMER_DELAY_MS = 2147e6;
16156
+ var RUNNER_READINESS_TRANSIENT_MAX_RETRY_AFTER_MS = 3e5;
16157
+ var RUNNER_READINESS_IPC_ACK_TIMEOUT_MS = 5e3;
16158
+ var RUNNER_READINESS_DEFERRAL_MESSAGE_TYPE = "vo-runner-readiness-deferred-v1";
16159
+ var RUNNER_READINESS_DEFERRAL_ACK_TYPE = "vo-runner-readiness-deferred-ack-v1";
16160
+ var RUNNER_REPOSITORY = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/u;
16161
+ function runnerRepositoryScopeFromEnv(env2 = {}) {
16162
+ return String(env2.VO_CODE_RUNNER_REPOS || "").split(/[\s,]+/u).map((repo) => repo.trim()).filter(Boolean);
16163
+ }
16164
+ function normalizeRunnerRepositoryScope(repositories) {
16165
+ if (!Array.isArray(repositories) || repositories.length < 1) {
16166
+ throw new Error("VO_CODE_RUNNER_REPOS must name at least one owner/name repository");
16167
+ }
16168
+ if (repositories.length > RUNNER_READINESS_MAX_REPOSITORIES) {
16169
+ throw new Error(`VO_CODE_RUNNER_REPOS supports at most ${RUNNER_READINESS_MAX_REPOSITORIES} repositories`);
16170
+ }
16171
+ const normalized = repositories.map((repo) => {
16172
+ const [owner, name] = typeof repo === "string" ? repo.split("/") : [];
16173
+ if (typeof repo !== "string" || repo.length > 140 || !RUNNER_REPOSITORY.test(repo) || owner === "." || owner === ".." || name === "." || name === "..") {
16174
+ throw new Error("VO_CODE_RUNNER_REPOS entries must be canonical owner/name repositories");
16175
+ }
16176
+ return repo.toLowerCase();
16177
+ });
16178
+ if (new Set(normalized).size !== normalized.length) {
16179
+ throw new Error("VO_CODE_RUNNER_REPOS entries must be unique");
16180
+ }
16181
+ const owners = new Set(normalized.map((repo) => repo.split("/")[0]));
16182
+ if (owners.size !== 1) {
16183
+ throw new Error("VO_CODE_RUNNER_REPOS entries must share one owner");
16184
+ }
16185
+ return Object.freeze(normalized.sort());
16186
+ }
16187
+ function runnerRepositoryScopeDigest(repositories) {
16188
+ const scope = normalizeRunnerRepositoryScope(repositories);
16189
+ return createHash("sha256").update(JSON.stringify({
16190
+ version: 1,
16191
+ repositories: scope
16192
+ }), "utf8").digest("hex");
16193
+ }
16194
+ function runnerReadinessRetryDelayMs(readiness) {
16195
+ if (readiness?.paired !== true || readiness?.githubReady !== false) return null;
16196
+ if (Number.isFinite(readiness.retryAfterMs) && readiness.retryAfterMs > 0) {
16197
+ return Math.ceil(readiness.retryAfterMs);
16198
+ }
16199
+ return null;
16200
+ }
16201
+ function runnerReadinessFailureDisposition(readiness, delivery = null) {
16202
+ const retryAfterMs = runnerReadinessRetryDelayMs(readiness);
16203
+ if (retryAfterMs === null) {
16204
+ return Object.freeze({ action: "exit", exitCode: 1, retryAfterMs: null });
16205
+ }
16206
+ if (delivery?.attempted === true) {
16207
+ return Object.freeze({
16208
+ action: "exit",
16209
+ exitCode: delivery.delivered === true ? 75 : 1,
16210
+ retryAfterMs
16211
+ });
16212
+ }
16213
+ return Object.freeze({ action: "wait", exitCode: null, retryAfterMs });
16214
+ }
16215
+ async function notifySupervisorReadinessDeferral({
16216
+ processLike,
16217
+ retryAfterMs,
16218
+ error = null,
16219
+ ackTimeoutMs = RUNNER_READINESS_IPC_ACK_TIMEOUT_MS
16220
+ }) {
16221
+ if (processLike?.connected !== true || typeof processLike.send !== "function") {
16222
+ return Object.freeze({ attempted: false, delivered: false });
16223
+ }
16224
+ if (typeof processLike.on !== "function" || typeof processLike.off !== "function") {
16225
+ return Object.freeze({ attempted: true, delivered: false });
16226
+ }
16227
+ const nonce = randomUUID();
16228
+ const delivered = await new Promise((resolve3) => {
16229
+ let settled = false;
16230
+ const onMessage = (message) => {
16231
+ if (message?.type === RUNNER_READINESS_DEFERRAL_ACK_TYPE && message.nonce === nonce) {
16232
+ finish(true);
16233
+ }
16234
+ };
16235
+ const onDisconnect = () => finish(false);
16236
+ const finish = (value) => {
16237
+ if (settled) return;
16238
+ settled = true;
16239
+ clearTimeout(timer);
16240
+ processLike.off("message", onMessage);
16241
+ processLike.off("disconnect", onDisconnect);
16242
+ resolve3(value);
16243
+ };
16244
+ const timer = setTimeout(() => finish(false), ackTimeoutMs);
16245
+ processLike.on("message", onMessage);
16246
+ processLike.on("disconnect", onDisconnect);
16247
+ try {
16248
+ processLike.send({
16249
+ type: RUNNER_READINESS_DEFERRAL_MESSAGE_TYPE,
16250
+ nonce,
16251
+ retryAfterMs,
16252
+ error
16253
+ }, (sendError) => {
16254
+ if (sendError) finish(false);
16255
+ });
16256
+ } catch {
16257
+ finish(false);
16258
+ }
16259
+ });
16260
+ return Object.freeze({ attempted: true, delivered });
16261
+ }
16262
+ async function waitForRunnerReadinessRetry(delayMs, {
16263
+ nowMs = () => Date.now(),
16264
+ wait: wait3 = (ms) => new Promise((resolve3) => setTimeout(resolve3, ms))
16265
+ } = {}) {
16266
+ if (!Number.isFinite(delayMs) || delayMs <= 0) {
16267
+ throw new TypeError("runner readiness retry delay is invalid");
16268
+ }
16269
+ const deadline = nowMs() + Math.ceil(delayMs);
16270
+ while (nowMs() < deadline) {
16271
+ await wait3(Math.min(RUNNER_READINESS_MAX_TIMER_DELAY_MS, deadline - nowMs()));
16272
+ }
16273
+ }
16274
+ function failed({
16275
+ paired = false,
16276
+ operatorId = null,
16277
+ tenantId = null,
16278
+ githubReady = null,
16279
+ retryAfterMs = null,
16280
+ error,
16281
+ message
16282
+ }) {
16283
+ return {
16284
+ ok: false,
16285
+ paired,
16286
+ operatorId,
16287
+ tenantId,
16288
+ githubReady,
16289
+ ...Number.isFinite(retryAfterMs) && retryAfterMs > 0 ? { retryAfterMs } : {},
16290
+ error,
16291
+ message
16292
+ };
16293
+ }
16294
+ function responseRetryAfterMs(response, body) {
16295
+ const transientReadinessFailure = response.status === 503 && body?.error === "github_installation_readiness_retryable";
16296
+ if (response.status !== 429 && !transientReadinessFailure) return null;
16297
+ const bound = (retryAfterMs) => transientReadinessFailure ? Math.min(RUNNER_READINESS_TRANSIENT_MAX_RETRY_AFTER_MS, retryAfterMs) : retryAfterMs;
16298
+ const value = response.headers?.get?.("retry-after")?.trim() ?? "";
16299
+ const seconds = Number(value);
16300
+ if (value && Number.isFinite(seconds) && seconds >= 0) {
16301
+ return bound(Math.max(1e3, Math.ceil(seconds * 1e3)));
16302
+ }
16303
+ const at = value ? Date.parse(value) : Number.NaN;
16304
+ if (Number.isFinite(at)) {
16305
+ return bound(Math.max(1e3, Math.ceil(at - Date.now())));
16306
+ }
16307
+ return bound(6e4);
16100
16308
  }
16101
16309
  async function responseBody(response) {
16102
16310
  try {
@@ -16109,11 +16317,21 @@ async function responseBody(response) {
16109
16317
  function serverMessage(body, fallback) {
16110
16318
  return typeof body.message === "string" && body.message.trim() ? body.message.trim() : fallback;
16111
16319
  }
16112
- async function fetchWithTimeout(fetchImpl, url, init, timeoutMs) {
16320
+ async function fetchJsonWithTimeout(fetchImpl, url, init, timeoutMs) {
16113
16321
  const controller = new AbortController();
16114
- const timer = setTimeout(() => controller.abort(), timeoutMs);
16322
+ let timer;
16323
+ const timeout = new Promise((_, reject) => {
16324
+ timer = setTimeout(() => {
16325
+ controller.abort();
16326
+ reject(new Error(`request aborted after ${timeoutMs}ms`));
16327
+ }, timeoutMs);
16328
+ });
16115
16329
  try {
16116
- return await fetchImpl(url, { ...init, signal: controller.signal });
16330
+ const requestAndBody = (async () => {
16331
+ const response = await fetchImpl(url, { ...init, signal: controller.signal });
16332
+ return { response, body: await responseBody(response) };
16333
+ })();
16334
+ return await Promise.race([requestAndBody, timeout]);
16117
16335
  } finally {
16118
16336
  clearTimeout(timer);
16119
16337
  }
@@ -16123,18 +16341,24 @@ async function probeRunnerReadiness({
16123
16341
  token: token2,
16124
16342
  fetchImpl = fetch,
16125
16343
  requireGithub = false,
16126
- timeoutMs = 1e4
16344
+ repositories = [],
16345
+ timeoutMs = RUNNER_IDENTITY_READINESS_TIMEOUT_MS,
16346
+ // The server performs at most five sequential App-JWT GETs (installation plus
16347
+ // every one of at most four configured repositories), each bounded at 8s.
16348
+ // Keep a transport margin while preventing a stuck proof from hanging startup.
16349
+ githubTimeoutMs = RUNNER_GITHUB_READINESS_TIMEOUT_MS
16127
16350
  }) {
16128
16351
  const base = controlPlaneUrl2.replace(/\/+$/u, "");
16129
16352
  const headers = { authorization: `Bearer ${token2}` };
16130
16353
  let identityResponse;
16354
+ let identity;
16131
16355
  try {
16132
- identityResponse = await fetchWithTimeout(
16356
+ ({ response: identityResponse, body: identity } = await fetchJsonWithTimeout(
16133
16357
  fetchImpl,
16134
16358
  `${base}/api/v1/auth/me`,
16135
16359
  { headers },
16136
16360
  timeoutMs
16137
- );
16361
+ ));
16138
16362
  } catch (error) {
16139
16363
  const detail = error instanceof Error ? error.message : String(error);
16140
16364
  return failed({
@@ -16142,7 +16366,6 @@ async function probeRunnerReadiness({
16142
16366
  message: `AlgoHQ could not be reached: ${detail}`
16143
16367
  });
16144
16368
  }
16145
- const identity = await responseBody(identityResponse);
16146
16369
  if (!identityResponse.ok) {
16147
16370
  return failed({
16148
16371
  error: "credential_rejected",
@@ -16168,18 +16391,33 @@ async function probeRunnerReadiness({
16168
16391
  message: "Paired to AlgoHQ."
16169
16392
  };
16170
16393
  }
16394
+ let repositoryScope = null;
16395
+ try {
16396
+ if (!Array.isArray(repositories)) {
16397
+ throw new Error("VO_CODE_RUNNER_REPOS must be a repository array");
16398
+ }
16399
+ if (repositories.length > 0) repositoryScope = normalizeRunnerRepositoryScope(repositories);
16400
+ } catch (error) {
16401
+ return failed({
16402
+ paired: true,
16403
+ operatorId,
16404
+ tenantId,
16405
+ githubReady: false,
16406
+ error: "github_repository_scope_invalid",
16407
+ message: error instanceof Error ? error.message : String(error)
16408
+ });
16409
+ }
16171
16410
  let githubResponse;
16411
+ let github;
16172
16412
  try {
16173
- githubResponse = await fetchWithTimeout(
16413
+ const readinessUrl = new URL(`${base}/api/v1/github/installation-readiness`);
16414
+ for (const repo of repositoryScope ?? []) readinessUrl.searchParams.append("repo", repo);
16415
+ ({ response: githubResponse, body: github } = await fetchJsonWithTimeout(
16174
16416
  fetchImpl,
16175
- `${base}/api/v1/github/installation-token`,
16176
- {
16177
- method: "POST",
16178
- headers: { ...headers, "content-type": "application/json" },
16179
- body: "{}"
16180
- },
16181
- timeoutMs
16182
- );
16417
+ readinessUrl.toString(),
16418
+ { headers },
16419
+ githubTimeoutMs
16420
+ ));
16183
16421
  } catch (error) {
16184
16422
  const detail = error instanceof Error ? error.message : String(error);
16185
16423
  return failed({
@@ -16191,14 +16429,25 @@ async function probeRunnerReadiness({
16191
16429
  message: `GitHub publication readiness could not be checked: ${detail}`
16192
16430
  });
16193
16431
  }
16194
- const github = await responseBody(githubResponse);
16195
- if (!githubResponse.ok || typeof github.token !== "string" || !github.token) {
16432
+ const repositorySelection = github.repository_selection;
16433
+ const repositoriesVerified = github.repositories_verified;
16434
+ const returnedScope = github.repository_scope;
16435
+ let normalizedReturnedScope = null;
16436
+ try {
16437
+ normalizedReturnedScope = normalizeRunnerRepositoryScope(returnedScope);
16438
+ } catch {
16439
+ }
16440
+ const effectiveScope = repositoryScope ?? normalizedReturnedScope;
16441
+ const sourceVerified = repositoryScope === null ? github.repository_scope_source === "persisted_installation_singleton" && normalizedReturnedScope?.length === 1 : github.repository_scope_source === "runner_config";
16442
+ const repositoryScopeVerified = effectiveScope !== null && normalizedReturnedScope !== null && Array.isArray(returnedScope) && returnedScope.length === normalizedReturnedScope.length && returnedScope.every((repo, index) => repo === normalizedReturnedScope[index]) && normalizedReturnedScope.length === effectiveScope.length && normalizedReturnedScope.every((repo, index) => repo === effectiveScope[index]) && github.repository_scope_sha256 === runnerRepositoryScopeDigest(effectiveScope) && repositoriesVerified === effectiveScope.length && sourceVerified && (repositorySelection === "all" || repositorySelection === "selected");
16443
+ if (!githubResponse.ok || github.configured !== true || github.verified !== true || github.publication_ready !== true || !repositoryScopeVerified) {
16196
16444
  const error = typeof github.error === "string" && github.error ? github.error : "github_not_ready";
16197
16445
  return failed({
16198
16446
  paired: true,
16199
16447
  operatorId,
16200
16448
  tenantId,
16201
16449
  githubReady: false,
16450
+ retryAfterMs: responseRetryAfterMs(githubResponse, github),
16202
16451
  error,
16203
16452
  message: serverMessage(github, `GitHub publication preflight failed (HTTP ${githubResponse.status}).`)
16204
16453
  });
@@ -16209,8 +16458,10 @@ async function probeRunnerReadiness({
16209
16458
  operatorId,
16210
16459
  tenantId,
16211
16460
  githubReady: true,
16461
+ repositoryScope: effectiveScope,
16462
+ repositoryScopeSource: github.repository_scope_source,
16212
16463
  error: null,
16213
- message: "Paired and ready to publish through the Algosuite GitHub App."
16464
+ message: `Paired with a verified Algosuite GitHub App installation (${repositoriesVerified} repos).`
16214
16465
  };
16215
16466
  }
16216
16467
  function pairedOperatorScope(readiness) {
@@ -16221,7 +16472,7 @@ function pairedOperatorScope(readiness) {
16221
16472
  import { closeSync, existsSync, mkdirSync, openSync, unlinkSync } from "node:fs";
16222
16473
  import { homedir } from "node:os";
16223
16474
  import { posix, win32 } from "node:path";
16224
- import { randomUUID } from "node:crypto";
16475
+ import { randomUUID as randomUUID2 } from "node:crypto";
16225
16476
  var APP_IDENTIFIER = "ai.algosuite.vo-runner";
16226
16477
  var CLONES_DIR = "clones";
16227
16478
  function pathsFor(platform4) {
@@ -16291,7 +16542,7 @@ function runnerWorkingDirectory({ repoRoot: repoRoot2, clonesRoot: clonesRoot2 }
16291
16542
  }
16292
16543
  function assertWritableRunnerDirectory(root) {
16293
16544
  mkdirSync(root, { recursive: true });
16294
- const probe = pathsFor(process.platform).join(root, `.vo-runner-write-probe-${process.pid}-${randomUUID()}`);
16545
+ const probe = pathsFor(process.platform).join(root, `.vo-runner-write-probe-${process.pid}-${randomUUID2()}`);
16295
16546
  let handle;
16296
16547
  try {
16297
16548
  handle = openSync(probe, "wx", 384);
@@ -16411,21 +16662,51 @@ if (!token) {
16411
16662
  }
16412
16663
  var controlPlaneUrl = process.env.VO_CONTROL_PLANE_URL || DEFAULT_CONTROL_PLANE_URL3;
16413
16664
  var pairedOperatorId = null;
16414
- if (!explicitAdminToken) {
16415
- const readiness = await probeRunnerReadiness({
16416
- controlPlaneUrl,
16417
- token,
16418
- requireGithub: true
16665
+ async function deferRunnerReadinessRetry(readiness) {
16666
+ const initial = runnerReadinessFailureDisposition(readiness);
16667
+ if (initial.action === "exit") return initial;
16668
+ const { retryAfterMs } = initial;
16669
+ console.error(
16670
+ `[vo-mcp runner] GitHub readiness is temporarily unavailable; retrying after ${retryAfterMs}ms: ${readiness.message}`
16671
+ );
16672
+ const delivery = await notifySupervisorReadinessDeferral({
16673
+ processLike: process,
16674
+ retryAfterMs,
16675
+ error: readiness.error
16419
16676
  });
16420
- if (!readiness.ok) {
16421
- console.error(`[vo-mcp runner] Readiness check failed: ${readiness.message}`);
16422
- process.exit(1);
16677
+ if (delivery.attempted) {
16678
+ const disposition = runnerReadinessFailureDisposition(readiness, delivery);
16679
+ if (disposition.exitCode === 1) {
16680
+ console.error("[vo-mcp runner] Supervisor readiness deferral could not be delivered; failing closed.");
16681
+ }
16682
+ return disposition;
16683
+ }
16684
+ await waitForRunnerReadinessRetry(retryAfterMs);
16685
+ return Object.freeze({ action: "retry", exitCode: null, retryAfterMs });
16686
+ }
16687
+ if (!explicitAdminToken) {
16688
+ let readiness;
16689
+ while (true) {
16690
+ readiness = await probeRunnerReadiness({
16691
+ controlPlaneUrl,
16692
+ token,
16693
+ requireGithub: true,
16694
+ repositories: runnerRepositoryScopeFromEnv(process.env)
16695
+ });
16696
+ if (readiness.ok) break;
16697
+ const disposition = await deferRunnerReadinessRetry(readiness);
16698
+ if (disposition.action === "retry") continue;
16699
+ if (disposition.retryAfterMs === null) {
16700
+ console.error(`[vo-mcp runner] Readiness check failed: ${readiness.message}`);
16701
+ }
16702
+ process.exit(disposition.exitCode);
16423
16703
  }
16424
16704
  pairedOperatorId = pairedOperatorScope(readiness);
16425
16705
  if (!pairedOperatorId) {
16426
16706
  console.error("[vo-mcp runner] Readiness check failed: paired operator scope is missing. Pair this computer again.");
16427
16707
  process.exit(1);
16428
16708
  }
16709
+ process.env.VO_CODE_RUNNER_REPOS = readiness.repositoryScope.join(",");
16429
16710
  }
16430
16711
  var rootConfig;
16431
16712
  try {