@algosuite/vo-mcp 0.2.0-beta.29 → 0.2.0-beta.33

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,
@@ -1399,13 +1435,713 @@ var init_safe_memory_file = __esm({
1399
1435
  }
1400
1436
  });
1401
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
+ }
1447
+ }
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
+ };
1464
+ }
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
+ }
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";
1901
+ async function pullMemory(controlPlaneUrl, token, memoryDir, fetchFn) {
1902
+ const url = `${controlPlaneUrl}/api/v1/agent-config/memory/me`;
1903
+ const response = await withRequestTimeout(
1904
+ url,
1905
+ () => fetchFn(url, {
1906
+ method: "GET",
1907
+ headers: {
1908
+ authorization: `Bearer ${token}`
1909
+ }
1910
+ })
1911
+ );
1912
+ if (response.status !== 200) {
1913
+ const text = await response.text();
1914
+ throw new Error(`GET /api/v1/agent-config/memory/me returned HTTP ${response.status}: ${text.slice(0, 200)}`);
1915
+ }
1916
+ const data = JSON.parse(await response.text());
1917
+ if (!data.ok || !Array.isArray(data.entries)) {
1918
+ throw new Error("GET /api/v1/agent-config/memory/me response missing ok=true or entries array");
1919
+ }
1920
+ const writes = data.entries.map((entry) => ({
1921
+ entry,
1922
+ filePath: resolveMemoryFilePath(memoryDir, entry.file_name)
1923
+ }));
1924
+ mkdirSync6(memoryDir, { recursive: true });
1925
+ const files = [];
1926
+ for (const { entry, filePath } of writes) {
1927
+ writeFileSync6(filePath, entry.content, "utf8");
1928
+ files.push(entry.file_name);
1929
+ }
1930
+ return { pulled: data.entries.length, files };
1931
+ }
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 };
1985
+ if (!existsSync5(memoryDir)) {
1986
+ return empty;
1987
+ }
1988
+ const localFiles = listPushableFiles(memoryDir).map((f) => ({
1989
+ file_name: f,
1990
+ content: readFileSync11(resolveMemoryFilePath(memoryDir, f), "utf8"),
1991
+ entry_type: isMemoryIndexFile(f) ? "index" : "topic"
1992
+ }));
1993
+ if (localFiles.length === 0) {
1994
+ return empty;
1995
+ }
1996
+ const deadline = options.deadline ?? createSyncDeadline();
1997
+ const cache = options.cache ?? readPushCache(memoryDir, controlPlaneUrl);
1998
+ deadline.check();
1999
+ const getUrl = `${controlPlaneUrl}/api/v1/agent-config/memory/me`;
2000
+ const getResponse = await withRequestTimeout(
2001
+ getUrl,
2002
+ () => fetchFn(getUrl, { method: "GET", headers: { authorization: `Bearer ${token}` } })
2003
+ );
2004
+ const existingMap = /* @__PURE__ */ new Map();
2005
+ if (getResponse.status === 200) {
2006
+ const getData = JSON.parse(await getResponse.text());
2007
+ if (getData.ok && Array.isArray(getData.entries)) {
2008
+ for (const entry of getData.entries) {
2009
+ existingMap.set(entry.file_name, {
2010
+ memoryId: entry.memory_id,
2011
+ content: typeof entry.content === "string" ? entry.content : ""
2012
+ });
2013
+ }
2014
+ }
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
+ );
2046
+ let created = 0;
2047
+ let updated = 0;
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}`);
2122
+ }
2123
+ }
2124
+ if (fired.length === 0) return { disabled: false, reason: null };
2125
+ return { disabled: true, reason: `memory sync DISABLED by ${fired.join(" + ")}` };
2126
+ }
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
+
1402
2138
  // src/tools/memory/memory-knowledge-bridge.ts
1403
2139
  var memory_knowledge_bridge_exports = {};
1404
2140
  __export(memory_knowledge_bridge_exports, {
1405
2141
  extractMemoryTitle: () => extractMemoryTitle,
1406
2142
  upsertMemoryFilesAsKnowledge: () => upsertMemoryFilesAsKnowledge
1407
2143
  });
1408
- import { existsSync as existsSync5, readdirSync as readdirSync4, readFileSync as readFileSync8 } from "node:fs";
2144
+ import { existsSync as existsSync7, readdirSync as readdirSync5, readFileSync as readFileSync13 } from "node:fs";
1409
2145
  function extractMemoryTitle(fileName, content) {
1410
2146
  const frontmatter = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
1411
2147
  if (frontmatter) {
@@ -1417,13 +2153,13 @@ function extractMemoryTitle(fileName, content) {
1417
2153
  return fileName;
1418
2154
  }
1419
2155
  async function upsertMemoryFilesAsKnowledge(options) {
1420
- const { controlPlaneUrl, token, memoryDir, fetchFn } = options;
2156
+ const { controlPlaneUrl, token, memoryDir, fetchFn, cache, deadline } = options;
1421
2157
  let files;
1422
2158
  try {
1423
- if (!existsSync5(memoryDir)) {
1424
- return { attempted: 0, upserted: 0, failed: 0, failures: [] };
2159
+ if (!existsSync7(memoryDir)) {
2160
+ return { attempted: 0, upserted: 0, failed: 0, skipped: 0, failures: [] };
1425
2161
  }
1426
- files = readdirSync4(memoryDir).filter(
2162
+ files = readdirSync5(memoryDir).filter(
1427
2163
  (f) => f.endsWith(".md") && f.toUpperCase() !== "MEMORY.MD"
1428
2164
  );
1429
2165
  } catch (err) {
@@ -1431,54 +2167,81 @@ async function upsertMemoryFilesAsKnowledge(options) {
1431
2167
  attempted: 0,
1432
2168
  upserted: 0,
1433
2169
  failed: 1,
2170
+ skipped: 0,
1434
2171
  failures: [`memory dir scan: ${err instanceof Error ? err.message : String(err)}`]
1435
2172
  };
1436
2173
  }
1437
- let upserted = 0;
2174
+ const sweepDue = cache ? knowledgeSweepDue(cache) : true;
2175
+ const candidates = [];
1438
2176
  const failures = [];
2177
+ let skipped = 0;
1439
2178
  for (const fileName of files) {
1440
2179
  try {
1441
- const content = readFileSync8(resolveMemoryFilePath(memoryDir, fileName), "utf8");
2180
+ const content = readFileSync13(resolveMemoryFilePath(memoryDir, fileName), "utf8");
1442
2181
  if (content.length > CONTENT_HARD_LIMIT) {
1443
2182
  failures.push(`${fileName}: ${content.length} chars exceeds the ${CONTENT_HARD_LIMIT} server limit \u2014 split the memory file`);
1444
2183
  continue;
1445
2184
  }
1446
- const title = extractMemoryTitle(fileName, content);
1447
- const base = {
1448
- knowledge_class: "memory",
1449
- source_path: `memory/${fileName}`,
1450
- title,
1451
- content
1452
- };
1453
- const post = (body) => fetchFn(`${controlPlaneUrl}/api/v1/knowledge/private`, {
1454
- method: "POST",
1455
- headers: {
1456
- authorization: `Bearer ${token}`,
1457
- "content-type": "application/json"
1458
- },
1459
- body: JSON.stringify(body)
1460
- });
1461
- let response = await post({
1462
- ...base,
1463
- provenance: { written_by: "memory-bridge", source_kind: "operator_memory" }
1464
- });
1465
- if (response.status === 400) {
1466
- response = await post(base);
1467
- }
1468
- if (response.status >= 200 && response.status < 300) {
1469
- upserted += 1;
1470
- } else {
1471
- const text = await response.text();
1472
- failures.push(`${fileName}: HTTP ${response.status} ${text.slice(0, 80)}`);
2185
+ const hash = sha256(content);
2186
+ if (cache && !needsKnowledgePush(cache, fileName, hash, sweepDue)) {
2187
+ skipped += 1;
2188
+ continue;
1473
2189
  }
2190
+ candidates.push({ fileName, content, hash });
1474
2191
  } catch (err) {
1475
2192
  failures.push(`${fileName}: ${err instanceof Error ? err.message : String(err)}`);
1476
2193
  }
1477
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
+ }
1478
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".
1479
2241
  attempted: files.length,
1480
2242
  upserted,
1481
2243
  failed: failures.length,
2244
+ skipped,
1482
2245
  failures: failures.slice(0, 5)
1483
2246
  };
1484
2247
  }
@@ -1487,6 +2250,8 @@ var init_memory_knowledge_bridge = __esm({
1487
2250
  "src/tools/memory/memory-knowledge-bridge.ts"() {
1488
2251
  "use strict";
1489
2252
  init_safe_memory_file();
2253
+ init_bounded_sync();
2254
+ init_memory_push_cache();
1490
2255
  CONTENT_HARD_LIMIT = 5e5;
1491
2256
  }
1492
2257
  });
@@ -1503,9 +2268,9 @@ __export(sync_config_exports, {
1503
2268
  isNoopSyncReason: () => isNoopSyncReason,
1504
2269
  runMemorySync: () => runMemorySync
1505
2270
  });
1506
- import { homedir as homedir6 } from "node:os";
1507
- import { join as join8 } from "node:path";
1508
- import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync9, writeFileSync as writeFileSync4, readdirSync as readdirSync5 } from "node:fs";
2271
+ import { existsSync as existsSync8 } from "node:fs";
2272
+ import { homedir as homedir7 } from "node:os";
2273
+ import { join as join11 } from "node:path";
1509
2274
  function isToolInput22(v) {
1510
2275
  if (typeof v !== "object" || v === null) return false;
1511
2276
  const o = v;
@@ -1518,129 +2283,17 @@ function deriveProjectSlug(cwd) {
1518
2283
  }
1519
2284
  function getMemoryDir(cwd) {
1520
2285
  const slug = deriveProjectSlug(cwd);
1521
- return join8(homedir6(), ".claude", "projects", slug, "memory");
1522
- }
1523
- async function pullMemory(controlPlaneUrl, token, memoryDir, fetchFn) {
1524
- const url = `${controlPlaneUrl}/api/v1/agent-config/memory/me`;
1525
- const response = await fetchFn(url, {
1526
- method: "GET",
1527
- headers: {
1528
- authorization: `Bearer ${token}`
1529
- }
1530
- });
1531
- if (response.status !== 200) {
1532
- const text = await response.text();
1533
- throw new Error(`GET /api/v1/agent-config/memory/me returned HTTP ${response.status}: ${text.slice(0, 200)}`);
1534
- }
1535
- const data = JSON.parse(await response.text());
1536
- if (!data.ok || !Array.isArray(data.entries)) {
1537
- throw new Error("GET /api/v1/agent-config/memory/me response missing ok=true or entries array");
1538
- }
1539
- const writes = data.entries.map((entry) => ({
1540
- entry,
1541
- filePath: resolveMemoryFilePath(memoryDir, entry.file_name)
1542
- }));
1543
- mkdirSync5(memoryDir, { recursive: true });
1544
- const files = [];
1545
- for (const { entry, filePath } of writes) {
1546
- writeFileSync4(filePath, entry.content, "utf8");
1547
- files.push(entry.file_name);
1548
- }
1549
- return { pulled: data.entries.length, files };
1550
- }
1551
- async function pushMemory(controlPlaneUrl, token, memoryDir, sessionId, fetchFn) {
1552
- if (!existsSync6(memoryDir)) {
1553
- return { pushed: 0, created: 0, updated: 0 };
1554
- }
1555
- const localFiles = readdirSync5(memoryDir).filter((f) => f.endsWith(".md")).map((f) => ({
1556
- file_name: f,
1557
- content: readFileSync9(resolveMemoryFilePath(memoryDir, f), "utf8"),
1558
- entry_type: f === "MEMORY.md" ? "index" : "topic"
1559
- }));
1560
- if (localFiles.length === 0) {
1561
- return { pushed: 0, created: 0, updated: 0 };
1562
- }
1563
- const getUrl = `${controlPlaneUrl}/api/v1/agent-config/memory/me`;
1564
- const getResponse = await fetchFn(getUrl, {
1565
- method: "GET",
1566
- headers: {
1567
- authorization: `Bearer ${token}`
1568
- }
1569
- });
1570
- const existingMap = /* @__PURE__ */ new Map();
1571
- if (getResponse.status === 200) {
1572
- const getData = JSON.parse(await getResponse.text());
1573
- if (getData.ok && Array.isArray(getData.entries)) {
1574
- for (const entry of getData.entries) {
1575
- existingMap.set(entry.file_name, entry.memory_id);
1576
- }
1577
- }
1578
- }
1579
- let created = 0;
1580
- let updated = 0;
1581
- for (const localFile of localFiles) {
1582
- const memoryId = existingMap.get(localFile.file_name);
1583
- if (memoryId) {
1584
- const updateUrl = `${controlPlaneUrl}/api/v1/agent-config/memory/${memoryId}`;
1585
- const updateBody = {
1586
- content: localFile.content,
1587
- session_id: sessionId
1588
- };
1589
- const updateResponse = await fetchFn(updateUrl, {
1590
- method: "PUT",
1591
- headers: {
1592
- authorization: `Bearer ${token}`,
1593
- "content-type": "application/json"
1594
- },
1595
- body: JSON.stringify(updateBody)
1596
- });
1597
- if (updateResponse.status !== 200) {
1598
- const text = await updateResponse.text();
1599
- throw new Error(
1600
- `PUT /api/v1/agent-config/memory/${memoryId} returned HTTP ${updateResponse.status}: ${text.slice(0, 200)}`
1601
- );
1602
- }
1603
- const updateData = JSON.parse(await updateResponse.text());
1604
- if (!updateData.ok) {
1605
- throw new Error(`PUT /api/v1/agent-config/memory/${memoryId} returned ok=false`);
1606
- }
1607
- updated++;
1608
- } else {
1609
- const createUrl = `${controlPlaneUrl}/api/v1/agent-config/memory/me`;
1610
- const createBody = {
1611
- entry_type: localFile.entry_type,
1612
- file_name: localFile.file_name,
1613
- content: localFile.content,
1614
- session_id: sessionId
1615
- };
1616
- const createResponse = await fetchFn(createUrl, {
1617
- method: "POST",
1618
- headers: {
1619
- authorization: `Bearer ${token}`,
1620
- "content-type": "application/json"
1621
- },
1622
- body: JSON.stringify(createBody)
1623
- });
1624
- if (createResponse.status !== 200 && createResponse.status !== 201) {
1625
- const text = await createResponse.text();
1626
- throw new Error(
1627
- `POST /api/v1/agent-config/memory/me returned HTTP ${createResponse.status}: ${text.slice(0, 200)}`
1628
- );
1629
- }
1630
- const createData = JSON.parse(await createResponse.text());
1631
- if (!createData.ok) {
1632
- throw new Error("POST /api/v1/agent-config/memory/me returned ok=false");
1633
- }
1634
- created++;
1635
- }
1636
- }
1637
- return { pushed: localFiles.length, created, updated };
2286
+ return join11(homedir7(), ".claude", "projects", slug, "memory");
1638
2287
  }
1639
2288
  function isNoopSyncReason(reason) {
1640
2289
  if (!reason) return false;
1641
- return /not set|No auth configured|Failed to obtain auth token/.test(reason);
2290
+ return /not set|No auth configured|Failed to obtain auth token|DISABLED by/.test(reason);
1642
2291
  }
1643
- async function runMemorySync(action, cwd, sessionId, fetchFn = globalThis.fetch) {
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
+ }
1644
2297
  const controlPlaneUrl = process.env["VO_CONTROL_PLANE_URL"];
1645
2298
  if (!controlPlaneUrl) {
1646
2299
  return { synced: false, reason: "VO_CONTROL_PLANE_URL not set \u2014 cloud mode disabled" };
@@ -1657,39 +2310,79 @@ async function runMemorySync(action, cwd, sessionId, fetchFn = globalThis.fetch)
1657
2310
  }
1658
2311
  const memoryDir = getMemoryDir(cwd);
1659
2312
  const baseUrl = controlPlaneUrl.replace(/\/+$/, "");
1660
- try {
1661
- if (action === "pull") {
1662
- const result2 = await pullMemory(baseUrl, token, memoryDir, fetchFn);
1663
- return { synced: true, action: "pull", pulled: result2.pulled, files: result2.files, memory_dir: memoryDir };
1664
- }
1665
- const result = await pushMemory(baseUrl, token, memoryDir, sessionId, fetchFn);
1666
- let bridge = { upserted: 0, failed: 0, failures: [] };
1667
- try {
1668
- const { upsertMemoryFilesAsKnowledge: upsertMemoryFilesAsKnowledge2 } = await Promise.resolve().then(() => (init_memory_knowledge_bridge(), memory_knowledge_bridge_exports));
1669
- bridge = await upsertMemoryFilesAsKnowledge2({
1670
- controlPlaneUrl: baseUrl,
1671
- token,
1672
- memoryDir,
1673
- fetchFn
1674
- });
1675
- } catch (err) {
1676
- bridge = {
1677
- upserted: 0,
1678
- failed: 1,
1679
- failures: [`bridge unavailable: ${err instanceof Error ? err.message : String(err)}`]
1680
- };
1681
- }
2313
+ if (action === "push" && !existsSync8(memoryDir)) {
1682
2314
  return {
1683
2315
  synced: true,
1684
2316
  action: "push",
1685
- pushed: result.pushed,
1686
- created: result.created,
1687
- updated: result.updated,
1688
- memory_dir: memoryDir,
1689
- knowledge_upserted: bridge.upserted,
1690
- knowledge_failed: bridge.failed,
1691
- ...bridge.failed > 0 ? { knowledge_failures: bridge.failures } : {}
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,
2324
+ memory_dir: memoryDir
1692
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
+ });
1693
2386
  } catch (err) {
1694
2387
  const message = err instanceof Error ? err.message : String(err);
1695
2388
  return { synced: false, reason: `Sync failed: ${message}` };
@@ -1711,7 +2404,11 @@ var init_sync_config = __esm({
1711
2404
  "src/tools/memory/sync-config.ts"() {
1712
2405
  "use strict";
1713
2406
  init_common();
1714
- init_safe_memory_file();
2407
+ init_memory_sync_http();
2408
+ init_bounded_sync();
2409
+ init_memory_push_cache();
2410
+ init_sync_lock();
2411
+ init_sync_kill_switch();
1715
2412
  TOOL_NAME22 = "vo_sync_config";
1716
2413
  inputSchema22 = {
1717
2414
  type: "object",
@@ -1729,19 +2426,19 @@ var init_sync_config = __esm({
1729
2426
  required: ["action"],
1730
2427
  additionalProperties: false
1731
2428
  };
1732
- 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.";
1733
2430
  }
1734
2431
  });
1735
2432
 
1736
2433
  // src/cli.ts
1737
- import { homedir as homedir7, hostname } from "node:os";
1738
- import { randomUUID as randomUUID5 } from "node:crypto";
1739
- import { join as join11 } 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";
1740
2437
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
1741
2438
 
1742
2439
  // src/server.ts
1743
2440
  init_common();
1744
- import { randomUUID as randomUUID2 } from "node:crypto";
2441
+ import { randomUUID as randomUUID3 } from "node:crypto";
1745
2442
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
1746
2443
  import {
1747
2444
  CallToolRequestSchema,
@@ -2117,7 +2814,8 @@ async function handleCheckHollowTest(deps, rawInput, signal) {
2117
2814
  synthesized_verdict: synthForEvent,
2118
2815
  consensus_confidence: engineResult.synthesized_verdict.confidence,
2119
2816
  duration_ms: engineResult.duration_ms,
2120
- consensus_engine_version: engineResult.engine_version
2817
+ consensus_engine_version: engineResult.engine_version,
2818
+ ...aggregateEventTokenUsage(engineResult.per_model_verdicts, engineResult.token_usage)
2121
2819
  };
2122
2820
  const payload = {
2123
2821
  verdict: engineResult.synthesized_verdict.verdict,
@@ -2300,7 +2998,8 @@ async function handleVerifyAnswer(deps, rawInput, signal) {
2300
2998
  synthesized_verdict: synthForEvent,
2301
2999
  consensus_confidence: engineResult.synthesized_verdict.confidence,
2302
3000
  duration_ms: engineResult.duration_ms,
2303
- consensus_engine_version: engineResult.engine_version
3001
+ consensus_engine_version: engineResult.engine_version,
3002
+ ...aggregateEventTokenUsage(engineResult.per_model_verdicts, engineResult.token_usage)
2304
3003
  };
2305
3004
  const payload = {
2306
3005
  verdict: engineResult.synthesized_verdict.verdict,
@@ -2309,6 +3008,7 @@ async function handleVerifyAnswer(deps, rawInput, signal) {
2309
3008
  synthesized_verdict: synthForEvent,
2310
3009
  engine_version: engineResult.engine_version,
2311
3010
  degraded: engineResult.degraded,
3011
+ ...engineResult.quorum_failed === true ? { quorum_failed: true } : {},
2312
3012
  gate_type: gateType,
2313
3013
  ...kbResult.error !== null ? { kb_unavailable: true } : {},
2314
3014
  ...kbTruncated > 0 ? { kb_rules_truncated: kbTruncated } : {}
@@ -2565,7 +3265,8 @@ async function handleConsensusJudgment(deps, rawInput, signal) {
2565
3265
  duration_ms: engineResult.duration_ms,
2566
3266
  consensus_engine_version: engineResult.engine_version,
2567
3267
  per_model_verdicts: perModelForEvent,
2568
- synthesized_verdict: synthForEvent
3268
+ synthesized_verdict: synthForEvent,
3269
+ ...aggregateEventTokenUsage(engineResult.per_model_verdicts, engineResult.token_usage)
2569
3270
  };
2570
3271
  const payload = {
2571
3272
  verdict: engineResult.synthesized_verdict.verdict,
@@ -2574,6 +3275,7 @@ async function handleConsensusJudgment(deps, rawInput, signal) {
2574
3275
  synthesized_verdict: synthForEvent,
2575
3276
  engine_version: engineResult.engine_version,
2576
3277
  degraded: engineResult.degraded,
3278
+ ...engineResult.quorum_failed === true ? { quorum_failed: true } : {},
2577
3279
  gate_type: gateType,
2578
3280
  // ─── Consensus-engine feature outputs (additive; 2026-06-13) ─────────────
2579
3281
  // Feature 2 (calibrated-confidence) — ON by default; the engine attaches
@@ -2774,7 +3476,8 @@ async function handleArchitectureReview(deps, rawInput, signal) {
2774
3476
  synthesized_verdict: synthForEvent,
2775
3477
  consensus_confidence: engineResult.synthesized_verdict.confidence,
2776
3478
  duration_ms: engineResult.duration_ms,
2777
- consensus_engine_version: engineResult.engine_version
3479
+ consensus_engine_version: engineResult.engine_version,
3480
+ ...aggregateEventTokenUsage(engineResult.per_model_verdicts, engineResult.token_usage)
2778
3481
  };
2779
3482
  const escalationRequired = engineResult.escalation_required === true || engineResult.escalation_required === void 0 && engineResult.synthesized_verdict.dissent_summary !== null;
2780
3483
  const escalationReason = engineResult.escalation_reason ?? engineResult.synthesized_verdict.dissent_summary ?? "";
@@ -4261,7 +4964,8 @@ Produce the JSON dispatch plan now.`;
4261
4964
  duration_ms: engineResult.duration_ms,
