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

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
  }
@@ -1023,7 +1023,16 @@ function assertWithinByteCap(toolName, fieldName, value, maxBytes) {
1023
1023
  );
1024
1024
  }
1025
1025
  }
1026
+ function subjectFromEnv(env = process.env) {
1027
+ const code_task_id = (env["VO_CODE_TASK_ID"] ?? "").trim().slice(0, 120);
1028
+ const repo = (env["VO_CODE_TASK_REPO"] ?? "").trim();
1029
+ const subject = {};
1030
+ if (code_task_id) subject.code_task_id = code_task_id;
1031
+ if (repo && REPO_RE.test(repo) && repo.length <= 200) subject.repo = repo;
1032
+ return subject.code_task_id || subject.repo ? subject : null;
1033
+ }
1026
1034
  function buildBaseEvent(args) {
1035
+ const subject = args.subject === void 0 ? subjectFromEnv() : args.subject;
1027
1036
  return {
1028
1037
  schema_version: 1,
1029
1038
  event_id: args.eventId ?? randomUUID(),
@@ -1049,7 +1058,12 @@ function buildBaseEvent(args) {
1049
1058
  downstream_outcome: null,
1050
1059
  vo_mcp_version: VO_MCP_VERSION,
1051
1060
  consensus_engine_version: null,
1052
- cache_hit: false
1061
+ cache_hit: false,
1062
+ // OMIT the key when there is no subject (rather than `subject: null`): the
1063
+ // ingest schema is `.strict()`, so an event with no subject stays valid on a
1064
+ // sink that has not learned the field yet — only subject-carrying events
1065
+ // depend on the sink being current (deploy ordering: sink before producer).
1066
+ ...subject ? { subject } : {}
1053
1067
  };
1054
1068
  }
1055
1069
  function jsonContent(value) {
@@ -1115,6 +1129,16 @@ function aggregateEventTokenUsage(src, engineUsage) {
1115
1129
  total_cost_usd: anyCost ? costMicroUsd / 1e6 : null
1116
1130
  };
1117
1131
  }
1132
+ function trajectoryFromEngine(result, known = {}) {
1133
+ const rounds = typeof result.token_usage?.rounds_counted === "number" && Number.isInteger(result.token_usage.rounds_counted) && result.token_usage.rounds_counted >= 0 ? result.token_usage.rounds_counted : null;
1134
+ const called = result.fan_out_diagnostics?.models_called;
1135
+ const turns = typeof called === "number" && Number.isInteger(called) && called >= 0 ? called : rounds !== null && rounds > 0 ? result.per_model_verdicts.length * rounds : null;
1136
+ const sourceGrounded = result.citation_grade !== void 0 || result.low_confidence_sources !== void 0;
1137
+ const tool_calls = typeof known.tool_calls === "number" && Number.isInteger(known.tool_calls) && known.tool_calls >= 0 ? known.tool_calls : sourceGrounded ? null : 0;
1138
+ if (rounds === null && turns === null && tool_calls === null) return {};
1139
+ const trajectory = { rounds, turns, tool_calls };
1140
+ return { trajectory };
1141
+ }
1118
1142
  function toEventSynthesizedVerdict(src) {
1119
1143
  return {
1120
1144
  verdict: src.verdict,
@@ -1122,12 +1146,13 @@ function toEventSynthesizedVerdict(src) {
1122
1146
  reasoning_excerpt: sanitizeExcerpt(src.reasoning_excerpt)
1123
1147
  };
1124
1148
  }
1125
- var VO_MCP_VERSION;
1149
+ var VO_MCP_VERSION, REPO_RE;
1126
1150
  var init_common = __esm({
1127
1151
  "src/tools/common.ts"() {
1128
1152
  "use strict";
1129
1153
  init_events_writer();
1130
1154
  VO_MCP_VERSION = readVoMcpVersion();
1155
+ REPO_RE = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/u;
1131
1156
  }
1132
1157
  });
1133
1158
 
@@ -1436,7 +1461,7 @@ var init_safe_memory_file = __esm({
1436
1461
  });
1437
1462
 
1438
1463
  // src/tools/memory/sync-lock-liveness.ts
1439
- import { statSync as statSync4, readFileSync as readFileSync8 } from "node:fs";
1464
+ import { statSync as statSync5, readFileSync as readFileSync8 } from "node:fs";
1440
1465
  function defaultIsProcessAlive(pid) {
1441
1466
  try {
1442
1467
  process.kill(pid, 0);
@@ -1462,10 +1487,10 @@ function toPayload(parsed) {
1462
1487
  acquiredAtMs: typeof acquiredAtMs === "number" && Number.isFinite(acquiredAtMs) ? acquiredAtMs : Number.NaN
1463
1488
  };
1464
1489
  }
1465
- function readLockRecord(path3) {
1490
+ function readLockRecord(path4) {
1466
1491
  let raw;
1467
1492
  try {
1468
- raw = readFileSync8(path3, "utf8");
1493
+ raw = readFileSync8(path4, "utf8");
1469
1494
  } catch {
1470
1495
  return null;
1471
1496
  }
@@ -1475,7 +1500,7 @@ function readLockRecord(path3) {
1475
1500
  return { raw, payload: null };
1476
1501
  }
1477
1502
  }
1478
- function lockAgeMs(record, path3, nowMs) {
1503
+ function lockAgeMs(record, path4, nowMs) {
1479
1504
  let startedMs = Number.NaN;
1480
1505
  if (record.payload) {
1481
1506
  if (Number.isFinite(record.payload.acquiredAtMs)) {
@@ -1486,7 +1511,7 @@ function lockAgeMs(record, path3, nowMs) {
1486
1511
  }
1487
1512
  if (!Number.isFinite(startedMs)) {
1488
1513
  try {
1489
- startedMs = statSync4(path3).mtimeMs;
1514
+ startedMs = statSync5(path4).mtimeMs;
1490
1515
  } catch {
1491
1516
  return null;
1492
1517
  }
@@ -1515,17 +1540,17 @@ var init_sync_lock_liveness = __esm({
1515
1540
  });
1516
1541
 
1517
1542
  // src/tools/memory/sync-lock.ts
1518
- 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";
1519
1544
  import { hostname } from "node:os";
1520
1545
  import { join as join8 } from "node:path";
1521
1546
  import { randomUUID as randomUUID2 } from "node:crypto";
1522
1547
  function positiveOr(value, fallback) {
1523
1548
  return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : fallback;
1524
1549
  }
1525
- function createExclusive2(path3, contents) {
1550
+ function createExclusive2(path4, contents) {
1526
1551
  let fd;
1527
1552
  try {
1528
- fd = openSync3(path3, "wx");
1553
+ fd = openSync3(path4, "wx");
1529
1554
  } catch (err) {
1530
1555
  const code = err.code;
1531
1556
  return { ok: false, exists: code === "EEXIST", message: err instanceof Error ? err.message : String(err) };
@@ -1533,37 +1558,37 @@ function createExclusive2(path3, contents) {
1533
1558
  try {
1534
1559
  writeFileSync4(fd, contents, "utf8");
1535
1560
  } catch (err) {
1536
- closeSync2(fd);
1561
+ closeSync3(fd);
1537
1562
  try {
1538
- unlinkSync2(path3);
1563
+ unlinkSync2(path4);
1539
1564
  } catch {
1540
1565
  }
1541
1566
  return { ok: false, exists: false, message: err instanceof Error ? err.message : String(err) };
1542
1567
  }
1543
- closeSync2(fd);
1568
+ closeSync3(fd);
1544
1569
  return { ok: true };
1545
1570
  }
1546
- function removeAbandoned(path3, expectedRaw) {
1571
+ function removeAbandoned(path4, expectedRaw) {
1547
1572
  let current;
1548
1573
  try {
1549
- current = readFileSync9(path3, "utf8");
1574
+ current = readFileSync9(path4, "utf8");
1550
1575
  } catch {
1551
1576
  return;
1552
1577
  }
1553
1578
  if (current !== expectedRaw) return;
1554
1579
  try {
1555
- unlinkSync2(path3);
1580
+ unlinkSync2(path4);
1556
1581
  } catch {
1557
1582
  }
1558
1583
  }
1559
- function makeRelease(path3, token) {
1584
+ function makeRelease(path4, token) {
1560
1585
  let released = false;
1561
1586
  return () => {
1562
1587
  if (released) return;
1563
1588
  released = true;
1564
1589
  let raw;
1565
1590
  try {
1566
- raw = readFileSync9(path3, "utf8");
1591
+ raw = readFileSync9(path4, "utf8");
1567
1592
  } catch {
1568
1593
  return;
1569
1594
  }
@@ -1575,7 +1600,7 @@ function makeRelease(path3, token) {
1575
1600
  }
1576
1601
  if (!stillOurs) return;
1577
1602
  try {
1578
- unlinkSync2(path3);
1603
+ unlinkSync2(path4);
1579
1604
  } catch {
1580
1605
  }
1581
1606
  };
@@ -1594,7 +1619,7 @@ async function acquireMemorySyncLock(options) {
1594
1619
  }));
1595
1620
  const isProcessAlive = options.isProcessAlive ?? defaultIsProcessAlive;
1596
1621
  const thisHost = hostname();
1597
- const path3 = join8(options.memoryDir, MEMORY_SYNC_LOCK_FILE);
1622
+ const path4 = join8(options.memoryDir, MEMORY_SYNC_LOCK_FILE);
1598
1623
  if (options.createDir === true) mkdirSync5(options.memoryDir, { recursive: true });
1599
1624
  const deadline = now() + waitMs;
1600
1625
  let backoffMs = INITIAL_BACKOFF_MS;
@@ -1610,30 +1635,30 @@ async function acquireMemorySyncLock(options) {
1610
1635
  acquiredAt: new Date(acquiredAtMs).toISOString(),
1611
1636
  acquiredAtMs
1612
1637
  };
1613
- const created = createExclusive2(path3, `${JSON.stringify(payload, null, 2)}
1638
+ const created = createExclusive2(path4, `${JSON.stringify(payload, null, 2)}
1614
1639
  `);
1615
1640
  if (created.ok) {
1616
- return { path: path3, payload, tookOverFrom, release: makeRelease(path3, payload.token) };
1641
+ return { path: path4, payload, tookOverFrom, release: makeRelease(path4, payload.token) };
1617
1642
  }
1618
1643
  if (!created.exists) {
1619
1644
  throw new Error(
1620
- `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`
1621
1646
  );
1622
1647
  }
1623
- const record = readLockRecord(path3);
1648
+ const record = readLockRecord(path4);
1624
1649
  let reclaimed = false;
1625
1650
  if (record) {
1626
1651
  holderDescription = describeHolder(record);
1627
- const age = lockAgeMs(record, path3, now());
1652
+ const age = lockAgeMs(record, path4, now());
1628
1653
  if (isLockAbandoned(record, age, ttlMs, isProcessAlive, thisHost)) {
1629
1654
  tookOverFrom = record.payload;
1630
- removeAbandoned(path3, record.raw);
1655
+ removeAbandoned(path4, record.raw);
1631
1656
  reclaimed = true;
1632
1657
  }
1633
1658
  }
1634
1659
  if (now() >= deadline) {
1635
1660
  throw new Error(
1636
- `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.`
1637
1662
  );
1638
1663
  }
1639
1664
  if (reclaimed) backoffMs = INITIAL_BACKOFF_MS;
@@ -1897,7 +1922,7 @@ var init_memory_push_cache = __esm({
1897
1922
  });
1898
1923
 
1899
1924
  // src/tools/memory/memory-sync-http.ts
1900
- 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";
1901
1926
  async function pullMemory(controlPlaneUrl, token, memoryDir, fetchFn) {
1902
1927
  const url = `${controlPlaneUrl}/api/v1/agent-config/memory/me`;
1903
1928
  const response = await withRequestTimeout(
@@ -1982,7 +2007,7 @@ async function uploadOne(item, controlPlaneUrl, token, sessionId, fetchFn, deadl
1982
2007
  }
1983
2008
  async function pushMemory(controlPlaneUrl, token, memoryDir, sessionId, fetchFn, options = {}) {
1984
2009
  const empty = { pushed: 0, created: 0, updated: 0, skipped: 0, indexRowsPreserved: 0 };
1985
- if (!existsSync5(memoryDir)) {
2010
+ if (!existsSync6(memoryDir)) {
1986
2011
  return empty;
1987
2012
  }
1988
2013
  const localFiles = listPushableFiles(memoryDir).map((f) => ({
@@ -2076,7 +2101,7 @@ var init_memory_sync_http = __esm({
2076
2101
  });
2077
2102
 
2078
2103
  // src/tools/memory/sync-kill-switch.ts
2079
- import { existsSync as existsSync6, readFileSync as readFileSync12 } from "node:fs";
2104
+ import { existsSync as existsSync7, readFileSync as readFileSync12 } from "node:fs";
2080
2105
  import { homedir as homedir6 } from "node:os";
2081
2106
  import { join as join10 } from "node:path";
2082
2107
  function memorySyncSentinelPath(home) {
@@ -2095,7 +2120,7 @@ function clip(raw) {
2095
2120
  function evaluateMemorySyncKillSwitch(deps = {}) {
2096
2121
  const env = deps.env ?? process.env;
2097
2122
  const home = deps.home ?? homedir6();
2098
- const fileExists = deps.fileExists ?? existsSync6;
2123
+ const fileExists = deps.fileExists ?? existsSync7;
2099
2124
  const readFile3 = deps.readFile ?? ((p) => readFileSync12(p, "utf8"));
2100
2125
  const fired = [];
2101
2126
  const rawEnv = env[MEMORY_SYNC_DISABLE_ENV];
@@ -2141,7 +2166,7 @@ __export(memory_knowledge_bridge_exports, {
2141
2166
  extractMemoryTitle: () => extractMemoryTitle,
2142
2167
  upsertMemoryFilesAsKnowledge: () => upsertMemoryFilesAsKnowledge
2143
2168
  });
2144
- 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";
2145
2170
  function extractMemoryTitle(fileName, content) {
2146
2171
  const frontmatter = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
2147
2172
  if (frontmatter) {
@@ -2156,7 +2181,7 @@ async function upsertMemoryFilesAsKnowledge(options) {
2156
2181
  const { controlPlaneUrl, token, memoryDir, fetchFn, cache, deadline } = options;
2157
2182
  let files;
2158
2183
  try {
2159
- if (!existsSync7(memoryDir)) {
2184
+ if (!existsSync8(memoryDir)) {
2160
2185
  return { attempted: 0, upserted: 0, failed: 0, skipped: 0, failures: [] };
2161
2186
  }
2162
2187
  files = readdirSync5(memoryDir).filter(
@@ -2268,7 +2293,7 @@ __export(sync_config_exports, {
2268
2293
  isNoopSyncReason: () => isNoopSyncReason,
2269
2294
  runMemorySync: () => runMemorySync
2270
2295
  });
2271
- import { existsSync as existsSync8 } from "node:fs";
2296
+ import { existsSync as existsSync9 } from "node:fs";
2272
2297
  import { homedir as homedir7 } from "node:os";
2273
2298
  import { join as join11 } from "node:path";
2274
2299
  function isToolInput22(v) {
@@ -2310,7 +2335,7 @@ async function runMemorySync(action, cwd, sessionId, fetchFn = globalThis.fetch,
2310
2335
  }
2311
2336
  const memoryDir = getMemoryDir(cwd);
2312
2337
  const baseUrl = controlPlaneUrl.replace(/\/+$/, "");
2313
- if (action === "push" && !existsSync8(memoryDir)) {
2338
+ if (action === "push" && !existsSync9(memoryDir)) {
2314
2339
  return {
2315
2340
  synced: true,
2316
2341
  action: "push",
@@ -2431,9 +2456,9 @@ var init_sync_config = __esm({
2431
2456
  });
2432
2457
 
2433
2458
  // src/cli.ts
2434
- import { homedir as homedir8, hostname as hostname2 } from "node:os";
2459
+ import { homedir as homedir9, hostname as hostname2 } from "node:os";
2435
2460
  import { randomUUID as randomUUID6 } from "node:crypto";
2436
- import { join as join14 } from "node:path";
2461
+ import { join as join15 } from "node:path";
2437
2462
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
2438
2463
 
2439
2464
  // src/server.ts
@@ -2815,7 +2840,8 @@ async function handleCheckHollowTest(deps, rawInput, signal) {
2815
2840
  consensus_confidence: engineResult.synthesized_verdict.confidence,
2816
2841
  duration_ms: engineResult.duration_ms,
2817
2842
  consensus_engine_version: engineResult.engine_version,
2818
- ...aggregateEventTokenUsage(engineResult.per_model_verdicts, engineResult.token_usage)
2843
+ ...aggregateEventTokenUsage(engineResult.per_model_verdicts, engineResult.token_usage),
2844
+ ...trajectoryFromEngine(engineResult)
2819
2845
  };
2820
2846
  const payload = {
2821
2847
  verdict: engineResult.synthesized_verdict.verdict,
@@ -2999,7 +3025,8 @@ async function handleVerifyAnswer(deps, rawInput, signal) {
2999
3025
  consensus_confidence: engineResult.synthesized_verdict.confidence,
3000
3026
  duration_ms: engineResult.duration_ms,
3001
3027
  consensus_engine_version: engineResult.engine_version,
3002
- ...aggregateEventTokenUsage(engineResult.per_model_verdicts, engineResult.token_usage)
3028
+ ...aggregateEventTokenUsage(engineResult.per_model_verdicts, engineResult.token_usage),
3029
+ ...trajectoryFromEngine(engineResult)
3003
3030
  };
3004
3031
  const payload = {
3005
3032
  verdict: engineResult.synthesized_verdict.verdict,
@@ -3268,7 +3295,8 @@ async function handleConsensusJudgment(deps, rawInput, signal) {
3268
3295
  consensus_engine_version: engineResult.engine_version,
3269
3296
  per_model_verdicts: perModelForEvent,
3270
3297
  synthesized_verdict: synthForEvent,
3271
- ...aggregateEventTokenUsage(engineResult.per_model_verdicts, engineResult.token_usage)
3298
+ ...aggregateEventTokenUsage(engineResult.per_model_verdicts, engineResult.token_usage),
3299
+ ...trajectoryFromEngine(engineResult)
3272
3300
  };
3273
3301
  const payload = {
3274
3302
  verdict: engineResult.synthesized_verdict.verdict,
@@ -3483,7 +3511,8 @@ async function handleArchitectureReview(deps, rawInput, signal) {
3483
3511
  consensus_confidence: engineResult.synthesized_verdict.confidence,
3484
3512
  duration_ms: engineResult.duration_ms,
3485
3513
  consensus_engine_version: engineResult.engine_version,
3486
- ...aggregateEventTokenUsage(engineResult.per_model_verdicts, engineResult.token_usage)
3514
+ ...aggregateEventTokenUsage(engineResult.per_model_verdicts, engineResult.token_usage),
3515
+ ...trajectoryFromEngine(engineResult)
3487
3516
  };
3488
3517
  const escalationRequired = engineResult.escalation_required === true || engineResult.escalation_required === void 0 && engineResult.synthesized_verdict.dissent_summary !== null;
3489
3518
  const escalationReason = engineResult.escalation_reason ?? engineResult.synthesized_verdict.dissent_summary ?? "";
@@ -4971,7 +5000,8 @@ Produce the JSON dispatch plan now.`;
4971
5000
  consensus_engine_version: engineResult.engine_version,
4972
5001
  per_model_verdicts: toEventPerModelVerdicts(engineResult.per_model_verdicts),
4973
5002
  synthesized_verdict: toEventSynthesizedVerdict(engineResult.synthesized_verdict),
4974
- ...aggregateEventTokenUsage(engineResult.per_model_verdicts, engineResult.token_usage)
5003
+ ...aggregateEventTokenUsage(engineResult.per_model_verdicts, engineResult.token_usage),
5004
+ ...trajectoryFromEngine(engineResult)
4975
5005
  };
4976
5006
  deps.events.append(enrichedEvent);
4977
5007
  return jsonContent(envelope);
@@ -4984,10 +5014,10 @@ init_common();
4984
5014
  init_auth_token_source();
4985
5015
  init_credential_store();
4986
5016
  var AdminCallableError = class extends Error {
4987
- constructor(status, path3, message) {
5017
+ constructor(status, path4, message) {
4988
5018
  super(message);
4989
5019
  this.status = status;
4990
- this.path = path3;
5020
+ this.path = path4;
4991
5021
  this.name = "AdminCallableError";
4992
5022
  }
4993
5023
  status;
@@ -5014,21 +5044,21 @@ var HttpAdminCallableClient = class {
5014
5044
  this.fetchFn = config.fetchFn ?? globalThis.fetch;
5015
5045
  this.readOnly = config.readOnly ?? false;
5016
5046
  }
5017
- async invoke(path3, body, opts) {
5018
- if (!path3.startsWith("/")) {
5047
+ async invoke(path4, body, opts) {
5048
+ if (!path4.startsWith("/")) {
5019
5049
  throw new Error(
5020
- `HttpAdminCallableClient.invoke: path must start with '/', got '${path3}'`
5050
+ `HttpAdminCallableClient.invoke: path must start with '/', got '${path4}'`
5021
5051
  );
5022
5052
  }
5023
5053
  const token = await this.tokenSource.getToken();
5024
5054
  if (!token) {
5025
5055
  throw new AdminCallableError(
5026
5056
  401,
5027
- path3,
5028
- `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)`
5029
5059
  );
5030
5060
  }
5031
- const url = `${this.baseUrl}${path3}`;
5061
+ const url = `${this.baseUrl}${path4}`;
5032
5062
  const response = await this.fetchFn(url, {
5033
5063
  method: "POST",
5034
5064
  headers: {
@@ -5041,8 +5071,8 @@ var HttpAdminCallableClient = class {
5041
5071
  if (response.status < 200 || response.status >= 300) {
5042
5072
  throw new AdminCallableError(
5043
5073
  response.status,
5044
- path3,
5045
- `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)}`
5046
5076
  );
5047
5077
  }
5048
5078
  let parsed;
@@ -5051,23 +5081,23 @@ var HttpAdminCallableClient = class {
5051
5081
  } catch {
5052
5082
  throw new AdminCallableError(
5053
5083
  response.status,
5054
- path3,
5055
- `admin proxy ${path3} returned non-JSON body`
5084
+ path4,
5085
+ `admin proxy ${path4} returned non-JSON body`
5056
5086
  );
5057
5087
  }
5058
5088
  if (typeof parsed !== "object" || parsed === null) {
5059
5089
  throw new AdminCallableError(
5060
5090
  response.status,
5061
- path3,
5062
- `admin proxy ${path3} response not an object`
5091
+ path4,
5092
+ `admin proxy ${path4} response not an object`
5063
5093
  );
5064
5094
  }
5065
5095
  const obj = parsed;
5066
5096
  if (obj["ok"] !== true) {
5067
5097
  throw new AdminCallableError(
5068
5098
  response.status,
5069
- path3,
5070
- `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)}`
5071
5101
  );
5072
5102
  }
5073
5103
  if (opts?.rawEnvelope) {
@@ -5080,8 +5110,8 @@ var HttpAdminCallableClient = class {
5080
5110
  if (!("result" in obj)) {
5081
5111
  throw new AdminCallableError(
5082
5112
  response.status,
5083
- path3,
5084
- `admin proxy ${path3} response missing .result field`
5113
+ path4,
5114
+ `admin proxy ${path4} response missing .result field`
5085
5115
  );
5086
5116
  }
5087
5117
  return obj["result"];
@@ -5673,6 +5703,7 @@ async function handleRejectAndRetry(deps, rawInput, _signal) {
5673
5703
  init_common();
5674
5704
  var TOOL_NAME18 = "vo_review_merge";
5675
5705
  var LIST_PATH = "/api/v1/admin/pr/list";
5706
+ var DEFAULT_REVIEW_REPO = "Algosuite-ai/Nexus";
5676
5707
  var ENGINE_GATE = "final-deep-verify";
5677
5708
  var EVENT_GATE = "merge-review";
5678
5709
  var UNAVAILABLE_REASON = "vo_review_merge needs cloud mode to fetch PR context \u2014 set VO_CONTROL_PLANE_URL + VO_CONTROL_PLANE_ADMIN_TOKEN in the MCP env. (It is read-only; it never merges.)";
@@ -5737,6 +5768,11 @@ async function handleReviewMerge(deps, rawInput, signal) {
5737
5768
  if (rawInput.notes !== void 0) normalizedInput.notes = rawInput.notes;
5738
5769
  const inputJson = JSON.stringify(normalizedInput);
5739
5770
  const key = deps.cache.keyFor(TOOL_NAME18, normalizedInput);
5771
+ const subject = {
5772
+ ...subjectFromEnv() ?? {},
5773
+ repo: subjectFromEnv()?.repo ?? DEFAULT_REVIEW_REPO,
5774
+ pr_number: prNumber
5775
+ };
5740
5776
  const baseEvent = buildBaseEvent({
5741
5777
  tool: TOOL_NAME18,
5742
5778
  gateType: EVENT_GATE,
@@ -5744,7 +5780,8 @@ async function handleReviewMerge(deps, rawInput, signal) {
5744
5780
  inputExcerpt: inputJson.slice(0, 300),
5745
5781
  inputSizeBytes: bytesOf(inputJson),
5746
5782
  session: deps.session,
5747
- now: deps.now()
5783
+ now: deps.now(),
5784
+ subject
5748
5785
  });
5749
5786
  const emit = (payload2, eventExtra) => {
5750
5787
  deps.events.append(eventExtra ? { ...baseEvent, ...eventExtra } : baseEvent);
@@ -5846,7 +5883,8 @@ async function handleReviewMerge(deps, rawInput, signal) {
5846
5883
  consensus_engine_version: result.engine_version,
5847
5884
  per_model_verdicts: perModel,
5848
5885
  synthesized_verdict: synth,
5849
- ...aggregateEventTokenUsage(result.per_model_verdicts, result.token_usage)
5886
+ ...aggregateEventTokenUsage(result.per_model_verdicts, result.token_usage),
5887
+ ...trajectoryFromEngine(result)
5850
5888
  });
5851
5889
  }
5852
5890
 
@@ -6107,7 +6145,7 @@ init_common();
6107
6145
  import { spawn } from "node:child_process";
6108
6146
  import { homedir as homedir5 } from "node:os";
6109
6147
  import { join as join7 } from "node:path";
6110
- 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";
6111
6149
 
6112
6150
  // src/swarm/tier-binding.ts
6113
6151
  var SWARM_TIERS = Object.freeze([
@@ -6313,10 +6351,10 @@ function sanitizeSwarmId(raw) {
6313
6351
  return id;
6314
6352
  }
6315
6353
  var CEILING_FILE = "ceiling.json";
6316
- function createExclusive(path3, contents) {
6354
+ function createExclusive(path4, contents) {
6317
6355
  let fd;
6318
6356
  try {
6319
- fd = openSync(path3, "wx");
6357
+ fd = openSync(path4, "wx");
6320
6358
  } catch {
6321
6359
  return false;
6322
6360
  }
@@ -6331,18 +6369,18 @@ function capToCents(cap) {
6331
6369
  return isPositiveCap(cap) ? Math.round(cap * 100) : 0;
6332
6370
  }
6333
6371
  function readOrRecordLedgerHead(swarmDir, proposedCeiling, proposedCapCents, nowIso) {
6334
- const path3 = join6(swarmDir, CEILING_FILE);
6372
+ const path4 = join6(swarmDir, CEILING_FILE);
6335
6373
  const head = JSON.stringify({
6336
6374
  ceiling: proposedCeiling,
6337
6375
  cap_cents: proposedCapCents,
6338
6376
  recorded_at: nowIso
6339
6377
  });
6340
- if (createExclusive(path3, head)) {
6378
+ if (createExclusive(path4, head)) {
6341
6379
  return { ceiling: proposedCeiling, capCents: proposedCapCents };
6342
6380
  }
6343
6381
  let parsed;
6344
6382
  try {
6345
- parsed = JSON.parse(readFileSync6(path3, "utf8"));
6383
+ parsed = JSON.parse(readFileSync6(path4, "utf8"));
6346
6384
  } catch {
6347
6385
  return null;
6348
6386
  }
@@ -6422,6 +6460,288 @@ var claimSpawnSlot = ({ swarmId, proposedCeiling, proposedCapUsd, dir, nowIso })
6422
6460
  };
6423
6461
  };
6424
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
+
6425
6745
  // src/tools/session/spawn-successor.ts
6426
6746
  var TOOL_NAME20 = "vo_spawn_successor";
6427
6747
  var MAX_HANDOFF_BYTES = 64e3;
@@ -6453,7 +6773,7 @@ var inputSchema20 = {
6453
6773
  additionalProperties: false
6454
6774
  };
6455
6775
  var RETIRED_COUNTER_INPUT = "spawns_so_far";
6456
- 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.";
6457
6777
  function isToolInput20(v) {
6458
6778
  if (typeof v !== "object" || v === null) return false;
6459
6779
  const o = v;
@@ -6471,7 +6791,7 @@ function retiredCounterRefusal(v) {
6471
6791
  }
6472
6792
  function newestHandoff(dir = join7(homedir5(), ".vo", "handoffs")) {
6473
6793
  try {
6474
- 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);
6475
6795
  return entries.length > 0 && entries[0] ? join7(dir, entries[0].f) : null;
6476
6796
  } catch {
6477
6797
  return null;
@@ -6509,97 +6829,14 @@ function buildSuccessorPrompt(handoffMarkdown, goal) {
6509
6829
  if (goal && goal.trim().length > 0) lines.push("", `OPERATOR GOAL OVERRIDE: ${goal.trim()}`);
6510
6830
  return lines.join("\n");
6511
6831
  }
6512
- function buildSuccessorArgs(maxTurns) {
6513
- const args = ["-p", "--permission-mode", "acceptEdits"];
6514
- if (Number.isInteger(maxTurns) && maxTurns > 0) {
6515
- args.push("--max-turns", String(maxTurns));
6516
- }
6517
- return args;
6518
- }
6519
- function resolveSpawnPlan(env, input, nowIso, platform = process.platform, claim = claimSpawnSlot) {
6520
- const rawBinding = env[SWARM_TIER_BINDING_ENV];
6521
- const hasBinding = typeof rawBinding === "string" && rawBinding.trim().length > 0;
6522
- if (!hasBinding) {
6523
- const explicit = input.agent?.trim();
6524
- if (explicit) {
6525
- const resolved2 = resolveSuccessorLaunch({ agent: explicit, maxTurns: input.max_turns, platform });
6526
- if (!resolved2.ok) return { ok: false, reason: resolved2.reason, tier: "unbound" };
6527
- return {
6528
- ok: true,
6529
- bin: resolved2.bin,
6530
- args: resolved2.args,
6531
- agent: resolved2.agent,
6532
- tier: "unbound",
6533
- bound: false,
6534
- env: {},
6535
- slot: null,
6536
- capUsd: null,
6537
- capRemainingUsd: null
6538
- };
6539
- }
6540
- return {
6541
- ok: true,
6542
- bin: "claude",
6543
- args: buildSuccessorArgs(input.max_turns),
6544
- agent: "claude",
6545
- tier: "unbound",
6546
- bound: false,
6547
- env: {},
6548
- slot: null,
6549
- capUsd: null,
6550
- capRemainingUsd: null
6551
- };
6552
- }
6553
- const binding = inheritSwarmTierBinding(env, nowIso);
6554
- const admission = admitSubagentSpawn(binding);
6555
- if (!admission.allowed) {
6556
- return { ok: false, reason: admission.reason, tier: binding.tier };
6557
- }
6558
- const agentRefusal = agentBindingRefusal(binding, input.agent);
6559
- if (agentRefusal !== null) return { ok: false, reason: agentRefusal, tier: binding.tier };
6560
- const resolved = resolveSuccessorLaunch({
6561
- agent: binding.agent,
6562
- maxTurns: input.max_turns,
6563
- platform
6564
- });
6565
- if (!resolved.ok) return { ok: false, reason: resolved.reason, tier: binding.tier };
6566
- const slot = claim({
6567
- swarmId: binding.swarm_id,
6568
- proposedCeiling: binding.subagent_budget,
6569
- // The spend-cap POOL, recorded once per swarm exactly like the ceiling. The
6570
- // child's cap is DEBITED from it below, not recomputed from this binding.
6571
- proposedCapUsd: binding.spend_cap_usd,
6572
- dir: resolveLedgerDir(env),
6573
- nowIso
6574
- });
6575
- if (!slot.ok) return { ok: false, reason: slot.reason, tier: binding.tier };
6576
- return {
6577
- ok: true,
6578
- bin: resolved.bin,
6579
- args: resolved.args,
6580
- agent: resolved.agent,
6581
- tier: binding.tier,
6582
- bound: true,
6583
- // Re-export the same TIER with a DECREMENTED budget and the spend cap the
6584
- // ledger just DEBITED. Exporting the binding verbatim (what this did before
6585
- // #9312) meant the child re-read the full budget and every generation
6586
- // restarted at zero. Recomputing the cap from THIS binding (what #9312 did)
6587
- // bounded a chain but not a tree: three siblings each re-halved the parent's
6588
- // untouched $50 and walked away with $75 between them.
6589
- env: childBindingEnvFragment(binding, slot.capUsd),
6590
- slot: slot.slot,
6591
- capUsd: slot.capUsd,
6592
- capRemainingUsd: slot.capRemainingUsd
6593
- };
6594
- }
6595
- async function handleSpawnSuccessor(_deps, rawInput, _signal, spawnImpl = spawn) {
6832
+ async function handleSpawnSuccessor(_deps, rawInput, _signal, spawnImpl = spawn, overrides = {}) {
6596
6833
  const retired = retiredCounterRefusal(rawInput);
6597
6834
  if (retired !== null) throw invalidParams(TOOL_NAME20, retired);
6598
6835
  if (!isToolInput20(rawInput)) {
6599
6836
  throw invalidParams(TOOL_NAME20, "invalid input. Optional: { handoff_path, goal, cwd, max_turns, agent }.");
6600
6837
  }
6601
6838
  const handoffPath = rawInput.handoff_path?.trim() || newestHandoff();
6602
- if (!handoffPath || !existsSync4(handoffPath)) {
6839
+ if (!handoffPath || !existsSync5(handoffPath)) {
6603
6840
  return jsonContent({
6604
6841
  tool: TOOL_NAME20,
6605
6842
  schema_version: 1,
@@ -6624,27 +6861,52 @@ async function handleSpawnSuccessor(_deps, rawInput, _signal, spawnImpl = spawn)
6624
6861
  }
6625
6862
  });
6626
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
+ }
6627
6883
  const logDir = process.env["VO_MCP_SUCCESSOR_LOG_DIR"]?.trim() || join7(homedir5(), ".vo", "successors");
6628
6884
  mkdirSync4(logDir, { recursive: true });
6629
6885
  const logPath = join7(logDir, `successor-${Date.now()}.log`);
6630
6886
  const logFd = openSync2(logPath, "a");
6631
- const child = spawnImpl(plan.bin, [...plan.args], {
6887
+ const child = spawnImpl(resolvedBin, [...plan.args], {
6632
6888
  cwd: rawInput.cwd?.trim() || process.cwd(),
6633
6889
  detached: true,
6634
6890
  stdio: ["pipe", logFd, logFd],
6635
- // Windows: the agent CLIs are .cmd shims they need a shell to resolve.
6636
- // The prompt goes via STDIN below, never argv, so the shell never sees it.
6637
- 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,
6638
6896
  windowsHide: true,
6897
+ windowsVerbatimArguments: false,
6639
6898
  // Carry the SAME binding to the child. Without this the successor inherits
6640
6899
  // no tier and re-resolves its own — which is the split-payer defect one
6641
6900
  // generation down.
6642
6901
  ...plan.bound ? { env: { ...process.env, ...plan.env } } : {}
6643
6902
  });
6903
+ closeSync2(logFd);
6644
6904
  let spawnError = null;
6645
6905
  child.on("error", (e) => {
6646
6906
  spawnError = e.message;
6647
6907
  });
6908
+ child.stdin.on?.("error", () => {
6909
+ });
6648
6910
  try {
6649
6911
  child.stdin.write(prompt);
6650
6912
  child.stdin.end();
@@ -6652,10 +6914,40 @@ async function handleSpawnSuccessor(_deps, rawInput, _signal, spawnImpl = spawn)
6652
6914
  }
6653
6915
  child.unref();
6654
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
+ }
6655
6947
  return jsonContent({
6656
6948
  tool: TOOL_NAME20,
6657
6949
  schema_version: 1,
6658
- payload: spawnError ? { spawned: false, reason: `spawn failed: ${spawnError}`, agent: plan.agent, tier: plan.tier, handoff_path: handoffPath } : {
6950
+ payload: {
6659
6951
  spawned: true,
6660
6952
  pid: child.pid ?? null,
6661
6953
  log_path: logPath,
@@ -6667,7 +6959,10 @@ async function handleSpawnSuccessor(_deps, rawInput, _signal, spawnImpl = spawn)
6667
6959
  // The debit, surfaced so an operator can reconcile a fan-out's spend
6668
6960
  // against the pool without reading the ledger directory by hand.
6669
6961
  ledger_cap_usd: plan.capUsd,
6670
- 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
6671
6966
  }
6672
6967
  });
6673
6968
  }
@@ -6851,10 +7146,10 @@ async function getCloudAuth(fetchFn) {
6851
7146
  if (!token) return { ok: false, reason: "Failed to obtain auth token. Run `vo-mcp login` again." };
6852
7147
  return { ok: true, controlPlaneUrl, token };
6853
7148
  }
6854
- async function callPrivateKnowledge(path3, body, fetchFn) {
7149
+ async function callPrivateKnowledge(path4, body, fetchFn) {
6855
7150
  const auth = await getCloudAuth(fetchFn);
6856
7151
  if (!auth.ok) return { ok: false, reason: auth.reason };
6857
- const response = await fetchFn(`${auth.controlPlaneUrl}${path3}`, {
7152
+ const response = await fetchFn(`${auth.controlPlaneUrl}${path4}`, {
6858
7153
  method: "POST",
6859
7154
  headers: {
6860
7155
  authorization: `Bearer ${auth.token}`,
@@ -7072,11 +7367,11 @@ async function handleHqWhiteboardRead(_deps, rawInput, signal) {
7072
7367
  }
7073
7368
 
7074
7369
  // src/tools/skills/skill-corpus.ts
7075
- import { existsSync as existsSync9, statSync as statSync6 } from "node:fs";
7370
+ import { existsSync as existsSync10, statSync as statSync7 } from "node:fs";
7076
7371
  import { dirname as dirname5, isAbsolute, join as join13, resolve as resolve2 } from "node:path";
7077
7372
 
7078
7373
  // ../skill-registry/src/loader.ts
7079
- 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";
7080
7375
  import { join as join12 } from "node:path";
7081
7376
  var InvalidSkillFrontmatterError = class extends Error {
7082
7377
  constructor(skillFile, reason) {
@@ -7133,7 +7428,7 @@ function loadSkillsFromDir(skillsDir) {
7133
7428
  const entryPath = join12(skillsDir, entry);
7134
7429
  let stat;
7135
7430
  try {
7136
- stat = statSync5(entryPath);
7431
+ stat = statSync6(entryPath);
7137
7432
  } catch {
7138
7433
  continue;
7139
7434
  }
@@ -7183,12 +7478,12 @@ function resolveSkillsDir(env = process.env, startDir = process.cwd()) {
7183
7478
  const override = env.VO_SKILLS_DIR;
7184
7479
  if (typeof override === "string" && override.length > 0) {
7185
7480
  const abs = isAbsolute(override) ? override : resolve2(startDir, override);
7186
- return existsSync9(abs) && statSync6(abs).isDirectory() ? abs : null;
7481
+ return existsSync10(abs) && statSync7(abs).isDirectory() ? abs : null;
7187
7482
  }
7188
7483
  let dir = resolve2(startDir);
7189
7484
  for (let i = 0; i < MAX_WALK_UP_LEVELS; i += 1) {
7190
7485
  const candidate = join13(dir, ".claude", "skills");
7191
- if (existsSync9(candidate) && statSync6(candidate).isDirectory()) return candidate;
7486
+ if (existsSync10(candidate) && statSync7(candidate).isDirectory()) return candidate;
7192
7487
  const parent = dirname5(dir);
7193
7488
  if (parent === dir) break;
7194
7489
  dir = parent;
@@ -8396,6 +8691,109 @@ function createConsensusFallbackClient(primary, fallback, options = {}) {
8396
8691
  };
8397
8692
  }
8398
8693
 
8694
+ // src/consensus/shadow-client.ts
8695
+ import { appendFileSync as appendFileSync2, chmodSync as chmodSync4, mkdirSync as mkdirSync8, renameSync, statSync as statSync8 } from "node:fs";
8696
+ import { homedir as homedir8 } from "node:os";
8697
+ import { dirname as dirname7, join as join14 } from "node:path";
8698
+ var DEFAULT_SHADOW_TIMEOUT_MS = 2e4;
8699
+ var SHADOW_RECEIPT_MAX_BYTES = 20 * 1024 * 1024;
8700
+ function defaultShadowReceiptPath(env = process.env) {
8701
+ const p = (env["VO_MCP_MOAT_SHADOW_PATH"] ?? "").trim();
8702
+ return p || join14(homedir8(), ".claude", "vo-mcp-moat-shadow.jsonl");
8703
+ }
8704
+ function appendShadowReceipt(receipt, path4 = defaultShadowReceiptPath()) {
8705
+ try {
8706
+ mkdirSync8(dirname7(path4), { recursive: true, mode: 448 });
8707
+ try {
8708
+ if (statSync8(path4).size > SHADOW_RECEIPT_MAX_BYTES) renameSync(path4, `${path4}.1`);
8709
+ } catch {
8710
+ }
8711
+ appendFileSync2(path4, `${JSON.stringify(receipt)}
8712
+ `, "utf8");
8713
+ try {
8714
+ chmodSync4(path4, 384);
8715
+ } catch {
8716
+ }
8717
+ } catch {
8718
+ }
8719
+ }
8720
+ function summarize(result) {
8721
+ if (result.ok) {
8722
+ return {
8723
+ ok: true,
8724
+ verdict: result.synthesized_verdict.verdict,
8725
+ confidence: result.synthesized_verdict.confidence,
8726
+ ...result.receipt_id ? { receipt_id: result.receipt_id } : {}
8727
+ };
8728
+ }
8729
+ return { ok: false, reason: result.reason };
8730
+ }
8731
+ async function runMoat(shadow, request, timeoutMs, linkCallerSignal) {
8732
+ const callerSignal = linkCallerSignal ? request.signal : void 0;
8733
+ if (callerSignal?.aborted) return { ok: false, reason: CANCELLED_REASON };
8734
+ const controller = new AbortController();
8735
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
8736
+ const onCallerAbort = () => controller.abort();
8737
+ callerSignal?.addEventListener("abort", onCallerAbort, { once: true });
8738
+ try {
8739
+ const { signal: _ignored, ...rest } = request;
8740
+ void _ignored;
8741
+ return await shadow.run({ ...rest, signal: controller.signal });
8742
+ } catch (err) {
8743
+ return { ok: false, reason: `moat-shadow-threw: ${err instanceof Error ? err.message : String(err)}`.slice(0, 200) };
8744
+ } finally {
8745
+ clearTimeout(timer);
8746
+ callerSignal?.removeEventListener("abort", onCallerAbort);
8747
+ }
8748
+ }
8749
+ function createConsensusShadowClient(primary, shadow, options = {}) {
8750
+ const authoritative = options.authoritative === true;
8751
+ const timeoutMs = options.timeoutMs ?? DEFAULT_SHADOW_TIMEOUT_MS;
8752
+ const now = options.now ?? (() => Date.now());
8753
+ const random = options.random ?? Math.random;
8754
+ const samplePct = Math.min(100, Math.max(0, options.samplePct ?? 100));
8755
+ const onReceipt = options.onReceipt ?? ((r) => appendShadowReceipt(r));
8756
+ function receiptFor(request, local, moat, startedAt) {
8757
+ const l = summarize(local);
8758
+ const m = summarize(moat);
8759
+ return {
8760
+ ts: new Date(now()).toISOString(),
8761
+ gate_type: request.gate_type,
8762
+ mode: authoritative ? "authoritative" : "shadow",
8763
+ local: l,
8764
+ moat: m,
8765
+ agree: l.ok && m.ok ? l.verdict === m.verdict : null,
8766
+ duration_ms: Math.max(0, now() - startedAt)
8767
+ };
8768
+ }
8769
+ return {
8770
+ async run(request) {
8771
+ const startedAt = now();
8772
+ const primaryResult = await primary.run(request);
8773
+ if (request.signal?.aborted || !primaryResult.ok && primaryResult.reason === CANCELLED_REASON) {
8774
+ return primaryResult;
8775
+ }
8776
+ if (!primaryResult.ok || primaryResult.receipt_id) return primaryResult;
8777
+ if (authoritative) {
8778
+ const moatResult = await runMoat(shadow, request, timeoutMs, true);
8779
+ try {
8780
+ onReceipt(receiptFor(request, primaryResult, moatResult, startedAt));
8781
+ } catch {
8782
+ }
8783
+ return moatResult.ok ? moatResult : primaryResult;
8784
+ }
8785
+ if (samplePct < 100 && random() * 100 >= samplePct) return primaryResult;
8786
+ void runMoat(shadow, request, timeoutMs, false).then((moatResult) => {
8787
+ try {
8788
+ onReceipt(receiptFor(request, primaryResult, moatResult, startedAt));
8789
+ } catch {
8790
+ }
8791
+ });
8792
+ return primaryResult;
8793
+ }
8794
+ };
8795
+ }
8796
+
8399
8797
  // src/consensus/local-credential-env.ts
8400
8798
  import { createRequire as createRequire2 } from "node:module";
8401
8799
  var require2 = createRequire2(import.meta.url);
@@ -8466,11 +8864,11 @@ function processCapture(rawBody, expectedState, store) {
8466
8864
  return { ok: false, httpStatus: 400, error: "login response missing refresh_token / api_key" };
8467
8865
  }
8468
8866
  const email = typeof data.email === "string" && data.email.trim() ? data.email.trim() : void 0;
8469
- const path3 = store({ refresh_token: refresh, api_key: apiKey, ...email ? { email } : {} });
8867
+ const path4 = store({ refresh_token: refresh, api_key: apiKey, ...email ? { email } : {} });
8470
8868
  return {
8471
8869
  ok: true,
8472
8870
  httpStatus: 200,
8473
- result: { ...email ? { email } : {}, credentialPath: path3 },
8871
+ result: { ...email ? { email } : {}, credentialPath: path4 },
8474
8872
  captured: { refresh_token: refresh, api_key: apiKey, ...email ? { email } : {} }
8475
8873
  };
8476
8874
  }
@@ -8558,8 +8956,8 @@ async function runLogin(opts = {}) {
8558
8956
  }
8559
8957
  } catch {
8560
8958
  }
8561
- const path3 = writeNow(cred);
8562
- result = { ...capt.email ? { email: capt.email } : {}, credentialPath: path3 };
8959
+ const path4 = writeNow(cred);
8960
+ result = { ...capt.email ? { email: capt.email } : {}, credentialPath: path4 };
8563
8961
  }
8564
8962
  res.writeHead(outcome.httpStatus, { "content-type": "text/html; charset=utf-8" });
8565
8963
  res.end(outcome.ok ? "<h2>AlgoHQ login complete \u2014 you can close this tab.</h2>" : `<h2>Login failed: ${outcome.error}</h2>`);
@@ -8629,7 +9027,7 @@ init_common();
8629
9027
  function defaultCacheDbPath() {
8630
9028
  const env = process.env["VO_MCP_DB_PATH"];
8631
9029
  if (env && env.length > 0) return env;
8632
- return join14(homedir8(), ".claude", "vo-mcp-cache.db");
9030
+ return join15(homedir9(), ".claude", "vo-mcp-cache.db");
8633
9031
  }
8634
9032
  async function probeEngineVersion() {
8635
9033
  try {
@@ -8717,6 +9115,16 @@ async function main() {
8717
9115
  consensus = createConsensusFallbackClient(localConsensus, cloudConsensus, {
8718
9116
  onFallback: (reason) => console.error(`[vo-mcp] local consensus unavailable (${reason}); using cloud moat fallback`)
8719
9117
  });
9118
+ const moatAuthoritative = process.env["VO_CONSENSUS_MOAT_AUTHORITATIVE"] === "1";
9119
+ if (moatAuthoritative || process.env["VO_CONSENSUS_MOAT_SHADOW"] === "1") {
9120
+ const pctRaw = Number(process.env["VO_CONSENSUS_MOAT_SHADOW_PCT"] ?? "100");
9121
+ const samplePct = Number.isFinite(pctRaw) ? Math.min(100, Math.max(0, pctRaw)) : 100;
9122
+ const shadowedLocal = createConsensusShadowClient(localConsensus, cloudConsensus, { authoritative: moatAuthoritative, samplePct });
9123
+ consensus = createConsensusFallbackClient(shadowedLocal, cloudConsensus, {
9124
+ onFallback: (reason) => console.error(`[vo-mcp] local consensus unavailable (${reason}); using cloud moat fallback`)
9125
+ });
9126
+ console.error(`[vo-mcp] moat ${moatAuthoritative ? "AUTHORITATIVE" : `SHADOW (${samplePct}%)`} active \u2014 local verdicts also reach the moat verify path (receipts: ${defaultShadowReceiptPath()})`);
9127
+ }
8720
9128
  } else if (!testClient && cloudConsensus) {
8721
9129
  console.error("[vo-mcp] fewer than 2 linked local providers; cloud moat consensus active");
8722
9130
  consensus = cloudConsensus;
@@ -8785,12 +9193,12 @@ if (process.argv[2] === "login") {
8785
9193
  const sessionId = randomUUID6();
8786
9194
  const appendSyncLog = async (line) => {
8787
9195
  try {
8788
- const { appendFileSync: appendFileSync2, mkdirSync: mkdirSync8 } = await import("node:fs");
8789
- const { join: join15 } = await import("node:path");
8790
- const { homedir: homedir9 } = await import("node:os");
8791
- const dir = join15(homedir9(), ".claude");
8792
- mkdirSync8(dir, { recursive: true });
8793
- appendFileSync2(join15(dir, "vo-mcp-sync.log"), `${line}
9196
+ const { appendFileSync: appendFileSync3, mkdirSync: mkdirSync9 } = await import("node:fs");
9197
+ const { join: join16 } = await import("node:path");
9198
+ const { homedir: homedir10 } = await import("node:os");
9199
+ const dir = join16(homedir10(), ".claude");
9200
+ mkdirSync9(dir, { recursive: true });
9201
+ appendFileSync3(join16(dir, "vo-mcp-sync.log"), `${line}
8794
9202
  `, "utf8");
8795
9203
  } catch {
8796
9204
  }