@algosuite/vo-mcp 0.2.0-beta.50 → 0.2.0-beta.52

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -203,24 +203,24 @@ function defaultOverridePath() {
203
203
  return join2(homedir(), ".claude", "vo-arch-defaults.local.json");
204
204
  }
205
205
  function loadTenantOverride(opts = {}) {
206
- const path3 = opts.path ?? defaultOverridePath();
207
- if (!existsSync2(path3)) {
206
+ const path4 = opts.path ?? defaultOverridePath();
207
+ if (!existsSync2(path4)) {
208
208
  return { override: null, source_path: null };
209
209
  }
210
- const raw = readFileSync2(path3, "utf8");
210
+ const raw = readFileSync2(path4, "utf8");
211
211
  let parsed;
212
212
  try {
213
213
  parsed = JSON.parse(raw);
214
214
  } catch (err) {
215
215
  const m = err instanceof Error ? err.message : String(err);
216
- throw new Error(`vo-arch-defaults: invalid JSON in override ${path3}: ${m}`, { cause: err });
216
+ throw new Error(`vo-arch-defaults: invalid JSON in override ${path4}: ${m}`, { cause: err });
217
217
  }
218
218
  try {
219
219
  const override = parseOverride(parsed);
220
- return { override, source_path: path3 };
220
+ return { override, source_path: path4 };
221
221
  } catch (err) {
222
222
  const m = err instanceof Error ? err.message : String(err);
223
- throw new Error(`vo-arch-defaults: override schema validation failed for ${path3}: ${m}`, { cause: err });
223
+ throw new Error(`vo-arch-defaults: override schema validation failed for ${path4}: ${m}`, { cause: err });
224
224
  }
225
225
  }
226
226
  var init_load_override = __esm({
@@ -371,9 +371,9 @@ function globToRegExp(glob) {
371
371
  }
372
372
  return new RegExp("^" + out + "$");
373
373
  }
374
- function matchesAnyGlob(path3, globs) {
374
+ function matchesAnyGlob(path4, globs) {
375
375
  for (const g of globs) {
376
- if (globToRegExp(g).test(path3)) return true;
376
+ if (globToRegExp(g).test(path4)) return true;
377
377
  }
378
378
  return false;
379
379
  }
@@ -1461,7 +1461,7 @@ var init_safe_memory_file = __esm({
1461
1461
  });
1462
1462
 
1463
1463
  // src/tools/memory/sync-lock-liveness.ts
1464
- import { statSync as statSync4, readFileSync as readFileSync8 } from "node:fs";
1464
+ import { statSync as statSync5, readFileSync as readFileSync8 } from "node:fs";
1465
1465
  function defaultIsProcessAlive(pid) {
1466
1466
  try {
1467
1467
  process.kill(pid, 0);
@@ -1487,10 +1487,10 @@ function toPayload(parsed) {
1487
1487
  acquiredAtMs: typeof acquiredAtMs === "number" && Number.isFinite(acquiredAtMs) ? acquiredAtMs : Number.NaN
1488
1488
  };
1489
1489
  }
1490
- function readLockRecord(path3) {
1490
+ function readLockRecord(path4) {
1491
1491
  let raw;
1492
1492
  try {
1493
- raw = readFileSync8(path3, "utf8");
1493
+ raw = readFileSync8(path4, "utf8");
1494
1494
  } catch {
1495
1495
  return null;
1496
1496
  }
@@ -1500,7 +1500,7 @@ function readLockRecord(path3) {
1500
1500
  return { raw, payload: null };
1501
1501
  }
1502
1502
  }
1503
- function lockAgeMs(record, path3, nowMs) {
1503
+ function lockAgeMs(record, path4, nowMs) {
1504
1504
  let startedMs = Number.NaN;
1505
1505
  if (record.payload) {
1506
1506
  if (Number.isFinite(record.payload.acquiredAtMs)) {
@@ -1511,7 +1511,7 @@ function lockAgeMs(record, path3, nowMs) {
1511
1511
  }
1512
1512
  if (!Number.isFinite(startedMs)) {
1513
1513
  try {
1514
- startedMs = statSync4(path3).mtimeMs;
1514
+ startedMs = statSync5(path4).mtimeMs;
1515
1515
  } catch {
1516
1516
  return null;
1517
1517
  }
@@ -1540,17 +1540,17 @@ var init_sync_lock_liveness = __esm({
1540
1540
  });
1541
1541
 
1542
1542
  // src/tools/memory/sync-lock.ts
1543
- import { closeSync as closeSync2, mkdirSync as mkdirSync5, openSync as openSync3, readFileSync as readFileSync9, unlinkSync as unlinkSync2, writeFileSync as writeFileSync4 } from "node:fs";
1543
+ import { closeSync as closeSync3, mkdirSync as mkdirSync5, openSync as openSync3, readFileSync as readFileSync9, unlinkSync as unlinkSync2, writeFileSync as writeFileSync4 } from "node:fs";
1544
1544
  import { hostname } from "node:os";
1545
1545
  import { join as join8 } from "node:path";
1546
1546
  import { randomUUID as randomUUID2 } from "node:crypto";
1547
1547
  function positiveOr(value, fallback) {
1548
1548
  return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : fallback;
1549
1549
  }
1550
- function createExclusive2(path3, contents) {
1550
+ function createExclusive2(path4, contents) {
1551
1551
  let fd;
1552
1552
  try {
1553
- fd = openSync3(path3, "wx");
1553
+ fd = openSync3(path4, "wx");
1554
1554
  } catch (err) {
1555
1555
  const code = err.code;
1556
1556
  return { ok: false, exists: code === "EEXIST", message: err instanceof Error ? err.message : String(err) };
@@ -1558,37 +1558,37 @@ function createExclusive2(path3, contents) {
1558
1558
  try {
1559
1559
  writeFileSync4(fd, contents, "utf8");
1560
1560
  } catch (err) {
1561
- closeSync2(fd);
1561
+ closeSync3(fd);
1562
1562
  try {
1563
- unlinkSync2(path3);
1563
+ unlinkSync2(path4);
1564
1564
  } catch {
1565
1565
  }
1566
1566
  return { ok: false, exists: false, message: err instanceof Error ? err.message : String(err) };
1567
1567
  }
1568
- closeSync2(fd);
1568
+ closeSync3(fd);
1569
1569
  return { ok: true };
1570
1570
  }
1571
- function removeAbandoned(path3, expectedRaw) {
1571
+ function removeAbandoned(path4, expectedRaw) {
1572
1572
  let current;
1573
1573
  try {
1574
- current = readFileSync9(path3, "utf8");
1574
+ current = readFileSync9(path4, "utf8");
1575
1575
  } catch {
1576
1576
  return;
1577
1577
  }
1578
1578
  if (current !== expectedRaw) return;
1579
1579
  try {
1580
- unlinkSync2(path3);
1580
+ unlinkSync2(path4);
1581
1581
  } catch {
1582
1582
  }
1583
1583
  }
1584
- function makeRelease(path3, token) {
1584
+ function makeRelease(path4, token) {
1585
1585
  let released = false;
1586
1586
  return () => {
1587
1587
  if (released) return;
1588
1588
  released = true;
1589
1589
  let raw;
1590
1590
  try {
1591
- raw = readFileSync9(path3, "utf8");
1591
+ raw = readFileSync9(path4, "utf8");
1592
1592
  } catch {
1593
1593
  return;
1594
1594
  }
@@ -1600,7 +1600,7 @@ function makeRelease(path3, token) {
1600
1600
  }
1601
1601
  if (!stillOurs) return;
1602
1602
  try {
1603
- unlinkSync2(path3);
1603
+ unlinkSync2(path4);
1604
1604
  } catch {
1605
1605
  }
1606
1606
  };
@@ -1619,7 +1619,7 @@ async function acquireMemorySyncLock(options) {
1619
1619
  }));
1620
1620
  const isProcessAlive = options.isProcessAlive ?? defaultIsProcessAlive;
1621
1621
  const thisHost = hostname();
1622
- const path3 = join8(options.memoryDir, MEMORY_SYNC_LOCK_FILE);
1622
+ const path4 = join8(options.memoryDir, MEMORY_SYNC_LOCK_FILE);
1623
1623
  if (options.createDir === true) mkdirSync5(options.memoryDir, { recursive: true });
1624
1624
  const deadline = now() + waitMs;
1625
1625
  let backoffMs = INITIAL_BACKOFF_MS;
@@ -1635,30 +1635,30 @@ async function acquireMemorySyncLock(options) {
1635
1635
  acquiredAt: new Date(acquiredAtMs).toISOString(),
1636
1636
  acquiredAtMs
1637
1637
  };
1638
- const created = createExclusive2(path3, `${JSON.stringify(payload, null, 2)}
1638
+ const created = createExclusive2(path4, `${JSON.stringify(payload, null, 2)}
1639
1639
  `);
1640
1640
  if (created.ok) {
1641
- return { path: path3, payload, tookOverFrom, release: makeRelease(path3, payload.token) };
1641
+ return { path: path4, payload, tookOverFrom, release: makeRelease(path4, payload.token) };
1642
1642
  }
1643
1643
  if (!created.exists) {
1644
1644
  throw new Error(
1645
- `memory sync lock ${path3} could not be created (${created.message}) \u2014 refusing to sync without exclusion`
1645
+ `memory sync lock ${path4} could not be created (${created.message}) \u2014 refusing to sync without exclusion`
1646
1646
  );
1647
1647
  }
1648
- const record = readLockRecord(path3);
1648
+ const record = readLockRecord(path4);
1649
1649
  let reclaimed = false;
1650
1650
  if (record) {
1651
1651
  holderDescription = describeHolder(record);
1652
- const age = lockAgeMs(record, path3, now());
1652
+ const age = lockAgeMs(record, path4, now());
1653
1653
  if (isLockAbandoned(record, age, ttlMs, isProcessAlive, thisHost)) {
1654
1654
  tookOverFrom = record.payload;
1655
- removeAbandoned(path3, record.raw);
1655
+ removeAbandoned(path4, record.raw);
1656
1656
  reclaimed = true;
1657
1657
  }
1658
1658
  }
1659
1659
  if (now() >= deadline) {
1660
1660
  throw new Error(
1661
- `memory sync lock ${path3} is held by ${holderDescription}; waited ${waitMs}ms \u2014 refusing to sync unlocked (concurrent memory writes corrupt the shared index). If that holder is provably gone, delete the lock file.`
1661
+ `memory sync lock ${path4} is held by ${holderDescription}; waited ${waitMs}ms \u2014 refusing to sync unlocked (concurrent memory writes corrupt the shared index). If that holder is provably gone, delete the lock file.`
1662
1662
  );
1663
1663
  }
1664
1664
  if (reclaimed) backoffMs = INITIAL_BACKOFF_MS;
@@ -1922,7 +1922,7 @@ var init_memory_push_cache = __esm({
1922
1922
  });
1923
1923
 
1924
1924
  // src/tools/memory/memory-sync-http.ts
1925
- import { existsSync as existsSync5, mkdirSync as mkdirSync6, readFileSync as readFileSync11, writeFileSync as writeFileSync6, readdirSync as readdirSync4 } from "node:fs";
1925
+ import { existsSync as existsSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync11, writeFileSync as writeFileSync6, readdirSync as readdirSync4 } from "node:fs";
1926
1926
  async function pullMemory(controlPlaneUrl, token, memoryDir, fetchFn) {
1927
1927
  const url = `${controlPlaneUrl}/api/v1/agent-config/memory/me`;
1928
1928
  const response = await withRequestTimeout(
@@ -2007,7 +2007,7 @@ async function uploadOne(item, controlPlaneUrl, token, sessionId, fetchFn, deadl
2007
2007
  }
2008
2008
  async function pushMemory(controlPlaneUrl, token, memoryDir, sessionId, fetchFn, options = {}) {
2009
2009
  const empty = { pushed: 0, created: 0, updated: 0, skipped: 0, indexRowsPreserved: 0 };
2010
- if (!existsSync5(memoryDir)) {
2010
+ if (!existsSync6(memoryDir)) {
2011
2011
  return empty;
2012
2012
  }
2013
2013
  const localFiles = listPushableFiles(memoryDir).map((f) => ({
@@ -2101,7 +2101,7 @@ var init_memory_sync_http = __esm({
2101
2101
  });
2102
2102
 
2103
2103
  // src/tools/memory/sync-kill-switch.ts
2104
- import { existsSync as existsSync6, readFileSync as readFileSync12 } from "node:fs";
2104
+ import { existsSync as existsSync7, readFileSync as readFileSync12 } from "node:fs";
2105
2105
  import { homedir as homedir6 } from "node:os";
2106
2106
  import { join as join10 } from "node:path";
2107
2107
  function memorySyncSentinelPath(home) {
@@ -2120,7 +2120,7 @@ function clip(raw) {
2120
2120
  function evaluateMemorySyncKillSwitch(deps = {}) {
2121
2121
  const env = deps.env ?? process.env;
2122
2122
  const home = deps.home ?? homedir6();
2123
- const fileExists = deps.fileExists ?? existsSync6;
2123
+ const fileExists = deps.fileExists ?? existsSync7;
2124
2124
  const readFile3 = deps.readFile ?? ((p) => readFileSync12(p, "utf8"));
2125
2125
  const fired = [];
2126
2126
  const rawEnv = env[MEMORY_SYNC_DISABLE_ENV];
@@ -2166,7 +2166,7 @@ __export(memory_knowledge_bridge_exports, {
2166
2166
  extractMemoryTitle: () => extractMemoryTitle,
2167
2167
  upsertMemoryFilesAsKnowledge: () => upsertMemoryFilesAsKnowledge
2168
2168
  });
2169
- import { existsSync as existsSync7, readdirSync as readdirSync5, readFileSync as readFileSync13 } from "node:fs";
2169
+ import { existsSync as existsSync8, readdirSync as readdirSync5, readFileSync as readFileSync13 } from "node:fs";
2170
2170
  function extractMemoryTitle(fileName, content) {
2171
2171
  const frontmatter = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
2172
2172
  if (frontmatter) {
@@ -2181,7 +2181,7 @@ async function upsertMemoryFilesAsKnowledge(options) {
2181
2181
  const { controlPlaneUrl, token, memoryDir, fetchFn, cache, deadline } = options;
2182
2182
  let files;
2183
2183
  try {
2184
- if (!existsSync7(memoryDir)) {
2184
+ if (!existsSync8(memoryDir)) {
2185
2185
  return { attempted: 0, upserted: 0, failed: 0, skipped: 0, failures: [] };
2186
2186
  }
2187
2187
  files = readdirSync5(memoryDir).filter(
@@ -2293,7 +2293,7 @@ __export(sync_config_exports, {
2293
2293
  isNoopSyncReason: () => isNoopSyncReason,
2294
2294
  runMemorySync: () => runMemorySync
2295
2295
  });
2296
- import { existsSync as existsSync8 } from "node:fs";
2296
+ import { existsSync as existsSync9 } from "node:fs";
2297
2297
  import { homedir as homedir7 } from "node:os";
2298
2298
  import { join as join11 } from "node:path";
2299
2299
  function isToolInput22(v) {
@@ -2335,7 +2335,7 @@ async function runMemorySync(action, cwd, sessionId, fetchFn = globalThis.fetch,
2335
2335
  }
2336
2336
  const memoryDir = getMemoryDir(cwd);
2337
2337
  const baseUrl = controlPlaneUrl.replace(/\/+$/, "");
2338
- if (action === "push" && !existsSync8(memoryDir)) {
2338
+ if (action === "push" && !existsSync9(memoryDir)) {
2339
2339
  return {
2340
2340
  synced: true,
2341
2341
  action: "push",
@@ -5014,10 +5014,10 @@ init_common();
5014
5014
  init_auth_token_source();
5015
5015
  init_credential_store();
5016
5016
  var AdminCallableError = class extends Error {
5017
- constructor(status, path3, message) {
5017
+ constructor(status, path4, message) {
5018
5018
  super(message);
5019
5019
  this.status = status;
5020
- this.path = path3;
5020
+ this.path = path4;
5021
5021
  this.name = "AdminCallableError";
5022
5022
  }
5023
5023
  status;
@@ -5044,21 +5044,21 @@ var HttpAdminCallableClient = class {
5044
5044
  this.fetchFn = config.fetchFn ?? globalThis.fetch;
5045
5045
  this.readOnly = config.readOnly ?? false;
5046
5046
  }
5047
- async invoke(path3, body, opts) {
5048
- if (!path3.startsWith("/")) {
5047
+ async invoke(path4, body, opts) {
5048
+ if (!path4.startsWith("/")) {
5049
5049
  throw new Error(
5050
- `HttpAdminCallableClient.invoke: path must start with '/', got '${path3}'`
5050
+ `HttpAdminCallableClient.invoke: path must start with '/', got '${path4}'`
5051
5051
  );
5052
5052
  }
5053
5053
  const token = await this.tokenSource.getToken();
5054
5054
  if (!token) {
5055
5055
  throw new AdminCallableError(
5056
5056
  401,
5057
- path3,
5058
- `admin proxy ${path3}: no auth token available (check VO_USER_REFRESH_TOKEN / VO_USER_ID_TOKEN / VO_CONTROL_PLANE_ADMIN_TOKEN)`
5057
+ path4,
5058
+ `admin proxy ${path4}: no auth token available (check VO_USER_REFRESH_TOKEN / VO_USER_ID_TOKEN / VO_CONTROL_PLANE_ADMIN_TOKEN)`
5059
5059
  );
5060
5060
  }
5061
- const url = `${this.baseUrl}${path3}`;
5061
+ const url = `${this.baseUrl}${path4}`;
5062
5062
  const response = await this.fetchFn(url, {
5063
5063
  method: "POST",
5064
5064
  headers: {
@@ -5071,8 +5071,8 @@ var HttpAdminCallableClient = class {
5071
5071
  if (response.status < 200 || response.status >= 300) {
5072
5072
  throw new AdminCallableError(
5073
5073
  response.status,
5074
- path3,
5075
- `admin proxy ${path3} returned HTTP ${response.status}: ${text.slice(0, 200)}`
5074
+ path4,
5075
+ `admin proxy ${path4} returned HTTP ${response.status}: ${text.slice(0, 200)}`
5076
5076
  );
5077
5077
  }
5078
5078
  let parsed;
@@ -5081,23 +5081,23 @@ var HttpAdminCallableClient = class {
5081
5081
  } catch {
5082
5082
  throw new AdminCallableError(
5083
5083
  response.status,
5084
- path3,
5085
- `admin proxy ${path3} returned non-JSON body`
5084
+ path4,
5085
+ `admin proxy ${path4} returned non-JSON body`
5086
5086
  );
5087
5087
  }
5088
5088
  if (typeof parsed !== "object" || parsed === null) {
5089
5089
  throw new AdminCallableError(
5090
5090
  response.status,
5091
- path3,
5092
- `admin proxy ${path3} response not an object`
5091
+ path4,
5092
+ `admin proxy ${path4} response not an object`
5093
5093
  );
5094
5094
  }
5095
5095
  const obj = parsed;
5096
5096
  if (obj["ok"] !== true) {
5097
5097
  throw new AdminCallableError(
5098
5098
  response.status,
5099
- path3,
5100
- `admin proxy ${path3} returned ok=false: ${JSON.stringify(obj).slice(0, 200)}`
5099
+ path4,
5100
+ `admin proxy ${path4} returned ok=false: ${JSON.stringify(obj).slice(0, 200)}`
5101
5101
  );
5102
5102
  }
5103
5103
  if (opts?.rawEnvelope) {
@@ -5110,8 +5110,8 @@ var HttpAdminCallableClient = class {
5110
5110
  if (!("result" in obj)) {
5111
5111
  throw new AdminCallableError(
5112
5112
  response.status,
5113
- path3,
5114
- `admin proxy ${path3} response missing .result field`
5113
+ path4,
5114
+ `admin proxy ${path4} response missing .result field`
5115
5115
  );
5116
5116
  }
5117
5117
  return obj["result"];
@@ -6145,7 +6145,7 @@ init_common();
6145
6145
  import { spawn } from "node:child_process";
6146
6146
  import { homedir as homedir5 } from "node:os";
6147
6147
  import { join as join7 } from "node:path";
6148
- import { existsSync as existsSync4, mkdirSync as mkdirSync4, openSync as openSync2, readFileSync as readFileSync7, readdirSync as readdirSync3, statSync as statSync3 } from "node:fs";
6148
+ import { closeSync as closeSync2, existsSync as existsSync5, mkdirSync as mkdirSync4, openSync as openSync2, readFileSync as readFileSync7, readdirSync as readdirSync3, statSync as statSync4 } from "node:fs";
6149
6149
 
6150
6150
  // src/swarm/tier-binding.ts
6151
6151
  var SWARM_TIERS = Object.freeze([
@@ -6351,10 +6351,10 @@ function sanitizeSwarmId(raw) {
6351
6351
  return id;
6352
6352
  }
6353
6353
  var CEILING_FILE = "ceiling.json";
6354
- function createExclusive(path3, contents) {
6354
+ function createExclusive(path4, contents) {
6355
6355
  let fd;
6356
6356
  try {
6357
- fd = openSync(path3, "wx");
6357
+ fd = openSync(path4, "wx");
6358
6358
  } catch {
6359
6359
  return false;
6360
6360
  }
@@ -6369,18 +6369,18 @@ function capToCents(cap) {
6369
6369
  return isPositiveCap(cap) ? Math.round(cap * 100) : 0;
6370
6370
  }
6371
6371
  function readOrRecordLedgerHead(swarmDir, proposedCeiling, proposedCapCents, nowIso) {
6372
- const path3 = join6(swarmDir, CEILING_FILE);
6372
+ const path4 = join6(swarmDir, CEILING_FILE);
6373
6373
  const head = JSON.stringify({
6374
6374
  ceiling: proposedCeiling,
6375
6375
  cap_cents: proposedCapCents,
6376
6376
  recorded_at: nowIso
6377
6377
  });
6378
- if (createExclusive(path3, head)) {
6378
+ if (createExclusive(path4, head)) {
6379
6379
  return { ceiling: proposedCeiling, capCents: proposedCapCents };
6380
6380
  }
6381
6381
  let parsed;
6382
6382
  try {
6383
- parsed = JSON.parse(readFileSync6(path3, "utf8"));
6383
+ parsed = JSON.parse(readFileSync6(path4, "utf8"));
6384
6384
  } catch {
6385
6385
  return null;
6386
6386
  }
@@ -6460,6 +6460,288 @@ var claimSpawnSlot = ({ swarmId, proposedCeiling, proposedCapUsd, dir, nowIso })
6460
6460
  };
6461
6461
  };
6462
6462
 
6463
+ // src/swarm/spawn-plan.ts
6464
+ function resolveSpawnPlan(env, input, nowIso, platform = process.platform, claim = claimSpawnSlot) {
6465
+ const rawBinding = env[SWARM_TIER_BINDING_ENV];
6466
+ const hasBinding = typeof rawBinding === "string" && rawBinding.trim().length > 0;
6467
+ if (!hasBinding) {
6468
+ const explicit = input.agent?.trim();
6469
+ const resolved2 = resolveSuccessorLaunch({ agent: explicit || "claude", maxTurns: input.max_turns, platform });
6470
+ if (!resolved2.ok) return { ok: false, reason: resolved2.reason, tier: "unbound" };
6471
+ return {
6472
+ ok: true,
6473
+ bin: resolved2.bin,
6474
+ args: resolved2.args,
6475
+ agent: resolved2.agent,
6476
+ tier: "unbound",
6477
+ bound: false,
6478
+ env: {},
6479
+ slot: null,
6480
+ capUsd: null,
6481
+ capRemainingUsd: null
6482
+ };
6483
+ }
6484
+ const binding = inheritSwarmTierBinding(env, nowIso);
6485
+ const admission = admitSubagentSpawn(binding);
6486
+ if (!admission.allowed) {
6487
+ return { ok: false, reason: admission.reason, tier: binding.tier };
6488
+ }
6489
+ const agentRefusal = agentBindingRefusal(binding, input.agent);
6490
+ if (agentRefusal !== null) return { ok: false, reason: agentRefusal, tier: binding.tier };
6491
+ const resolved = resolveSuccessorLaunch({
6492
+ agent: binding.agent,
6493
+ maxTurns: input.max_turns,
6494
+ platform
6495
+ });
6496
+ if (!resolved.ok) return { ok: false, reason: resolved.reason, tier: binding.tier };
6497
+ const slot = claim({
6498
+ swarmId: binding.swarm_id,
6499
+ proposedCeiling: binding.subagent_budget,
6500
+ // The spend-cap POOL, recorded once per swarm exactly like the ceiling. The
6501
+ // child's cap is DEBITED from it below, not recomputed from this binding.
6502
+ proposedCapUsd: binding.spend_cap_usd,
6503
+ dir: resolveLedgerDir(env),
6504
+ nowIso
6505
+ });
6506
+ if (!slot.ok) return { ok: false, reason: slot.reason, tier: binding.tier };
6507
+ return {
6508
+ ok: true,
6509
+ bin: resolved.bin,
6510
+ args: resolved.args,
6511
+ agent: resolved.agent,
6512
+ tier: binding.tier,
6513
+ bound: true,
6514
+ // Re-export the same TIER with a DECREMENTED budget and the spend cap the
6515
+ // ledger just DEBITED. Exporting the binding verbatim (what this did before
6516
+ // #9312) meant the child re-read the full budget and every generation
6517
+ // restarted at zero. Recomputing the cap from THIS binding (what #9312 did)
6518
+ // bounded a chain but not a tree: three siblings each re-halved the parent's
6519
+ // untouched $50 and walked away with $75 between them.
6520
+ env: childBindingEnvFragment(binding, slot.capUsd),
6521
+ slot: slot.slot,
6522
+ capUsd: slot.capUsd,
6523
+ capRemainingUsd: slot.capRemainingUsd
6524
+ };
6525
+ }
6526
+
6527
+ // ../../scripts/virtual-office/code-runner/windows-claude-launch.mjs
6528
+ import { existsSync as existsSync4, realpathSync } from "node:fs";
6529
+ import { win32 as path3 } from "node:path";
6530
+ import { spawnSync as spawnSync2 } from "node:child_process";
6531
+ var NATIVE_CLAUDE_PARTS = [
6532
+ "node_modules",
6533
+ "@anthropic-ai",
6534
+ "claude-code",
6535
+ "bin",
6536
+ "claude.exe"
6537
+ ];
6538
+ function pathValue(env) {
6539
+ for (const key of ["Path", "PATH", "path"]) {
6540
+ if (typeof env?.[key] === "string") return env[key];
6541
+ }
6542
+ return "";
6543
+ }
6544
+ function cleanPathSegment(value) {
6545
+ const trimmed = String(value || "").trim();
6546
+ return trimmed.startsWith('"') && trimmed.endsWith('"') ? trimmed.slice(1, -1) : trimmed;
6547
+ }
6548
+ function envValue(env, name) {
6549
+ const exact = env?.[name];
6550
+ if (typeof exact === "string") return exact.trim();
6551
+ const key = Object.keys(env || {}).find((candidate) => candidate.toLowerCase() === name.toLowerCase());
6552
+ return typeof env?.[key] === "string" ? env[key].trim() : "";
6553
+ }
6554
+ function userClaudeCandidates(bin, env) {
6555
+ if (!/^claude(?:\.(?:exe|cmd|ps1))?$/iu.test(bin)) return [];
6556
+ const userProfile = envValue(env, "USERPROFILE");
6557
+ const appData = envValue(env, "APPDATA") || (userProfile ? path3.join(userProfile, "AppData", "Roaming") : "");
6558
+ const localAppData = envValue(env, "LOCALAPPDATA") || (userProfile ? path3.join(userProfile, "AppData", "Local") : "");
6559
+ const candidates = [];
6560
+ if (appData) {
6561
+ const npmBin = path3.join(appData, "npm");
6562
+ candidates.push(
6563
+ path3.join(npmBin, "claude.exe"),
6564
+ path3.join(npmBin, "claude.cmd"),
6565
+ path3.join(npmBin, "claude.ps1"),
6566
+ path3.join(npmBin, "claude"),
6567
+ path3.join(npmBin, ...NATIVE_CLAUDE_PARTS)
6568
+ );
6569
+ }
6570
+ if (userProfile) candidates.push(path3.join(userProfile, ".local", "bin", "claude.exe"));
6571
+ if (localAppData) {
6572
+ candidates.push(
6573
+ path3.join(localAppData, "Microsoft", "WinGet", "Links", "claude.exe"),
6574
+ path3.join(localAppData, "Microsoft", "WindowsApps", "claude.exe")
6575
+ );
6576
+ }
6577
+ return candidates;
6578
+ }
6579
+ function pathCandidates(bin, env) {
6580
+ if (path3.isAbsolute(bin) || /[\\/]/u.test(bin)) {
6581
+ return [path3.resolve(bin)];
6582
+ }
6583
+ const extension = path3.extname(bin);
6584
+ const fromPath = pathValue(env).split(";").map(cleanPathSegment).filter(Boolean).flatMap((directory) => extension ? [path3.join(directory, bin)] : [
6585
+ path3.join(directory, `${bin}.exe`),
6586
+ path3.join(directory, `${bin}.cmd`),
6587
+ path3.join(directory, `${bin}.ps1`),
6588
+ path3.join(directory, bin)
6589
+ ]);
6590
+ const seen = /* @__PURE__ */ new Set();
6591
+ return [...fromPath, ...userClaudeCandidates(bin, env)].filter((candidate) => {
6592
+ const key = candidate.toLowerCase();
6593
+ if (seen.has(key)) return false;
6594
+ seen.add(key);
6595
+ return true;
6596
+ });
6597
+ }
6598
+ function canonicalExistingPath(candidate, exists, canonicalize2) {
6599
+ if (!exists(candidate)) return null;
6600
+ try {
6601
+ return canonicalize2(candidate);
6602
+ } catch {
6603
+ return null;
6604
+ }
6605
+ }
6606
+ function resolveWindowsClaudeExecutable({
6607
+ bin = "claude",
6608
+ env = process.env,
6609
+ exists = existsSync4,
6610
+ canonicalize: canonicalize2 = realpathSync
6611
+ } = {}) {
6612
+ const requested = String(bin || "").trim();
6613
+ if (!requested || requested.includes("\0")) {
6614
+ throw new TypeError("Claude executable must be a non-empty path without NUL bytes");
6615
+ }
6616
+ for (const candidate of pathCandidates(requested, env)) {
6617
+ const found = canonicalExistingPath(candidate, exists, canonicalize2);
6618
+ if (!found) continue;
6619
+ if (path3.extname(found).toLowerCase() === ".exe") return found;
6620
+ const native = path3.join(path3.dirname(found), ...NATIVE_CLAUDE_PARTS);
6621
+ const resolvedNative = canonicalExistingPath(native, exists, canonicalize2);
6622
+ if (resolvedNative) return resolvedNative;
6623
+ }
6624
+ const error = new Error(
6625
+ `Could not resolve a native claude.exe for "${requested}". Install or update Claude Code with the native Windows installer (recommended) or npm install -g @anthropic-ai/claude-code; the HQ runner will not execute a shell-only .cmd/.ps1 shim.`
6626
+ );
6627
+ error.code = "ENOENT";
6628
+ throw error;
6629
+ }
6630
+
6631
+ // src/swarm/successor-windows-exe.ts
6632
+ var resolveWindowsClaudeExe = (bin, env) => resolveWindowsClaudeExecutable({ bin, env });
6633
+ function resolveNativeWindowsExecutable(bin, env = process.env, resolve3 = resolveWindowsClaudeExe) {
6634
+ try {
6635
+ return { ok: true, bin: resolve3(bin, env) };
6636
+ } catch (error) {
6637
+ const message = error instanceof Error ? error.message : String(error);
6638
+ return {
6639
+ ok: false,
6640
+ reason: `could not resolve a native Windows executable for '${bin}': ${message}`
6641
+ };
6642
+ }
6643
+ }
6644
+
6645
+ // src/swarm/successor-liveness.ts
6646
+ import { statSync as statSync3 } from "node:fs";
6647
+ var DEFAULT_EARLY_EXIT_SEC = 10;
6648
+ var DEFAULT_NO_OUTPUT_SEC = 0;
6649
+ var DEFAULT_POLL_MS = 200;
6650
+ var KILL_ESCALATION_MS = 2e3;
6651
+ function positiveSeconds(raw, fallback) {
6652
+ if (raw === void 0) return fallback;
6653
+ const n = Number(raw.trim());
6654
+ return Number.isFinite(n) && n > 0 ? n : fallback;
6655
+ }
6656
+ function resolveLivenessConfigFromEnv(env = process.env, explicitOverride = {}) {
6657
+ const earlyExitMs = explicitOverride.earlyExitMs ?? positiveSeconds(env["VO_MCP_SUCCESSOR_EXIT_CHECK_SEC"], DEFAULT_EARLY_EXIT_SEC) * 1e3;
6658
+ const noOutputMs = explicitOverride.noOutputMs ?? positiveSeconds(env["VO_MCP_SUCCESSOR_OUTPUT_CHECK_SEC"], DEFAULT_NO_OUTPUT_SEC) * 1e3;
6659
+ return { earlyExitMs, noOutputMs };
6660
+ }
6661
+ function defaultStatLogBytes(path4) {
6662
+ try {
6663
+ return statSync3(path4).size;
6664
+ } catch {
6665
+ return 0;
6666
+ }
6667
+ }
6668
+ function defaultKillChild(child) {
6669
+ try {
6670
+ child.kill("SIGTERM");
6671
+ } catch {
6672
+ }
6673
+ const escalation = setTimeout(() => {
6674
+ try {
6675
+ child.kill("SIGKILL");
6676
+ } catch {
6677
+ }
6678
+ }, KILL_ESCALATION_MS);
6679
+ escalation.unref();
6680
+ }
6681
+ function checkSuccessorLiveness(child, logPath, config, deps = {}) {
6682
+ const statLogBytes = deps.statLogBytes ?? defaultStatLogBytes;
6683
+ const now = deps.now ?? Date.now;
6684
+ const pollIntervalMs = deps.pollIntervalMs ?? DEFAULT_POLL_MS;
6685
+ const killChild = deps.killChild ?? defaultKillChild;
6686
+ const outputGateEnabled = Number.isFinite(config.noOutputMs) && config.noOutputMs > 0;
6687
+ const startedAt = now();
6688
+ return new Promise((resolve3) => {
6689
+ let settled = false;
6690
+ let pollTimer = null;
6691
+ const onExit = (code, signal) => {
6692
+ const elapsedMs = now() - startedAt;
6693
+ finish({
6694
+ ok: false,
6695
+ reason: `child_exited_early: exit code ${code ?? "null"} signal ${signal ?? "none"} after ${elapsedMs}ms`,
6696
+ detail: { exitCode: code, signal, elapsedMs }
6697
+ });
6698
+ };
6699
+ const cleanup = () => {
6700
+ if (pollTimer !== null) clearInterval(pollTimer);
6701
+ child.off?.("exit", onExit);
6702
+ };
6703
+ const finish = (result) => {
6704
+ if (settled) return;
6705
+ settled = true;
6706
+ cleanup();
6707
+ if (!result.ok) {
6708
+ try {
6709
+ killChild(child);
6710
+ } catch {
6711
+ }
6712
+ }
6713
+ resolve3(result);
6714
+ };
6715
+ if (typeof child.exitCode === "number" || typeof child.signalCode === "string" && child.signalCode.length > 0) {
6716
+ finish({
6717
+ ok: false,
6718
+ reason: `child_exited_early: exit code ${child.exitCode ?? "null"} signal ${child.signalCode ?? "none"} before the liveness watch attached`,
6719
+ detail: { exitCode: child.exitCode ?? null, signal: child.signalCode ?? null, elapsedMs: 0 }
6720
+ });
6721
+ return;
6722
+ }
6723
+ child.on("exit", onExit);
6724
+ const tick = () => {
6725
+ if (settled) return;
6726
+ const elapsedMs = now() - startedAt;
6727
+ const outputSeen = outputGateEnabled ? statLogBytes(logPath) > 0 : true;
6728
+ if (outputSeen && elapsedMs >= config.earlyExitMs) {
6729
+ finish({ ok: true });
6730
+ return;
6731
+ }
6732
+ if (outputGateEnabled && !outputSeen && elapsedMs >= config.noOutputMs) {
6733
+ finish({
6734
+ ok: false,
6735
+ reason: `no_output: log stayed empty for ${elapsedMs}ms (limit ${config.noOutputMs}ms)`,
6736
+ detail: { elapsedMs, logPath }
6737
+ });
6738
+ }
6739
+ };
6740
+ pollTimer = setInterval(tick, pollIntervalMs);
6741
+ tick();
6742
+ });
6743
+ }
6744
+
6463
6745
  // src/tools/session/spawn-successor.ts
6464
6746
  var TOOL_NAME20 = "vo_spawn_successor";
6465
6747
  var MAX_HANDOFF_BYTES = 64e3;
@@ -6491,7 +6773,7 @@ var inputSchema20 = {
6491
6773
  additionalProperties: false
6492
6774
  };
6493
6775
  var RETIRED_COUNTER_INPUT = "spawns_so_far";
6494
- var description20 = "Mode B auto-handoff (roadmap \xA73.4): spawn a DETACHED headless `claude -p` successor with a handoff doc pre-injected into its prompt. Defaults to the newest handoff in ~/.vo/handoffs/. Returns {spawned, pid, log_path, handoff_path}. The successor works under the same gates as any session (ADR-001: verify-before-act, human merge approval) \u2014 this tool never fires autonomously.";
6776
+ var description20 = "Mode B auto-handoff (roadmap \xA73.4): spawn a DETACHED headless `claude -p` successor with a handoff doc pre-injected into its prompt. Defaults to the newest handoff in ~/.vo/handoffs/. Verifies the child is actually alive (survives an early-exit window; an optional log-output window is off by default) before reporting success. Returns {spawned, pid, log_path, handoff_path}. The successor works under the same gates as any session (ADR-001: verify-before-act, human merge approval) \u2014 this tool never fires autonomously.";
6495
6777
  function isToolInput20(v) {
6496
6778
  if (typeof v !== "object" || v === null) return false;
6497
6779
  const o = v;
@@ -6509,7 +6791,7 @@ function retiredCounterRefusal(v) {
6509
6791
  }
6510
6792
  function newestHandoff(dir = join7(homedir5(), ".vo", "handoffs")) {
6511
6793
  try {
6512
- const entries = readdirSync3(dir).filter((f) => f.endsWith(".md")).map((f) => ({ f, m: statSync3(join7(dir, f)).mtimeMs })).sort((a, b) => b.m - a.m);
6794
+ const entries = readdirSync3(dir).filter((f) => f.endsWith(".md")).map((f) => ({ f, m: statSync4(join7(dir, f)).mtimeMs })).sort((a, b) => b.m - a.m);
6513
6795
  return entries.length > 0 && entries[0] ? join7(dir, entries[0].f) : null;
6514
6796
  } catch {
6515
6797
  return null;
@@ -6547,97 +6829,14 @@ function buildSuccessorPrompt(handoffMarkdown, goal) {
6547
6829
  if (goal && goal.trim().length > 0) lines.push("", `OPERATOR GOAL OVERRIDE: ${goal.trim()}`);
6548
6830
  return lines.join("\n");
6549
6831
  }
6550
- function buildSuccessorArgs(maxTurns) {
6551
- const args = ["-p", "--permission-mode", "acceptEdits"];
6552
- if (Number.isInteger(maxTurns) && maxTurns > 0) {
6553
- args.push("--max-turns", String(maxTurns));
6554
- }
6555
- return args;
6556
- }
6557
- function resolveSpawnPlan(env, input, nowIso, platform = process.platform, claim = claimSpawnSlot) {
6558
- const rawBinding = env[SWARM_TIER_BINDING_ENV];
6559
- const hasBinding = typeof rawBinding === "string" && rawBinding.trim().length > 0;
6560
- if (!hasBinding) {
6561
- const explicit = input.agent?.trim();
6562
- if (explicit) {
6563
- const resolved2 = resolveSuccessorLaunch({ agent: explicit, maxTurns: input.max_turns, platform });
6564
- if (!resolved2.ok) return { ok: false, reason: resolved2.reason, tier: "unbound" };
6565
- return {
6566
- ok: true,
6567
- bin: resolved2.bin,
6568
- args: resolved2.args,
6569
- agent: resolved2.agent,
6570
- tier: "unbound",
6571
- bound: false,
6572
- env: {},
6573
- slot: null,
6574
- capUsd: null,
6575
- capRemainingUsd: null
6576
- };
6577
- }
6578
- return {
6579
- ok: true,
6580
- bin: "claude",
6581
- args: buildSuccessorArgs(input.max_turns),
6582
- agent: "claude",
6583
- tier: "unbound",
6584
- bound: false,
6585
- env: {},
6586
- slot: null,
6587
- capUsd: null,
6588
- capRemainingUsd: null
6589
- };
6590
- }
6591
- const binding = inheritSwarmTierBinding(env, nowIso);
6592
- const admission = admitSubagentSpawn(binding);
6593
- if (!admission.allowed) {
6594
- return { ok: false, reason: admission.reason, tier: binding.tier };
6595
- }
6596
- const agentRefusal = agentBindingRefusal(binding, input.agent);
6597
- if (agentRefusal !== null) return { ok: false, reason: agentRefusal, tier: binding.tier };
6598
- const resolved = resolveSuccessorLaunch({
6599
- agent: binding.agent,
6600
- maxTurns: input.max_turns,
6601
- platform
6602
- });
6603
- if (!resolved.ok) return { ok: false, reason: resolved.reason, tier: binding.tier };
6604
- const slot = claim({
6605
- swarmId: binding.swarm_id,
6606
- proposedCeiling: binding.subagent_budget,
6607
- // The spend-cap POOL, recorded once per swarm exactly like the ceiling. The
6608
- // child's cap is DEBITED from it below, not recomputed from this binding.
6609
- proposedCapUsd: binding.spend_cap_usd,
6610
- dir: resolveLedgerDir(env),
6611
- nowIso
6612
- });
6613
- if (!slot.ok) return { ok: false, reason: slot.reason, tier: binding.tier };
6614
- return {
6615
- ok: true,
6616
- bin: resolved.bin,
6617
- args: resolved.args,
6618
- agent: resolved.agent,
6619
- tier: binding.tier,
6620
- bound: true,
6621
- // Re-export the same TIER with a DECREMENTED budget and the spend cap the
6622
- // ledger just DEBITED. Exporting the binding verbatim (what this did before
6623
- // #9312) meant the child re-read the full budget and every generation
6624
- // restarted at zero. Recomputing the cap from THIS binding (what #9312 did)
6625
- // bounded a chain but not a tree: three siblings each re-halved the parent's
6626
- // untouched $50 and walked away with $75 between them.
6627
- env: childBindingEnvFragment(binding, slot.capUsd),
6628
- slot: slot.slot,
6629
- capUsd: slot.capUsd,
6630
- capRemainingUsd: slot.capRemainingUsd
6631
- };
6632
- }
6633
- async function handleSpawnSuccessor(_deps, rawInput, _signal, spawnImpl = spawn) {
6832
+ async function handleSpawnSuccessor(_deps, rawInput, _signal, spawnImpl = spawn, overrides = {}) {
6634
6833
  const retired = retiredCounterRefusal(rawInput);
6635
6834
  if (retired !== null) throw invalidParams(TOOL_NAME20, retired);
6636
6835
  if (!isToolInput20(rawInput)) {
6637
6836
  throw invalidParams(TOOL_NAME20, "invalid input. Optional: { handoff_path, goal, cwd, max_turns, agent }.");
6638
6837
  }
6639
6838
  const handoffPath = rawInput.handoff_path?.trim() || newestHandoff();
6640
- if (!handoffPath || !existsSync4(handoffPath)) {
6839
+ if (!handoffPath || !existsSync5(handoffPath)) {
6641
6840
  return jsonContent({
6642
6841
  tool: TOOL_NAME20,
6643
6842
  schema_version: 1,
@@ -6662,27 +6861,52 @@ async function handleSpawnSuccessor(_deps, rawInput, _signal, spawnImpl = spawn)
6662
6861
  }
6663
6862
  });
6664
6863
  }
6864
+ const platform = overrides.platform ?? process.platform;
6865
+ let resolvedBin = plan.bin;
6866
+ if (platform === "win32") {
6867
+ const resolution = resolveNativeWindowsExecutable(plan.bin, process.env, overrides.resolveWindowsExecutable);
6868
+ if (!resolution.ok) {
6869
+ return jsonContent({
6870
+ tool: TOOL_NAME20,
6871
+ schema_version: 1,
6872
+ payload: {
6873
+ spawned: false,
6874
+ reason: resolution.reason,
6875
+ agent: plan.agent,
6876
+ tier: plan.tier,
6877
+ handoff_path: handoffPath
6878
+ }
6879
+ });
6880
+ }
6881
+ resolvedBin = resolution.bin;
6882
+ }
6665
6883
  const logDir = process.env["VO_MCP_SUCCESSOR_LOG_DIR"]?.trim() || join7(homedir5(), ".vo", "successors");
6666
6884
  mkdirSync4(logDir, { recursive: true });
6667
6885
  const logPath = join7(logDir, `successor-${Date.now()}.log`);
6668
6886
  const logFd = openSync2(logPath, "a");
6669
- const child = spawnImpl(plan.bin, [...plan.args], {
6887
+ const child = spawnImpl(resolvedBin, [...plan.args], {
6670
6888
  cwd: rawInput.cwd?.trim() || process.cwd(),
6671
6889
  detached: true,
6672
6890
  stdio: ["pipe", logFd, logFd],
6673
- // Windows: the agent CLIs are .cmd shims they need a shell to resolve.
6674
- // The prompt goes via STDIN below, never argv, so the shell never sees it.
6675
- shell: process.platform === "win32",
6891
+ // Never a shell: `resolvedBin` is either the bare platform-neutral name
6892
+ // (POSIX, resolved by the OS via PATH + shebang) or the native win32 exe
6893
+ // resolved above — routing either through cmd.exe/sh is the extra layer
6894
+ // a detached, unref'd child can lose silently (2026-08-16 incident).
6895
+ shell: false,
6676
6896
  windowsHide: true,
6897
+ windowsVerbatimArguments: false,
6677
6898
  // Carry the SAME binding to the child. Without this the successor inherits
6678
6899
  // no tier and re-resolves its own — which is the split-payer defect one
6679
6900
  // generation down.
6680
6901
  ...plan.bound ? { env: { ...process.env, ...plan.env } } : {}
6681
6902
  });
6903
+ closeSync2(logFd);
6682
6904
  let spawnError = null;
6683
6905
  child.on("error", (e) => {
6684
6906
  spawnError = e.message;
6685
6907
  });
6908
+ child.stdin.on?.("error", () => {
6909
+ });
6686
6910
  try {
6687
6911
  child.stdin.write(prompt);
6688
6912
  child.stdin.end();
@@ -6690,10 +6914,40 @@ async function handleSpawnSuccessor(_deps, rawInput, _signal, spawnImpl = spawn)
6690
6914
  }
6691
6915
  child.unref();
6692
6916
  await new Promise((r) => setTimeout(r, 150));
6917
+ if (spawnError) {
6918
+ return jsonContent({
6919
+ tool: TOOL_NAME20,
6920
+ schema_version: 1,
6921
+ payload: {
6922
+ spawned: false,
6923
+ reason: `spawn failed: ${spawnError}`,
6924
+ agent: plan.agent,
6925
+ tier: plan.tier,
6926
+ handoff_path: handoffPath
6927
+ }
6928
+ });
6929
+ }
6930
+ const checkLiveness = overrides.checkLiveness ?? ((c, p) => checkSuccessorLiveness(c, p, resolveLivenessConfigFromEnv(process.env, overrides.livenessConfig), overrides.livenessDeps));
6931
+ const liveness = await checkLiveness(child, logPath);
6932
+ if (!liveness.ok) {
6933
+ return jsonContent({
6934
+ tool: TOOL_NAME20,
6935
+ schema_version: 1,
6936
+ payload: {
6937
+ spawned: false,
6938
+ reason: liveness.reason,
6939
+ pid: child.pid ?? null,
6940
+ log_path: logPath,
6941
+ agent: plan.agent,
6942
+ tier: plan.tier,
6943
+ handoff_path: handoffPath
6944
+ }
6945
+ });
6946
+ }
6693
6947
  return jsonContent({
6694
6948
  tool: TOOL_NAME20,
6695
6949
  schema_version: 1,
6696
- payload: spawnError ? { spawned: false, reason: `spawn failed: ${spawnError}`, agent: plan.agent, tier: plan.tier, handoff_path: handoffPath } : {
6950
+ payload: {
6697
6951
  spawned: true,
6698
6952
  pid: child.pid ?? null,
6699
6953
  log_path: logPath,
@@ -6705,7 +6959,10 @@ async function handleSpawnSuccessor(_deps, rawInput, _signal, spawnImpl = spawn)
6705
6959
  // The debit, surfaced so an operator can reconcile a fan-out's spend
6706
6960
  // against the pool without reading the ledger directory by hand.
6707
6961
  ledger_cap_usd: plan.capUsd,
6708
- ledger_cap_remaining_usd: plan.capRemainingUsd
6962
+ ledger_cap_remaining_usd: plan.capRemainingUsd,
6963
+ // Additive (2026-08-17): true only once the child survived the
6964
+ // early-exit window (and the output window, when that gate is enabled).
6965
+ verified_alive: true
6709
6966
  }
6710
6967
  });
6711
6968
  }
@@ -6889,10 +7146,10 @@ async function getCloudAuth(fetchFn) {
6889
7146
  if (!token) return { ok: false, reason: "Failed to obtain auth token. Run `vo-mcp login` again." };
6890
7147
  return { ok: true, controlPlaneUrl, token };
6891
7148
  }
6892
- async function callPrivateKnowledge(path3, body, fetchFn) {
7149
+ async function callPrivateKnowledge(path4, body, fetchFn) {
6893
7150
  const auth = await getCloudAuth(fetchFn);
6894
7151
  if (!auth.ok) return { ok: false, reason: auth.reason };
6895
- const response = await fetchFn(`${auth.controlPlaneUrl}${path3}`, {
7152
+ const response = await fetchFn(`${auth.controlPlaneUrl}${path4}`, {
6896
7153
  method: "POST",
6897
7154
  headers: {
6898
7155
  authorization: `Bearer ${auth.token}`,
@@ -7110,11 +7367,11 @@ async function handleHqWhiteboardRead(_deps, rawInput, signal) {
7110
7367
  }
7111
7368
 
7112
7369
  // src/tools/skills/skill-corpus.ts
7113
- import { existsSync as existsSync9, statSync as statSync6 } from "node:fs";
7370
+ import { existsSync as existsSync10, statSync as statSync7 } from "node:fs";
7114
7371
  import { dirname as dirname5, isAbsolute, join as join13, resolve as resolve2 } from "node:path";
7115
7372
 
7116
7373
  // ../skill-registry/src/loader.ts
7117
- import { readdirSync as readdirSync6, readFileSync as readFileSync14, statSync as statSync5 } from "node:fs";
7374
+ import { readdirSync as readdirSync6, readFileSync as readFileSync14, statSync as statSync6 } from "node:fs";
7118
7375
  import { join as join12 } from "node:path";
7119
7376
  var InvalidSkillFrontmatterError = class extends Error {
7120
7377
  constructor(skillFile, reason) {
@@ -7171,7 +7428,7 @@ function loadSkillsFromDir(skillsDir) {
7171
7428
  const entryPath = join12(skillsDir, entry);
7172
7429
  let stat;
7173
7430
  try {
7174
- stat = statSync5(entryPath);
7431
+ stat = statSync6(entryPath);
7175
7432
  } catch {
7176
7433
  continue;
7177
7434
  }
@@ -7221,12 +7478,12 @@ function resolveSkillsDir(env = process.env, startDir = process.cwd()) {
7221
7478
  const override = env.VO_SKILLS_DIR;
7222
7479
  if (typeof override === "string" && override.length > 0) {
7223
7480
  const abs = isAbsolute(override) ? override : resolve2(startDir, override);
7224
- return existsSync9(abs) && statSync6(abs).isDirectory() ? abs : null;
7481
+ return existsSync10(abs) && statSync7(abs).isDirectory() ? abs : null;
7225
7482
  }
7226
7483
  let dir = resolve2(startDir);
7227
7484
  for (let i = 0; i < MAX_WALK_UP_LEVELS; i += 1) {
7228
7485
  const candidate = join13(dir, ".claude", "skills");
7229
- if (existsSync9(candidate) && statSync6(candidate).isDirectory()) return candidate;
7486
+ if (existsSync10(candidate) && statSync7(candidate).isDirectory()) return candidate;
7230
7487
  const parent = dirname5(dir);
7231
7488
  if (parent === dir) break;
7232
7489
  dir = parent;
@@ -8435,7 +8692,7 @@ function createConsensusFallbackClient(primary, fallback, options = {}) {
8435
8692
  }
8436
8693
 
8437
8694
  // src/consensus/shadow-client.ts
8438
- import { appendFileSync as appendFileSync2, chmodSync as chmodSync4, mkdirSync as mkdirSync8, renameSync, statSync as statSync7 } from "node:fs";
8695
+ import { appendFileSync as appendFileSync2, chmodSync as chmodSync4, mkdirSync as mkdirSync8, renameSync, statSync as statSync8 } from "node:fs";
8439
8696
  import { homedir as homedir8 } from "node:os";
8440
8697
  import { dirname as dirname7, join as join14 } from "node:path";
8441
8698
  var DEFAULT_SHADOW_TIMEOUT_MS = 2e4;
@@ -8444,17 +8701,17 @@ function defaultShadowReceiptPath(env = process.env) {
8444
8701
  const p = (env["VO_MCP_MOAT_SHADOW_PATH"] ?? "").trim();
8445
8702
  return p || join14(homedir8(), ".claude", "vo-mcp-moat-shadow.jsonl");
8446
8703
  }
8447
- function appendShadowReceipt(receipt, path3 = defaultShadowReceiptPath()) {
8704
+ function appendShadowReceipt(receipt, path4 = defaultShadowReceiptPath()) {
8448
8705
  try {
8449
- mkdirSync8(dirname7(path3), { recursive: true, mode: 448 });
8706
+ mkdirSync8(dirname7(path4), { recursive: true, mode: 448 });
8450
8707
  try {
8451
- if (statSync7(path3).size > SHADOW_RECEIPT_MAX_BYTES) renameSync(path3, `${path3}.1`);
8708
+ if (statSync8(path4).size > SHADOW_RECEIPT_MAX_BYTES) renameSync(path4, `${path4}.1`);
8452
8709
  } catch {
8453
8710
  }
8454
- appendFileSync2(path3, `${JSON.stringify(receipt)}
8711
+ appendFileSync2(path4, `${JSON.stringify(receipt)}
8455
8712
  `, "utf8");
8456
8713
  try {
8457
- chmodSync4(path3, 384);
8714
+ chmodSync4(path4, 384);
8458
8715
  } catch {
8459
8716
  }
8460
8717
  } catch {
@@ -8607,11 +8864,11 @@ function processCapture(rawBody, expectedState, store) {
8607
8864
  return { ok: false, httpStatus: 400, error: "login response missing refresh_token / api_key" };
8608
8865
  }
8609
8866
  const email = typeof data.email === "string" && data.email.trim() ? data.email.trim() : void 0;
8610
- const path3 = store({ refresh_token: refresh, api_key: apiKey, ...email ? { email } : {} });
8867
+ const path4 = store({ refresh_token: refresh, api_key: apiKey, ...email ? { email } : {} });
8611
8868
  return {
8612
8869
  ok: true,
8613
8870
  httpStatus: 200,
8614
- result: { ...email ? { email } : {}, credentialPath: path3 },
8871
+ result: { ...email ? { email } : {}, credentialPath: path4 },
8615
8872
  captured: { refresh_token: refresh, api_key: apiKey, ...email ? { email } : {} }
8616
8873
  };
8617
8874
  }
@@ -8699,8 +8956,8 @@ async function runLogin(opts = {}) {
8699
8956
  }
8700
8957
  } catch {
8701
8958
  }
8702
- const path3 = writeNow(cred);
8703
- result = { ...capt.email ? { email: capt.email } : {}, credentialPath: path3 };
8959
+ const path4 = writeNow(cred);
8960
+ result = { ...capt.email ? { email: capt.email } : {}, credentialPath: path4 };
8704
8961
  }
8705
8962
  res.writeHead(outcome.httpStatus, { "content-type": "text/html; charset=utf-8" });
8706
8963
  res.end(outcome.ok ? "<h2>AlgoHQ login complete \u2014 you can close this tab.</h2>" : `<h2>Login failed: ${outcome.error}</h2>`);