4262
4965
  consensus_engine_version: engineResult.engine_version,
4263
4966
  per_model_verdicts: toEventPerModelVerdicts(engineResult.per_model_verdicts),
4264
- synthesized_verdict: toEventSynthesizedVerdict(engineResult.synthesized_verdict)
4967
+ synthesized_verdict: toEventSynthesizedVerdict(engineResult.synthesized_verdict),
4968
+ ...aggregateEventTokenUsage(engineResult.per_model_verdicts, engineResult.token_usage)
4265
4969
  };
4266
4970
  deps.events.append(enrichedEvent);
4267
4971
  return jsonContent(envelope);
@@ -5135,7 +5839,8 @@ async function handleReviewMerge(deps, rawInput, signal) {
5135
5839
  duration_ms: result.duration_ms,
5136
5840
  consensus_engine_version: result.engine_version,
5137
5841
  per_model_verdicts: perModel,
5138
- synthesized_verdict: synth
5842
+ synthesized_verdict: synth,
5843
+ ...aggregateEventTokenUsage(result.per_model_verdicts, result.token_usage)
5139
5844
  });
5140
5845
  }
5141
5846
 
@@ -6319,12 +7024,12 @@ async function handleHqWhiteboardRead(_deps, rawInput, signal) {
6319
7024
  }
