@algosuite/vo-mcp 0.2.0-beta.4 → 0.2.0-beta.40

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
@@ -1079,6 +1079,42 @@ function toEventPerModelVerdicts(src) {
1079
1079
  };
1080
1080
  });
1081
1081
  }
1082
+ function aggregateEventTokenUsage(src, engineUsage) {
1083
+ if (engineUsage !== void 0) {
1084
+ const hasIn = Object.keys(engineUsage.per_model_tokens_in).length > 0;
1085
+ const hasOut = Object.keys(engineUsage.per_model_tokens_out).length > 0;
1086
+ return {
1087
+ per_model_tokens_in: hasIn ? engineUsage.per_model_tokens_in : null,
1088
+ per_model_tokens_out: hasOut ? engineUsage.per_model_tokens_out : null,
1089
+ total_cost_usd: engineUsage.cost_micro_usd === null ? null : engineUsage.cost_micro_usd / 1e6
1090
+ };
1091
+ }
1092
+ const tokensIn = {};
1093
+ const tokensOut = {};
1094
+ let anyTokensIn = false;
1095
+ let anyTokensOut = false;
1096
+ let costMicroUsd = 0;
1097
+ let anyCost = false;
1098
+ for (const v of src) {
1099
+ if (typeof v.input_tokens === "number") {
1100
+ tokensIn[v.model] = (tokensIn[v.model] ?? 0) + v.input_tokens;
1101
+ anyTokensIn = true;
1102
+ }
1103
+ if (typeof v.output_tokens === "number") {
1104
+ tokensOut[v.model] = (tokensOut[v.model] ?? 0) + v.output_tokens;
1105
+ anyTokensOut = true;
1106
+ }
1107
+ if (typeof v.cost_micro_usd === "number") {
1108
+ costMicroUsd += v.cost_micro_usd;
1109
+ anyCost = true;
1110
+ }
1111
+ }
1112
+ return {
1113
+ per_model_tokens_in: anyTokensIn ? tokensIn : null,
1114
+ per_model_tokens_out: anyTokensOut ? tokensOut : null,
1115
+ total_cost_usd: anyCost ? costMicroUsd / 1e6 : null
1116
+ };
1117
+ }
1082
1118
  function toEventSynthesizedVerdict(src) {
1083
1119
  return {
1084
1120
  verdict: src.verdict,
@@ -1261,6 +1297,7 @@ __export(credential_store_exports, {
1261
1297
  KEYCHAIN_LOCATION: () => KEYCHAIN_LOCATION,
1262
1298
  credentialPath: () => credentialPath,
1263
1299
  readStoredCredential: () => readStoredCredential,
1300
+ readStoredCredentialKeychainOnly: () => readStoredCredentialKeychainOnly,
1264
1301
  writeStoredCredential: () => writeStoredCredential
1265
1302
  });
1266
1303
  import { homedir as homedir3 } from "node:os";
@@ -1319,6 +1356,11 @@ function readStoredCredential(env = process.env, keychain = realKeychain) {
1319
1356
  }
1320
1357
  return readFromFile(env);
1321
1358
  }
1359
+ function readStoredCredentialKeychainOnly(env = process.env, keychain = realKeychain) {
1360
+ if (!keychainEnabled(env, keychain)) return null;
1361
+ const raw = keychain.get();
1362
+ return raw ? deserialize(raw) : null;
1363
+ }
1322
1364
  function deleteFile(env) {
1323
1365
  try {
1324
1366
  rmSync(credentialPath(env), { force: true });
@@ -1368,43 +1410,505 @@ var init_credential_store = __esm({
1368
1410
  }
1369
1411
  });
1370
1412
 
1371
- // src/tools/memory/sync-config.ts
1372
- var sync_config_exports = {};
1373
- __export(sync_config_exports, {
1374
- TOOL_NAME: () => TOOL_NAME22,
1375
- deriveProjectSlug: () => deriveProjectSlug,
1376
- description: () => description22,
1377
- getMemoryDir: () => getMemoryDir,
1378
- handleSyncConfig: () => handleSyncConfig,
1379
- inputSchema: () => inputSchema22,
1380
- isNoopSyncReason: () => isNoopSyncReason,
1381
- runMemorySync: () => runMemorySync
1413
+ // src/tools/memory/safe-memory-file.ts
1414
+ import { resolve, sep } from "node:path";
1415
+ function isSafeMemoryFileName(fileName) {
1416
+ return fileName.length <= 200 && fileName.trim() === fileName && !fileName.includes("/") && !fileName.includes("\\") && !fileName.includes(":") && SAFE_MEMORY_FILE_RE.test(fileName);
1417
+ }
1418
+ function resolveMemoryFilePath(memoryDir, fileName) {
1419
+ if (!isSafeMemoryFileName(fileName)) {
1420
+ throw new Error(`unsafe memory file_name: ${fileName.slice(0, 80)}`);
1421
+ }
1422
+ const root = resolve(memoryDir);
1423
+ const filePath = resolve(root, fileName);
1424
+ const rootPrefix = root.endsWith(sep) ? root : `${root}${sep}`;
1425
+ if (filePath !== root && !filePath.startsWith(rootPrefix)) {
1426
+ throw new Error(`memory file path escapes memory directory: ${fileName.slice(0, 80)}`);
1427
+ }
1428
+ return filePath;
1429
+ }
1430
+ var SAFE_MEMORY_FILE_RE;
1431
+ var init_safe_memory_file = __esm({
1432
+ "src/tools/memory/safe-memory-file.ts"() {
1433
+ "use strict";
1434
+ SAFE_MEMORY_FILE_RE = /^[A-Za-z0-9][A-Za-z0-9._ -]*\.md$/i;
1435
+ }
1382
1436
  });
1383
- import { homedir as homedir5 } from "node:os";
1384
- import { join as join7 } from "node:path";
1385
- import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync3, readdirSync as readdirSync4 } from "node:fs";
1386
- function isToolInput22(v) {
1387
- if (typeof v !== "object" || v === null) return false;
1388
- const o = v;
1389
- if (o["action"] !== "pull" && o["action"] !== "push") return false;
1390
- if (o["cwd"] !== void 0 && typeof o["cwd"] !== "string") return false;
1391
- return true;
1437
+
1438
+ // src/tools/memory/sync-lock-liveness.ts
1439
+ import { statSync as statSync4, readFileSync as readFileSync8 } from "node:fs";
1440
+ function defaultIsProcessAlive(pid) {
1441
+ try {
1442
+ process.kill(pid, 0);
1443
+ return true;
1444
+ } catch (err) {
1445
+ return err.code === "EPERM";
1446
+ }
1392
1447
  }
1393
- function deriveProjectSlug(cwd) {
1394
- return cwd.replace(/\\/g, "/").replace(/\/+$/g, "").replace(/^([a-zA-Z]):/, (_m, drive) => `${drive.toUpperCase()}:`).replace(/[^a-zA-Z0-9]/g, "-");
1448
+ function toPayload(parsed) {
1449
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return null;
1450
+ const record = parsed;
1451
+ const token = record["token"];
1452
+ const host = record["hostname"];
1453
+ if (typeof token !== "string" || token.length === 0) return null;
1454
+ const pid = record["pid"];
1455
+ const acquiredAtMs = record["acquiredAtMs"];
1456
+ return {
1457
+ pid: typeof pid === "number" && Number.isInteger(pid) && pid > 0 ? pid : 0,
1458
+ hostname: typeof host === "string" ? host : "",
1459
+ sessionId: typeof record["sessionId"] === "string" ? record["sessionId"] : null,
1460
+ token,
1461
+ acquiredAt: typeof record["acquiredAt"] === "string" ? record["acquiredAt"] : "",
1462
+ acquiredAtMs: typeof acquiredAtMs === "number" && Number.isFinite(acquiredAtMs) ? acquiredAtMs : Number.NaN
1463
+ };
1395
1464
  }
1396
- function getMemoryDir(cwd) {
1397
- const slug = deriveProjectSlug(cwd);
1398
- return join7(homedir5(), ".claude", "projects", slug, "memory");
1465
+ function readLockRecord(path3) {
1466
+ let raw;
1467
+ try {
1468
+ raw = readFileSync8(path3, "utf8");
1469
+ } catch {
1470
+ return null;
1471
+ }
1472
+ try {
1473
+ return { raw, payload: toPayload(JSON.parse(raw)) };
1474
+ } catch {
1475
+ return { raw, payload: null };
1476
+ }
1477
+ }
1478
+ function lockAgeMs(record, path3, nowMs) {
1479
+ let startedMs = Number.NaN;
1480
+ if (record.payload) {
1481
+ if (Number.isFinite(record.payload.acquiredAtMs)) {
1482
+ startedMs = record.payload.acquiredAtMs;
1483
+ } else if (record.payload.acquiredAt) {
1484
+ startedMs = Date.parse(record.payload.acquiredAt);
1485
+ }
1486
+ }
1487
+ if (!Number.isFinite(startedMs)) {
1488
+ try {
1489
+ startedMs = statSync4(path3).mtimeMs;
1490
+ } catch {
1491
+ return null;
1492
+ }
1493
+ }
1494
+ const age = nowMs - startedMs;
1495
+ return Number.isFinite(age) && age >= 0 ? age : null;
1496
+ }
1497
+ function classifyHolderLiveness(record, isProcessAlive, thisHost) {
1498
+ const payload = record.payload;
1499
+ if (payload === null) return "unknown";
1500
+ if (payload.pid <= 0) return "unknown";
1501
+ if (thisHost.length === 0) return "unknown";
1502
+ if (payload.hostname !== thisHost) return "unknown";
1503
+ return isProcessAlive(payload.pid) ? "alive" : "dead";
1504
+ }
1505
+ function isLockAbandoned(record, ageMs, ttlMs, isProcessAlive, thisHost) {
1506
+ const liveness = classifyHolderLiveness(record, isProcessAlive, thisHost);
1507
+ if (liveness === "alive") return false;
1508
+ if (liveness === "dead") return true;
1509
+ return ageMs !== null && ageMs > ttlMs;
1510
+ }
1511
+ var init_sync_lock_liveness = __esm({
1512
+ "src/tools/memory/sync-lock-liveness.ts"() {
1513
+ "use strict";
1514
+ }
1515
+ });
1516
+
1517
+ // 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";
1519
+ import { hostname } from "node:os";
1520
+ import { join as join8 } from "node:path";
1521
+ import { randomUUID as randomUUID2 } from "node:crypto";
1522
+ function positiveOr(value, fallback) {
1523
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : fallback;
1524
+ }
1525
+ function createExclusive2(path3, contents) {
1526
+ let fd;
1527
+ try {
1528
+ fd = openSync3(path3, "wx");
1529
+ } catch (err) {
1530
+ const code = err.code;
1531
+ return { ok: false, exists: code === "EEXIST", message: err instanceof Error ? err.message : String(err) };
1532
+ }
1533
+ try {
1534
+ writeFileSync4(fd, contents, "utf8");
1535
+ } catch (err) {
1536
+ closeSync2(fd);
1537
+ try {
1538
+ unlinkSync2(path3);
1539
+ } catch {
1540
+ }
1541
+ return { ok: false, exists: false, message: err instanceof Error ? err.message : String(err) };
1542
+ }
1543
+ closeSync2(fd);
1544
+ return { ok: true };
1545
+ }
1546
+ function removeAbandoned(path3, expectedRaw) {
1547
+ let current;
1548
+ try {
1549
+ current = readFileSync9(path3, "utf8");
1550
+ } catch {
1551
+ return;
1552
+ }
1553
+ if (current !== expectedRaw) return;
1554
+ try {
1555
+ unlinkSync2(path3);
1556
+ } catch {
1557
+ }
1558
+ }
1559
+ function makeRelease(path3, token) {
1560
+ let released = false;
1561
+ return () => {
1562
+ if (released) return;
1563
+ released = true;
1564
+ let raw;
1565
+ try {
1566
+ raw = readFileSync9(path3, "utf8");
1567
+ } catch {
1568
+ return;
1569
+ }
1570
+ let stillOurs;
1571
+ try {
1572
+ stillOurs = toPayload(JSON.parse(raw))?.token === token;
1573
+ } catch {
1574
+ stillOurs = false;
1575
+ }
1576
+ if (!stillOurs) return;
1577
+ try {
1578
+ unlinkSync2(path3);
1579
+ } catch {
1580
+ }
1581
+ };
1582
+ }
1583
+ function describeHolder(record) {
1584
+ const payload = record?.payload;
1585
+ if (!payload) return "an unreadable lock file";
1586
+ return `pid ${payload.pid} on ${payload.hostname || "(unknown host)"} (session ${payload.sessionId ?? "unknown"}, held since ${payload.acquiredAt || "unknown"})`;
1587
+ }
1588
+ async function acquireMemorySyncLock(options) {
1589
+ const waitMs = positiveOr(options.waitMs, DEFAULT_LOCK_WAIT_MS);
1590
+ const ttlMs = positiveOr(options.ttlMs, DEFAULT_LOCK_TTL_MS);
1591
+ const now = options.now ?? Date.now;
1592
+ const sleep = options.sleep ?? ((ms) => new Promise((resolve3) => {
1593
+ setTimeout(resolve3, ms);
1594
+ }));
1595
+ const isProcessAlive = options.isProcessAlive ?? defaultIsProcessAlive;
1596
+ const thisHost = hostname();
1597
+ const path3 = join8(options.memoryDir, MEMORY_SYNC_LOCK_FILE);
1598
+ if (options.createDir === true) mkdirSync5(options.memoryDir, { recursive: true });
1599
+ const deadline = now() + waitMs;
1600
+ let backoffMs = INITIAL_BACKOFF_MS;
1601
+ let tookOverFrom = null;
1602
+ let holderDescription = "another session";
1603
+ for (; ; ) {
1604
+ const acquiredAtMs = now();
1605
+ const payload = {
1606
+ pid: process.pid,
1607
+ hostname: thisHost,
1608
+ sessionId: options.sessionId ?? null,
1609
+ token: randomUUID2(),
1610
+ acquiredAt: new Date(acquiredAtMs).toISOString(),
1611
+ acquiredAtMs
1612
+ };
1613
+ const created = createExclusive2(path3, `${JSON.stringify(payload, null, 2)}
1614
+ `);
1615
+ if (created.ok) {
1616
+ return { path: path3, payload, tookOverFrom, release: makeRelease(path3, payload.token) };
1617
+ }
1618
+ if (!created.exists) {
1619
+ throw new Error(
1620
+ `memory sync lock ${path3} could not be created (${created.message}) \u2014 refusing to sync without exclusion`
1621
+ );
1622
+ }
1623
+ const record = readLockRecord(path3);
1624
+ let reclaimed = false;
1625
+ if (record) {
1626
+ holderDescription = describeHolder(record);
1627
+ const age = lockAgeMs(record, path3, now());
1628
+ if (isLockAbandoned(record, age, ttlMs, isProcessAlive, thisHost)) {
1629
+ tookOverFrom = record.payload;
1630
+ removeAbandoned(path3, record.raw);
1631
+ reclaimed = true;
1632
+ }
1633
+ }
1634
+ if (now() >= deadline) {
1635
+ 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.`
1637
+ );
1638
+ }
1639
+ if (reclaimed) backoffMs = INITIAL_BACKOFF_MS;
1640
+ await sleep(Math.max(1, Math.min(backoffMs, deadline - now())));
1641
+ if (!reclaimed) backoffMs = Math.min(MAX_BACKOFF_MS, Math.ceil(backoffMs * BACKOFF_FACTOR));
1642
+ }
1643
+ }
1644
+ async function withMemorySyncLock(options, fn) {
1645
+ const handle = await acquireMemorySyncLock(options);
1646
+ try {
1647
+ return await fn(handle);
1648
+ } finally {
1649
+ handle.release();
1650
+ }
1651
+ }
1652
+ var MEMORY_SYNC_LOCK_FILE, DEFAULT_LOCK_TTL_MS, DEFAULT_LOCK_WAIT_MS, INITIAL_BACKOFF_MS, MAX_BACKOFF_MS, BACKOFF_FACTOR;
1653
+ var init_sync_lock = __esm({
1654
+ "src/tools/memory/sync-lock.ts"() {
1655
+ "use strict";
1656
+ init_sync_lock_liveness();
1657
+ init_sync_lock_liveness();
1658
+ MEMORY_SYNC_LOCK_FILE = ".memory-sync.lock";
1659
+ DEFAULT_LOCK_TTL_MS = 15 * 6e4;
1660
+ DEFAULT_LOCK_WAIT_MS = 1e4;
1661
+ INITIAL_BACKOFF_MS = 25;
1662
+ MAX_BACKOFF_MS = 500;
1663
+ BACKOFF_FACTOR = 1.6;
1664
+ }
1665
+ });
1666
+
1667
+ // src/tools/memory/memory-index-merge.ts
1668
+ function isMemoryIndexFile(fileName) {
1669
+ return fileName.toUpperCase() === MEMORY_INDEX_FILE.toUpperCase();
1670
+ }
1671
+ function indexRowKey(line) {
1672
+ const match = INDEX_ROW_RE.exec(line);
1673
+ if (!match) return null;
1674
+ let target = match[1].trim();
1675
+ if (target.startsWith("<") && target.endsWith(">")) target = target.slice(1, -1).trim();
1676
+ target = target.replace(/\s+(["'])[\s\S]*\1$/, "").trim();
1677
+ if (!/^[a-z][a-z0-9+.-]*:\/\//i.test(target)) {
1678
+ target = target.replace(/\\/g, "/").replace(/\/{2,}/g, "/");
1679
+ target = target.replace(/^(?:\.\/)+/, "");
1680
+ }
1681
+ return target.length > 0 ? target.toLowerCase() : null;
1682
+ }
1683
+ function mergeMemoryIndex(localContent, cloudContent) {
1684
+ if (typeof cloudContent !== "string" || cloudContent.trim().length === 0) {
1685
+ return { content: localContent, addedFromCloud: [] };
1686
+ }
1687
+ const eol = localContent.includes("\r\n") ? "\r\n" : "\n";
1688
+ const localLines = localContent.split(/\r?\n/);
1689
+ const localKeys = /* @__PURE__ */ new Set();
1690
+ let lastLocalRowIndex = -1;
1691
+ for (let i = 0; i < localLines.length; i++) {
1692
+ const key = indexRowKey(localLines[i]);
1693
+ if (key === null) continue;
1694
+ localKeys.add(key);
1695
+ lastLocalRowIndex = i;
1696
+ }
1697
+ const addedFromCloud = [];
1698
+ const seenCloudKeys = /* @__PURE__ */ new Set();
1699
+ for (const rawLine of cloudContent.split(/\r?\n/)) {
1700
+ const key = indexRowKey(rawLine);
1701
+ if (key === null) continue;
1702
+ if (localKeys.has(key) || seenCloudKeys.has(key)) continue;
1703
+ seenCloudKeys.add(key);
1704
+ addedFromCloud.push(rawLine.replace(/\r$/, ""));
1705
+ }
1706
+ if (addedFromCloud.length === 0) {
1707
+ return { content: localContent, addedFromCloud: [] };
1708
+ }
1709
+ const merged = lastLocalRowIndex >= 0 ? [...localLines.slice(0, lastLocalRowIndex + 1), ...addedFromCloud, ...localLines.slice(lastLocalRowIndex + 1)] : [...localLines, ...addedFromCloud];
1710
+ return { content: merged.join(eol), addedFromCloud };
1711
+ }
1712
+ var MEMORY_INDEX_FILE, INDEX_ROW_RE;
1713
+ var init_memory_index_merge = __esm({
1714
+ "src/tools/memory/memory-index-merge.ts"() {
1715
+ "use strict";
1716
+ MEMORY_INDEX_FILE = "MEMORY.md";
1717
+ INDEX_ROW_RE = /^\s*[-*]\s+\[[^\]]*\]\(([^)]+)\)/;
1718
+ }
1719
+ });
1720
+
1721
+ // src/tools/memory/bounded-sync.ts
1722
+ function createSyncDeadline(budgetMs = SYNC_DEADLINE_MS, now = Date.now) {
1723
+ const startedAt = now();
1724
+ return {
1725
+ check() {
1726
+ const elapsed = now() - startedAt;
1727
+ if (elapsed > budgetMs) throw new SyncDeadlineExceededError(elapsed, budgetMs);
1728
+ },
1729
+ remainingMs() {
1730
+ return Math.max(0, budgetMs - (now() - startedAt));
1731
+ }
1732
+ };
1733
+ }
1734
+ async function withRequestTimeout(url, run, budgetMs = REQUEST_TIMEOUT_MS) {
1735
+ let timer;
1736
+ try {
1737
+ return await Promise.race([
1738
+ run(),
1739
+ new Promise((_resolve, reject) => {
1740
+ timer = setTimeout(() => reject(new RequestTimeoutError(url, budgetMs)), budgetMs);
1741
+ timer.unref?.();
1742
+ })
1743
+ ]);
1744
+ } finally {
1745
+ if (timer) clearTimeout(timer);
1746
+ }
1747
+ }
1748
+ async function mapWithConcurrency(items, limit, fn) {
1749
+ const results = new Array(items.length);
1750
+ const width = Math.max(1, Math.min(limit, items.length));
1751
+ let next = 0;
1752
+ async function worker() {
1753
+ for (; ; ) {
1754
+ const index = next++;
1755
+ if (index >= items.length) return;
1756
+ try {
1757
+ results[index] = { ok: true, value: await fn(items[index], index) };
1758
+ } catch (error) {
1759
+ results[index] = { ok: false, error };
1760
+ }
1761
+ }
1762
+ }
1763
+ await Promise.all(Array.from({ length: width }, () => worker()));
1764
+ return results;
1765
+ }
1766
+ var REQUEST_TIMEOUT_MS, SYNC_DEADLINE_MS, PUSH_CONCURRENCY, SyncDeadlineExceededError, RequestTimeoutError;
1767
+ var init_bounded_sync = __esm({
1768
+ "src/tools/memory/bounded-sync.ts"() {
1769
+ "use strict";
1770
+ REQUEST_TIMEOUT_MS = 15e3;
1771
+ SYNC_DEADLINE_MS = 12e4;
1772
+ PUSH_CONCURRENCY = 6;
1773
+ SyncDeadlineExceededError = class extends Error {
1774
+ constructor(elapsedMs, budgetMs) {
1775
+ super(
1776
+ `memory sync exceeded its ${budgetMs}ms deadline after ${elapsedMs}ms \u2014 aborting so the lock is released instead of held indefinitely`
1777
+ );
1778
+ this.name = "SyncDeadlineExceededError";
1779
+ }
1780
+ };
1781
+ RequestTimeoutError = class extends Error {
1782
+ constructor(url, budgetMs) {
1783
+ super(`memory sync request to ${url} exceeded ${budgetMs}ms`);
1784
+ this.name = "RequestTimeoutError";
1785
+ }
1786
+ };
1787
+ }
1788
+ });
1789
+
1790
+ // src/tools/memory/memory-push-cache.ts
1791
+ import { createHash as createHash3 } from "node:crypto";
1792
+ import { readFileSync as readFileSync10, writeFileSync as writeFileSync5 } from "node:fs";
1793
+ import { join as join9 } from "node:path";
1794
+ function sha256(content) {
1795
+ return createHash3("sha256").update(content, "utf8").digest("hex");
1796
+ }
1797
+ function statePath(memoryDir) {
1798
+ return join9(memoryDir, MEMORY_SYNC_STATE_FILE);
1799
+ }
1800
+ function readPushCache(memoryDir, controlPlaneUrl) {
1801
+ const empty = { controlPlaneUrl, entries: /* @__PURE__ */ new Map(), knowledgeSweptAtMs: null };
1802
+ let raw;
1803
+ try {
1804
+ raw = readFileSync10(statePath(memoryDir), "utf8");
1805
+ } catch {
1806
+ return empty;
1807
+ }
1808
+ let parsed;
1809
+ try {
1810
+ parsed = JSON.parse(raw);
1811
+ } catch {
1812
+ return empty;
1813
+ }
1814
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return empty;
1815
+ const obj = parsed;
1816
+ if (obj["version"] !== STATE_VERSION) return empty;
1817
+ if (obj["controlPlaneUrl"] !== controlPlaneUrl) return empty;
1818
+ const files = obj["entries"];
1819
+ if (typeof files !== "object" || files === null || Array.isArray(files)) return empty;
1820
+ const entries = /* @__PURE__ */ new Map();
1821
+ for (const [name, value] of Object.entries(files)) {
1822
+ if (typeof value !== "object" || value === null || Array.isArray(value)) continue;
1823
+ const row = value;
1824
+ const memoryHash = typeof row["memoryHash"] === "string" ? row["memoryHash"] : void 0;
1825
+ const knowledgeHash = typeof row["knowledgeHash"] === "string" ? row["knowledgeHash"] : void 0;
1826
+ if (memoryHash === void 0 && knowledgeHash === void 0) continue;
1827
+ entries.set(name, {
1828
+ ...memoryHash !== void 0 ? { memoryHash } : {},
1829
+ ...knowledgeHash !== void 0 ? { knowledgeHash } : {}
1830
+ });
1831
+ }
1832
+ const sweptAt = obj["knowledgeSweptAtMs"];
1833
+ return {
1834
+ controlPlaneUrl,
1835
+ entries,
1836
+ // An unreadable/absent sweep stamp reads as NEVER SWEPT, which forces a full
1837
+ // sweep — the fail-closed direction (more upserts, never fewer).
1838
+ knowledgeSweptAtMs: typeof sweptAt === "number" && Number.isFinite(sweptAt) ? sweptAt : null
1839
+ };
1840
+ }
1841
+ function writePushCache(memoryDir, cache) {
1842
+ const entries = {};
1843
+ for (const [name, value] of cache.entries) entries[name] = value;
1844
+ try {
1845
+ writeFileSync5(
1846
+ statePath(memoryDir),
1847
+ `${JSON.stringify(
1848
+ {
1849
+ version: STATE_VERSION,
1850
+ controlPlaneUrl: cache.controlPlaneUrl,
1851
+ knowledgeSweptAtMs: cache.knowledgeSweptAtMs,
1852
+ entries
1853
+ },
1854
+ null,
1855
+ 2
1856
+ )}
1857
+ `,
1858
+ "utf8"
1859
+ );
1860
+ } catch {
1861
+ }
1862
+ }
1863
+ function recordMemoryPush(cache, fileName, payloadHash) {
1864
+ cache.entries.set(fileName, { ...cache.entries.get(fileName), memoryHash: payloadHash });
1865
+ }
1866
+ function recordKnowledgePush(cache, fileName, contentHash) {
1867
+ cache.entries.set(fileName, { ...cache.entries.get(fileName), knowledgeHash: contentHash });
1868
+ }
1869
+ function pruneMissing(cache, presentFileNames) {
1870
+ const present = new Set(presentFileNames);
1871
+ for (const name of [...cache.entries.keys()]) {
1872
+ if (!present.has(name)) cache.entries.delete(name);
1873
+ }
1399
1874
  }
1875
+ function needsMemoryPush(cache, fileName, payloadHash, serverHasEntry) {
1876
+ if (!serverHasEntry) return true;
1877
+ return cache.entries.get(fileName)?.memoryHash !== payloadHash;
1878
+ }
1879
+ function knowledgeSweepDue(cache, nowMs = Date.now()) {
1880
+ const swept = cache.knowledgeSweptAtMs;
1881
+ if (swept === null || !Number.isFinite(swept)) return true;
1882
+ const age = nowMs - swept;
1883
+ return !(age >= 0 && age < KNOWLEDGE_FULL_SWEEP_MS);
1884
+ }
1885
+ function needsKnowledgePush(cache, fileName, contentHash, sweepDue = false) {
1886
+ if (sweepDue) return true;
1887
+ return cache.entries.get(fileName)?.knowledgeHash !== contentHash;
1888
+ }
1889
+ var MEMORY_SYNC_STATE_FILE, STATE_VERSION, KNOWLEDGE_FULL_SWEEP_MS;
1890
+ var init_memory_push_cache = __esm({
1891
+ "src/tools/memory/memory-push-cache.ts"() {
1892
+ "use strict";
1893
+ MEMORY_SYNC_STATE_FILE = ".memory-sync-state.json";
1894
+ STATE_VERSION = 1;
1895
+ KNOWLEDGE_FULL_SWEEP_MS = 24 * 60 * 6e4;
1896
+ }
1897
+ });
1898
+
1899
+ // 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";
1400
1901
  async function pullMemory(controlPlaneUrl, token, memoryDir, fetchFn) {
1401
1902
  const url = `${controlPlaneUrl}/api/v1/agent-config/memory/me`;
1402
- const response = await fetchFn(url, {
1403
- method: "GET",
1404
- headers: {
1405
- authorization: `Bearer ${token}`
1406
- }
1407
- });
1903
+ const response = await withRequestTimeout(
1904
+ url,
1905
+ () => fetchFn(url, {
1906
+ method: "GET",
1907
+ headers: {
1908
+ authorization: `Bearer ${token}`
1909
+ }
1910
+ })
1911
+ );
1408
1912
  if (response.status !== 200) {
1409
1913
  const text = await response.text();
1410
1914
  throw new Error(`GET /api/v1/agent-config/memory/me returned HTTP ${response.status}: ${text.slice(0, 200)}`);
@@ -1413,117 +1917,392 @@ async function pullMemory(controlPlaneUrl, token, memoryDir, fetchFn) {
1413
1917
  if (!data.ok || !Array.isArray(data.entries)) {
1414
1918
  throw new Error("GET /api/v1/agent-config/memory/me response missing ok=true or entries array");
1415
1919
  }
1416
- mkdirSync4(memoryDir, { recursive: true });
1920
+ const writes = data.entries.map((entry) => ({
1921
+ entry,
1922
+ filePath: resolveMemoryFilePath(memoryDir, entry.file_name)
1923
+ }));
1924
+ mkdirSync6(memoryDir, { recursive: true });
1417
1925
  const files = [];
1418
- for (const entry of data.entries) {
1419
- const filePath = join7(memoryDir, entry.file_name);
1420
- writeFileSync3(filePath, entry.content, "utf8");
1926
+ for (const { entry, filePath } of writes) {
1927
+ writeFileSync6(filePath, entry.content, "utf8");
1421
1928
  files.push(entry.file_name);
1422
1929
  }
1423
1930
  return { pulled: data.entries.length, files };
1424
1931
  }
1425
- async function pushMemory(controlPlaneUrl, token, memoryDir, sessionId, fetchFn) {
1932
+ function listPushableFiles(memoryDir) {
1933
+ return readdirSync4(memoryDir).filter((f) => f.endsWith(".md") && f !== MEMORY_SYNC_LOCK_FILE);
1934
+ }
1935
+ async function uploadOne(item, controlPlaneUrl, token, sessionId, fetchFn, deadline) {
1936
+ deadline.check();
1937
+ if (item.memoryId !== null) {
1938
+ const updateUrl = `${controlPlaneUrl}/api/v1/agent-config/memory/${item.memoryId}`;
1939
+ const updateBody = { content: item.content, session_id: sessionId };
1940
+ const updateResponse = await withRequestTimeout(
1941
+ updateUrl,
1942
+ () => fetchFn(updateUrl, {
1943
+ method: "PUT",
1944
+ headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
1945
+ body: JSON.stringify(updateBody)
1946
+ })
1947
+ );
1948
+ if (updateResponse.status !== 200) {
1949
+ const text = await updateResponse.text();
1950
+ throw new Error(
1951
+ `PUT /api/v1/agent-config/memory/${item.memoryId} returned HTTP ${updateResponse.status}: ${text.slice(0, 200)}`
1952
+ );
1953
+ }
1954
+ const updateData = JSON.parse(await updateResponse.text());
1955
+ if (!updateData.ok) throw new Error(`PUT /api/v1/agent-config/memory/${item.memoryId} returned ok=false`);
1956
+ return "updated";
1957
+ }
1958
+ const createUrl = `${controlPlaneUrl}/api/v1/agent-config/memory/me`;
1959
+ const createBody = {
1960
+ entry_type: item.entryType,
1961
+ file_name: item.fileName,
1962
+ content: item.content,
1963
+ session_id: sessionId
1964
+ };
1965
+ const createResponse = await withRequestTimeout(
1966
+ createUrl,
1967
+ () => fetchFn(createUrl, {
1968
+ method: "POST",
1969
+ headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
1970
+ body: JSON.stringify(createBody)
1971
+ })
1972
+ );
1973
+ if (createResponse.status !== 200 && createResponse.status !== 201) {
1974
+ const text = await createResponse.text();
1975
+ throw new Error(
1976
+ `POST /api/v1/agent-config/memory/me returned HTTP ${createResponse.status}: ${text.slice(0, 200)}`
1977
+ );
1978
+ }
1979
+ const createData = JSON.parse(await createResponse.text());
1980
+ if (!createData.ok) throw new Error("POST /api/v1/agent-config/memory/me returned ok=false");
1981
+ return "created";
1982
+ }
1983
+ async function pushMemory(controlPlaneUrl, token, memoryDir, sessionId, fetchFn, options = {}) {
1984
+ const empty = { pushed: 0, created: 0, updated: 0, skipped: 0, indexRowsPreserved: 0 };
1426
1985
  if (!existsSync5(memoryDir)) {
1427
- return { pushed: 0, created: 0, updated: 0 };
1986
+ return empty;
1428
1987
  }
1429
- const localFiles = readdirSync4(memoryDir).filter((f) => f.endsWith(".md")).map((f) => ({
1988
+ const localFiles = listPushableFiles(memoryDir).map((f) => ({
1430
1989
  file_name: f,
1431
- content: readFileSync7(join7(memoryDir, f), "utf8"),
1432
- entry_type: f === "MEMORY.md" ? "index" : "topic"
1990
+ content: readFileSync11(resolveMemoryFilePath(memoryDir, f), "utf8"),
1991
+ entry_type: isMemoryIndexFile(f) ? "index" : "topic"
1433
1992
  }));
1434
1993
  if (localFiles.length === 0) {
1435
- return { pushed: 0, created: 0, updated: 0 };
1994
+ return empty;
1436
1995
  }
1996
+ const deadline = options.deadline ?? createSyncDeadline();
1997
+ const cache = options.cache ?? readPushCache(memoryDir, controlPlaneUrl);
1998
+ deadline.check();
1437
1999
  const getUrl = `${controlPlaneUrl}/api/v1/agent-config/memory/me`;
1438
- const getResponse = await fetchFn(getUrl, {
1439
- method: "GET",
1440
- headers: {
1441
- authorization: `Bearer ${token}`
1442
- }
1443
- });
2000
+ const getResponse = await withRequestTimeout(
2001
+ getUrl,
2002
+ () => fetchFn(getUrl, { method: "GET", headers: { authorization: `Bearer ${token}` } })
2003
+ );
1444
2004
  const existingMap = /* @__PURE__ */ new Map();
1445
2005
  if (getResponse.status === 200) {
1446
2006
  const getData = JSON.parse(await getResponse.text());
1447
2007
  if (getData.ok && Array.isArray(getData.entries)) {
1448
2008
  for (const entry of getData.entries) {
1449
- existingMap.set(entry.file_name, entry.memory_id);
2009
+ existingMap.set(entry.file_name, {
2010
+ memoryId: entry.memory_id,
2011
+ content: typeof entry.content === "string" ? entry.content : ""
2012
+ });
1450
2013
  }
1451
2014
  }
1452
2015
  }
2016
+ const toUpload = [];
2017
+ let skipped = 0;
2018
+ for (const localFile of localFiles) {
2019
+ const existing = existingMap.get(localFile.file_name);
2020
+ let content = localFile.content;
2021
+ let rowsPreserved = 0;
2022
+ if (localFile.entry_type === "index") {
2023
+ const merged = mergeMemoryIndex(localFile.content, existing?.content);
2024
+ content = merged.content;
2025
+ rowsPreserved = merged.addedFromCloud.length;
2026
+ }
2027
+ const payloadHash = sha256(content);
2028
+ if (!needsMemoryPush(cache, localFile.file_name, payloadHash, existing !== void 0)) {
2029
+ skipped++;
2030
+ continue;
2031
+ }
2032
+ toUpload.push({
2033
+ fileName: localFile.file_name,
2034
+ entryType: localFile.entry_type,
2035
+ content,
2036
+ payloadHash,
2037
+ memoryId: existing?.memoryId ?? null,
2038
+ rowsPreserved
2039
+ });
2040
+ }
2041
+ const outcomes = await mapWithConcurrency(
2042
+ toUpload,
2043
+ options.concurrency ?? PUSH_CONCURRENCY,
2044
+ (item) => uploadOne(item, controlPlaneUrl, token, sessionId, fetchFn, deadline)
2045
+ );
1453
2046
  let created = 0;
1454
2047
  let updated = 0;
1455
- for (const localFile of localFiles) {
1456
- const memoryId = existingMap.get(localFile.file_name);
1457
- if (memoryId) {
1458
- const updateUrl = `${controlPlaneUrl}/api/v1/agent-config/memory/${memoryId}`;
1459
- const updateBody = {
1460
- content: localFile.content,
1461
- session_id: sessionId
1462
- };
1463
- const updateResponse = await fetchFn(updateUrl, {
1464
- method: "PUT",
1465
- headers: {
1466
- authorization: `Bearer ${token}`,
1467
- "content-type": "application/json"
1468
- },
1469
- body: JSON.stringify(updateBody)
1470
- });
1471
- if (updateResponse.status !== 200) {
1472
- const text = await updateResponse.text();
1473
- throw new Error(
1474
- `PUT /api/v1/agent-config/memory/${memoryId} returned HTTP ${updateResponse.status}: ${text.slice(0, 200)}`
1475
- );
1476
- }
1477
- const updateData = JSON.parse(await updateResponse.text());
1478
- if (!updateData.ok) {
1479
- throw new Error(`PUT /api/v1/agent-config/memory/${memoryId} returned ok=false`);
1480
- }
1481
- updated++;
1482
- } else {
1483
- const createUrl = `${controlPlaneUrl}/api/v1/agent-config/memory/me`;
1484
- const createBody = {
1485
- entry_type: localFile.entry_type,
1486
- file_name: localFile.file_name,
1487
- content: localFile.content,
1488
- session_id: sessionId
1489
- };
1490
- const createResponse = await fetchFn(createUrl, {
1491
- method: "POST",
1492
- headers: {
1493
- authorization: `Bearer ${token}`,
1494
- "content-type": "application/json"
1495
- },
1496
- body: JSON.stringify(createBody)
1497
- });
1498
- if (createResponse.status !== 200 && createResponse.status !== 201) {
1499
- const text = await createResponse.text();
1500
- throw new Error(
1501
- `POST /api/v1/agent-config/memory/me returned HTTP ${createResponse.status}: ${text.slice(0, 200)}`
1502
- );
1503
- }
1504
- const createData = JSON.parse(await createResponse.text());
1505
- if (!createData.ok) {
1506
- throw new Error("POST /api/v1/agent-config/memory/me returned ok=false");
1507
- }
1508
- created++;
2048
+ let indexRowsPreserved = 0;
2049
+ let firstError;
2050
+ for (let i = 0; i < outcomes.length; i++) {
2051
+ const outcome = outcomes[i];
2052
+ const item = toUpload[i];
2053
+ if (outcome.ok) {
2054
+ if (outcome.value === "created") created++;
2055
+ else updated++;
2056
+ indexRowsPreserved += item.rowsPreserved;
2057
+ recordMemoryPush(cache, item.fileName, item.payloadHash);
2058
+ } else if (firstError === void 0) {
2059
+ firstError = outcome.error;
2060
+ }
2061
+ }
2062
+ pruneMissing(cache, localFiles.map((f) => f.file_name));
2063
+ if (options.persistCache ?? options.cache === void 0) writePushCache(memoryDir, cache);
2064
+ if (firstError !== void 0) throw firstError;
2065
+ return { pushed: created + updated, created, updated, skipped, indexRowsPreserved };
2066
+ }
2067
+ var init_memory_sync_http = __esm({
2068
+ "src/tools/memory/memory-sync-http.ts"() {
2069
+ "use strict";
2070
+ init_safe_memory_file();
2071
+ init_sync_lock();
2072
+ init_memory_index_merge();
2073
+ init_bounded_sync();
2074
+ init_memory_push_cache();
2075
+ }
2076
+ });
2077
+
2078
+ // src/tools/memory/sync-kill-switch.ts
2079
+ import { existsSync as existsSync6, readFileSync as readFileSync12 } from "node:fs";
2080
+ import { homedir as homedir6 } from "node:os";
2081
+ import { join as join10 } from "node:path";
2082
+ function memorySyncSentinelPath(home) {
2083
+ return join10(home, ".claude", MEMORY_SYNC_DISABLE_SENTINEL);
2084
+ }
2085
+ function isKillSwitchValueOn(raw) {
2086
+ if (raw === void 0 || raw === null) return false;
2087
+ const v = raw.trim().toLowerCase();
2088
+ if (v === "") return false;
2089
+ return !NEGATIONS.has(v);
2090
+ }
2091
+ function clip(raw) {
2092
+ const v = raw.trim();
2093
+ return v.length > MAX_LOGGED_VALUE ? `${v.slice(0, MAX_LOGGED_VALUE)}\u2026` : v;
2094
+ }
2095
+ function evaluateMemorySyncKillSwitch(deps = {}) {
2096
+ const env = deps.env ?? process.env;
2097
+ const home = deps.home ?? homedir6();
2098
+ const fileExists = deps.fileExists ?? existsSync6;
2099
+ const readFile3 = deps.readFile ?? ((p) => readFileSync12(p, "utf8"));
2100
+ const fired = [];
2101
+ const rawEnv = env[MEMORY_SYNC_DISABLE_ENV];
2102
+ if (isKillSwitchValueOn(rawEnv)) {
2103
+ fired.push(`env ${MEMORY_SYNC_DISABLE_ENV}=${clip(rawEnv)}`);
2104
+ }
2105
+ const sentinel = memorySyncSentinelPath(home);
2106
+ let sentinelPresent;
2107
+ try {
2108
+ sentinelPresent = fileExists(sentinel);
2109
+ } catch {
2110
+ sentinelPresent = false;
2111
+ }
2112
+ if (sentinelPresent) {
2113
+ let contents = "";
2114
+ let readable = true;
2115
+ try {
2116
+ contents = readFile3(sentinel);
2117
+ } catch {
2118
+ readable = false;
2119
+ }
2120
+ if (!readable || isKillSwitchValueOn(contents) || contents.trim() === "") {
2121
+ fired.push(`sentinel file ${sentinel}`);
1509
2122
  }
1510
2123
  }
1511
- return { pushed: localFiles.length, created, updated };
1512
- }
1513
- function isNoopSyncReason(reason) {
1514
- if (!reason) return false;
1515
- return /not set|No auth configured|Failed to obtain auth token/.test(reason);
2124
+ if (fired.length === 0) return { disabled: false, reason: null };
2125
+ return { disabled: true, reason: `memory sync DISABLED by ${fired.join(" + ")}` };
1516
2126
  }
1517
- async function runMemorySync(action, cwd, sessionId, fetchFn = globalThis.fetch) {
1518
- const controlPlaneUrl = process.env["VO_CONTROL_PLANE_URL"];
1519
- if (!controlPlaneUrl) {
1520
- return { synced: false, reason: "VO_CONTROL_PLANE_URL not set \u2014 cloud mode disabled" };
1521
- }
1522
- const { createAuthTokenSourceFromEnv: createAuthTokenSourceFromEnv2 } = await Promise.resolve().then(() => (init_auth_token_source(), auth_token_source_exports));
1523
- const { readStoredCredential: readStoredCredential2 } = await Promise.resolve().then(() => (init_credential_store(), credential_store_exports));
1524
- const tokenSource = createAuthTokenSourceFromEnv2(process.env, fetchFn, () => readStoredCredential2(process.env));
1525
- if (!tokenSource) {
1526
- return { synced: false, reason: "No auth configured. Run `vo-mcp login` to authenticate as an operator." };
2127
+ var MEMORY_SYNC_DISABLE_ENV, MEMORY_SYNC_DISABLE_SENTINEL, NEGATIONS, MAX_LOGGED_VALUE;
2128
+ var init_sync_kill_switch = __esm({
2129
+ "src/tools/memory/sync-kill-switch.ts"() {
2130
+ "use strict";
2131
+ MEMORY_SYNC_DISABLE_ENV = "VO_MCP_DISABLE_MEMORY_SYNC";
2132
+ MEMORY_SYNC_DISABLE_SENTINEL = "vo-memory-sync-disabled";
2133
+ NEGATIONS = /* @__PURE__ */ new Set(["0", "false", "no"]);
2134
+ MAX_LOGGED_VALUE = 32;
2135
+ }
2136
+ });
2137
+
2138
+ // src/tools/memory/memory-knowledge-bridge.ts
2139
+ var memory_knowledge_bridge_exports = {};
2140
+ __export(memory_knowledge_bridge_exports, {
2141
+ extractMemoryTitle: () => extractMemoryTitle,
2142
+ upsertMemoryFilesAsKnowledge: () => upsertMemoryFilesAsKnowledge
2143
+ });
2144
+ import { existsSync as existsSync7, readdirSync as readdirSync5, readFileSync as readFileSync13 } from "node:fs";
2145
+ function extractMemoryTitle(fileName, content) {
2146
+ const frontmatter = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
2147
+ if (frontmatter) {
2148
+ const description23 = frontmatter[1].match(/^description:\s*(.+)$/m);
2149
+ if (description23 && description23[1].trim()) return description23[1].trim().slice(0, 200);
2150
+ }
2151
+ const heading = content.match(/^#\s+(.+)$/m);
2152
+ if (heading && heading[1].trim()) return heading[1].trim().slice(0, 200);
2153
+ return fileName;
2154
+ }
2155
+ async function upsertMemoryFilesAsKnowledge(options) {
2156
+ const { controlPlaneUrl, token, memoryDir, fetchFn, cache, deadline } = options;
2157
+ let files;
2158
+ try {
2159
+ if (!existsSync7(memoryDir)) {
2160
+ return { attempted: 0, upserted: 0, failed: 0, skipped: 0, failures: [] };
2161
+ }
2162
+ files = readdirSync5(memoryDir).filter(
2163
+ (f) => f.endsWith(".md") && f.toUpperCase() !== "MEMORY.MD"
2164
+ );
2165
+ } catch (err) {
2166
+ return {
2167
+ attempted: 0,
2168
+ upserted: 0,
2169
+ failed: 1,
2170
+ skipped: 0,
2171
+ failures: [`memory dir scan: ${err instanceof Error ? err.message : String(err)}`]
2172
+ };
2173
+ }
2174
+ const sweepDue = cache ? knowledgeSweepDue(cache) : true;
2175
+ const candidates = [];
2176
+ const failures = [];
2177
+ let skipped = 0;
2178
+ for (const fileName of files) {
2179
+ try {
2180
+ const content = readFileSync13(resolveMemoryFilePath(memoryDir, fileName), "utf8");
2181
+ if (content.length > CONTENT_HARD_LIMIT) {
2182
+ failures.push(`${fileName}: ${content.length} chars exceeds the ${CONTENT_HARD_LIMIT} server limit \u2014 split the memory file`);
2183
+ continue;
2184
+ }
2185
+ const hash = sha256(content);
2186
+ if (cache && !needsKnowledgePush(cache, fileName, hash, sweepDue)) {
2187
+ skipped += 1;
2188
+ continue;
2189
+ }
2190
+ candidates.push({ fileName, content, hash });
2191
+ } catch (err) {
2192
+ failures.push(`${fileName}: ${err instanceof Error ? err.message : String(err)}`);
2193
+ }
2194
+ }
2195
+ const url = `${controlPlaneUrl}/api/v1/knowledge/private`;
2196
+ const outcomes = await mapWithConcurrency(candidates, options.concurrency ?? PUSH_CONCURRENCY, async (candidate) => {
2197
+ deadline?.check();
2198
+ const base = {
2199
+ knowledge_class: "memory",
2200
+ source_path: `memory/${candidate.fileName}`,
2201
+ title: extractMemoryTitle(candidate.fileName, candidate.content),
2202
+ content: candidate.content
2203
+ };
2204
+ const post = (body) => withRequestTimeout(url, () => fetchFn(url, {
2205
+ method: "POST",
2206
+ headers: {
2207
+ authorization: `Bearer ${token}`,
2208
+ "content-type": "application/json"
2209
+ },
2210
+ body: JSON.stringify(body)
2211
+ }));
2212
+ let response = await post({
2213
+ ...base,
2214
+ provenance: { written_by: "memory-bridge", source_kind: "operator_memory" }
2215
+ });
2216
+ if (response.status === 400) {
2217
+ response = await post(base);
2218
+ }
2219
+ if (response.status >= 200 && response.status < 300) return true;
2220
+ const text = await response.text();
2221
+ throw new Error(`HTTP ${response.status} ${text.slice(0, 80)}`);
2222
+ });
2223
+ let upserted = 0;
2224
+ for (let i = 0; i < outcomes.length; i++) {
2225
+ const outcome = outcomes[i];
2226
+ const candidate = candidates[i];
2227
+ if (outcome.ok) {
2228
+ upserted += 1;
2229
+ if (cache) recordKnowledgePush(cache, candidate.fileName, candidate.hash);
2230
+ } else {
2231
+ const error = outcome.error;
2232
+ failures.push(`${candidate.fileName}: ${error instanceof Error ? error.message : String(error)}`);
2233
+ }
2234
+ }
2235
+ if (cache && sweepDue && failures.length === 0) {
2236
+ cache.knowledgeSweptAtMs = Date.now();
2237
+ }
2238
+ return {
2239
+ // Every memory file this run considered. `attempted === upserted + skipped
2240
+ // + failed` holds, so a caller can tell "nothing to do" from "nothing ran".
2241
+ attempted: files.length,
2242
+ upserted,
2243
+ failed: failures.length,
2244
+ skipped,
2245
+ failures: failures.slice(0, 5)
2246
+ };
2247
+ }
2248
+ var CONTENT_HARD_LIMIT;
2249
+ var init_memory_knowledge_bridge = __esm({
2250
+ "src/tools/memory/memory-knowledge-bridge.ts"() {
2251
+ "use strict";
2252
+ init_safe_memory_file();
2253
+ init_bounded_sync();
2254
+ init_memory_push_cache();
2255
+ CONTENT_HARD_LIMIT = 5e5;
2256
+ }
2257
+ });
2258
+
2259
+ // src/tools/memory/sync-config.ts
2260
+ var sync_config_exports = {};
2261
+ __export(sync_config_exports, {
2262
+ TOOL_NAME: () => TOOL_NAME22,
2263
+ deriveProjectSlug: () => deriveProjectSlug,
2264
+ description: () => description22,
2265
+ getMemoryDir: () => getMemoryDir,
2266
+ handleSyncConfig: () => handleSyncConfig,
2267
+ inputSchema: () => inputSchema22,
2268
+ isNoopSyncReason: () => isNoopSyncReason,
2269
+ runMemorySync: () => runMemorySync
2270
+ });
2271
+ import { existsSync as existsSync8 } from "node:fs";
2272
+ import { homedir as homedir7 } from "node:os";
2273
+ import { join as join11 } from "node:path";
2274
+ function isToolInput22(v) {
2275
+ if (typeof v !== "object" || v === null) return false;
2276
+ const o = v;
2277
+ if (o["action"] !== "pull" && o["action"] !== "push") return false;
2278
+ if (o["cwd"] !== void 0 && typeof o["cwd"] !== "string") return false;
2279
+ return true;
2280
+ }
2281
+ function deriveProjectSlug(cwd) {
2282
+ return cwd.replace(/([^:\\/])[\\/]+$/, "$1").replace(/\\/g, "/").replace(/^([a-zA-Z]):/, (_m, drive) => `${drive.toUpperCase()}:`).replace(/[^a-zA-Z0-9]/g, "-");
2283
+ }
2284
+ function getMemoryDir(cwd) {
2285
+ const slug = deriveProjectSlug(cwd);
2286
+ return join11(homedir7(), ".claude", "projects", slug, "memory");
2287
+ }
2288
+ function isNoopSyncReason(reason) {
2289
+ if (!reason) return false;
2290
+ return /not set|No auth configured|Failed to obtain auth token|DISABLED by/.test(reason);
2291
+ }
2292
+ async function runMemorySync(action, cwd, sessionId, fetchFn = globalThis.fetch, lockOptions = {}) {
2293
+ const killSwitch = evaluateMemorySyncKillSwitch();
2294
+ if (killSwitch.disabled) {
2295
+ return { synced: false, reason: killSwitch.reason };
2296
+ }
2297
+ const controlPlaneUrl = process.env["VO_CONTROL_PLANE_URL"];
2298
+ if (!controlPlaneUrl) {
2299
+ return { synced: false, reason: "VO_CONTROL_PLANE_URL not set \u2014 cloud mode disabled" };
2300
+ }
2301
+ const { createAuthTokenSourceFromEnv: createAuthTokenSourceFromEnv2 } = await Promise.resolve().then(() => (init_auth_token_source(), auth_token_source_exports));
2302
+ const { readStoredCredential: readStoredCredential2 } = await Promise.resolve().then(() => (init_credential_store(), credential_store_exports));
2303
+ const tokenSource = createAuthTokenSourceFromEnv2(process.env, fetchFn, () => readStoredCredential2(process.env));
2304
+ if (!tokenSource) {
2305
+ return { synced: false, reason: "No auth configured. Run `vo-mcp login` to authenticate as an operator." };
1527
2306
  }
1528
2307
  const token = await tokenSource.getToken();
1529
2308
  if (!token) {
@@ -1531,20 +2310,79 @@ async function runMemorySync(action, cwd, sessionId, fetchFn = globalThis.fetch)
1531
2310
  }
1532
2311
  const memoryDir = getMemoryDir(cwd);
1533
2312
  const baseUrl = controlPlaneUrl.replace(/\/+$/, "");
1534
- try {
1535
- if (action === "pull") {
1536
- const result2 = await pullMemory(baseUrl, token, memoryDir, fetchFn);
1537
- return { synced: true, action: "pull", pulled: result2.pulled, files: result2.files, memory_dir: memoryDir };
1538
- }
1539
- const result = await pushMemory(baseUrl, token, memoryDir, sessionId, fetchFn);
2313
+ if (action === "push" && !existsSync8(memoryDir)) {
1540
2314
  return {
1541
2315
  synced: true,
1542
2316
  action: "push",
1543
- pushed: result.pushed,
1544
- created: result.created,
1545
- updated: result.updated,
2317
+ pushed: 0,
2318
+ created: 0,
2319
+ updated: 0,
2320
+ skipped: 0,
2321
+ index_rows_preserved: 0,
2322
+ knowledge_upserted: 0,
2323
+ knowledge_failed: 0,
1546
2324
  memory_dir: memoryDir
1547
2325
  };
2326
+ }
2327
+ try {
2328
+ return await withMemorySyncLock({ ...lockOptions, memoryDir, sessionId, createDir: action === "pull" }, async (lock) => {
2329
+ const takeover = lock.tookOverFrom ? { lock_taken_over_from_pid: lock.tookOverFrom.pid } : {};
2330
+ if (action === "pull") {
2331
+ const result2 = await pullMemory(baseUrl, token, memoryDir, fetchFn);
2332
+ return {
2333
+ synced: true,
2334
+ action: "pull",
2335
+ pulled: result2.pulled,
2336
+ files: result2.files,
2337
+ memory_dir: memoryDir,
2338
+ ...takeover
2339
+ };
2340
+ }
2341
+ const deadline = createSyncDeadline();
2342
+ const cache = readPushCache(memoryDir, baseUrl);
2343
+ let result;
2344
+ try {
2345
+ result = await pushMemory(baseUrl, token, memoryDir, sessionId, fetchFn, { cache, deadline });
2346
+ } catch (err) {
2347
+ writePushCache(memoryDir, cache);
2348
+ throw err;
2349
+ }
2350
+ let bridge;
2351
+ try {
2352
+ const { upsertMemoryFilesAsKnowledge: upsertMemoryFilesAsKnowledge2 } = await Promise.resolve().then(() => (init_memory_knowledge_bridge(), memory_knowledge_bridge_exports));
2353
+ bridge = await upsertMemoryFilesAsKnowledge2({
2354
+ controlPlaneUrl: baseUrl,
2355
+ token,
2356
+ memoryDir,
2357
+ fetchFn,
2358
+ cache,
2359
+ deadline
2360
+ });
2361
+ } catch (err) {
2362
+ bridge = {
2363
+ upserted: 0,
2364
+ failed: 1,
2365
+ skipped: 0,
2366
+ failures: [`bridge unavailable: ${err instanceof Error ? err.message : String(err)}`]
2367
+ };
2368
+ }
2369
+ writePushCache(memoryDir, cache);
2370
+ return {
2371
+ synced: true,
2372
+ action: "push",
2373
+ pushed: result.pushed,
2374
+ created: result.created,
2375
+ updated: result.updated,
2376
+ skipped: result.skipped,
2377
+ index_rows_preserved: result.indexRowsPreserved,
2378
+ memory_dir: memoryDir,
2379
+ knowledge_upserted: bridge.upserted,
2380
+ knowledge_failed: bridge.failed,
2381
+ knowledge_skipped: bridge.skipped,
2382
+ ...bridge.failed > 0 ? { knowledge_failures: bridge.failures } : {},
2383
+ ...takeover
2384
+ };
2385
+ });
1548
2386
  } catch (err) {
1549
2387
  const message = err instanceof Error ? err.message : String(err);
1550
2388
  return { synced: false, reason: `Sync failed: ${message}` };
@@ -1566,6 +2404,11 @@ var init_sync_config = __esm({
1566
2404
  "src/tools/memory/sync-config.ts"() {
1567
2405
  "use strict";
1568
2406
  init_common();
2407
+ init_memory_sync_http();
2408
+ init_bounded_sync();
2409
+ init_memory_push_cache();
2410
+ init_sync_lock();
2411
+ init_sync_kill_switch();
1569
2412
  TOOL_NAME22 = "vo_sync_config";
1570
2413
  inputSchema22 = {
1571
2414
  type: "object",
@@ -1583,19 +2426,19 @@ var init_sync_config = __esm({
1583
2426
  required: ["action"],
1584
2427
  additionalProperties: false
1585
2428
  };
1586
- description22 = "Syncs memory entries between local ~/.claude/projects/<slug>/memory/ and cloud control-plane /api/v1/agent-config/memory/me. Requires operator auth (vo-mcp login). Actions: pull (cloud\u2192local), push (local\u2192cloud). Idempotent; push creates/updates as needed.";
2429
+ description22 = "Syncs memory entries between local ~/.claude/projects/<slug>/memory/ and cloud control-plane /api/v1/agent-config/memory/me. Requires operator auth (vo-mcp login). Actions: pull (cloud\u2192local), push (local\u2192cloud). Idempotent; push creates/updates as needed. Serialized across concurrent sessions by an exclusive lock in the memory dir.";
1587
2430
  }
1588
2431
  });
1589
2432
 
1590
2433
  // src/cli.ts
1591
- import { homedir as homedir6, hostname } from "node:os";
1592
- import { randomUUID as randomUUID5 } from "node:crypto";
1593
- import { join as join8 } from "node:path";
2434
+ import { homedir as homedir8, hostname as hostname2 } from "node:os";
2435
+ import { randomUUID as randomUUID6 } from "node:crypto";
2436
+ import { join as join14 } from "node:path";
1594
2437
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
1595
2438
 
1596
2439
  // src/server.ts
1597
2440
  init_common();
1598
- import { randomUUID as randomUUID2 } from "node:crypto";
2441
+ import { randomUUID as randomUUID3 } from "node:crypto";
1599
2442
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
1600
2443
  import {
1601
2444
  CallToolRequestSchema,
@@ -1971,7 +2814,8 @@ async function handleCheckHollowTest(deps, rawInput, signal) {
1971
2814
  synthesized_verdict: synthForEvent,
1972
2815
  consensus_confidence: engineResult.synthesized_verdict.confidence,
1973
2816
  duration_ms: engineResult.duration_ms,
1974
- consensus_engine_version: engineResult.engine_version
2817
+ consensus_engine_version: engineResult.engine_version,
2818
+ ...aggregateEventTokenUsage(engineResult.per_model_verdicts, engineResult.token_usage)
1975
2819
  };
1976
2820
  const payload = {
1977
2821
  verdict: engineResult.synthesized_verdict.verdict,
@@ -2154,7 +2998,8 @@ async function handleVerifyAnswer(deps, rawInput, signal) {
2154
2998
  synthesized_verdict: synthForEvent,
2155
2999
  consensus_confidence: engineResult.synthesized_verdict.confidence,
2156
3000
  duration_ms: engineResult.duration_ms,
2157
- consensus_engine_version: engineResult.engine_version
3001
+ consensus_engine_version: engineResult.engine_version,
3002
+ ...aggregateEventTokenUsage(engineResult.per_model_verdicts, engineResult.token_usage)
2158
3003
  };
2159
3004
  const payload = {
2160
3005
  verdict: engineResult.synthesized_verdict.verdict,
@@ -2163,6 +3008,9 @@ async function handleVerifyAnswer(deps, rawInput, signal) {
2163
3008
  synthesized_verdict: synthForEvent,
2164
3009
  engine_version: engineResult.engine_version,
2165
3010
  degraded: engineResult.degraded,
3011
+ ...engineResult.quorum_failed === true ? { quorum_failed: true } : {},
3012
+ // The verification receipt (moat decision_id) — the thing an agent pastes as `receipt id: <uuid>`.
3013
+ ...engineResult.receipt_id ? { receipt_id: engineResult.receipt_id } : {},
2166
3014
  gate_type: gateType,
2167
3015
  ...kbResult.error !== null ? { kb_unavailable: true } : {},
2168
3016
  ...kbTruncated > 0 ? { kb_rules_truncated: kbTruncated } : {}
@@ -2419,7 +3267,8 @@ async function handleConsensusJudgment(deps, rawInput, signal) {
2419
3267
  duration_ms: engineResult.duration_ms,
2420
3268
  consensus_engine_version: engineResult.engine_version,
2421
3269
  per_model_verdicts: perModelForEvent,
2422
- synthesized_verdict: synthForEvent
3270
+ synthesized_verdict: synthForEvent,
3271
+ ...aggregateEventTokenUsage(engineResult.per_model_verdicts, engineResult.token_usage)
2423
3272
  };
2424
3273
  const payload = {
2425
3274
  verdict: engineResult.synthesized_verdict.verdict,
@@ -2428,6 +3277,11 @@ async function handleConsensusJudgment(deps, rawInput, signal) {
2428
3277
  synthesized_verdict: synthForEvent,
2429
3278
  engine_version: engineResult.engine_version,
2430
3279
  degraded: engineResult.degraded,
3280
+ ...engineResult.quorum_failed === true ? { quorum_failed: true } : {},
3281
+ // The verification receipt (moat decision_id) — the thing an agent pastes as `receipt id: <uuid>`.
3282
+ // NOTE: a content-hash cache hit replays the ORIGINAL call's receipt_id (same claim, same verdict, no new spend) —
3283
+ // a receipt asserts the stage ran for this claim, not one-receipt-per-call.
3284
+ ...engineResult.receipt_id ? { receipt_id: engineResult.receipt_id } : {},
2431
3285
  gate_type: gateType,
2432
3286
  // ─── Consensus-engine feature outputs (additive; 2026-06-13) ─────────────
2433
3287
  // Feature 2 (calibrated-confidence) — ON by default; the engine attaches
@@ -2446,7 +3300,11 @@ async function handleConsensusJudgment(deps, rawInput, signal) {
2446
3300
  ...engineResult.low_confidence_sources !== void 0 ? { low_confidence_sources: engineResult.low_confidence_sources } : {},
2447
3301
  // Escalation (from citation grade or human-tiebreak synthesizer).
2448
3302
  ...engineResult.escalation_required !== void 0 ? { escalation_required: engineResult.escalation_required } : {},
2449
- ...engineResult.escalation_reason !== void 0 ? { escalation_reason: engineResult.escalation_reason } : {}
3303
+ ...engineResult.escalation_reason !== void 0 ? { escalation_reason: engineResult.escalation_reason } : {},
3304
+ // Critique-uptake (2026-07-20 red-team fix) — the engine computes this
3305
+ // on every call; this spread closes the gap where the visibility report
3306
+ // was itself silently dropped at the payload boundary.
3307
+ ...engineResult.critique_uptake !== void 0 ? { critique_uptake: engineResult.critique_uptake } : {}
2450
3308
  };
2451
3309
  const envelope = {
2452
3310
  tool: TOOL_NAME4,
@@ -2624,7 +3482,8 @@ async function handleArchitectureReview(deps, rawInput, signal) {
2624
3482
  synthesized_verdict: synthForEvent,
2625
3483
  consensus_confidence: engineResult.synthesized_verdict.confidence,
2626
3484
  duration_ms: engineResult.duration_ms,
2627
- consensus_engine_version: engineResult.engine_version
3485
+ consensus_engine_version: engineResult.engine_version,
3486
+ ...aggregateEventTokenUsage(engineResult.per_model_verdicts, engineResult.token_usage)
2628
3487
  };
2629
3488
  const escalationRequired = engineResult.escalation_required === true || engineResult.escalation_required === void 0 && engineResult.synthesized_verdict.dissent_summary !== null;
2630
3489
  const escalationReason = engineResult.escalation_reason ?? engineResult.synthesized_verdict.dissent_summary ?? "";
@@ -4111,7 +4970,8 @@ Produce the JSON dispatch plan now.`;
4111
4970
  duration_ms: engineResult.duration_ms,
4112
4971
  consensus_engine_version: engineResult.engine_version,
4113
4972
  per_model_verdicts: toEventPerModelVerdicts(engineResult.per_model_verdicts),
4114
- synthesized_verdict: toEventSynthesizedVerdict(engineResult.synthesized_verdict)
4973
+ synthesized_verdict: toEventSynthesizedVerdict(engineResult.synthesized_verdict),
4974
+ ...aggregateEventTokenUsage(engineResult.per_model_verdicts, engineResult.token_usage)
4115
4975
  };
4116
4976
  deps.events.append(enrichedEvent);
4117
4977
  return jsonContent(envelope);
@@ -4324,7 +5184,7 @@ async function buildCloudOrStubResponse(args) {
4324
5184
 
4325
5185
  // src/tools/heal/common-heal.ts
4326
5186
  init_common();
4327
- var HEAL_STUB_REASON = 'cloud-mode not yet wired; tool surface is live, admin-callable wiring pending vo-cloud-tenant-model dispatch (see packages/vo-mcp/src/modes/cloud.ts + EXTRACTION_AUDIT.md "Stub remaining")';
5187
+ var HEAL_STUB_REASON = "cloud mode is not configured on this session, so the admin callable was not reached. The wiring exists (see buildCloudOrStubResponse); sign in with `vo-mcp login` to route this tool to the control plane. Note that /api/v1/admin/* additionally requires a founding-operator credential.";
4328
5188
  var HEAL_GATE_TYPE = "admin-action";
4329
5189
 
4330
5190
  // src/tools/heal/trigger-heal.ts
@@ -4345,7 +5205,7 @@ var inputSchema8 = {
4345
5205
  },
4346
5206
  additionalProperties: false
4347
5207
  };
4348
- var description8 = "Triggers a self-heal pass against open PRs. Optionally scope to a `focus_page` (priority queue for one tester) or omit to fire the auto-process queue. Wraps the `voTriggerHeal` admin Cloud Function. V1 stub: returns `verdict: 'unimplemented'` with structured normalized_input \u2014 cloud-mode wiring is pending. The contract is locked; consumers can call this tool today and get the correct surface without working execution.";
5208
+ var description8 = "Triggers a self-heal pass against open PRs. Optionally scope to a `focus_page` (priority queue for one tester) or omit to fire the auto-process queue. Wraps the `voTriggerHeal` admin Cloud Function. Requires a founding-operator credential: the control plane's scoped-operator gate refuses `/api/v1/admin/*` for tenant-scoped accounts, which receive HTTP 403 `scoped_operator_forbidden`. Falls back to a clearly-marked `unimplemented` envelope with structured normalized_input when cloud mode is not configured.";
4349
5209
  function isToolInput8(v) {
4350
5210
  if (typeof v !== "object" || v === null) return false;
4351
5211
  const o = v;
@@ -4403,7 +5263,7 @@ var inputSchema9 = {
4403
5263
  },
4404
5264
  additionalProperties: false
4405
5265
  };
4406
- var description9 = "Retries one or more failed fix attempts by id. Pass `attempt_id` for the single case or `attempt_ids` (up to 50) for the batch case. Wraps `voRetryFixAttempt` / `voRetryFixAttempts` admin Cloud Functions. V1 stub: returns `verdict: 'unimplemented'` \u2014 cloud-mode wiring pending.";
5266
+ var description9 = "Retries one or more failed fix attempts by id. Pass `attempt_id` for the single case or `attempt_ids` (up to 50) for the batch case. Wraps `voRetryFixAttempt` / `voRetryFixAttempts` admin Cloud Functions. Requires a founding-operator credential: the control plane's scoped-operator gate refuses `/api/v1/admin/*` for tenant-scoped accounts, which receive HTTP 403 `scoped_operator_forbidden`. Falls back to a clearly-marked `unimplemented` envelope when cloud mode is not configured.";
4407
5267
  function isToolInput9(v) {
4408
5268
  if (typeof v !== "object" || v === null) return false;
4409
5269
  const o = v;
@@ -4491,7 +5351,7 @@ var inputSchema10 = {
4491
5351
  required: ["attempt_id"],
4492
5352
  additionalProperties: false
4493
5353
  };
4494
- var description10 = "Clears (cancels) a single fix attempt by id. Wraps `voClearFixAttempt` admin Cloud Function. V1 stub: returns `verdict: 'unimplemented'` \u2014 cloud-mode wiring pending.";
5354
+ var description10 = "Clears (cancels) a single fix attempt by id. Wraps `voClearFixAttempt` admin Cloud Function. Requires a founding-operator credential: the control plane's scoped-operator gate refuses `/api/v1/admin/*` for tenant-scoped accounts, which receive HTTP 403 `scoped_operator_forbidden`. Falls back to a clearly-marked `unimplemented` envelope when cloud mode is not configured.";
4495
5355
  function isToolInput10(v) {
4496
5356
  if (typeof v !== "object" || v === null) return false;
4497
5357
  const o = v;
@@ -4539,7 +5399,7 @@ var inputSchema11 = {
4539
5399
  required: ["run_id"],
4540
5400
  additionalProperties: false
4541
5401
  };
4542
- var description11 = "Cancels a running GitHub Actions workflow by run id. Wraps `voStopWorkflow` admin Cloud Function. V1 stub: returns `verdict: 'unimplemented'` \u2014 cloud-mode wiring pending.";
5402
+ var description11 = "Cancels a running GitHub Actions workflow by run id. Wraps `voStopWorkflow` admin Cloud Function. Requires a founding-operator credential: the control plane's scoped-operator gate refuses `/api/v1/admin/*` for tenant-scoped accounts, which receive HTTP 403 `scoped_operator_forbidden`. Falls back to a clearly-marked `unimplemented` envelope when cloud mode is not configured.";
4543
5403
  function isToolInput11(v) {
4544
5404
  if (typeof v !== "object" || v === null) return false;
4545
5405
  const o = v;
@@ -4585,7 +5445,7 @@ var inputSchema12 = {
4585
5445
  properties: {},
4586
5446
  additionalProperties: false
4587
5447
  };
4588
- var description12 = "Returns the current Command Center workflow-runs snapshot (Heal, Manager, Auto-Merge, Deploy on Merge, etc.). Wraps `voGetWorkflowRuns` admin Cloud Function. Read-only diagnostic. V1 stub: returns `verdict: 'unimplemented'` \u2014 cloud-mode wiring pending.";
5448
+ var description12 = "Returns the current Command Center workflow-runs snapshot (Heal, Manager, Auto-Merge, Deploy on Merge, etc.). Wraps `voGetWorkflowRuns` admin Cloud Function. Read-only diagnostic. Requires a founding-operator credential: the control plane's scoped-operator gate refuses `/api/v1/admin/*` for tenant-scoped accounts, which receive HTTP 403 `scoped_operator_forbidden`. Falls back to a clearly-marked `unimplemented` envelope when cloud mode is not configured.";
4589
5449
  function isToolInput12(v) {
4590
5450
  if (typeof v !== "object" || v === null) return false;
4591
5451
  return true;
@@ -4612,7 +5472,7 @@ init_common();
4612
5472
 
4613
5473
  // src/tools/pr/common-pr.ts
4614
5474
  init_common();
4615
- var PR_STUB_REASON = 'cloud-mode not yet wired; tool surface is live, admin-callable wiring pending vo-cloud-tenant-model dispatch (see packages/vo-mcp/src/modes/cloud.ts + EXTRACTION_AUDIT.md "Stub remaining")';
5475
+ var PR_STUB_REASON = "cloud mode is not configured on this session, so the admin callable was not reached. The wiring exists (see buildCloudOrStubResponse); sign in with `vo-mcp login` to route this tool to the control plane. Note that /api/v1/admin/* additionally requires a founding-operator credential.";
4616
5476
  var PR_GATE_TYPE = "admin-action";
4617
5477
 
4618
5478
  // src/tools/pr/list-pending-prs.ts
@@ -4624,7 +5484,7 @@ var inputSchema13 = {
4624
5484
  properties: {},
4625
5485
  additionalProperties: false
4626
5486
  };
4627
- var description13 = "Lists open VO-source pull requests with blocker / source / tester / specialist-context metadata. Read-only diagnostic for Command Center reads. Wraps `voListPendingPRs` admin Cloud Function. V1 stub: returns `verdict: 'unimplemented'` \u2014 cloud-mode wiring pending.";
5487
+ var description13 = "Lists open AlgoHQ-source pull requests with blocker / source / tester / specialist-context metadata. Read-only diagnostic for Command Center reads. Wraps `voListPendingPRs` admin Cloud Function. Requires a founding-operator credential: the control plane's scoped-operator gate refuses `/api/v1/admin/*` for tenant-scoped accounts, which receive HTTP 403 `scoped_operator_forbidden`. Falls back to a clearly-marked `unimplemented` envelope when cloud mode is not configured.";
4628
5488
  function isToolInput13(v) {
4629
5489
  return typeof v === "object" && v !== null;
4630
5490
  }
@@ -4661,7 +5521,7 @@ var inputSchema14 = {
4661
5521
  required: ["pr_number"],
4662
5522
  additionalProperties: false
4663
5523
  };
4664
- var description14 = "Approves + merges a single VO-source pull request by number. Wraps `voMergePR` admin Cloud Function (server-side refuses non-VO PRs with permission-denied). V1 stub: returns `verdict: 'unimplemented'` \u2014 cloud-mode wiring pending.";
5524
+ var description14 = "Approves + merges a single AlgoHQ-source pull request by number. Wraps `voMergePR` admin Cloud Function (server-side refuses non-AlgoHQ PRs with permission-denied). Requires a founding-operator credential: the control plane's scoped-operator gate refuses `/api/v1/admin/*` for tenant-scoped accounts, which receive HTTP 403 `scoped_operator_forbidden`. Falls back to a clearly-marked `unimplemented` envelope when cloud mode is not configured.";
4665
5525
  function isToolInput14(v) {
4666
5526
  if (typeof v !== "object" || v === null) return false;
4667
5527
  const o = v;
@@ -4706,7 +5566,7 @@ var inputSchema15 = {
4706
5566
  required: ["pr_number"],
4707
5567
  additionalProperties: false
4708
5568
  };
4709
- var description15 = "Closes a pull request without merging. No retry dispatched \u2014 use `vo_reject_and_retry` for close+retry. Wraps `voRejectPR` admin Cloud Function. V1 stub: returns `verdict: 'unimplemented'` \u2014 cloud-mode wiring pending.";
5569
+ var description15 = "Closes a pull request without merging. No retry dispatched \u2014 use `vo_reject_and_retry` for close+retry. Wraps `voRejectPR` admin Cloud Function. Requires a founding-operator credential: the control plane's scoped-operator gate refuses `/api/v1/admin/*` for tenant-scoped accounts, which receive HTTP 403 `scoped_operator_forbidden`. Falls back to a clearly-marked `unimplemented` envelope when cloud mode is not configured.";
4710
5570
  function isToolInput15(v) {
4711
5571
  if (typeof v !== "object" || v === null) return false;
4712
5572
  const o = v;
@@ -4745,7 +5605,7 @@ var inputSchema16 = {
4745
5605
  properties: {},
4746
5606
  additionalProperties: false
4747
5607
  };
4748
- var description16 = "Iterates all open VO-source pull requests and merges (or arms auto-merge) on each. Returns counts of merged / accepted / total plus per-PR results. Wraps `voApproveAllFixes` admin Cloud Function. V1 stub: returns `verdict: 'unimplemented'` \u2014 cloud-mode wiring pending.";
5608
+ var description16 = "Iterates all open AlgoHQ-source pull requests and merges (or arms auto-merge) on each. Returns counts of merged / accepted / total plus per-PR results. Wraps `voApproveAllFixes` admin Cloud Function. Requires a founding-operator credential: the control plane's scoped-operator gate refuses `/api/v1/admin/*` for tenant-scoped accounts, which receive HTTP 403 `scoped_operator_forbidden`. Falls back to a clearly-marked `unimplemented` envelope when cloud mode is not configured.";
4749
5609
  function isToolInput16(v) {
4750
5610
  return typeof v === "object" && v !== null;
4751
5611
  }
@@ -4780,7 +5640,7 @@ var inputSchema17 = {
4780
5640
  required: ["pr_number"],
4781
5641
  additionalProperties: false
4782
5642
  };
4783
- var description17 = "Closes a VO pull request and dispatches a self-heal pass to retry the same focus page. Cloud callable refuses non-VO PRs and respects the self-heal kill switch + per-PR retry block. Wraps `voRejectAndRetry` admin Cloud Function. V1 stub: returns `verdict: 'unimplemented'` \u2014 cloud-mode wiring pending.";
5643
+ var description17 = "Closes an AlgoHQ pull request and dispatches a self-heal pass to retry the same focus page. Cloud callable refuses non-AlgoHQ PRs and respects the self-heal kill switch + per-PR retry block. Wraps `voRejectAndRetry` admin Cloud Function. Requires a founding-operator credential: the control plane's scoped-operator gate refuses `/api/v1/admin/*` for tenant-scoped accounts, which receive HTTP 403 `scoped_operator_forbidden`. Falls back to a clearly-marked `unimplemented` envelope when cloud mode is not configured.";
4784
5644
  function isToolInput17(v) {
4785
5645
  if (typeof v !== "object" || v === null) return false;
4786
5646
  const o = v;
@@ -4857,7 +5717,7 @@ function buildPrompt4(pr, notes) {
4857
5717
  const lines = [
4858
5718
  "You are a release gatekeeper deciding whether a pull request is safe to MERGE.",
4859
5719
  "Recommend exactly one of: merge / hold / reject. Be conservative \u2014 this is a high-stakes irreversible action.",
4860
- "Rules: HOLD if CI is failing/blocked, there is a merge conflict, or the change is a draft. REJECT if the PR is not a legitimate VO-source change or has no clear purpose. MERGE only if it looks complete, scoped, and unblocked.",
5720
+ "Rules: HOLD if CI is failing/blocked, there is a merge conflict, or the change is a draft. REJECT if the PR is not a legitimate AlgoHQ-source change or has no clear purpose. MERGE only if it looks complete, scoped, and unblocked.",
4861
5721
  "",
4862
5722
  `PR #${pr.number}: ${pr.title}`,
4863
5723
  `Source: ${pr.source ?? "unknown"}`,
@@ -4925,7 +5785,7 @@ async function handleReviewMerge(deps, rawInput, signal) {
4925
5785
  }
4926
5786
  if (pr === null) {
4927
5787
  return emit(
4928
- emptyPayload("hold", `PR #${prNumber} is not among open VO PRs (already merged/closed, or not a VO-source PR).`, null)
5788
+ emptyPayload("hold", `PR #${prNumber} is not among open AlgoHQ PRs (already merged/closed, or not an AlgoHQ-source PR).`, null)
4929
5789
  );
4930
5790
  }
4931
5791
  const hasBlocker = pr.blocker !== null && pr.blocker !== "none";
@@ -4985,7 +5845,8 @@ async function handleReviewMerge(deps, rawInput, signal) {
4985
5845
  duration_ms: result.duration_ms,
4986
5846
  consensus_engine_version: result.engine_version,
4987
5847
  per_model_verdicts: perModel,
4988
- synthesized_verdict: synth
5848
+ synthesized_verdict: synth,
5849
+ ...aggregateEventTokenUsage(result.per_model_verdicts, result.token_usage)
4989
5850
  });
4990
5851
  }
4991
5852
 
@@ -5023,6 +5884,8 @@ function suggestedHandoffPath(session_id, isoTimestamp) {
5023
5884
  }
5024
5885
 
5025
5886
  // src/tools/session/report-session-state.ts
5887
+ init_auth_token_source();
5888
+ init_credential_store();
5026
5889
  var TOOL_NAME19 = "vo_report_session_state";
5027
5890
  var VALID_AGENT_TYPES = ["claude-code", "codex", "cursor", "continue"];
5028
5891
  var MAX_GOAL_CHARS = 500;
@@ -5073,7 +5936,7 @@ var inputSchema19 = {
5073
5936
  required: ["operator_id", "session_id", "agent_type", "context_used_pct"],
5074
5937
  additionalProperties: false
5075
5938
  };
5076
- var description19 = "Reports per-session context-window utilization to VO and returns a directive: 'continue' (under 70%), 'prepare_handoff' (70-84%), or 'execute_handoff_now' (\u226585%). Implements V1 launch gate #9 (fleet context lifecycle management) per the official VO roadmap. Cloud-control-plane mode when VO_CONTROL_PLANE_URL + VO_CONTROL_PLANE_ADMIN_TOKEN + VO_TENANT_ID are set; auto-allocates the session on first report so interactive agents (Claude Code, Cursor, Codex, Continue) appear on the live fleet whiteboard. Stub-local fallback when cloud config is absent or fails. The response shape stays stable across modes (`backend_mode` field in the payload tells the caller which mode produced the verdict).";
5939
+ var description19 = "Reports per-session context-window utilization to AlgoHQ and returns a directive: 'continue' (under 70%), 'prepare_handoff' (70-84%), or 'execute_handoff_now' (\u226585%). Implements V1 launch gate #9 (fleet context lifecycle management) per the official AlgoHQ roadmap. Cloud-control-plane mode when VO_CONTROL_PLANE_URL plus a user/scoped HQ credential (or legacy admin token) is available; auto-allocates the session on first report so interactive agents (Claude Code, Cursor, Codex, Continue) appear on the live fleet whiteboard. Stub-local fallback when cloud config is absent or fails. The response shape stays stable across modes (`backend_mode` field in the payload tells the caller which mode produced the verdict).";
5077
5940
  function isStringArray2(v, maxItems) {
5078
5941
  if (!Array.isArray(v)) return false;
5079
5942
  if (v.length > maxItems) return false;
@@ -5098,14 +5961,39 @@ function isToolInput19(v) {
5098
5961
  }
5099
5962
  return true;
5100
5963
  }
5101
- function getCloudConfig() {
5102
- const url = process.env["VO_CONTROL_PLANE_URL"];
5103
- const token = process.env["VO_CONTROL_PLANE_ADMIN_TOKEN"];
5104
- const tenant_id = process.env["VO_TENANT_ID"];
5105
- if (!url || !token || !tenant_id) return null;
5106
- return { url, token, tenant_id };
5964
+ async function fetchCloudIdentity(url, token, fetchFn) {
5965
+ try {
5966
+ const response = await fetchFn(`${url}/api/v1/auth/me`, {
5967
+ method: "GET",
5968
+ headers: {
5969
+ "Authorization": `Bearer ${token}`
5970
+ }
5971
+ });
5972
+ if (!response.ok) return null;
5973
+ const data = await response.json();
5974
+ if (!data.ok || !data.provisioned || !data.operator_id || !data.tenant_id) return null;
5975
+ return { operator_id: data.operator_id, tenant_id: data.tenant_id };
5976
+ } catch {
5977
+ return null;
5978
+ }
5979
+ }
5980
+ async function getCloudConfig(fetchFn = fetch) {
5981
+ const url = process.env["VO_CONTROL_PLANE_URL"]?.trim();
5982
+ if (!url) return null;
5983
+ const tokenSource = createAuthTokenSourceFromEnv(
5984
+ process.env,
5985
+ fetchFn,
5986
+ () => readStoredCredential(process.env)
5987
+ );
5988
+ const token = await tokenSource?.getToken();
5989
+ if (!token) return null;
5990
+ const tenant_id = process.env["VO_TENANT_ID"]?.trim();
5991
+ if (tenant_id) return { url, token, tenant_id };
5992
+ const identity = await fetchCloudIdentity(url, token, fetchFn);
5993
+ if (!identity) return null;
5994
+ return { url, token, tenant_id: identity.tenant_id, operator_id: identity.operator_id };
5107
5995
  }
5108
- async function tryCloudReportState(cloud, input) {
5996
+ async function tryCloudReportState(cloud, input, fetchFn = fetch) {
5109
5997
  try {
5110
5998
  const reportBody = {
5111
5999
  context_used_pct: input.context_used_pct
@@ -5118,7 +6006,7 @@ async function tryCloudReportState(cloud, input) {
5118
6006
  reportBody["recent_tool_uses"] = input.recent_tool_uses;
5119
6007
  }
5120
6008
  const reportUrl = `${cloud.url}/api/v1/session/${input.session_id}/report-state`;
5121
- let response = await fetch(reportUrl, {
6009
+ let response = await fetchFn(reportUrl, {
5122
6010
  method: "POST",
5123
6011
  headers: {
5124
6012
  "Content-Type": "application/json",
@@ -5128,7 +6016,7 @@ async function tryCloudReportState(cloud, input) {
5128
6016
  });
5129
6017
  if (response.status === 404) {
5130
6018
  const allocateBody = {
5131
- operator_id: input.operator_id,
6019
+ operator_id: cloud.operator_id ?? input.operator_id,
5132
6020
  tenant_id: cloud.tenant_id,
5133
6021
  agent_type: input.agent_type,
5134
6022
  current_goal: input.current_goal ?? "Interactive session"
@@ -5137,7 +6025,7 @@ async function tryCloudReportState(cloud, input) {
5137
6025
  allocateBody["initial_context_used_pct"] = input.context_used_pct;
5138
6026
  }
5139
6027
  const allocateUrl = `${cloud.url}/api/v1/session`;
5140
- const allocateResponse = await fetch(allocateUrl, {
6028
+ const allocateResponse = await fetchFn(allocateUrl, {
5141
6029
  method: "POST",
5142
6030
  headers: {
5143
6031
  "Content-Type": "application/json",
@@ -5148,7 +6036,10 @@ async function tryCloudReportState(cloud, input) {
5148
6036
  if (!allocateResponse.ok) {
5149
6037
  return null;
5150
6038
  }
5151
- response = await fetch(reportUrl, {
6039
+ const allocateData = await allocateResponse.json();
6040
+ const retrySessionId = typeof allocateData.session?.session_id === "string" && allocateData.session.session_id.length > 0 ? allocateData.session.session_id : input.session_id;
6041
+ const retryReportUrl = `${cloud.url}/api/v1/session/${retrySessionId}/report-state`;
6042
+ response = await fetchFn(retryReportUrl, {
5152
6043
  method: "POST",
5153
6044
  headers: {
5154
6045
  "Content-Type": "application/json",
@@ -5187,7 +6078,7 @@ async function handleReportSessionState(deps, rawInput, _signal) {
5187
6078
  `invalid input. Required fields: operator_id (non-empty string), session_id (non-empty string), agent_type (one of: ${VALID_AGENT_TYPES.join(" | ")}), context_used_pct (number 0-100). Optional: current_goal (string \u2264${MAX_GOAL_CHARS} chars), recent_files_touched (string[] \u2264${MAX_RECENT_FILES}), recent_tool_uses (string[] \u2264${MAX_RECENT_TOOLS}).`
5188
6079
  );
5189
6080
  }
5190
- const cloud = getCloudConfig();
6081
+ const cloud = await getCloudConfig();
5191
6082
  if (cloud !== null) {
5192
6083
  const cloudPayload = await tryCloudReportState(cloud, rawInput);
5193
6084
  if (cloudPayload !== null) {
@@ -5214,9 +6105,324 @@ async function handleReportSessionState(deps, rawInput, _signal) {
5214
6105
  // src/tools/session/spawn-successor.ts
5215
6106
  init_common();
5216
6107
  import { spawn } from "node:child_process";
6108
+ import { homedir as homedir5 } from "node:os";
6109
+ 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";
6111
+
6112
+ // src/swarm/tier-binding.ts
6113
+ var SWARM_TIERS = Object.freeze([
6114
+ "tier1_subscription",
6115
+ "tier1_local",
6116
+ "tier2_user_key",
6117
+ "tier3_platform_key",
6118
+ "refused",
6119
+ "unresolved"
6120
+ ]);
6121
+ var TIER_ADMITS_SPAWN = /* @__PURE__ */ new Set([
6122
+ "tier1_subscription",
6123
+ "tier1_local",
6124
+ "tier2_user_key",
6125
+ "tier3_platform_key"
6126
+ ]);
6127
+ var SWARM_TIER_BINDING_ENV = "VO_SWARM_TIER_BINDING";
6128
+ var MAX_BOUND_SUBAGENTS = 20;
6129
+ function isPositiveCap(cap) {
6130
+ return typeof cap === "number" && Number.isFinite(cap) && cap > 0;
6131
+ }
6132
+ function unresolvedBinding(swarmId, nowIso, reason) {
6133
+ return {
6134
+ schema_version: 1,
6135
+ swarm_id: swarmId,
6136
+ tier: "unresolved",
6137
+ agent: null,
6138
+ reason,
6139
+ exhausted_agents: [],
6140
+ subagent_budget: 0,
6141
+ spend_cap_usd: null,
6142
+ resolved_at: nowIso
6143
+ };
6144
+ }
6145
+ function serializeSwarmTierBinding(binding) {
6146
+ return JSON.stringify(binding);
6147
+ }
6148
+ function parseSwarmTierBinding(raw, nowIso) {
6149
+ if (typeof raw !== "string" || raw.trim().length === 0) {
6150
+ return unresolvedBinding("", nowIso, "no swarm tier binding present in the environment");
6151
+ }
6152
+ let parsed;
6153
+ try {
6154
+ parsed = JSON.parse(raw);
6155
+ } catch {
6156
+ return unresolvedBinding("", nowIso, "swarm tier binding is not valid JSON");
6157
+ }
6158
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
6159
+ return unresolvedBinding("", nowIso, "swarm tier binding is not an object");
6160
+ }
6161
+ const o = parsed;
6162
+ const swarmId = typeof o["swarm_id"] === "string" ? o["swarm_id"] : "";
6163
+ if (o["schema_version"] !== 1) {
6164
+ return unresolvedBinding(swarmId, nowIso, "swarm tier binding has an unsupported schema_version");
6165
+ }
6166
+ const tier = o["tier"];
6167
+ if (typeof tier !== "string" || !SWARM_TIERS.includes(tier)) {
6168
+ return unresolvedBinding(swarmId, nowIso, "swarm tier binding names an unknown tier");
6169
+ }
6170
+ const budget = o["subagent_budget"];
6171
+ const cap = o["spend_cap_usd"];
6172
+ const capNum = isPositiveCap(cap) ? cap : null;
6173
+ if (tier === "tier3_platform_key" && capNum === null) {
6174
+ return unresolvedBinding(
6175
+ swarmId,
6176
+ nowIso,
6177
+ "inherited tier3_platform_key binding carries no positive numeric spend cap \u2014 refusing an uncapped platform-billed fan-out"
6178
+ );
6179
+ }
6180
+ return {
6181
+ schema_version: 1,
6182
+ swarm_id: swarmId,
6183
+ tier,
6184
+ agent: typeof o["agent"] === "string" ? o["agent"] : null,
6185
+ reason: typeof o["reason"] === "string" ? o["reason"] : "inherited binding carried no reason",
6186
+ exhausted_agents: Array.isArray(o["exhausted_agents"]) ? o["exhausted_agents"].filter((v) => typeof v === "string") : [],
6187
+ subagent_budget: typeof budget === "number" && Number.isFinite(budget) && budget > 0 ? Math.min(Math.floor(budget), MAX_BOUND_SUBAGENTS) : 0,
6188
+ spend_cap_usd: capNum,
6189
+ resolved_at: typeof o["resolved_at"] === "string" ? o["resolved_at"] : nowIso
6190
+ };
6191
+ }
6192
+ function inheritSwarmTierBinding(env, nowIso) {
6193
+ return parseSwarmTierBinding(env[SWARM_TIER_BINDING_ENV], nowIso);
6194
+ }
6195
+ function bindingEnvFragment(binding) {
6196
+ return { [SWARM_TIER_BINDING_ENV]: serializeSwarmTierBinding(binding) };
6197
+ }
6198
+ function childBindingEnvFragment(binding, allocatedCapUsd = null) {
6199
+ return bindingEnvFragment(childBinding(binding, allocatedCapUsd));
6200
+ }
6201
+ function admitSubagentSpawn(binding, spawnsSoFar = 0) {
6202
+ if (!TIER_ADMITS_SPAWN.has(binding.tier)) {
6203
+ return { allowed: false, reason: `tier '${binding.tier}' admits no spawn: ${binding.reason}` };
6204
+ }
6205
+ if (binding.tier === "tier3_platform_key" && !isPositiveCap(binding.spend_cap_usd)) {
6206
+ return {
6207
+ allowed: false,
6208
+ reason: `swarm ${binding.swarm_id} is tier3_platform_key with no positive spend cap \u2014 refusing to spend the platform owner's money uncapped`
6209
+ };
6210
+ }
6211
+ if (!Number.isFinite(spawnsSoFar) || spawnsSoFar < 0) {
6212
+ return { allowed: false, reason: "spawn counter is not a finite non-negative number" };
6213
+ }
6214
+ if (spawnsSoFar >= binding.subagent_budget) {
6215
+ return {
6216
+ allowed: false,
6217
+ reason: `swarm ${binding.swarm_id} exhausted its bound subagent budget (${binding.subagent_budget})`
6218
+ };
6219
+ }
6220
+ return { allowed: true, reason: `admitted under tier '${binding.tier}'` };
6221
+ }
6222
+ function childBinding(binding, allocatedCapUsd = null) {
6223
+ const allocated = isPositiveCap(allocatedCapUsd) ? allocatedCapUsd : null;
6224
+ const parentCap = isPositiveCap(binding.spend_cap_usd) ? binding.spend_cap_usd : null;
6225
+ return {
6226
+ ...binding,
6227
+ subagent_budget: Math.max(0, binding.subagent_budget - 1),
6228
+ // A child never carries more than its parent, whatever the ledger says: a
6229
+ // forged or hand-edited pool cannot inflate a descendant above the binding
6230
+ // it descends from.
6231
+ spend_cap_usd: allocated === null || parentCap === null ? null : Math.min(allocated, parentCap)
6232
+ };
6233
+ }
6234
+ function agentBindingRefusal(binding, requestedAgent) {
6235
+ const requested = typeof requestedAgent === "string" ? requestedAgent.trim() : "";
6236
+ if (requested.length === 0) return null;
6237
+ if (binding.agent !== null && requested === binding.agent) return null;
6238
+ return `swarm '${binding.swarm_id}' is bound to agent '${binding.agent ?? "none"}' under tier '${binding.tier}'; a caller-supplied agent '${requested}' would move this fan-out onto a different payer \u2014 refusing (the tier is decided once, at admission, and an inherited binding cannot be renegotiated)`;
6239
+ }
6240
+
6241
+ // src/swarm/successor-launch.ts
6242
+ var AGENT_LAUNCH_SHAPES = Object.freeze({
6243
+ claude: {
6244
+ bin: "claude",
6245
+ baseArgs: ["-p", "--permission-mode", "acceptEdits"],
6246
+ enforcesMaxTurns: true,
6247
+ maxTurnsFlag: "--max-turns",
6248
+ windowsShellSafe: true
6249
+ },
6250
+ codex: {
6251
+ bin: "codex",
6252
+ baseArgs: ["exec", "--json", "-c", 'approval_policy="never"', "--sandbox", "workspace-write", "--skip-git-repo-check"],
6253
+ enforcesMaxTurns: false,
6254
+ // `-` makes codex read the prompt from stdin (injection-safe), matching how
6255
+ // codex-runner.mjs already spawns it.
6256
+ trailingArgs: ["-"],
6257
+ // `approval_policy="never"` carries embedded quotes; cmd.exe re-parsing is
6258
+ // unverified, so win32 refuses rather than risking a mangled sandbox flag.
6259
+ windowsShellSafe: false
6260
+ }
6261
+ });
6262
+ function resolveSuccessorLaunch(input) {
6263
+ const agent = typeof input.agent === "string" ? input.agent.trim() : "";
6264
+ if (!agent) {
6265
+ return { ok: false, reason: "no agent bound for this spawn \u2014 refusing rather than defaulting to claude" };
6266
+ }
6267
+ const shape = AGENT_LAUNCH_SHAPES[agent];
6268
+ if (!shape) {
6269
+ const known = Object.keys(AGENT_LAUNCH_SHAPES).join(", ");
6270
+ return {
6271
+ ok: false,
6272
+ reason: `no known headless launch shape for agent '${agent}' (known: ${known}) \u2014 refusing rather than guessing its argv`
6273
+ };
6274
+ }
6275
+ const wantsMaxTurns = Number.isInteger(input.maxTurns) && input.maxTurns > 0;
6276
+ if (wantsMaxTurns && !shape.enforcesMaxTurns) {
6277
+ return {
6278
+ ok: false,
6279
+ reason: `agent '${agent}' cannot enforce a max_turns cap \u2014 refusing rather than spawning it unbounded`
6280
+ };
6281
+ }
6282
+ const platform = input.platform ?? process.platform;
6283
+ if (platform === "win32" && !shape.windowsShellSafe) {
6284
+ return {
6285
+ ok: false,
6286
+ reason: `agent '${agent}' has an argv whose behaviour under Windows cmd.exe re-parsing is unverified \u2014 refusing rather than emitting a command line that may mean something else`
6287
+ };
6288
+ }
6289
+ const args = [...shape.baseArgs];
6290
+ if (wantsMaxTurns && shape.maxTurnsFlag) {
6291
+ args.push(shape.maxTurnsFlag, String(input.maxTurns));
6292
+ }
6293
+ if (shape.trailingArgs) args.push(...shape.trailingArgs);
6294
+ return { ok: true, agent, bin: shape.bin, args };
6295
+ }
6296
+
6297
+ // src/swarm/spawn-ledger.ts
6298
+ import { mkdirSync as mkdirSync3, openSync, closeSync, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "node:fs";
5217
6299
  import { homedir as homedir4 } from "node:os";
5218
6300
  import { join as join6 } from "node:path";
5219
- import { existsSync as existsSync4, mkdirSync as mkdirSync3, openSync, readFileSync as readFileSync6, readdirSync as readdirSync3, statSync as statSync3 } from "node:fs";
6301
+ var SWARM_LEDGER_DIR_ENV = "VO_SWARM_LEDGER_DIR";
6302
+ function resolveLedgerDir(env) {
6303
+ const override = env[SWARM_LEDGER_DIR_ENV];
6304
+ if (typeof override === "string" && override.trim().length > 0) return override.trim();
6305
+ return join6(homedir4(), ".vo", "swarm-ledger");
6306
+ }
6307
+ function sanitizeSwarmId(raw) {
6308
+ if (typeof raw !== "string") return null;
6309
+ const id = raw.trim();
6310
+ if (id.length === 0 || id.length > 128) return null;
6311
+ if (!/^[A-Za-z0-9._-]+$/u.test(id)) return null;
6312
+ if (id === "." || id === "..") return null;
6313
+ return id;
6314
+ }
6315
+ var CEILING_FILE = "ceiling.json";
6316
+ function createExclusive(path3, contents) {
6317
+ let fd;
6318
+ try {
6319
+ fd = openSync(path3, "wx");
6320
+ } catch {
6321
+ return false;
6322
+ }
6323
+ try {
6324
+ writeFileSync3(fd, contents, "utf8");
6325
+ } finally {
6326
+ closeSync(fd);
6327
+ }
6328
+ return true;
6329
+ }
6330
+ function capToCents(cap) {
6331
+ return isPositiveCap(cap) ? Math.round(cap * 100) : 0;
6332
+ }
6333
+ function readOrRecordLedgerHead(swarmDir, proposedCeiling, proposedCapCents, nowIso) {
6334
+ const path3 = join6(swarmDir, CEILING_FILE);
6335
+ const head = JSON.stringify({
6336
+ ceiling: proposedCeiling,
6337
+ cap_cents: proposedCapCents,
6338
+ recorded_at: nowIso
6339
+ });
6340
+ if (createExclusive(path3, head)) {
6341
+ return { ceiling: proposedCeiling, capCents: proposedCapCents };
6342
+ }
6343
+ let parsed;
6344
+ try {
6345
+ parsed = JSON.parse(readFileSync6(path3, "utf8"));
6346
+ } catch {
6347
+ return null;
6348
+ }
6349
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return null;
6350
+ const record = parsed;
6351
+ const recorded = record["ceiling"];
6352
+ if (typeof recorded !== "number" || !Number.isFinite(recorded) || recorded < 1) return null;
6353
+ const recordedCap = record["cap_cents"];
6354
+ const capCents = typeof recordedCap === "number" && Number.isFinite(recordedCap) && recordedCap > 0 ? Math.floor(recordedCap) : 0;
6355
+ return { ceiling: Math.min(Math.floor(recorded), MAX_BOUND_SUBAGENTS), capCents };
6356
+ }
6357
+ var claimSpawnSlot = ({ swarmId, proposedCeiling, proposedCapUsd, dir, nowIso }) => {
6358
+ const id = sanitizeSwarmId(swarmId);
6359
+ if (id === null) {
6360
+ return {
6361
+ ok: false,
6362
+ reason: `swarm id ${JSON.stringify(swarmId)} is absent or unusable as a ledger key \u2014 refusing a spawn that cannot be counted against a fan-out ceiling`
6363
+ };
6364
+ }
6365
+ const proposed = Number.isFinite(proposedCeiling) ? Math.floor(proposedCeiling) : 0;
6366
+ if (proposed < 1) {
6367
+ return { ok: false, reason: `swarm '${id}' proposes a ceiling of ${proposed} \u2014 no allowance to claim` };
6368
+ }
6369
+ const swarmDir = join6(dir, id);
6370
+ try {
6371
+ mkdirSync3(swarmDir, { recursive: true });
6372
+ } catch (err) {
6373
+ return {
6374
+ ok: false,
6375
+ reason: `swarm '${id}' ledger directory is unwritable (${err instanceof Error ? err.message : String(err)}) \u2014 refusing rather than spawning uncounted`
6376
+ };
6377
+ }
6378
+ const wantedCents = capToCents(proposedCapUsd);
6379
+ const head = readOrRecordLedgerHead(swarmDir, Math.min(proposed, MAX_BOUND_SUBAGENTS), wantedCents, nowIso);
6380
+ if (head === null) {
6381
+ return { ok: false, reason: `swarm '${id}' ledger carries no readable ceiling \u2014 refusing rather than spawning uncounted` };
6382
+ }
6383
+ const { ceiling, capCents } = head;
6384
+ const shareCents = capCents > 0 ? Math.floor(capCents / ceiling) : 0;
6385
+ if (wantedCents > 0 && shareCents < 1) {
6386
+ return {
6387
+ ok: false,
6388
+ reason: `swarm '${id}' has no spend allowance left to debit (recorded pool $${(capCents / 100).toFixed(2)} across a ceiling of ${ceiling} leaves under one cent per spawn) \u2014 refusing a platform-billed spawn it cannot fund`
6389
+ };
6390
+ }
6391
+ for (let slot = 0; slot < ceiling; slot++) {
6392
+ const debitedCents = shareCents;
6393
+ const remainingCents = capCents > 0 ? capCents - (slot + 1) * shareCents : 0;
6394
+ const claimed = createExclusive(
6395
+ join6(swarmDir, `slot-${slot}.json`),
6396
+ JSON.stringify({
6397
+ slot,
6398
+ ceiling,
6399
+ pid: process.pid,
6400
+ claimed_at: nowIso,
6401
+ // The debit record. Durable and atomic with the claim: this file is
6402
+ // created with O_EXCL, so exactly one claimant ever writes this line.
6403
+ cap_cents_pool: capCents,
6404
+ cap_cents_debited: debitedCents,
6405
+ cap_cents_remaining: remainingCents
6406
+ })
6407
+ );
6408
+ if (claimed) {
6409
+ return {
6410
+ ok: true,
6411
+ slot,
6412
+ ceiling,
6413
+ remaining: ceiling - slot - 1,
6414
+ capUsd: debitedCents > 0 ? debitedCents / 100 : null,
6415
+ capRemainingUsd: capCents > 0 ? remainingCents / 100 : null
6416
+ };
6417
+ }
6418
+ }
6419
+ return {
6420
+ ok: false,
6421
+ reason: `swarm '${id}' has spent its whole fan-out ceiling (${ceiling} spawns across every generation) \u2014 refusing`
6422
+ };
6423
+ };
6424
+
6425
+ // src/tools/session/spawn-successor.ts
5220
6426
  var TOOL_NAME20 = "vo_spawn_successor";
5221
6427
  var MAX_HANDOFF_BYTES = 64e3;
5222
6428
  var inputSchema20 = {
@@ -5237,11 +6443,16 @@ var inputSchema20 = {
5237
6443
  max_turns: {
5238
6444
  type: "number",
5239
6445
  description: "Optional --max-turns bound for the successor."
6446
+ },
6447
+ agent: {
6448
+ type: "string",
6449
+ description: `Which agent to spawn ('claude' | 'codex'). Normally omitted: the agent comes from the swarm tier binding inherited via ${SWARM_TIER_BINDING_ENV}. When a binding IS inherited this may only RESTATE the bound agent \u2014 an agent that contradicts the binding is REFUSED, because a different agent is a different payer and the payer was decided once, at admission.`
5240
6450
  }
5241
6451
  },
5242
6452
  required: [],
5243
6453
  additionalProperties: false
5244
6454
  };
6455
+ var RETIRED_COUNTER_INPUT = "spawns_so_far";
5245
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.";
5246
6457
  function isToolInput20(v) {
5247
6458
  if (typeof v !== "object" || v === null) return false;
@@ -5250,12 +6461,18 @@ function isToolInput20(v) {
5250
6461
  if (o["goal"] !== void 0 && typeof o["goal"] !== "string") return false;
5251
6462
  if (o["cwd"] !== void 0 && typeof o["cwd"] !== "string") return false;
5252
6463
  if (o["max_turns"] !== void 0 && typeof o["max_turns"] !== "number") return false;
6464
+ if (o["agent"] !== void 0 && typeof o["agent"] !== "string") return false;
5253
6465
  return true;
5254
6466
  }
5255
- function newestHandoff(dir = join6(homedir4(), ".vo", "handoffs")) {
6467
+ function retiredCounterRefusal(v) {
6468
+ if (typeof v !== "object" || v === null) return null;
6469
+ if (!(RETIRED_COUNTER_INPUT in v)) return null;
6470
+ return `\`${RETIRED_COUNTER_INPUT}\` is no longer accepted: a spawn counter supplied by the process being bounded bounds nothing, and an absent one read as zero. The fan-out ceiling is now enforced by the durable per-swarm spawn ledger; remove the field.`;
6471
+ }
6472
+ function newestHandoff(dir = join7(homedir5(), ".vo", "handoffs")) {
5256
6473
  try {
5257
- const entries = readdirSync3(dir).filter((f) => f.endsWith(".md")).map((f) => ({ f, m: statSync3(join6(dir, f)).mtimeMs })).sort((a, b) => b.m - a.m);
5258
- return entries.length > 0 && entries[0] ? join6(dir, entries[0].f) : null;
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);
6475
+ return entries.length > 0 && entries[0] ? join7(dir, entries[0].f) : null;
5259
6476
  } catch {
5260
6477
  return null;
5261
6478
  }
@@ -5271,7 +6488,7 @@ var MANDATORY_READS = [
5271
6488
  function buildSuccessorPrompt(handoffMarkdown, goal) {
5272
6489
  const reads = MANDATORY_READS.map((r, i) => ` ${i + 1}. ${r}`).join("\n");
5273
6490
  const lines = [
5274
- "You are the SUCCESSOR agent for a Virtual Office lane. The previous session",
6491
+ "You are the SUCCESSOR agent for an AlgoHQ lane. The previous session",
5275
6492
  "exhausted its context and wrote the handoff below. Read it fully, verify its",
5276
6493
  '"verification needed" items against live state (a handoff is a claim, not',
5277
6494
  "evidence \u2014 verify via `git show origin/main:<path>`), then continue the lane.",
@@ -5282,7 +6499,7 @@ function buildSuccessorPrompt(handoffMarkdown, goal) {
5282
6499
  "NON-NEGOTIABLES: multi-model consensus verification is the core; test honesty",
5283
6500
  "(verified-answer-only, no fake green); verify-before-act + human merge approval;",
5284
6501
  "never a full functions-shared deploy; Gen2 only; work in a worktree on your own",
5285
- "branch; finish line is MERGED + DEPLOYED + LIVE-VERIFIED, and VO changes update",
6502
+ "branch; finish line is MERGED + DEPLOYED + LIVE-VERIFIED, and AlgoHQ changes update",
5286
6503
  "the roadmap in the same PR.",
5287
6504
  "",
5288
6505
  "--- HANDOFF ---",
@@ -5292,147 +6509,759 @@ function buildSuccessorPrompt(handoffMarkdown, goal) {
5292
6509
  if (goal && goal.trim().length > 0) lines.push("", `OPERATOR GOAL OVERRIDE: ${goal.trim()}`);
5293
6510
  return lines.join("\n");
5294
6511
  }
5295
- function buildSuccessorArgs(maxTurns) {
5296
- const args = ["-p", "--permission-mode", "acceptEdits"];
5297
- if (Number.isInteger(maxTurns) && maxTurns > 0) {
5298
- args.push("--max-turns", String(maxTurns));
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) {
6596
+ const retired = retiredCounterRefusal(rawInput);
6597
+ if (retired !== null) throw invalidParams(TOOL_NAME20, retired);
6598
+ if (!isToolInput20(rawInput)) {
6599
+ throw invalidParams(TOOL_NAME20, "invalid input. Optional: { handoff_path, goal, cwd, max_turns, agent }.");
6600
+ }
6601
+ const handoffPath = rawInput.handoff_path?.trim() || newestHandoff();
6602
+ if (!handoffPath || !existsSync4(handoffPath)) {
6603
+ return jsonContent({
6604
+ tool: TOOL_NAME20,
6605
+ schema_version: 1,
6606
+ payload: {
6607
+ spawned: false,
6608
+ reason: rawInput.handoff_path ? `handoff not found: ${rawInput.handoff_path}` : "no handoff docs in ~/.vo/handoffs \u2014 write one first (the 85% directive does this)"
6609
+ }
6610
+ });
6611
+ }
6612
+ const handoff = readFileSync7(handoffPath, "utf8").slice(0, MAX_HANDOFF_BYTES);
6613
+ const prompt = buildSuccessorPrompt(handoff, rawInput.goal);
6614
+ const plan = resolveSpawnPlan(process.env, rawInput, (/* @__PURE__ */ new Date()).toISOString());
6615
+ if (!plan.ok) {
6616
+ return jsonContent({
6617
+ tool: TOOL_NAME20,
6618
+ schema_version: 1,
6619
+ payload: {
6620
+ spawned: false,
6621
+ reason: `swarm tier binding refused this spawn: ${plan.reason}`,
6622
+ tier: plan.tier,
6623
+ handoff_path: handoffPath
6624
+ }
6625
+ });
6626
+ }
6627
+ const logDir = process.env["VO_MCP_SUCCESSOR_LOG_DIR"]?.trim() || join7(homedir5(), ".vo", "successors");
6628
+ mkdirSync4(logDir, { recursive: true });
6629
+ const logPath = join7(logDir, `successor-${Date.now()}.log`);
6630
+ const logFd = openSync2(logPath, "a");
6631
+ const child = spawnImpl(plan.bin, [...plan.args], {
6632
+ cwd: rawInput.cwd?.trim() || process.cwd(),
6633
+ detached: true,
6634
+ 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",
6638
+ windowsHide: true,
6639
+ // Carry the SAME binding to the child. Without this the successor inherits
6640
+ // no tier and re-resolves its own — which is the split-payer defect one
6641
+ // generation down.
6642
+ ...plan.bound ? { env: { ...process.env, ...plan.env } } : {}
6643
+ });
6644
+ let spawnError = null;
6645
+ child.on("error", (e) => {
6646
+ spawnError = e.message;
6647
+ });
6648
+ try {
6649
+ child.stdin.write(prompt);
6650
+ child.stdin.end();
6651
+ } catch {
6652
+ }
6653
+ child.unref();
6654
+ await new Promise((r) => setTimeout(r, 150));
6655
+ return jsonContent({
6656
+ tool: TOOL_NAME20,
6657
+ schema_version: 1,
6658
+ payload: spawnError ? { spawned: false, reason: `spawn failed: ${spawnError}`, agent: plan.agent, tier: plan.tier, handoff_path: handoffPath } : {
6659
+ spawned: true,
6660
+ pid: child.pid ?? null,
6661
+ log_path: logPath,
6662
+ handoff_path: handoffPath,
6663
+ agent: plan.agent,
6664
+ tier: plan.tier,
6665
+ tier_bound: plan.bound,
6666
+ ledger_slot: plan.slot,
6667
+ // The debit, surfaced so an operator can reconcile a fan-out's spend
6668
+ // against the pool without reading the ledger directory by hand.
6669
+ ledger_cap_usd: plan.capUsd,
6670
+ ledger_cap_remaining_usd: plan.capRemainingUsd
6671
+ }
6672
+ });
6673
+ }
6674
+
6675
+ // src/tools/concierge/dispatch.ts
6676
+ init_common();
6677
+
6678
+ // src/tools/concierge/common-concierge.ts
6679
+ var CONCIERGE_STUB_REASON = "cloud mode not active in this MCP runtime \u2014 set VO_CONTROL_PLANE_URL + VO_CONTROL_PLANE_ADMIN_TOKEN to enable. The server-side /api/v1/admin/concierge/dispatch endpoint IS built + deployed (vo-control-plane #5724/#5734); in cloud mode this tool returns the routed pack README + file index.";
6680
+ var CONCIERGE_GATE_TYPE = "concierge-dispatch";
6681
+ var KNOWN_CONCIERGE_PACKS = [
6682
+ "gcp",
6683
+ "firebase",
6684
+ "aws",
6685
+ "cloudflare",
6686
+ "vercel",
6687
+ "netlify",
6688
+ "tax",
6689
+ "hybrid"
6690
+ ];
6691
+ function isKnownConciergePack(value) {
6692
+ return typeof value === "string" && KNOWN_CONCIERGE_PACKS.includes(value);
6693
+ }
6694
+
6695
+ // src/tools/concierge/dispatch.ts
6696
+ var TOOL_NAME21 = "vo_concierge_dispatch";
6697
+ var CALLABLE_NAME10 = "voConciergeDispatch";
6698
+ var ADMIN_PATH10 = "/api/v1/admin/concierge/dispatch";
6699
+ var inputSchema21 = {
6700
+ type: "object",
6701
+ properties: {
6702
+ pack: {
6703
+ type: "string",
6704
+ description: `Explicit knowledge pack name. One of: ${KNOWN_CONCIERGE_PACKS.join(", ")}. When provided, overrides tenant-based routing. Omit to let server route by tenant_id.`,
6705
+ enum: [...KNOWN_CONCIERGE_PACKS]
6706
+ },
6707
+ tenant_id: {
6708
+ type: "string",
6709
+ description: "Tenant identifier. When provided (and pack is omitted), the server looks up Tenant.cloud_provider and dispatches to the matching pack. Ignored when pack is also provided."
6710
+ }
6711
+ },
6712
+ additionalProperties: false
6713
+ };
6714
+ var description21 = "Dispatches a provider-scoped knowledge pack (gcp | firebase | aws | cloudflare | vercel | netlify | tax | hybrid). Cross-vendor MCP equivalent of the /vo-concierge Claude-Code slash command. Route explicitly via `pack`, or via tenant.cloud_provider by passing `tenant_id`. Returns the pack's README (`readme_markdown`) + file index. In cloud mode, dispatches via vo-control-plane and returns `verdict: 'pass'` with the pack/directory data; without cloud config, returns `verdict: 'unimplemented'`.";
6715
+ function isToolInput21(v) {
6716
+ if (typeof v !== "object" || v === null) return false;
6717
+ const obj = v;
6718
+ if (obj.pack !== void 0 && typeof obj.pack !== "string") return false;
6719
+ if (obj.tenant_id !== void 0 && typeof obj.tenant_id !== "string") return false;
6720
+ return true;
6721
+ }
6722
+ async function handleConciergeDispatch(deps, rawInput, _signal) {
6723
+ if (!isToolInput21(rawInput)) {
6724
+ throw invalidParams(
6725
+ TOOL_NAME21,
6726
+ "invalid input. Expected { pack?: string, tenant_id?: string }."
6727
+ );
6728
+ }
6729
+ if (rawInput.pack !== void 0 && rawInput.pack !== "" && !isKnownConciergePack(rawInput.pack)) {
6730
+ throw invalidParams(
6731
+ TOOL_NAME21,
6732
+ `unknown pack: ${JSON.stringify(rawInput.pack)}. Known packs: ${KNOWN_CONCIERGE_PACKS.join(", ")}.`
6733
+ );
6734
+ }
6735
+ const normalizedInput = {};
6736
+ if (rawInput.pack) normalizedInput.pack = rawInput.pack;
6737
+ if (rawInput.tenant_id) normalizedInput.tenant_id = rawInput.tenant_id;
6738
+ const cloudBody = {};
6739
+ if (rawInput.pack) cloudBody.pack = rawInput.pack;
6740
+ if (rawInput.tenant_id) cloudBody.tenantId = rawInput.tenant_id;
6741
+ return buildCloudOrStubResponse({
6742
+ toolName: TOOL_NAME21,
6743
+ callableName: CALLABLE_NAME10,
6744
+ adminPath: ADMIN_PATH10,
6745
+ normalizedInput,
6746
+ cloudBody,
6747
+ // Concierge dispatch returns `{ok, mode, routed_from, pack|packs}`,
6748
+ // NOT the callable-proxy `{ok, callable, result}` shape — take the
6749
+ // raw envelope as response_data instead of mandating `.result`.
6750
+ rawEnvelope: true,
6751
+ gateType: CONCIERGE_GATE_TYPE,
6752
+ stubReason: CONCIERGE_STUB_REASON,
6753
+ // Read-only pack fetch — stays live under VO_ADMIN_CALLABLES_READONLY.
6754
+ readOnly: true,
6755
+ deps
6756
+ });
6757
+ }
6758
+
6759
+ // src/server.ts
6760
+ init_sync_config();
6761
+
6762
+ // src/tools/memory/private-knowledge.ts
6763
+ init_common();
6764
+ var UPSERT_TOOL_NAME = "vo_private_knowledge_upsert";
6765
+ var CONTEXT_TOOL_NAME = "vo_private_knowledge_context";
6766
+ var INVALIDATE_TOOL_NAME = "vo_private_knowledge_invalidate";
6767
+ var STALE_TOOL_NAME = "vo_private_knowledge_stale";
6768
+ var KNOWLEDGE_CLASSES = ["memory", "skill", "doctrine", "hook", "command"];
6769
+ var PRECISION_CHAR_BUDGET = 12e3;
6770
+ var upsertInputSchema = {
6771
+ type: "object",
6772
+ properties: {
6773
+ knowledge_class: { type: "string", enum: KNOWLEDGE_CLASSES },
6774
+ source_path: { type: "string", description: "Stable private source identifier; not exposed to other users." },
6775
+ title: { type: "string", description: 'Descriptive, retrieval-friendly title (e.g. "AlgoTax OCR redaction architecture", not "notes") \u2014 retrieval matches on it.' },
6776
+ content: { type: "string", description: "Private knowledge text to store server-side. Keep each entry tight and focused (~1-3 pages, under ~12k chars); split larger corpora into separate entries." }
6777
+ },
6778
+ required: ["knowledge_class", "source_path", "title", "content"],
6779
+ additionalProperties: false
6780
+ };
6781
+ var contextInputSchema = {
6782
+ type: "object",
6783
+ properties: {
6784
+ query: { type: "string" },
6785
+ limit: { type: "number", minimum: 1, maximum: 50 },
6786
+ knowledge_class: { type: "string", enum: KNOWLEDGE_CLASSES }
6787
+ },
6788
+ required: ["query"],
6789
+ additionalProperties: false
6790
+ };
6791
+ var invalidateInputSchema = {
6792
+ type: "object",
6793
+ properties: {
6794
+ knowledge_class: { type: "string", enum: KNOWLEDGE_CLASSES },
6795
+ source_path: { type: "string", minLength: 1, maxLength: 400, description: "Stable private source identifier of the entry to invalidate \u2014 must match the source_path used at upsert." }
6796
+ },
6797
+ required: ["knowledge_class", "source_path"],
6798
+ additionalProperties: false
6799
+ };
6800
+ var staleInputSchema = {
6801
+ type: "object",
6802
+ properties: {
6803
+ days: { type: "number", minimum: 1, maximum: 3650, description: "Window in days (default 90): entries at least this old that were never recalled into an agent context, or not within the window." },
6804
+ limit: { type: "number", minimum: 1, maximum: 500 }
6805
+ },
6806
+ additionalProperties: false
6807
+ };
6808
+ var staleDescription = `The FORGETTING REPORT: lists the authenticated operator\u2019s live private-knowledge entries that are at least N days old and have never been recalled into an agent context (or not within N days). Metadata only. SURFACES ONLY \u2014 never auto-invalidates or auto-merges: two memories that disagree may both have been right in different contexts, so you decide. Act on a candidate deliberately with ${INVALIDATE_TOOL_NAME}; recall counts come from ${CONTEXT_TOOL_NAME} reads that actually placed the entry into returned context.`;
6809
+ var upsertDescription = "Uploads or refreshes the authenticated operator\u2019s private cloud knowledge. Works for Claude, Codex, Cursor, and cowork clients via the same vo-mcp login credential. Returns metadata only, not raw stored content. PRECISION DISCIPLINE: keep each entry tight and focused (~1-3 pages) with a descriptive retrieval-friendly title \u2014 retrieval surfaces whole entries, so small dense entries beat bulk dumps. Split large corpora into focused entries, then run a retrieval self-test via vo_private_knowledge_context before relying on the knowledge.";
6810
+ var contextDescription = "Retrieves prompt-ready private knowledge context for the authenticated operator. Returns snippets/context only; no raw corpus download. Also the retrieval self-test surface: after upserting critical knowledge, query for it here and confirm the entry surfaces before trusting it in downstream work.";
6811
+ var invalidateDescription = `Soft-deletes one private-knowledge entry for the authenticated operator: closes the live entry\u2019s validity window (bi-temporal) so it stops surfacing in retrieval. Never destroys data \u2014 invalidated versions remain queryable server-side via include_invalidated. Identify the entry by the same { knowledge_class, source_path } used at upsert; a not_found response means no live entry matches. After invalidating, self-test via ${CONTEXT_TOOL_NAME} to confirm the entry no longer surfaces.`;
6812
+ function isStaleInput(value) {
6813
+ if (value === void 0 || value === null) return true;
6814
+ if (typeof value !== "object") return false;
6815
+ const input = value;
6816
+ if (input["days"] !== void 0 && typeof input["days"] !== "number") return false;
6817
+ if (input["limit"] !== void 0 && typeof input["limit"] !== "number") return false;
6818
+ return true;
6819
+ }
6820
+ function isKnowledgeClass(value) {
6821
+ return typeof value === "string" && KNOWLEDGE_CLASSES.includes(value);
6822
+ }
6823
+ function isUpsertInput(value) {
6824
+ if (typeof value !== "object" || value === null) return false;
6825
+ const input = value;
6826
+ return isKnowledgeClass(input["knowledge_class"]) && typeof input["source_path"] === "string" && typeof input["title"] === "string" && typeof input["content"] === "string";
6827
+ }
6828
+ function isInvalidateInput(value) {
6829
+ if (typeof value !== "object" || value === null) return false;
6830
+ const input = value;
6831
+ return isKnowledgeClass(input["knowledge_class"]) && typeof input["source_path"] === "string";
6832
+ }
6833
+ function isContextInput(value) {
6834
+ if (typeof value !== "object" || value === null) return false;
6835
+ const input = value;
6836
+ if (typeof input["query"] !== "string") return false;
6837
+ if (input["limit"] !== void 0 && typeof input["limit"] !== "number") return false;
6838
+ if (input["knowledge_class"] !== void 0 && !isKnowledgeClass(input["knowledge_class"])) return false;
6839
+ return true;
6840
+ }
6841
+ async function getCloudAuth(fetchFn) {
6842
+ const controlPlaneUrl = process.env["VO_CONTROL_PLANE_URL"]?.replace(/\/+$/, "");
6843
+ if (!controlPlaneUrl) {
6844
+ return { ok: false, reason: "VO_CONTROL_PLANE_URL not set \u2014 cloud mode disabled" };
6845
+ }
6846
+ const { createAuthTokenSourceFromEnv: createAuthTokenSourceFromEnv2 } = await Promise.resolve().then(() => (init_auth_token_source(), auth_token_source_exports));
6847
+ const { readStoredCredential: readStoredCredential2 } = await Promise.resolve().then(() => (init_credential_store(), credential_store_exports));
6848
+ const tokenSource = createAuthTokenSourceFromEnv2(process.env, fetchFn, () => readStoredCredential2(process.env));
6849
+ if (!tokenSource) return { ok: false, reason: "No auth configured. Run `vo-mcp login`." };
6850
+ const token = await tokenSource.getToken();
6851
+ if (!token) return { ok: false, reason: "Failed to obtain auth token. Run `vo-mcp login` again." };
6852
+ return { ok: true, controlPlaneUrl, token };
6853
+ }
6854
+ async function callPrivateKnowledge(path3, body, fetchFn) {
6855
+ const auth = await getCloudAuth(fetchFn);
6856
+ if (!auth.ok) return { ok: false, reason: auth.reason };
6857
+ const response = await fetchFn(`${auth.controlPlaneUrl}${path3}`, {
6858
+ method: "POST",
6859
+ headers: {
6860
+ authorization: `Bearer ${auth.token}`,
6861
+ "content-type": "application/json"
6862
+ },
6863
+ body: JSON.stringify(body)
6864
+ });
6865
+ const text = await response.text();
6866
+ let parsed;
6867
+ try {
6868
+ parsed = text ? JSON.parse(text) : null;
6869
+ } catch {
6870
+ parsed = null;
6871
+ }
6872
+ if (response.status < 200 || response.status >= 300) {
6873
+ return { ok: false, status: response.status, response: parsed ?? text };
6874
+ }
6875
+ return parsed;
6876
+ }
6877
+ async function handlePrivateKnowledgeUpsert(_deps, rawInput, _signal, fetchFn = globalThis.fetch) {
6878
+ if (!isUpsertInput(rawInput)) {
6879
+ throw invalidParams(UPSERT_TOOL_NAME, "expected { knowledge_class, source_path, title, content }.");
6880
+ }
6881
+ const payload = await callPrivateKnowledge("/api/v1/knowledge/private", rawInput, fetchFn);
6882
+ const envelope = {
6883
+ tool: UPSERT_TOOL_NAME,
6884
+ schema_version: 1,
6885
+ payload
6886
+ };
6887
+ if (rawInput.content.length > PRECISION_CHAR_BUDGET) {
6888
+ envelope.precision_note = `content is ${rawInput.content.length} chars (> ${PRECISION_CHAR_BUDGET}). Tight 1-3 page entries retrieve better \u2014 consider splitting into focused entries, then re-test retrieval via ${CONTEXT_TOOL_NAME}.`;
6889
+ }
6890
+ return jsonContent(envelope);
6891
+ }
6892
+ async function handlePrivateKnowledgeInvalidate(_deps, rawInput, _signal, fetchFn = globalThis.fetch) {
6893
+ if (!isInvalidateInput(rawInput)) {
6894
+ throw invalidParams(INVALIDATE_TOOL_NAME, "expected { knowledge_class, source_path }.");
6895
+ }
6896
+ const payload = await callPrivateKnowledge("/api/v1/knowledge/private/invalidate", rawInput, fetchFn);
6897
+ return jsonContent({ tool: INVALIDATE_TOOL_NAME, schema_version: 1, payload });
6898
+ }
6899
+ async function handlePrivateKnowledgeStale(_deps, rawInput, _signal, fetchFn = globalThis.fetch) {
6900
+ if (!isStaleInput(rawInput)) {
6901
+ throw invalidParams(STALE_TOOL_NAME, "expected { optional days, optional limit }.");
6902
+ }
6903
+ const auth = await getCloudAuth(fetchFn);
6904
+ if (!auth.ok) return jsonContent({ tool: STALE_TOOL_NAME, schema_version: 1, payload: { ok: false, reason: auth.reason } });
6905
+ const params = new URLSearchParams();
6906
+ if (rawInput?.days !== void 0) params.set("days", String(Math.trunc(rawInput.days)));
6907
+ if (rawInput?.limit !== void 0) params.set("limit", String(Math.trunc(rawInput.limit)));
6908
+ const qs = params.toString();
6909
+ const response = await fetchFn(`${auth.controlPlaneUrl}/api/v1/knowledge/private/stale${qs ? `?${qs}` : ""}`, {
6910
+ method: "GET",
6911
+ headers: { authorization: `Bearer ${auth.token}` }
6912
+ });
6913
+ const text = await response.text();
6914
+ let parsed;
6915
+ try {
6916
+ parsed = text ? JSON.parse(text) : null;
6917
+ } catch {
6918
+ parsed = null;
6919
+ }
6920
+ const payload = response.status < 200 || response.status >= 300 ? { ok: false, status: response.status, response: parsed ?? text } : parsed;
6921
+ return jsonContent({ tool: STALE_TOOL_NAME, schema_version: 1, payload });
6922
+ }
6923
+ async function handlePrivateKnowledgeContext(_deps, rawInput, _signal, fetchFn = globalThis.fetch) {
6924
+ if (!isContextInput(rawInput)) {
6925
+ throw invalidParams(CONTEXT_TOOL_NAME, "expected { query, optional limit, optional knowledge_class }.");
6926
+ }
6927
+ const payload = await callPrivateKnowledge("/api/v1/knowledge/private/context", rawInput, fetchFn);
6928
+ return jsonContent({ tool: CONTEXT_TOOL_NAME, schema_version: 1, payload });
6929
+ }
6930
+
6931
+ // src/tools/hq/whiteboard.ts
6932
+ init_auth_token_source();
6933
+ init_credential_store();
6934
+ init_common();
6935
+ var POST_TOOL_NAME = "hq_whiteboard_post";
6936
+ var READ_TOOL_NAME = "hq_whiteboard_read";
6937
+ var postDescription = "Post an append-only coordination note to the live AlgoHQ whiteboard. Uses the scoped credential from vo-mcp login; operator and tenant ownership are derived by the server.";
6938
+ var readDescription = "Read recent coordination notes from the caller's live AlgoHQ whiteboard. Uses the scoped credential from vo-mcp login and cannot widen tenant scope.";
6939
+ var postInputSchema = {
6940
+ type: "object",
6941
+ properties: {
6942
+ from: { type: "string", minLength: 1, maxLength: 100, description: "Agent/session display name." },
6943
+ type: { type: "string", minLength: 1, maxLength: 64, description: "Message kind, such as intent, worklog, blocker, or completion." },
6944
+ content: { type: "string", minLength: 1, maxLength: 500, description: "Short coordination note." },
6945
+ targetAgent: { type: "string", maxLength: 100 },
6946
+ tester: { type: "string", maxLength: 100 },
6947
+ tier: { type: "string", maxLength: 32 }
6948
+ },
6949
+ required: ["from", "type", "content"],
6950
+ additionalProperties: false
6951
+ };
6952
+ var readInputSchema = {
6953
+ type: "object",
6954
+ properties: {
6955
+ limit: { type: "integer", minimum: 1, maximum: 100, default: 25 },
6956
+ since: { type: "string", description: "Optional ISO-8601 lower bound." },
6957
+ type: { type: "string", minLength: 1, maxLength: 64 }
6958
+ },
6959
+ additionalProperties: false
6960
+ };
6961
+ function resolveTimeoutMs() {
6962
+ const parsed = Number(process.env["HQ_WHITEBOARD_TIMEOUT_MS"]);
6963
+ return Number.isFinite(parsed) && parsed >= 10 && parsed <= 12e4 ? parsed : 1e4;
6964
+ }
6965
+ function isRecord(value) {
6966
+ return typeof value === "object" && value !== null && !Array.isArray(value);
6967
+ }
6968
+ function onlyKeys(value, allowed) {
6969
+ return Object.keys(value).every((key) => allowed.includes(key));
6970
+ }
6971
+ function isBoundedString(value, min, max) {
6972
+ return typeof value === "string" && value.trim().length >= min && value.trim().length <= max;
6973
+ }
6974
+ function parsePostInput(value) {
6975
+ if (!isRecord(value) || !onlyKeys(value, ["from", "type", "content", "targetAgent", "tester", "tier"])) return null;
6976
+ if (!isBoundedString(value["from"], 1, 100)) return null;
6977
+ if (!isBoundedString(value["type"], 1, 64) || !/^[a-zA-Z0-9_-]+$/.test(value["type"].trim())) return null;
6978
+ if (!isBoundedString(value["content"], 1, 500)) return null;
6979
+ for (const [key, max] of [["targetAgent", 100], ["tester", 100], ["tier", 32]]) {
6980
+ if (value[key] !== void 0 && !isBoundedString(value[key], 0, max)) return null;
6981
+ }
6982
+ return {
6983
+ from: value["from"].trim(),
6984
+ type: value["type"].trim(),
6985
+ content: value["content"].trim(),
6986
+ ...typeof value["targetAgent"] === "string" ? { targetAgent: value["targetAgent"].trim() } : {},
6987
+ ...typeof value["tester"] === "string" ? { tester: value["tester"].trim() } : {},
6988
+ ...typeof value["tier"] === "string" ? { tier: value["tier"].trim() } : {}
6989
+ };
6990
+ }
6991
+ function parseReadInput(value) {
6992
+ if (!isRecord(value) || !onlyKeys(value, ["limit", "since", "type"])) return null;
6993
+ if (value["limit"] !== void 0 && (!Number.isInteger(value["limit"]) || Number(value["limit"]) < 1 || Number(value["limit"]) > 100)) return null;
6994
+ if (value["since"] !== void 0 && (typeof value["since"] !== "string" || Number.isNaN(Date.parse(value["since"])))) return null;
6995
+ if (value["type"] !== void 0 && !isBoundedString(value["type"], 1, 64)) return null;
6996
+ return {
6997
+ ...typeof value["limit"] === "number" ? { limit: value["limit"] } : {},
6998
+ ...typeof value["since"] === "string" ? { since: value["since"] } : {},
6999
+ ...typeof value["type"] === "string" ? { type: value["type"].trim() } : {}
7000
+ };
7001
+ }
7002
+ async function resolveCloud(fetchFn) {
7003
+ const url = process.env["VO_CONTROL_PLANE_URL"]?.trim().replace(/\/$/, "");
7004
+ if (!url) return null;
7005
+ try {
7006
+ const source = createAuthTokenSourceFromEnv(process.env, fetchFn, () => readStoredCredential(process.env));
7007
+ const token = await source?.getToken();
7008
+ return token ? { url, token } : null;
7009
+ } catch {
7010
+ return null;
5299
7011
  }
5300
- return args;
5301
7012
  }
5302
- async function handleSpawnSuccessor(_deps, rawInput, _signal, spawnImpl = spawn) {
5303
- if (!isToolInput20(rawInput)) {
5304
- throw invalidParams(TOOL_NAME20, "invalid input. Optional: { handoff_path, goal, cwd, max_turns }.");
7013
+ async function callWhiteboard(method, bodyOrQuery, signal, fetchFn = fetch) {
7014
+ const cloud = await resolveCloud(fetchFn);
7015
+ if (!cloud) {
7016
+ return {
7017
+ ok: false,
7018
+ error: "hq_whiteboard_not_configured",
7019
+ message: "Set VO_CONTROL_PLANE_URL and run vo-mcp login to install a scoped HQ credential."
7020
+ };
5305
7021
  }
5306
- const handoffPath = rawInput.handoff_path?.trim() || newestHandoff();
5307
- if (!handoffPath || !existsSync4(handoffPath)) {
5308
- return jsonContent({
5309
- tool: TOOL_NAME20,
5310
- schema_version: 1,
5311
- payload: {
5312
- spawned: false,
5313
- reason: rawInput.handoff_path ? `handoff not found: ${rawInput.handoff_path}` : "no handoff docs in ~/.vo/handoffs \u2014 write one first (the 85% directive does this)"
5314
- }
5315
- });
7022
+ const timeoutSignal = AbortSignal.timeout(resolveTimeoutMs());
7023
+ const requestSignal = signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;
7024
+ const query = new URLSearchParams();
7025
+ if (method === "GET") {
7026
+ const input = bodyOrQuery;
7027
+ query.set("limit", String(input.limit ?? 25));
7028
+ if (input.since) query.set("since", input.since);
7029
+ if (input.type) query.set("type", input.type);
5316
7030
  }
5317
- const handoff = readFileSync6(handoffPath, "utf8").slice(0, MAX_HANDOFF_BYTES);
5318
- const prompt = buildSuccessorPrompt(handoff, rawInput.goal);
5319
- const logDir = process.env["VO_MCP_SUCCESSOR_LOG_DIR"]?.trim() || join6(homedir4(), ".vo", "successors");
5320
- mkdirSync3(logDir, { recursive: true });
5321
- const logPath = join6(logDir, `successor-${Date.now()}.log`);
5322
- const logFd = openSync(logPath, "a");
5323
- const child = spawnImpl("claude", buildSuccessorArgs(rawInput.max_turns), {
5324
- cwd: rawInput.cwd?.trim() || process.cwd(),
5325
- detached: true,
5326
- stdio: ["pipe", logFd, logFd],
5327
- // Windows: `claude` is a .cmd shim — needs a shell to resolve. The prompt
5328
- // goes via STDIN below, never argv, so the shell never sees it.
5329
- shell: process.platform === "win32",
5330
- windowsHide: true
5331
- });
5332
- let spawnError = null;
5333
- child.on("error", (e) => {
5334
- spawnError = e.message;
5335
- });
5336
7031
  try {
5337
- child.stdin.write(prompt);
5338
- child.stdin.end();
5339
- } catch {
7032
+ const response = await fetchFn(
7033
+ `${cloud.url}/api/v1/hq/whiteboard/messages${query.size ? `?${query}` : ""}`,
7034
+ {
7035
+ method,
7036
+ headers: {
7037
+ Authorization: `Bearer ${cloud.token}`,
7038
+ ...method === "POST" ? { "Content-Type": "application/json" } : {}
7039
+ },
7040
+ ...method === "POST" ? { body: JSON.stringify(bodyOrQuery) } : {},
7041
+ signal: requestSignal
7042
+ }
7043
+ );
7044
+ const text = await response.text();
7045
+ let payload;
7046
+ try {
7047
+ payload = JSON.parse(text);
7048
+ } catch {
7049
+ payload = { ok: false, error: "invalid_response", message: text.slice(0, 200) };
7050
+ }
7051
+ if (!response.ok) {
7052
+ return { ok: false, error: "hq_whiteboard_http_error", status: response.status, response: payload };
7053
+ }
7054
+ return payload;
7055
+ } catch (error) {
7056
+ return {
7057
+ ok: false,
7058
+ error: signal?.aborted ? "cancelled" : timeoutSignal.aborted ? "hq_whiteboard_timeout" : "hq_whiteboard_unreachable",
7059
+ message: error instanceof Error ? error.message.slice(0, 200) : String(error).slice(0, 200)
7060
+ };
5340
7061
  }
5341
- child.unref();
5342
- await new Promise((r) => setTimeout(r, 150));
5343
- return jsonContent({
5344
- tool: TOOL_NAME20,
5345
- schema_version: 1,
5346
- payload: spawnError ? { spawned: false, reason: `spawn failed: ${spawnError}`, handoff_path: handoffPath } : { spawned: true, pid: child.pid ?? null, log_path: logPath, handoff_path: handoffPath }
5347
- });
7062
+ }
7063
+ async function handleHqWhiteboardPost(_deps, rawInput, signal) {
7064
+ const input = parsePostInput(rawInput);
7065
+ if (!input) throw invalidParams(POST_TOOL_NAME, "requires from, type, and 1-500 character content; unknown fields are rejected");
7066
+ return jsonContent(await callWhiteboard("POST", input, signal));
7067
+ }
7068
+ async function handleHqWhiteboardRead(_deps, rawInput, signal) {
7069
+ const input = parseReadInput(rawInput);
7070
+ if (!input) throw invalidParams(READ_TOOL_NAME, "limit must be 1-100, since must be ISO-8601, and unknown fields are rejected");
7071
+ return jsonContent(await callWhiteboard("GET", input, signal));
5348
7072
  }
5349
7073
 
5350
- // src/tools/concierge/dispatch.ts
5351
- init_common();
7074
+ // src/tools/skills/skill-corpus.ts
7075
+ import { existsSync as existsSync9, statSync as statSync6 } from "node:fs";
7076
+ import { dirname as dirname5, isAbsolute, join as join13, resolve as resolve2 } from "node:path";
5352
7077
 
5353
- // src/tools/concierge/common-concierge.ts
5354
- var CONCIERGE_STUB_REASON = "cloud mode not active in this MCP runtime \u2014 set VO_CONTROL_PLANE_URL + VO_CONTROL_PLANE_ADMIN_TOKEN to enable. The server-side /api/v1/admin/concierge/dispatch endpoint IS built + deployed (vo-control-plane #5724/#5734); in cloud mode this tool returns the routed pack README + file index.";
5355
- var CONCIERGE_GATE_TYPE = "concierge-dispatch";
5356
- var KNOWN_CONCIERGE_PACKS = [
5357
- "gcp",
5358
- "firebase",
5359
- "aws",
5360
- "cloudflare",
5361
- "vercel",
5362
- "netlify",
5363
- "tax",
5364
- "hybrid"
5365
- ];
5366
- function isKnownConciergePack(value) {
5367
- return typeof value === "string" && KNOWN_CONCIERGE_PACKS.includes(value);
7078
+ // ../skill-registry/src/loader.ts
7079
+ import { readdirSync as readdirSync6, readFileSync as readFileSync14, statSync as statSync5 } from "node:fs";
7080
+ import { join as join12 } from "node:path";
7081
+ var InvalidSkillFrontmatterError = class extends Error {
7082
+ constructor(skillFile, reason) {
7083
+ super(`Invalid frontmatter in ${skillFile}: ${reason}`);
7084
+ this.skillFile = skillFile;
7085
+ this.reason = reason;
7086
+ }
7087
+ skillFile;
7088
+ reason;
7089
+ name = "InvalidSkillFrontmatterError";
7090
+ };
7091
+ var FRONTMATTER_DELIMITER = "---";
7092
+ function parseFrontmatter(rawInput, sourcePath) {
7093
+ const raw = rawInput.replace(/\r\n/g, "\n");
7094
+ if (!raw.startsWith(`${FRONTMATTER_DELIMITER}
7095
+ `)) {
7096
+ throw new InvalidSkillFrontmatterError(sourcePath, 'file does not start with frontmatter delimiter "---"');
7097
+ }
7098
+ const afterFirst = raw.slice(FRONTMATTER_DELIMITER.length + 1);
7099
+ const closingIdx = afterFirst.indexOf(`
7100
+ ${FRONTMATTER_DELIMITER}
7101
+ `);
7102
+ if (closingIdx === -1) {
7103
+ throw new InvalidSkillFrontmatterError(sourcePath, 'missing closing frontmatter delimiter "---"');
7104
+ }
7105
+ const frontmatterText = afterFirst.slice(0, closingIdx);
7106
+ const body = afterFirst.slice(closingIdx + `
7107
+ ${FRONTMATTER_DELIMITER}
7108
+ `.length);
7109
+ let name = "";
7110
+ let description23 = "";
7111
+ for (const line of frontmatterText.split("\n")) {
7112
+ const trimmed = line.trim();
7113
+ if (trimmed.length === 0) continue;
7114
+ const colonIdx = trimmed.indexOf(":");
7115
+ if (colonIdx === -1) continue;
7116
+ const key = trimmed.slice(0, colonIdx).trim();
7117
+ const value = trimmed.slice(colonIdx + 1).trim();
7118
+ if (key === "name") name = value;
7119
+ else if (key === "description") description23 = value;
7120
+ }
7121
+ if (name.length === 0) {
7122
+ throw new InvalidSkillFrontmatterError(sourcePath, 'missing required field "name"');
7123
+ }
7124
+ if (description23.length === 0) {
7125
+ throw new InvalidSkillFrontmatterError(sourcePath, 'missing required field "description"');
7126
+ }
7127
+ return { name, description: description23, body };
7128
+ }
7129
+ function loadSkillsFromDir(skillsDir) {
7130
+ const entries = readdirSync6(skillsDir);
7131
+ const skills = [];
7132
+ for (const entry of entries) {
7133
+ const entryPath = join12(skillsDir, entry);
7134
+ let stat;
7135
+ try {
7136
+ stat = statSync5(entryPath);
7137
+ } catch {
7138
+ continue;
7139
+ }
7140
+ if (!stat.isDirectory()) continue;
7141
+ const skillFile = join12(entryPath, "SKILL.md");
7142
+ let raw;
7143
+ try {
7144
+ raw = readFileSync14(skillFile, "utf8");
7145
+ } catch {
7146
+ continue;
7147
+ }
7148
+ const { name, description: description23, body } = parseFrontmatter(raw, skillFile);
7149
+ skills.push({ name, description: description23, body, sourcePath: skillFile });
7150
+ }
7151
+ return [...skills].sort((a, b) => a.name.localeCompare(b.name));
5368
7152
  }
5369
7153
 
5370
- // src/tools/concierge/dispatch.ts
5371
- var TOOL_NAME21 = "vo_concierge_dispatch";
5372
- var CALLABLE_NAME10 = "voConciergeDispatch";
5373
- var ADMIN_PATH10 = "/api/v1/admin/concierge/dispatch";
5374
- var inputSchema21 = {
7154
+ // src/tools/skills/skill-corpus.ts
7155
+ init_common();
7156
+ var LIST_TOOL_NAME = "vo_skill_list";
7157
+ var GET_TOOL_NAME = "vo_skill_get";
7158
+ var listDescription = "List the Algosuite skill corpus (name + trigger description for every skill). Call once near session start to learn which skills exist; then fetch the full instructions for a relevant skill with vo_skill_get. This is the same corpus Claude Code loads natively from .claude/skills \u2014 served over MCP so every vendor works from identical playbooks. Pass refresh:true to re-scan from disk.";
7159
+ var getDescription = "Fetch the full markdown instructions of one Algosuite skill by name. Follow the returned instructions for the current task the same way a native skill invocation would. Use vo_skill_list to discover skill names.";
7160
+ var listInputSchema = {
5375
7161
  type: "object",
5376
7162
  properties: {
5377
- pack: {
5378
- type: "string",
5379
- description: `Explicit knowledge pack name. One of: ${KNOWN_CONCIERGE_PACKS.join(", ")}. When provided, overrides tenant-based routing. Omit to let server route by tenant_id.`,
5380
- enum: [...KNOWN_CONCIERGE_PACKS]
5381
- },
5382
- tenant_id: {
7163
+ refresh: {
7164
+ type: "boolean",
7165
+ description: "Re-scan the skills directory instead of using the cached corpus."
7166
+ }
7167
+ },
7168
+ required: []
7169
+ };
7170
+ var getInputSchema = {
7171
+ type: "object",
7172
+ properties: {
7173
+ name: {
5383
7174
  type: "string",
5384
- description: "Tenant identifier. When provided (and pack is omitted), the server looks up Tenant.cloud_provider and dispatches to the matching pack. Ignored when pack is also provided."
7175
+ description: "Skill name exactly as returned by vo_skill_list."
5385
7176
  }
5386
7177
  },
5387
- additionalProperties: false
7178
+ required: ["name"]
5388
7179
  };
5389
- var description21 = "Dispatches a provider-scoped knowledge pack (gcp | firebase | aws | cloudflare | vercel | netlify | tax | hybrid). Cross-vendor MCP equivalent of the /vo-concierge Claude-Code slash command. Route explicitly via `pack`, or via tenant.cloud_provider by passing `tenant_id`. Returns the pack's README (`readme_markdown`) + file index. In cloud mode, dispatches via vo-control-plane and returns `verdict: 'pass'` with the pack/directory data; without cloud config, returns `verdict: 'unimplemented'`.";
5390
- function isToolInput21(v) {
5391
- if (typeof v !== "object" || v === null) return false;
5392
- const obj = v;
5393
- if (obj.pack !== void 0 && typeof obj.pack !== "string") return false;
5394
- if (obj.tenant_id !== void 0 && typeof obj.tenant_id !== "string") return false;
5395
- return true;
7180
+ var MAX_WALK_UP_LEVELS = 8;
7181
+ var cachedCorpus = null;
7182
+ function resolveSkillsDir(env = process.env, startDir = process.cwd()) {
7183
+ const override = env.VO_SKILLS_DIR;
7184
+ if (typeof override === "string" && override.length > 0) {
7185
+ const abs = isAbsolute(override) ? override : resolve2(startDir, override);
7186
+ return existsSync9(abs) && statSync6(abs).isDirectory() ? abs : null;
7187
+ }
7188
+ let dir = resolve2(startDir);
7189
+ for (let i = 0; i < MAX_WALK_UP_LEVELS; i += 1) {
7190
+ const candidate = join13(dir, ".claude", "skills");
7191
+ if (existsSync9(candidate) && statSync6(candidate).isDirectory()) return candidate;
7192
+ const parent = dirname5(dir);
7193
+ if (parent === dir) break;
7194
+ dir = parent;
7195
+ }
7196
+ return null;
5396
7197
  }
5397
- async function handleConciergeDispatch(deps, rawInput, _signal) {
5398
- if (!isToolInput21(rawInput)) {
5399
- throw invalidParams(
5400
- TOOL_NAME21,
5401
- "invalid input. Expected { pack?: string, tenant_id?: string }."
5402
- );
7198
+ function loadCorpus() {
7199
+ const skillsDir = resolveSkillsDir();
7200
+ if (skillsDir === null) {
7201
+ return {
7202
+ skills: [],
7203
+ skillsDir: null,
7204
+ unavailableReason: "No skills directory found. Set VO_SKILLS_DIR or run inside a repo with .claude/skills."
7205
+ };
5403
7206
  }
5404
- if (rawInput.pack !== void 0 && rawInput.pack !== "" && !isKnownConciergePack(rawInput.pack)) {
7207
+ try {
7208
+ return { skills: loadSkillsFromDir(skillsDir), skillsDir, unavailableReason: null };
7209
+ } catch (err) {
7210
+ const message = err instanceof Error ? `${err.name}: ${err.message}` : String(err);
7211
+ return { skills: [], skillsDir, unavailableReason: message };
7212
+ }
7213
+ }
7214
+ function getCorpus(refresh) {
7215
+ if (refresh || cachedCorpus === null) {
7216
+ cachedCorpus = loadCorpus();
7217
+ }
7218
+ return cachedCorpus;
7219
+ }
7220
+ async function handleSkillList(_deps, rawInput) {
7221
+ const input = rawInput ?? {};
7222
+ const refresh = input.refresh === true;
7223
+ const corpus = getCorpus(refresh);
7224
+ return jsonContent({
7225
+ corpus_available: corpus.unavailableReason === null,
7226
+ skills_dir: corpus.skillsDir,
7227
+ unavailable_reason: corpus.unavailableReason,
7228
+ skill_count: corpus.skills.length,
7229
+ skills: corpus.skills.map((s) => ({ name: s.name, description: s.description }))
7230
+ });
7231
+ }
7232
+ async function handleSkillGet(_deps, rawInput) {
7233
+ const input = rawInput ?? {};
7234
+ if (typeof input.name !== "string" || input.name.trim().length === 0) {
7235
+ throw invalidParams(GET_TOOL_NAME, 'input field "name" (non-empty string) is required');
7236
+ }
7237
+ const requested = input.name.trim();
7238
+ const corpus = getCorpus(false);
7239
+ if (corpus.unavailableReason !== null) {
7240
+ return jsonContent({
7241
+ corpus_available: false,
7242
+ unavailable_reason: corpus.unavailableReason,
7243
+ skill: null
7244
+ });
7245
+ }
7246
+ const skill = corpus.skills.find((s) => s.name === requested);
7247
+ if (skill === void 0) {
5405
7248
  throw invalidParams(
5406
- TOOL_NAME21,
5407
- `unknown pack: ${JSON.stringify(rawInput.pack)}. Known packs: ${KNOWN_CONCIERGE_PACKS.join(", ")}.`
7249
+ GET_TOOL_NAME,
7250
+ `unknown skill "${requested}". Known skills: ${corpus.skills.map((s) => s.name).join(", ")}`
5408
7251
  );
5409
7252
  }
5410
- const normalizedInput = {};
5411
- if (rawInput.pack) normalizedInput.pack = rawInput.pack;
5412
- if (rawInput.tenant_id) normalizedInput.tenant_id = rawInput.tenant_id;
5413
- const cloudBody = {};
5414
- if (rawInput.pack) cloudBody.pack = rawInput.pack;
5415
- if (rawInput.tenant_id) cloudBody.tenantId = rawInput.tenant_id;
5416
- return buildCloudOrStubResponse({
5417
- toolName: TOOL_NAME21,
5418
- callableName: CALLABLE_NAME10,
5419
- adminPath: ADMIN_PATH10,
5420
- normalizedInput,
5421
- cloudBody,
5422
- // Concierge dispatch returns `{ok, mode, routed_from, pack|packs}`,
5423
- // NOT the callable-proxy `{ok, callable, result}` shape — take the
5424
- // raw envelope as response_data instead of mandating `.result`.
5425
- rawEnvelope: true,
5426
- gateType: CONCIERGE_GATE_TYPE,
5427
- stubReason: CONCIERGE_STUB_REASON,
5428
- // Read-only pack fetch — stays live under VO_ADMIN_CALLABLES_READONLY.
5429
- readOnly: true,
5430
- deps
7253
+ return jsonContent({
7254
+ corpus_available: true,
7255
+ skill: {
7256
+ name: skill.name,
7257
+ description: skill.description,
7258
+ instructions: skill.body,
7259
+ source_path: skill.sourcePath
7260
+ }
5431
7261
  });
5432
7262
  }
5433
7263
 
5434
7264
  // src/server.ts
5435
- init_sync_config();
5436
7265
  function buildToolRegistry() {
5437
7266
  return {
5438
7267
  [TOOL_NAME]: {
@@ -5610,11 +7439,75 @@ function buildToolRegistry() {
5610
7439
  inputSchema: inputSchema22
5611
7440
  },
5612
7441
  handler: handleSyncConfig
7442
+ },
7443
+ [UPSERT_TOOL_NAME]: {
7444
+ definition: {
7445
+ name: UPSERT_TOOL_NAME,
7446
+ description: upsertDescription,
7447
+ inputSchema: upsertInputSchema
7448
+ },
7449
+ handler: handlePrivateKnowledgeUpsert
7450
+ },
7451
+ [CONTEXT_TOOL_NAME]: {
7452
+ definition: {
7453
+ name: CONTEXT_TOOL_NAME,
7454
+ description: contextDescription,
7455
+ inputSchema: contextInputSchema
7456
+ },
7457
+ handler: handlePrivateKnowledgeContext
7458
+ },
7459
+ [INVALIDATE_TOOL_NAME]: {
7460
+ definition: {
7461
+ name: INVALIDATE_TOOL_NAME,
7462
+ description: invalidateDescription,
7463
+ inputSchema: invalidateInputSchema
7464
+ },
7465
+ handler: handlePrivateKnowledgeInvalidate
7466
+ },
7467
+ [STALE_TOOL_NAME]: {
7468
+ definition: {
7469
+ name: STALE_TOOL_NAME,
7470
+ description: staleDescription,
7471
+ inputSchema: staleInputSchema
7472
+ },
7473
+ handler: handlePrivateKnowledgeStale
7474
+ },
7475
+ [POST_TOOL_NAME]: {
7476
+ definition: {
7477
+ name: POST_TOOL_NAME,
7478
+ description: postDescription,
7479
+ inputSchema: postInputSchema
7480
+ },
7481
+ handler: handleHqWhiteboardPost
7482
+ },
7483
+ [READ_TOOL_NAME]: {
7484
+ definition: {
7485
+ name: READ_TOOL_NAME,
7486
+ description: readDescription,
7487
+ inputSchema: readInputSchema
7488
+ },
7489
+ handler: handleHqWhiteboardRead
7490
+ },
7491
+ [LIST_TOOL_NAME]: {
7492
+ definition: {
7493
+ name: LIST_TOOL_NAME,
7494
+ description: listDescription,
7495
+ inputSchema: listInputSchema
7496
+ },
7497
+ handler: handleSkillList
7498
+ },
7499
+ [GET_TOOL_NAME]: {
7500
+ definition: {
7501
+ name: GET_TOOL_NAME,
7502
+ description: getDescription,
7503
+ inputSchema: getInputSchema
7504
+ },
7505
+ handler: handleSkillGet
5613
7506
  }
5614
7507
  };
5615
7508
  }
5616
7509
  function createServer(options) {
5617
- const sessionId = options.sessionId ?? randomUUID2();
7510
+ const sessionId = options.sessionId ?? randomUUID3();
5618
7511
  const mode = createLocalMode();
5619
7512
  const now = options.now ?? (() => /* @__PURE__ */ new Date());
5620
7513
  const server = new Server(
@@ -5663,9 +7556,9 @@ function createServer(options) {
5663
7556
  }
5664
7557
 
5665
7558
  // src/cache/sqlite-cache.ts
5666
- import { createHash as createHash3 } from "node:crypto";
5667
- import { chmodSync as chmodSync3, mkdirSync as mkdirSync5 } from "node:fs";
5668
- import { dirname as dirname5 } from "node:path";
7559
+ import { createHash as createHash4 } from "node:crypto";
7560
+ import { chmodSync as chmodSync3, mkdirSync as mkdirSync7 } from "node:fs";
7561
+ import { dirname as dirname6 } from "node:path";
5669
7562
  import { DatabaseSync } from "node:sqlite";
5670
7563
 
5671
7564
  // src/cache/canonicalize.ts
@@ -5710,7 +7603,7 @@ function normalizeString(s) {
5710
7603
  function createSqliteCache(options) {
5711
7604
  const fileBacked = options.dbPath !== ":memory:";
5712
7605
  if (fileBacked) {
5713
- mkdirSync5(dirname5(options.dbPath), { recursive: true, mode: 448 });
7606
+ mkdirSync7(dirname6(options.dbPath), { recursive: true, mode: 448 });
5714
7607
  }
5715
7608
  const versionNamespace = options.cacheVersionNamespace ?? "";
5716
7609
  const db = new DatabaseSync(options.dbPath);
@@ -5741,7 +7634,7 @@ function createSqliteCache(options) {
5741
7634
  return {
5742
7635
  keyFor(toolName, input, opts) {
5743
7636
  const canonical = canonicalize(input, opts);
5744
- const hash = createHash3("sha256");
7637
+ const hash = createHash4("sha256");
5745
7638
  if (versionNamespace.length > 0) {
5746
7639
  hash.update(versionNamespace);
5747
7640
  hash.update("|");
@@ -5836,7 +7729,7 @@ function createStubRatchetClient() {
5836
7729
  let m;
5837
7730
  while ((m = pat.regex.exec(req.source)) !== null) {
5838
7731
  findings.push({
5839
- line_excerpt: clip(m[0], 80),
7732
+ line_excerpt: clip2(m[0], 80),
5840
7733
  severity: pat.severity,
5841
7734
  code: pat.code,
5842
7735
  message: pat.message
@@ -5868,7 +7761,7 @@ function createStubRatchetClient() {
5868
7761
  }
5869
7762
  };
5870
7763
  }
5871
- function clip(s, n) {
7764
+ function clip2(s, n) {
5872
7765
  return s.length <= n ? s : s.slice(0, n) + "\u2026";
5873
7766
  }
5874
7767
  function buildSummary2(args) {
@@ -5879,7 +7772,7 @@ function buildSummary2(args) {
5879
7772
  // src/consensus/engine-client.ts
5880
7773
  init_events_writer();
5881
7774
  init_common();
5882
- import { randomUUID as randomUUID3 } from "node:crypto";
7775
+ import { randomUUID as randomUUID4 } from "node:crypto";
5883
7776
 
5884
7777
  // src/consensus/null-client.ts
5885
7778
  var NULL_CLIENT_DEFAULT_REASON = "consensus-engine-package-pending";
@@ -5901,6 +7794,60 @@ function createNullConsensusEngineClient(reason = NULL_CLIENT_DEFAULT_REASON) {
5901
7794
  };
5902
7795
  }
5903
7796
 
7797
+ // src/consensus/meta-model-caller.ts
7798
+ var META_CONSENSUS_MODEL = "muse-spark-1.1";
7799
+ function createMetaModelCaller(options = {}) {
7800
+ void options;
7801
+ return async function callMetaWithMetrics2() {
7802
+ throw new Error(
7803
+ "Muse Spark direct consensus is disabled. Use an explicit sanitized task capsule through the AlgoSuite Model Firewall."
7804
+ );
7805
+ };
7806
+ }
7807
+ var callMetaWithMetrics = createMetaModelCaller();
7808
+
7809
+ // src/consensus/consensus-panel.ts
7810
+ var VO_MCP_CONSENSUS_PANEL = {
7811
+ // claude-opus-5 (2026-07-24). Opus 4.7 was STRICTLY DOMINATED, not merely old:
7812
+ // Opus 5 is $5/$25 per MTok vs Opus 4.7's $15/$75 — a 3x cost cut on this slot,
7813
+ // corroborated by our own catalog (constants/pricing/sciencePricing.ts prices
7814
+ // claude-opus-5 at 0.010 vs claude-opus-4-7 at 0.030) — AND the same 2026-07-24
7815
+ // release note REMOVED fast mode from Opus 4.7 outright: `speed: "fast"` now
7816
+ // returns an error there rather than degrading, unlike the Opus 4.6 removal.
7817
+ // Verified served: GET /v1/models/claude-opus-5 -> HTTP 200 (2026-07-30).
7818
+ //
7819
+ // Claude-5 API safety checked before this swap: Opus 5 rejects `temperature` /
7820
+ // `top_p` / `top_k` and manual `thinking.budget_tokens` with HTTP 400. Neither
7821
+ // the consensus-engine Anthropic adapter nor functions-shared `callAnthropic`
7822
+ // sends any of them, and buildAdaptiveThinking emits `thinking: {type:'adaptive'}`
7823
+ // (the supported form) — so this swap cannot 400.
7824
+ anthropic: "claude-opus-5",
7825
+ // gpt-5.6-terra (GA 2026-07-09; −20% price cut 2026-07-30). NOTE the real IDs
7826
+ // are tiered — `gpt-5.6-sol` / `-terra` / `-luna`; there is NO bare `gpt-5.6`
7827
+ // alias (verified against the served model list, 2026-07-30). Terra is the
7828
+ // cost/capability balance point and the right default for a judgment panel;
7829
+ // Sol is available if verdict quality ever needs it.
7830
+ openai: "gpt-5.6-terra",
7831
+ // gemini-2.5-FLASH (not -pro): flash accepts the default thinkingBudget=0 from
7832
+ // callGeminiWithMetrics; 2.5-pro REJECTS budget 0 ("only works in thinking mode").
7833
+ // Flash is also ~10x cheaper. 2026-06-02.
7834
+ google: "gemini-2.5-flash",
7835
+ deepseek: "deepseek-chat",
7836
+ // Muse Spark identity is owned by meta-model-caller.ts (single source of
7837
+ // truth for the meta slot); re-exported here so the panel stays complete.
7838
+ meta: META_CONSENSUS_MODEL
7839
+ };
7840
+ function getVoMcpConsensusPanel(panel = VO_MCP_CONSENSUS_PANEL) {
7841
+ for (const [provider, modelId] of Object.entries(panel)) {
7842
+ if (typeof modelId !== "string" || modelId.trim().length === 0) {
7843
+ throw new Error(
7844
+ `getVoMcpConsensusPanel: panel slot "${provider}" has a missing or blank model ID`
7845
+ );
7846
+ }
7847
+ }
7848
+ return panel;
7849
+ }
7850
+
5904
7851
  // src/consensus/engine-options.ts
5905
7852
  var AGREEMENT_GATE_ENV_VAR = "VO_CONSENSUS_AGREEMENT_GATE";
5906
7853
  function isTruthyFlag(raw) {
@@ -5954,6 +7901,14 @@ function shadowEnabled(env) {
5954
7901
  const norm = raw.trim().toLowerCase();
5955
7902
  return !(norm === "0" || norm === "false" || norm === "no" || norm === "off" || norm === "");
5956
7903
  }
7904
+ var MIN_RESPONDERS_ENV_VAR = "VO_CONSENSUS_MIN_RESPONDERS";
7905
+ function resolveMinResponders(env) {
7906
+ const raw = (env ?? {})[MIN_RESPONDERS_ENV_VAR];
7907
+ if (raw === void 0 || raw.trim() === "") return 2;
7908
+ const parsed = Number.parseInt(raw.trim(), 10);
7909
+ if (!Number.isFinite(parsed) || parsed <= 0) return void 0;
7910
+ return parsed;
7911
+ }
5957
7912
  function mapShadowSynthesis(s) {
5958
7913
  if (s === void 0) return void 0;
5959
7914
  return {
@@ -6103,9 +8058,11 @@ function createEngineConsensusClient(options) {
6103
8058
  ...options.agreement_gate_enabled !== void 0 ? { configEnabled: options.agreement_gate_enabled } : {},
6104
8059
  ...options.env !== void 0 ? { env: options.env } : {}
6105
8060
  });
8061
+ const minResponders = resolveMinResponders(options.env);
6106
8062
  const engineOptions = {
6107
8063
  panel,
6108
8064
  ...options.per_model_timeout_ms !== void 0 ? { per_model_timeout_ms: options.per_model_timeout_ms } : {},
8065
+ ...minResponders !== void 0 ? { min_responders: minResponders } : {},
6109
8066
  ...agreementGate !== void 0 ? { agreement_gate: agreementGate } : {},
6110
8067
  // Stage A7-shadow: run the adaptive verdict alongside the live one for grading.
6111
8068
  // Cheap (pure log-odds over already-fetched verdicts; no extra model calls),
@@ -6158,8 +8115,13 @@ function createEngineConsensusClient(options) {
6158
8115
  synthesized_verdict: response.synthesized_verdict,
6159
8116
  per_model_verdicts: response.per_model_verdicts,
6160
8117
  degraded: response.degraded,
8118
+ ...response.quorum_failed === true ? { quorum_failed: true } : {},
6161
8119
  duration_ms: response.duration_ms,
6162
8120
  engine_version: response.engine_version,
8121
+ // Cumulative cross-round inference usage (B44-3). Absent when no panel
8122
+ // member reported usage; forwarded verbatim — the aggregator prefers it
8123
+ // over summing final-round verdicts (which under-reports deliberation).
8124
+ ...response.token_usage !== void 0 ? { token_usage: response.token_usage } : {},
6163
8125
  // Phase 2 Lane D-1 — forward escalation signal when present. The
6164
8126
  // source-grounded layer's own escalation (from the citation grade)
6165
8127
  // takes precedence when set, else the synthesizer's.
@@ -6169,6 +8131,10 @@ function createEngineConsensusClient(options) {
6169
8131
  ...mapFanOutDiagnostics(response.fan_out_diagnostics) !== void 0 ? { fan_out_diagnostics: mapFanOutDiagnostics(response.fan_out_diagnostics) } : {},
6170
8132
  // Stage A7-shadow — adaptive-vs-incumbent comparison (PII-free; present iff shadow on).
6171
8133
  ...mapShadowSynthesis(response.shadow_synthesis) !== void 0 ? { shadow_synthesis: mapShadowSynthesis(response.shadow_synthesis) } : {},
8134
+ // Critique-uptake (2026-07-20 red-team fix) — verifier-critique
8135
+ // visibility report; previously computed by the engine on every
8136
+ // call but dropped at this boundary.
8137
+ ...response.critique_uptake !== void 0 ? { critique_uptake: response.critique_uptake } : {},
6172
8138
  // Source-grounded additive outputs (Tier-4 features).
6173
8139
  ...useSourceGrounded ? { source_grounded: true } : {},
6174
8140
  ...sourceExtras?.citation_grade !== void 0 ? { citation_grade: sourceExtras.citation_grade } : {},
@@ -6184,25 +8150,12 @@ function createEngineConsensusClient(options) {
6184
8150
  }
6185
8151
  };
6186
8152
  }
6187
- var DEFAULT_MODELS = {
6188
- // These ids match the strategic-roadmap §4 `newsStandard` / `newsDeep` panel
6189
- // intent — current production model ids. Per handoff §C-3 these MUST come
6190
- // from `CONSENSUS_PANELS` in `functions-shared/shared-model-resolvers.ts`
6191
- // for V1; placeholder defaults here keep Phase 2 Lane A non-blocking.
6192
- anthropic: "claude-opus-4-7",
6193
- openai: "gpt-5",
6194
- // gemini-2.5-FLASH (not -pro): flash accepts the default thinkingBudget=0 from
6195
- // callGeminiWithMetrics; 2.5-pro REJECTS budget 0 ("only works in thinking mode").
6196
- // Flash is also ~10x cheaper. 2026-06-02.
6197
- google: "gemini-2.5-flash",
6198
- deepseek: "deepseek-chat"
6199
- };
8153
+ var DEFAULT_MODELS = getVoMcpConsensusPanel();
6200
8154
  function probeProviders(env = process.env) {
6201
8155
  const out = [];
6202
8156
  if ((env["ANTHROPIC_API_KEY"] ?? "").trim().length > 0) out.push("anthropic");
6203
8157
  if ((env["OPENAI_API_KEY"] ?? "").trim().length > 0) out.push("openai");
6204
8158
  if ((env["GOOGLE_API_KEY"] ?? "").trim().length > 0) out.push("google");
6205
- if ((env["DEEPSEEK_API_KEY"] ?? "").trim().length > 0) out.push("deepseek");
6206
8159
  return out;
6207
8160
  }
6208
8161
  async function loadFactoryAndCallers(injectedEngine, injectedShared) {
@@ -6247,14 +8200,12 @@ async function tryCreateEngineConsensusClientFromEnvAsync(options = {}) {
6247
8200
  const callerByProvider = {
6248
8201
  anthropic: loaded.shared.callAnthropicWithMetrics,
6249
8202
  openai: loaded.shared.callOpenAIWithMetrics,
6250
- google: loaded.shared.callGeminiWithMetrics,
6251
- deepseek: loaded.shared.callDeepSeekWithMetrics
8203
+ google: loaded.shared.callGeminiWithMetrics
6252
8204
  };
6253
8205
  const modelByProvider = {
6254
8206
  anthropic: options.models?.anthropic ?? DEFAULT_MODELS.anthropic,
6255
8207
  openai: options.models?.openai ?? DEFAULT_MODELS.openai,
6256
- google: options.models?.google ?? DEFAULT_MODELS.google,
6257
- deepseek: options.models?.deepseek ?? DEFAULT_MODELS.deepseek
8208
+ google: options.models?.google ?? DEFAULT_MODELS.google
6258
8209
  };
6259
8210
  const panel = [];
6260
8211
  for (const p of providers) {
@@ -6296,7 +8247,7 @@ function tryCreateEngineConsensusClientFromEnv(options = {}) {
6296
8247
  }
6297
8248
 
6298
8249
  // src/consensus/moat-client.ts
6299
- import { randomUUID as randomUUID4 } from "node:crypto";
8250
+ import { randomUUID as randomUUID5 } from "node:crypto";
6300
8251
 
6301
8252
  // src/consensus/client.ts
6302
8253
  var CANCELLED_REASON = "cancelled";
@@ -6348,7 +8299,7 @@ function createMoatConsensusClient(opts) {
6348
8299
  request.signal?.addEventListener("abort", onAbort, { once: true });
6349
8300
  const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
6350
8301
  const body = JSON.stringify({
6351
- task_id: randomUUID4(),
8302
+ task_id: randomUUID5(),
6352
8303
  gate_type: request.gate_type,
6353
8304
  excerpt: request.prompt,
6354
8305
  ...request.system_prompt ? { question: request.system_prompt } : {},
@@ -6377,8 +8328,10 @@ function createMoatConsensusClient(opts) {
6377
8328
  }
6378
8329
  const agreeing = normalizeAgreeing(parsed.models_agreeing);
6379
8330
  const reasoning = agreeing !== void 0 ? `${parsed.reason} (${agreeing} models agreeing)` : parsed.reason;
8331
+ const receiptId = typeof parsed.decision_id === "string" && parsed.decision_id.trim().length > 0 ? parsed.decision_id.trim().slice(0, 120) : null;
6380
8332
  return {
6381
8333
  ok: true,
8334
+ ...receiptId ? { receipt_id: receiptId } : {},
6382
8335
  synthesized_verdict: {
6383
8336
  verdict: parsed.approved ? "pass" : "fail",
6384
8337
  confidence: normalizeConfidence(parsed.confidence),
@@ -6419,6 +8372,68 @@ function tryCreateMoatConsensusClientFromEnv(env = process.env, fetchFn) {
6419
8372
  });
6420
8373
  }
6421
8374
 
8375
+ // src/consensus/fallback-client.ts
8376
+ var MIN_VALID_LOCAL_VERDICTS = 2;
8377
+ var INSUFFICIENT_LOCAL_VERDICTS_REASON = "local-panel-insufficient-valid-verdicts";
8378
+ function createConsensusFallbackClient(primary, fallback, options = {}) {
8379
+ return {
8380
+ async run(request) {
8381
+ const primaryResult = await primary.run(request);
8382
+ if (request.signal?.aborted || !primaryResult.ok && primaryResult.reason === CANCELLED_REASON) {
8383
+ return primaryResult;
8384
+ }
8385
+ if (primaryResult.ok) {
8386
+ const validVerdicts = primaryResult.per_model_verdicts.filter(
8387
+ (verdict) => verdict.verdict !== "error"
8388
+ );
8389
+ if (validVerdicts.length >= MIN_VALID_LOCAL_VERDICTS) return primaryResult;
8390
+ options.onFallback?.(INSUFFICIENT_LOCAL_VERDICTS_REASON);
8391
+ return fallback.run(request);
8392
+ }
8393
+ options.onFallback?.(primaryResult.reason);
8394
+ return fallback.run(request);
8395
+ }
8396
+ };
8397
+ }
8398
+
8399
+ // src/consensus/local-credential-env.ts
8400
+ import { createRequire as createRequire2 } from "node:module";
8401
+ var require2 = createRequire2(import.meta.url);
8402
+ var KEY_SERVICE = "algosuite-vo";
8403
+ var KEYCHAIN_TARGETS = [
8404
+ { account: "anthropic-api-key", envVar: "ANTHROPIC_API_KEY" },
8405
+ { account: "openai-api-key", envVar: "OPENAI_API_KEY" },
8406
+ { account: "meta-api-key", envVar: "MODEL_API_KEY" }
8407
+ ];
8408
+ function loadEntryCtor() {
8409
+ try {
8410
+ return require2("@napi-rs/keyring").Entry ?? null;
8411
+ } catch {
8412
+ return null;
8413
+ }
8414
+ }
8415
+ function readKey(EntryCtor, account) {
8416
+ try {
8417
+ return new EntryCtor(KEY_SERVICE, account).getPassword()?.trim() || null;
8418
+ } catch {
8419
+ return null;
8420
+ }
8421
+ }
8422
+ function withLocalConsensusCredentials(baseEnv = process.env, options = {}) {
8423
+ const env = { ...baseEnv };
8424
+ if (!env.OPENAI_API_KEY?.trim() && env.CODEX_API_KEY?.trim()) {
8425
+ env.OPENAI_API_KEY = env.CODEX_API_KEY;
8426
+ }
8427
+ const EntryCtor = options.EntryCtor === void 0 ? loadEntryCtor() : options.EntryCtor;
8428
+ if (!EntryCtor) return env;
8429
+ for (const target of KEYCHAIN_TARGETS) {
8430
+ if (env[target.envVar]?.trim()) continue;
8431
+ const key = readKey(EntryCtor, target.account);
8432
+ if (key) env[target.envVar] = key;
8433
+ }
8434
+ return env;
8435
+ }
8436
+
6422
8437
  // src/cloud/login.ts
6423
8438
  init_credential_store();
6424
8439
  import { createServer as createServer2 } from "node:http";
@@ -6453,7 +8468,7 @@ function processCapture(rawBody, expectedState, store) {
6453
8468
  };
6454
8469
  }
6455
8470
  function captureHtml() {
6456
- return `<!doctype html><html><head><meta charset="utf-8"><title>VO login</title></head>
8471
+ return `<!doctype html><html><head><meta charset="utf-8"><title>AlgoHQ login</title></head>
6457
8472
  <body style="font-family:system-ui;max-width:32rem;margin:4rem auto;text-align:center">
6458
8473
  <h2 id="m">Completing sign-in\u2026</h2>
6459
8474
  <script>
@@ -6469,7 +8484,7 @@ function captureHtml() {
6469
8484
  function defaultOpenBrowser(url) {
6470
8485
  const platform = process.platform;
6471
8486
  if (platform === "win32") {
6472
- spawn2("cmd", ["/c", "start", "", url], { detached: true, stdio: "ignore" }).unref();
8487
+ spawn2("rundll32", ["url.dll,FileProtocolHandler", url], { detached: true, stdio: "ignore" }).unref();
6473
8488
  } else if (platform === "darwin") {
6474
8489
  spawn2("open", [url], { detached: true, stdio: "ignore" }).unref();
6475
8490
  } else {
@@ -6484,7 +8499,7 @@ async function runLogin(opts = {}) {
6484
8499
  const nowIso = opts.nowIso ?? (() => (/* @__PURE__ */ new Date()).toISOString());
6485
8500
  const openBrowser = opts.openBrowser ?? defaultOpenBrowser;
6486
8501
  const state = randomBytes(32).toString("base64url");
6487
- return new Promise((resolve, reject) => {
8502
+ return new Promise((resolve3, reject) => {
6488
8503
  let settled = false;
6489
8504
  const finish = (err, result) => {
6490
8505
  if (settled) return;
@@ -6492,7 +8507,7 @@ async function runLogin(opts = {}) {
6492
8507
  clearTimeout(timer);
6493
8508
  server.close();
6494
8509
  if (err) reject(err);
6495
- else resolve(result);
8510
+ else resolve3(result);
6496
8511
  };
6497
8512
  const server = createServer2((req, res) => {
6498
8513
  const url = new URL(req.url ?? "/", "http://127.0.0.1");
@@ -6540,7 +8555,7 @@ async function runLogin(opts = {}) {
6540
8555
  result = { ...capt.email ? { email: capt.email } : {}, credentialPath: path3 };
6541
8556
  }
6542
8557
  res.writeHead(outcome.httpStatus, { "content-type": "text/html; charset=utf-8" });
6543
- res.end(outcome.ok ? "<h2>VO login complete \u2014 you can close this tab.</h2>" : `<h2>Login failed: ${outcome.error}</h2>`);
8558
+ res.end(outcome.ok ? "<h2>AlgoHQ login complete \u2014 you can close this tab.</h2>" : `<h2>Login failed: ${outcome.error}</h2>`);
6544
8559
  finish(outcome.ok ? null : new Error(outcome.error ?? "login failed"), result);
6545
8560
  })();
6546
8561
  });
@@ -6607,7 +8622,7 @@ init_common();
6607
8622
  function defaultCacheDbPath() {
6608
8623
  const env = process.env["VO_MCP_DB_PATH"];
6609
8624
  if (env && env.length > 0) return env;
6610
- return join8(homedir6(), ".claude", "vo-mcp-cache.db");
8625
+ return join14(homedir8(), ".claude", "vo-mcp-cache.db");
6611
8626
  }
6612
8627
  async function probeEngineVersion() {
6613
8628
  try {
@@ -6682,11 +8697,23 @@ async function main() {
6682
8697
  const ratchets = createStubRatchetClient();
6683
8698
  const testModule = process.env["VO_MCP_TEST_ENGINE_MODULE"];
6684
8699
  const testClient = testModule !== void 0 && testModule.length > 0 ? await loadTestEngineClient(testModule) : null;
8700
+ const localEnv = withLocalConsensusCredentials();
8701
+ const localProviders = probeProviders(localEnv);
8702
+ for (const key of ["ANTHROPIC_API_KEY", "OPENAI_API_KEY", "GOOGLE_API_KEY", "DEEPSEEK_API_KEY", "MODEL_API_KEY"]) {
8703
+ if (!process.env[key] && localEnv[key]) process.env[key] = localEnv[key];
8704
+ }
8705
+ const localConsensus = tryCreateEngineConsensusClientFromEnv({ envSource: localEnv });
6685
8706
  const cloudConsensus = testClient ? null : tryCreateMoatConsensusClientFromEnv();
6686
- if (cloudConsensus) {
6687
- console.error("[vo-mcp] cloud consensus active \u2014 verdicts via vo-moat-plane (no local model keys)");
8707
+ let consensus = testClient ?? localConsensus;
8708
+ if (!testClient && cloudConsensus && localProviders.length >= 2) {
8709
+ console.error(`[vo-mcp] local-first consensus active (${localProviders.join(", ")}); cloud moat is fallback-only`);
8710
+ consensus = createConsensusFallbackClient(localConsensus, cloudConsensus, {
8711
+ onFallback: (reason) => console.error(`[vo-mcp] local consensus unavailable (${reason}); using cloud moat fallback`)
8712
+ });
8713
+ } else if (!testClient && cloudConsensus) {
8714
+ console.error("[vo-mcp] fewer than 2 linked local providers; cloud moat consensus active");
8715
+ consensus = cloudConsensus;
6688
8716
  }
6689
- const consensus = testClient ?? cloudConsensus ?? tryCreateEngineConsensusClientFromEnv();
6690
8717
  let adminCallables = null;
6691
8718
  try {
6692
8719
  adminCallables = buildAdminCallableClientFromEnv();
@@ -6722,7 +8749,7 @@ async function main() {
6722
8749
  }
6723
8750
  if (process.argv[2] === "login") {
6724
8751
  const controlPlaneUrl = process.env["VO_CONTROL_PLANE_URL"]?.trim();
6725
- const credentialLabel = `vo-mcp-cli@${hostname()}`.slice(0, 200);
8752
+ const credentialLabel = `vo-mcp-cli@${hostname2()}`.slice(0, 200);
6726
8753
  runLogin(
6727
8754
  controlPlaneUrl ? {
6728
8755
  exchange: (refreshToken, apiKey) => exchangeForVoCredential({ refreshToken, apiKey, controlPlaneUrl, label: credentialLabel })
@@ -6731,35 +8758,53 @@ if (process.argv[2] === "login") {
6731
8758
  console.error(`[vo-mcp] login successful${r.email ? ` as ${r.email}` : ""}. Credential stored at ${r.credentialPath}.`);
6732
8759
  console.error("[vo-mcp] You can now remove VO_CONTROL_PLANE_ADMIN_TOKEN (the god-token) from your MCP config.");
6733
8760
  console.error("[vo-mcp] NOTE: per-user auth requires VO_OPERATOR_ALLOWED_EMAILS (with your email) on the deployed control-plane.");
6734
- process.exit(0);
6735
8761
  }).catch((err) => {
6736
8762
  console.error("[vo-mcp] login failed:", err instanceof Error ? err.message : String(err));
6737
- process.exit(1);
8763
+ process.exitCode = 1;
6738
8764
  });
6739
8765
  } else if (process.argv[2] === "sync") {
6740
8766
  const action = process.argv[3];
6741
8767
  if (action !== "push" && action !== "pull") {
6742
- console.error("[vo-mcp] usage: vo-mcp sync <push|pull> [--cwd <path>]");
8768
+ console.error("[vo-mcp] usage: vo-mcp sync <push|pull> [--cwd <path>] [--lock-wait-ms <n>]");
6743
8769
  process.exit(2);
6744
8770
  }
6745
8771
  const cwdFlag = process.argv.indexOf("--cwd");
6746
8772
  const cwd = cwdFlag >= 0 && typeof process.argv[cwdFlag + 1] === "string" ? process.argv[cwdFlag + 1] : process.cwd();
6747
- const sessionId = randomUUID5();
8773
+ const waitFlag = process.argv.indexOf("--lock-wait-ms");
8774
+ const parsedWait = waitFlag >= 0 ? Number(process.argv[waitFlag + 1]) : Number.NaN;
8775
+ const lockOptions = Number.isFinite(parsedWait) && parsedWait >= 0 ? { waitMs: parsedWait } : {};
8776
+ const sessionId = randomUUID6();
8777
+ const appendSyncLog = async (line) => {
8778
+ try {
8779
+ const { appendFileSync: appendFileSync2, mkdirSync: mkdirSync8 } = await import("node:fs");
8780
+ const { join: join15 } = await import("node:path");
8781
+ const { homedir: homedir9 } = await import("node:os");
8782
+ const dir = join15(homedir9(), ".claude");
8783
+ mkdirSync8(dir, { recursive: true });
8784
+ appendFileSync2(join15(dir, "vo-mcp-sync.log"), `${line}
8785
+ `, "utf8");
8786
+ } catch {
8787
+ }
8788
+ };
6748
8789
  Promise.resolve().then(() => (init_sync_config(), sync_config_exports)).then(async ({ runMemorySync: runMemorySync2, isNoopSyncReason: isNoopSyncReason2 }) => {
6749
- const r = await runMemorySync2(action, cwd, sessionId);
8790
+ const r = await runMemorySync2(action, cwd, sessionId, void 0, lockOptions);
8791
+ const stamp = `${sessionId} ${action}`;
6750
8792
  if (r.synced) {
6751
8793
  console.error(`[vo-mcp] sync ${action} ok: ${JSON.stringify(r)}`);
6752
- process.exit(0);
6753
- }
6754
- if (isNoopSyncReason2(r.reason)) {
8794
+ await appendSyncLog(`ok ${stamp} ${JSON.stringify(r)}`);
8795
+ } else if (isNoopSyncReason2(r.reason)) {
6755
8796
  console.error(`[vo-mcp] sync ${action} skipped: ${r.reason}`);
6756
- process.exit(0);
8797
+ await appendSyncLog(`skip ${stamp} ${r.reason ?? ""}`);
8798
+ } else {
8799
+ console.error(`[vo-mcp] sync ${action} failed: ${r.reason}`);
8800
+ await appendSyncLog(`FAIL ${stamp} ${r.reason ?? ""}`);
8801
+ process.exitCode = 1;
6757
8802
  }
6758
- console.error(`[vo-mcp] sync ${action} failed: ${r.reason}`);
6759
- process.exit(1);
6760
- }).catch((err) => {
6761
- console.error("[vo-mcp] sync fatal:", err instanceof Error ? err.message : String(err));
6762
- process.exit(1);
8803
+ }).catch(async (err) => {
8804
+ const message = err instanceof Error ? err.message : String(err);
8805
+ console.error("[vo-mcp] sync fatal:", message);
8806
+ await appendSyncLog(`FATAL ${sessionId} ${action} ${message}`);
8807
+ process.exitCode = 1;
6763
8808
  });
6764
8809
  } else {
6765
8810
  main().catch((err) => {