6320
7025
 
6321
7026
  // src/tools/skills/skill-corpus.ts
6322
- import { existsSync as existsSync7, statSync as statSync5 } from "node:fs";
6323
- import { dirname as dirname5, isAbsolute, join as join10, resolve as resolve2 } from "node:path";
7027
+ import { existsSync as existsSync9, statSync as statSync6 } from "node:fs";
7028
+ import { dirname as dirname5, isAbsolute, join as join13, resolve as resolve2 } from "node:path";
6324
7029
 
6325
7030
  // ../skill-registry/src/loader.ts
6326
- import { readdirSync as readdirSync6, readFileSync as readFileSync10, statSync as statSync4 } from "node:fs";
6327
- import { join as join9 } from "node:path";
7031
+ import { readdirSync as readdirSync6, readFileSync as readFileSync14, statSync as statSync5 } from "node:fs";
7032
+ import { join as join12 } from "node:path";
6328
7033
  var InvalidSkillFrontmatterError = class extends Error {
6329
7034
  constructor(skillFile, reason) {
6330
7035
  super(`Invalid frontmatter in ${skillFile}: ${reason}`);
@@ -6377,18 +7082,18 @@ function loadSkillsFromDir(skillsDir) {
6377
7082
  const entries = readdirSync6(skillsDir);
6378
7083
  const skills = [];
6379
7084
  for (const entry of entries) {
6380
- const entryPath = join9(skillsDir, entry);
7085
+ const entryPath = join12(skillsDir, entry);
6381
7086
  let stat;
6382
7087
  try {
6383
- stat = statSync4(entryPath);
7088
+ stat = statSync5(entryPath);
6384
7089
  } catch {
6385
7090
  continue;
6386
7091
  }
6387
7092
  if (!stat.isDirectory()) continue;
6388
- const skillFile = join9(entryPath, "SKILL.md");
7093
+ const skillFile = join12(entryPath, "SKILL.md");
6389
7094
  let raw;
6390
7095
  try {
6391
- raw = readFileSync10(skillFile, "utf8");
7096
+ raw = readFileSync14(skillFile, "utf8");
6392
7097
  } catch {
6393
7098
  continue;
6394
7099
  }
@@ -6430,12 +7135,12 @@ function resolveSkillsDir(env = process.env, startDir = process.cwd()) {
6430
7135
  const override = env.VO_SKILLS_DIR;
6431
7136
  if (typeof override === "string" && override.length > 0) {
6432
7137
  const abs = isAbsolute(override) ? override : resolve2(startDir, override);
6433
- return existsSync7(abs) && statSync5(abs).isDirectory() ? abs : null;
7138
+ return existsSync9(abs) && statSync6(abs).isDirectory() ? abs : null;
6434
7139
  }
6435
7140
  let dir = resolve2(startDir);
6436
7141
  for (let i = 0; i < MAX_WALK_UP_LEVELS; i += 1) {
6437
- const candidate = join10(dir, ".claude", "skills");
6438
- if (existsSync7(candidate) && statSync5(candidate).isDirectory()) return candidate;
7142
+ const candidate = join13(dir, ".claude", "skills");
7143
+ if (existsSync9(candidate) && statSync6(candidate).isDirectory()) return candidate;
6439
7144
  const parent = dirname5(dir);
6440
7145
  if (parent === dir) break;
6441
7146
  dir = parent;
@@ -6746,7 +7451,7 @@ function buildToolRegistry() {
6746
7451
  };
6747
7452
  }
6748
7453
  function createServer(options) {
6749
- const sessionId = options.sessionId ?? randomUUID2();
7454
+ const sessionId = options.sessionId ?? randomUUID3();
6750
7455
  const mode = createLocalMode();
6751
7456
  const now = options.now ?? (() => /* @__PURE__ */ new Date());
6752
7457
  const server = new Server(
@@ -6795,8 +7500,8 @@ function createServer(options) {
6795
7500
  }
6796
7501
 
6797
7502
  // src/cache/sqlite-cache.ts
6798
- import { createHash as createHash3 } from "node:crypto";
6799
- import { chmodSync as chmodSync3, mkdirSync as mkdirSync6 } from "node:fs";
7503
+ import { createHash as createHash4 } from "node:crypto";
7504
+ import { chmodSync as chmodSync3, mkdirSync as mkdirSync7 } from "node:fs";
6800
7505
  import { dirname as dirname6 } from "node:path";
6801
7506
  import { DatabaseSync } from "node:sqlite";
6802
7507
 
@@ -6842,7 +7547,7 @@ function normalizeString(s) {
6842
7547
  function createSqliteCache(options) {
6843
7548
  const fileBacked = options.dbPath !== ":memory:";
6844
7549
  if (fileBacked) {
6845
- mkdirSync6(dirname6(options.dbPath), { recursive: true, mode: 448 });
7550
+ mkdirSync7(dirname6(options.dbPath), { recursive: true, mode: 448 });
6846
7551
  }
6847
7552
  const versionNamespace = options.cacheVersionNamespace ?? "";
6848
7553
  const db = new DatabaseSync(options.dbPath);
@@ -6873,7 +7578,7 @@ function createSqliteCache(options) {
6873
7578
  return {
6874
7579
  keyFor(toolName, input, opts) {
6875
7580
  const canonical = canonicalize(input, opts);
6876
- const hash = createHash3("sha256");
7581
+ const hash = createHash4("sha256");
6877
7582
  if (versionNamespace.length > 0) {
6878
7583
  hash.update(versionNamespace);
6879
7584
  hash.update("|");
@@ -6968,7 +7673,7 @@ function createStubRatchetClient() {
6968
7673
  let m;
6969
7674
  while ((m = pat.regex.exec(req.source)) !== null) {
6970
7675
  findings.push({
6971
- line_excerpt: clip(m[0], 80),
7676
+ line_excerpt: clip2(m[0], 80),
6972
7677
  severity: pat.severity,
6973
7678
  code: pat.code,
6974
7679
  message: pat.message
@@ -7000,7 +7705,7 @@ function createStubRatchetClient() {
7000
7705
  }
7001
7706
  };
7002
7707
  }
7003
- function clip(s, n) {
7708
+ function clip2(s, n) {
7004
7709
  return s.length <= n ? s : s.slice(0, n) + "\u2026";
7005
7710
  }
7006
7711
  function buildSummary2(args) {
@@ -7011,7 +7716,7 @@ function buildSummary2(args) {
7011
7716
  // src/consensus/engine-client.ts
7012
7717
  init_events_writer();
7013
7718
  init_common();
7014
- import { randomUUID as randomUUID3 } from "node:crypto";
7719
+ import { randomUUID as randomUUID4 } from "node:crypto";
7015
7720
 
7016
7721
  // src/consensus/null-client.ts
7017
7722
  var NULL_CLIENT_DEFAULT_REASON = "consensus-engine-package-pending";
@@ -7140,6 +7845,14 @@ function shadowEnabled(env) {
7140
7845
  const norm = raw.trim().toLowerCase();
7141
7846
  return !(norm === "0" || norm === "false" || norm === "no" || norm === "off" || norm === "");
7142
7847
  }
7848
+ var MIN_RESPONDERS_ENV_VAR = "VO_CONSENSUS_MIN_RESPONDERS";
7849
+ function resolveMinResponders(env) {
7850
+ const raw = (env ?? {})[MIN_RESPONDERS_ENV_VAR];
7851
+ if (raw === void 0 || raw.trim() === "") return 2;
7852
+ const parsed = Number.parseInt(raw.trim(), 10);
7853
+ if (!Number.isFinite(parsed) || parsed <= 0) return void 0;
7854
+ return parsed;
7855
+ }
7143
7856
  function mapShadowSynthesis(s) {
7144
7857
  if (s === void 0) return void 0;
7145
7858
  return {
@@ -7289,9 +8002,11 @@ function createEngineConsensusClient(options) {
7289
8002
  ...options.agreement_gate_enabled !== void 0 ? { configEnabled: options.agreement_gate_enabled } : {},
7290
8003
  ...options.env !== void 0 ? { env: options.env } : {}
7291
8004
  });
8005
+ const minResponders = resolveMinResponders(options.env);
7292
8006
  const engineOptions = {
7293
8007
  panel,
7294
8008
  ...options.per_model_timeout_ms !== void 0 ? { per_model_timeout_ms: options.per_model_timeout_ms } : {},
8009
+ ...minResponders !== void 0 ? { min_responders: minResponders } : {},
7295
8010
  ...agreementGate !== void 0 ? { agreement_gate: agreementGate } : {},
7296
8011
  // Stage A7-shadow: run the adaptive verdict alongside the live one for grading.
7297
8012
  // Cheap (pure log-odds over already-fetched verdicts; no extra model calls),
@@ -7344,8 +8059,13 @@ function createEngineConsensusClient(options) {
7344
8059
  synthesized_verdict: response.synthesized_verdict,
7345
8060
  per_model_verdicts: response.per_model_verdicts,
7346
8061
  degraded: response.degraded,
8062
+ ...response.quorum_failed === true ? { quorum_failed: true } : {},
7347
8063
  duration_ms: response.duration_ms,
7348
8064
  engine_version: response.engine_version,
8065
+ // Cumulative cross-round inference usage (B44-3). Absent when no panel
8066
+ // member reported usage; forwarded verbatim — the aggregator prefers it
8067
+ // over summing final-round verdicts (which under-reports deliberation).
8068
+ ...response.token_usage !== void 0 ? { token_usage: response.token_usage } : {},
7349
8069
  // Phase 2 Lane D-1 — forward escalation signal when present. The
7350
8070
  // source-grounded layer's own escalation (from the citation grade)
7351
8071
  // takes precedence when set, else the synthesizer's.
@@ -7471,7 +8191,7 @@ function tryCreateEngineConsensusClientFromEnv(options = {}) {
7471
8191
  }
7472
8192
 
7473
8193
  // src/consensus/moat-client.ts
7474
- import { randomUUID as randomUUID4 } from "node:crypto";
8194
+ import { randomUUID as randomUUID5 } from "node:crypto";
7475
8195
 
7476
8196
  // src/consensus/client.ts
7477
8197
  var CANCELLED_REASON = "cancelled";
@@ -7523,7 +8243,7 @@ function createMoatConsensusClient(opts) {
7523
8243
  request.signal?.addEventListener("abort", onAbort, { once: true });
7524
8244
  const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
7525
8245
  const body = JSON.stringify({
7526
- task_id: randomUUID4(),
8246
+ task_id: randomUUID5(),
7527
8247
  gate_type: request.gate_type,
7528
8248
  excerpt: request.prompt,
7529
8249
  ...request.system_prompt ? { question: request.system_prompt } : {},
@@ -7844,7 +8564,7 @@ init_common();
7844
8564
  function defaultCacheDbPath() {
7845
8565
  const env = process.env["VO_MCP_DB_PATH"];
7846
8566
  if (env && env.length > 0) return env;
7847
- return join11(homedir7(), ".claude", "vo-mcp-cache.db");
8567
+ return join14(homedir8(), ".claude", "vo-mcp-cache.db");
7848
8568
  }
7849
8569
  async function probeEngineVersion() {
7850
8570
  try {
@@ -7971,7 +8691,7 @@ async function main() {
7971
8691
  }
7972
8692
  if (process.argv[2] === "login") {
7973
8693
  const controlPlaneUrl = process.env["VO_CONTROL_PLANE_URL"]?.trim();
7974
- const credentialLabel = `vo-mcp-cli@${hostname()}`.slice(0, 200);
8694
+ const credentialLabel = `vo-mcp-cli@${hostname2()}`.slice(0, 200);
7975
8695
  runLogin(
7976
8696
  controlPlaneUrl ? {
7977
8697
  exchange: (refreshToken, apiKey) => exchangeForVoCredential({ refreshToken, apiKey, controlPlaneUrl, label: credentialLabel })
@@ -7987,26 +8707,29 @@ if (process.argv[2] === "login") {
7987
8707
  } else if (process.argv[2] === "sync") {
7988
8708
  const action = process.argv[3];
7989
8709
  if (action !== "push" && action !== "pull") {
7990
- console.error("[vo-mcp] usage: vo-mcp sync <push|pull> [--cwd <path>]");
8710
+ console.error("[vo-mcp] usage: vo-mcp sync <push|pull> [--cwd <path>] [--lock-wait-ms <n>]");
7991
8711
  process.exit(2);
7992
8712
  }
7993
8713
  const cwdFlag = process.argv.indexOf("--cwd");
7994
8714
  const cwd = cwdFlag >= 0 && typeof process.argv[cwdFlag + 1] === "string" ? process.argv[cwdFlag + 1] : process.cwd();
7995
- const sessionId = randomUUID5();
8715
+ const waitFlag = process.argv.indexOf("--lock-wait-ms");
8716
+ const parsedWait = waitFlag >= 0 ? Number(process.argv[waitFlag + 1]) : Number.NaN;
8717
+ const lockOptions = Number.isFinite(parsedWait) && parsedWait >= 0 ? { waitMs: parsedWait } : {};
8718
+ const sessionId = randomUUID6();
7996
8719
  const appendSyncLog = async (line) => {
7997
8720
  try {
7998
- const { appendFileSync: appendFileSync2, mkdirSync: mkdirSync7 } = await import("node:fs");
7999
- const { join: join12 } = await import("node:path");
8000
- const { homedir: homedir8 } = await import("node:os");
8001
- const dir = join12(homedir8(), ".claude");
8002
- mkdirSync7(dir, { recursive: true });
8003
- appendFileSync2(join12(dir, "vo-mcp-sync.log"), `${line}
8721
+ const { appendFileSync: appendFileSync2, mkdirSync: mkdirSync8 } = await import("node:fs");
8722
+ const { join: join15 } = await import("node:path");
8723
+ const { homedir: homedir9 } = await import("node:os");
8724
+ const dir = join15(homedir9(), ".claude");
8725
+ mkdirSync8(dir, { recursive: true });
8726
+ appendFileSync2(join15(dir, "vo-mcp-sync.log"), `${line}
8004
8727
  `, "utf8");
8005
8728
  } catch {
8006
8729
  }
8007
8730
  };
8008
8731
  Promise.resolve().then(() => (init_sync_config(), sync_config_exports)).then(async ({ runMemorySync: runMemorySync2, isNoopSyncReason: isNoopSyncReason2 }) => {
8009
- const r = await runMemorySync2(action, cwd, sessionId);
8732
+ const r = await runMemorySync2(action, cwd, sessionId, void 0, lockOptions);
8010
8733
  const stamp = `${sessionId} ${action}`;
8011
8734
  if (r.synced) {
8012
8735
  console.error(`[vo-mcp] sync ${action} ok: ${JSON.stringify(r)}`);