@algosuite/vo-mcp 0.2.0-beta.29 → 0.2.0-beta.30
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/autostart-cli.js +62 -46
- package/dist/autostart-cli.js.map +2 -2
- package/dist/cli.js +887 -223
- package/dist/cli.js.map +4 -4
- package/dist/index.js +826 -192
- package/dist/index.js.map +4 -4
- package/dist/install-cli.js +56 -42
- package/dist/install-cli.js.map +3 -3
- package/dist/runner-cli.js +568 -308
- package/dist/runner-cli.js.map +4 -4
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1399,13 +1399,713 @@ var init_safe_memory_file = __esm({
|
|
|
1399
1399
|
}
|
|
1400
1400
|
});
|
|
1401
1401
|
|
|
1402
|
+
// src/tools/memory/sync-lock-liveness.ts
|
|
1403
|
+
import { statSync as statSync4, readFileSync as readFileSync8 } from "node:fs";
|
|
1404
|
+
function defaultIsProcessAlive(pid) {
|
|
1405
|
+
try {
|
|
1406
|
+
process.kill(pid, 0);
|
|
1407
|
+
return true;
|
|
1408
|
+
} catch (err) {
|
|
1409
|
+
return err.code === "EPERM";
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
function toPayload(parsed) {
|
|
1413
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return null;
|
|
1414
|
+
const record = parsed;
|
|
1415
|
+
const token = record["token"];
|
|
1416
|
+
const host = record["hostname"];
|
|
1417
|
+
if (typeof token !== "string" || token.length === 0) return null;
|
|
1418
|
+
const pid = record["pid"];
|
|
1419
|
+
const acquiredAtMs = record["acquiredAtMs"];
|
|
1420
|
+
return {
|
|
1421
|
+
pid: typeof pid === "number" && Number.isInteger(pid) && pid > 0 ? pid : 0,
|
|
1422
|
+
hostname: typeof host === "string" ? host : "",
|
|
1423
|
+
sessionId: typeof record["sessionId"] === "string" ? record["sessionId"] : null,
|
|
1424
|
+
token,
|
|
1425
|
+
acquiredAt: typeof record["acquiredAt"] === "string" ? record["acquiredAt"] : "",
|
|
1426
|
+
acquiredAtMs: typeof acquiredAtMs === "number" && Number.isFinite(acquiredAtMs) ? acquiredAtMs : Number.NaN
|
|
1427
|
+
};
|
|
1428
|
+
}
|
|
1429
|
+
function readLockRecord(path3) {
|
|
1430
|
+
let raw;
|
|
1431
|
+
try {
|
|
1432
|
+
raw = readFileSync8(path3, "utf8");
|
|
1433
|
+
} catch {
|
|
1434
|
+
return null;
|
|
1435
|
+
}
|
|
1436
|
+
try {
|
|
1437
|
+
return { raw, payload: toPayload(JSON.parse(raw)) };
|
|
1438
|
+
} catch {
|
|
1439
|
+
return { raw, payload: null };
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
function lockAgeMs(record, path3, nowMs) {
|
|
1443
|
+
let startedMs = Number.NaN;
|
|
1444
|
+
if (record.payload) {
|
|
1445
|
+
if (Number.isFinite(record.payload.acquiredAtMs)) {
|
|
1446
|
+
startedMs = record.payload.acquiredAtMs;
|
|
1447
|
+
} else if (record.payload.acquiredAt) {
|
|
1448
|
+
startedMs = Date.parse(record.payload.acquiredAt);
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
if (!Number.isFinite(startedMs)) {
|
|
1452
|
+
try {
|
|
1453
|
+
startedMs = statSync4(path3).mtimeMs;
|
|
1454
|
+
} catch {
|
|
1455
|
+
return null;
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
const age = nowMs - startedMs;
|
|
1459
|
+
return Number.isFinite(age) && age >= 0 ? age : null;
|
|
1460
|
+
}
|
|
1461
|
+
function classifyHolderLiveness(record, isProcessAlive, thisHost) {
|
|
1462
|
+
const payload = record.payload;
|
|
1463
|
+
if (payload === null) return "unknown";
|
|
1464
|
+
if (payload.pid <= 0) return "unknown";
|
|
1465
|
+
if (thisHost.length === 0) return "unknown";
|
|
1466
|
+
if (payload.hostname !== thisHost) return "unknown";
|
|
1467
|
+
return isProcessAlive(payload.pid) ? "alive" : "dead";
|
|
1468
|
+
}
|
|
1469
|
+
function isLockAbandoned(record, ageMs, ttlMs, isProcessAlive, thisHost) {
|
|
1470
|
+
const liveness = classifyHolderLiveness(record, isProcessAlive, thisHost);
|
|
1471
|
+
if (liveness === "alive") return false;
|
|
1472
|
+
if (liveness === "dead") return true;
|
|
1473
|
+
return ageMs !== null && ageMs > ttlMs;
|
|
1474
|
+
}
|
|
1475
|
+
var init_sync_lock_liveness = __esm({
|
|
1476
|
+
"src/tools/memory/sync-lock-liveness.ts"() {
|
|
1477
|
+
"use strict";
|
|
1478
|
+
}
|
|
1479
|
+
});
|
|
1480
|
+
|
|
1481
|
+
// src/tools/memory/sync-lock.ts
|
|
1482
|
+
import { closeSync as closeSync2, mkdirSync as mkdirSync5, openSync as openSync3, readFileSync as readFileSync9, unlinkSync as unlinkSync2, writeFileSync as writeFileSync4 } from "node:fs";
|
|
1483
|
+
import { hostname } from "node:os";
|
|
1484
|
+
import { join as join8 } from "node:path";
|
|
1485
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
1486
|
+
function positiveOr(value, fallback) {
|
|
1487
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : fallback;
|
|
1488
|
+
}
|
|
1489
|
+
function createExclusive2(path3, contents) {
|
|
1490
|
+
let fd;
|
|
1491
|
+
try {
|
|
1492
|
+
fd = openSync3(path3, "wx");
|
|
1493
|
+
} catch (err) {
|
|
1494
|
+
const code = err.code;
|
|
1495
|
+
return { ok: false, exists: code === "EEXIST", message: err instanceof Error ? err.message : String(err) };
|
|
1496
|
+
}
|
|
1497
|
+
try {
|
|
1498
|
+
writeFileSync4(fd, contents, "utf8");
|
|
1499
|
+
} catch (err) {
|
|
1500
|
+
closeSync2(fd);
|
|
1501
|
+
try {
|
|
1502
|
+
unlinkSync2(path3);
|
|
1503
|
+
} catch {
|
|
1504
|
+
}
|
|
1505
|
+
return { ok: false, exists: false, message: err instanceof Error ? err.message : String(err) };
|
|
1506
|
+
}
|
|
1507
|
+
closeSync2(fd);
|
|
1508
|
+
return { ok: true };
|
|
1509
|
+
}
|
|
1510
|
+
function removeAbandoned(path3, expectedRaw) {
|
|
1511
|
+
let current;
|
|
1512
|
+
try {
|
|
1513
|
+
current = readFileSync9(path3, "utf8");
|
|
1514
|
+
} catch {
|
|
1515
|
+
return;
|
|
1516
|
+
}
|
|
1517
|
+
if (current !== expectedRaw) return;
|
|
1518
|
+
try {
|
|
1519
|
+
unlinkSync2(path3);
|
|
1520
|
+
} catch {
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
function makeRelease(path3, token) {
|
|
1524
|
+
let released = false;
|
|
1525
|
+
return () => {
|
|
1526
|
+
if (released) return;
|
|
1527
|
+
released = true;
|
|
1528
|
+
let raw;
|
|
1529
|
+
try {
|
|
1530
|
+
raw = readFileSync9(path3, "utf8");
|
|
1531
|
+
} catch {
|
|
1532
|
+
return;
|
|
1533
|
+
}
|
|
1534
|
+
let stillOurs;
|
|
1535
|
+
try {
|
|
1536
|
+
stillOurs = toPayload(JSON.parse(raw))?.token === token;
|
|
1537
|
+
} catch {
|
|
1538
|
+
stillOurs = false;
|
|
1539
|
+
}
|
|
1540
|
+
if (!stillOurs) return;
|
|
1541
|
+
try {
|
|
1542
|
+
unlinkSync2(path3);
|
|
1543
|
+
} catch {
|
|
1544
|
+
}
|
|
1545
|
+
};
|
|
1546
|
+
}
|
|
1547
|
+
function describeHolder(record) {
|
|
1548
|
+
const payload = record?.payload;
|
|
1549
|
+
if (!payload) return "an unreadable lock file";
|
|
1550
|
+
return `pid ${payload.pid} on ${payload.hostname || "(unknown host)"} (session ${payload.sessionId ?? "unknown"}, held since ${payload.acquiredAt || "unknown"})`;
|
|
1551
|
+
}
|
|
1552
|
+
async function acquireMemorySyncLock(options) {
|
|
1553
|
+
const waitMs = positiveOr(options.waitMs, DEFAULT_LOCK_WAIT_MS);
|
|
1554
|
+
const ttlMs = positiveOr(options.ttlMs, DEFAULT_LOCK_TTL_MS);
|
|
1555
|
+
const now = options.now ?? Date.now;
|
|
1556
|
+
const sleep = options.sleep ?? ((ms) => new Promise((resolve3) => {
|
|
1557
|
+
setTimeout(resolve3, ms);
|
|
1558
|
+
}));
|
|
1559
|
+
const isProcessAlive = options.isProcessAlive ?? defaultIsProcessAlive;
|
|
1560
|
+
const thisHost = hostname();
|
|
1561
|
+
const path3 = join8(options.memoryDir, MEMORY_SYNC_LOCK_FILE);
|
|
1562
|
+
if (options.createDir === true) mkdirSync5(options.memoryDir, { recursive: true });
|
|
1563
|
+
const deadline = now() + waitMs;
|
|
1564
|
+
let backoffMs = INITIAL_BACKOFF_MS;
|
|
1565
|
+
let tookOverFrom = null;
|
|
1566
|
+
let holderDescription = "another session";
|
|
1567
|
+
for (; ; ) {
|
|
1568
|
+
const acquiredAtMs = now();
|
|
1569
|
+
const payload = {
|
|
1570
|
+
pid: process.pid,
|
|
1571
|
+
hostname: thisHost,
|
|
1572
|
+
sessionId: options.sessionId ?? null,
|
|
1573
|
+
token: randomUUID2(),
|
|
1574
|
+
acquiredAt: new Date(acquiredAtMs).toISOString(),
|
|
1575
|
+
acquiredAtMs
|
|
1576
|
+
};
|
|
1577
|
+
const created = createExclusive2(path3, `${JSON.stringify(payload, null, 2)}
|
|
1578
|
+
`);
|
|
1579
|
+
if (created.ok) {
|
|
1580
|
+
return { path: path3, payload, tookOverFrom, release: makeRelease(path3, payload.token) };
|
|
1581
|
+
}
|
|
1582
|
+
if (!created.exists) {
|
|
1583
|
+
throw new Error(
|
|
1584
|
+
`memory sync lock ${path3} could not be created (${created.message}) \u2014 refusing to sync without exclusion`
|
|
1585
|
+
);
|
|
1586
|
+
}
|
|
1587
|
+
const record = readLockRecord(path3);
|
|
1588
|
+
let reclaimed = false;
|
|
1589
|
+
if (record) {
|
|
1590
|
+
holderDescription = describeHolder(record);
|
|
1591
|
+
const age = lockAgeMs(record, path3, now());
|
|
1592
|
+
if (isLockAbandoned(record, age, ttlMs, isProcessAlive, thisHost)) {
|
|
1593
|
+
tookOverFrom = record.payload;
|
|
1594
|
+
removeAbandoned(path3, record.raw);
|
|
1595
|
+
reclaimed = true;
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
if (now() >= deadline) {
|
|
1599
|
+
throw new Error(
|
|
1600
|
+
`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.`
|
|
1601
|
+
);
|
|
1602
|
+
}
|
|
1603
|
+
if (reclaimed) backoffMs = INITIAL_BACKOFF_MS;
|
|
1604
|
+
await sleep(Math.max(1, Math.min(backoffMs, deadline - now())));
|
|
1605
|
+
if (!reclaimed) backoffMs = Math.min(MAX_BACKOFF_MS, Math.ceil(backoffMs * BACKOFF_FACTOR));
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
async function withMemorySyncLock(options, fn) {
|
|
1609
|
+
const handle = await acquireMemorySyncLock(options);
|
|
1610
|
+
try {
|
|
1611
|
+
return await fn(handle);
|
|
1612
|
+
} finally {
|
|
1613
|
+
handle.release();
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
var MEMORY_SYNC_LOCK_FILE, DEFAULT_LOCK_TTL_MS, DEFAULT_LOCK_WAIT_MS, INITIAL_BACKOFF_MS, MAX_BACKOFF_MS, BACKOFF_FACTOR;
|
|
1617
|
+
var init_sync_lock = __esm({
|
|
1618
|
+
"src/tools/memory/sync-lock.ts"() {
|
|
1619
|
+
"use strict";
|
|
1620
|
+
init_sync_lock_liveness();
|
|
1621
|
+
init_sync_lock_liveness();
|
|
1622
|
+
MEMORY_SYNC_LOCK_FILE = ".memory-sync.lock";
|
|
1623
|
+
DEFAULT_LOCK_TTL_MS = 15 * 6e4;
|
|
1624
|
+
DEFAULT_LOCK_WAIT_MS = 1e4;
|
|
1625
|
+
INITIAL_BACKOFF_MS = 25;
|
|
1626
|
+
MAX_BACKOFF_MS = 500;
|
|
1627
|
+
BACKOFF_FACTOR = 1.6;
|
|
1628
|
+
}
|
|
1629
|
+
});
|
|
1630
|
+
|
|
1631
|
+
// src/tools/memory/memory-index-merge.ts
|
|
1632
|
+
function isMemoryIndexFile(fileName) {
|
|
1633
|
+
return fileName.toUpperCase() === MEMORY_INDEX_FILE.toUpperCase();
|
|
1634
|
+
}
|
|
1635
|
+
function indexRowKey(line) {
|
|
1636
|
+
const match = INDEX_ROW_RE.exec(line);
|
|
1637
|
+
if (!match) return null;
|
|
1638
|
+
let target = match[1].trim();
|
|
1639
|
+
if (target.startsWith("<") && target.endsWith(">")) target = target.slice(1, -1).trim();
|
|
1640
|
+
target = target.replace(/\s+(["'])[\s\S]*\1$/, "").trim();
|
|
1641
|
+
if (!/^[a-z][a-z0-9+.-]*:\/\//i.test(target)) {
|
|
1642
|
+
target = target.replace(/\\/g, "/").replace(/\/{2,}/g, "/");
|
|
1643
|
+
target = target.replace(/^(?:\.\/)+/, "");
|
|
1644
|
+
}
|
|
1645
|
+
return target.length > 0 ? target.toLowerCase() : null;
|
|
1646
|
+
}
|
|
1647
|
+
function mergeMemoryIndex(localContent, cloudContent) {
|
|
1648
|
+
if (typeof cloudContent !== "string" || cloudContent.trim().length === 0) {
|
|
1649
|
+
return { content: localContent, addedFromCloud: [] };
|
|
1650
|
+
}
|
|
1651
|
+
const eol = localContent.includes("\r\n") ? "\r\n" : "\n";
|
|
1652
|
+
const localLines = localContent.split(/\r?\n/);
|
|
1653
|
+
const localKeys = /* @__PURE__ */ new Set();
|
|
1654
|
+
let lastLocalRowIndex = -1;
|
|
1655
|
+
for (let i = 0; i < localLines.length; i++) {
|
|
1656
|
+
const key = indexRowKey(localLines[i]);
|
|
1657
|
+
if (key === null) continue;
|
|
1658
|
+
localKeys.add(key);
|
|
1659
|
+
lastLocalRowIndex = i;
|
|
1660
|
+
}
|
|
1661
|
+
const addedFromCloud = [];
|
|
1662
|
+
const seenCloudKeys = /* @__PURE__ */ new Set();
|
|
1663
|
+
for (const rawLine of cloudContent.split(/\r?\n/)) {
|
|
1664
|
+
const key = indexRowKey(rawLine);
|
|
1665
|
+
if (key === null) continue;
|
|
1666
|
+
if (localKeys.has(key) || seenCloudKeys.has(key)) continue;
|
|
1667
|
+
seenCloudKeys.add(key);
|
|
1668
|
+
addedFromCloud.push(rawLine.replace(/\r$/, ""));
|
|
1669
|
+
}
|
|
1670
|
+
if (addedFromCloud.length === 0) {
|
|
1671
|
+
return { content: localContent, addedFromCloud: [] };
|
|
1672
|
+
}
|
|
1673
|
+
const merged = lastLocalRowIndex >= 0 ? [...localLines.slice(0, lastLocalRowIndex + 1), ...addedFromCloud, ...localLines.slice(lastLocalRowIndex + 1)] : [...localLines, ...addedFromCloud];
|
|
1674
|
+
return { content: merged.join(eol), addedFromCloud };
|
|
1675
|
+
}
|
|
1676
|
+
var MEMORY_INDEX_FILE, INDEX_ROW_RE;
|
|
1677
|
+
var init_memory_index_merge = __esm({
|
|
1678
|
+
"src/tools/memory/memory-index-merge.ts"() {
|
|
1679
|
+
"use strict";
|
|
1680
|
+
MEMORY_INDEX_FILE = "MEMORY.md";
|
|
1681
|
+
INDEX_ROW_RE = /^\s*[-*]\s+\[[^\]]*\]\(([^)]+)\)/;
|
|
1682
|
+
}
|
|
1683
|
+
});
|
|
1684
|
+
|
|
1685
|
+
// src/tools/memory/bounded-sync.ts
|
|
1686
|
+
function createSyncDeadline(budgetMs = SYNC_DEADLINE_MS, now = Date.now) {
|
|
1687
|
+
const startedAt = now();
|
|
1688
|
+
return {
|
|
1689
|
+
check() {
|
|
1690
|
+
const elapsed = now() - startedAt;
|
|
1691
|
+
if (elapsed > budgetMs) throw new SyncDeadlineExceededError(elapsed, budgetMs);
|
|
1692
|
+
},
|
|
1693
|
+
remainingMs() {
|
|
1694
|
+
return Math.max(0, budgetMs - (now() - startedAt));
|
|
1695
|
+
}
|
|
1696
|
+
};
|
|
1697
|
+
}
|
|
1698
|
+
async function withRequestTimeout(url, run, budgetMs = REQUEST_TIMEOUT_MS) {
|
|
1699
|
+
let timer;
|
|
1700
|
+
try {
|
|
1701
|
+
return await Promise.race([
|
|
1702
|
+
run(),
|
|
1703
|
+
new Promise((_resolve, reject) => {
|
|
1704
|
+
timer = setTimeout(() => reject(new RequestTimeoutError(url, budgetMs)), budgetMs);
|
|
1705
|
+
timer.unref?.();
|
|
1706
|
+
})
|
|
1707
|
+
]);
|
|
1708
|
+
} finally {
|
|
1709
|
+
if (timer) clearTimeout(timer);
|
|
1710
|
+
}
|
|
1711
|
+
}
|
|
1712
|
+
async function mapWithConcurrency(items, limit, fn) {
|
|
1713
|
+
const results = new Array(items.length);
|
|
1714
|
+
const width = Math.max(1, Math.min(limit, items.length));
|
|
1715
|
+
let next = 0;
|
|
1716
|
+
async function worker() {
|
|
1717
|
+
for (; ; ) {
|
|
1718
|
+
const index = next++;
|
|
1719
|
+
if (index >= items.length) return;
|
|
1720
|
+
try {
|
|
1721
|
+
results[index] = { ok: true, value: await fn(items[index], index) };
|
|
1722
|
+
} catch (error) {
|
|
1723
|
+
results[index] = { ok: false, error };
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
}
|
|
1727
|
+
await Promise.all(Array.from({ length: width }, () => worker()));
|
|
1728
|
+
return results;
|
|
1729
|
+
}
|
|
1730
|
+
var REQUEST_TIMEOUT_MS, SYNC_DEADLINE_MS, PUSH_CONCURRENCY, SyncDeadlineExceededError, RequestTimeoutError;
|
|
1731
|
+
var init_bounded_sync = __esm({
|
|
1732
|
+
"src/tools/memory/bounded-sync.ts"() {
|
|
1733
|
+
"use strict";
|
|
1734
|
+
REQUEST_TIMEOUT_MS = 15e3;
|
|
1735
|
+
SYNC_DEADLINE_MS = 12e4;
|
|
1736
|
+
PUSH_CONCURRENCY = 6;
|
|
1737
|
+
SyncDeadlineExceededError = class extends Error {
|
|
1738
|
+
constructor(elapsedMs, budgetMs) {
|
|
1739
|
+
super(
|
|
1740
|
+
`memory sync exceeded its ${budgetMs}ms deadline after ${elapsedMs}ms \u2014 aborting so the lock is released instead of held indefinitely`
|
|
1741
|
+
);
|
|
1742
|
+
this.name = "SyncDeadlineExceededError";
|
|
1743
|
+
}
|
|
1744
|
+
};
|
|
1745
|
+
RequestTimeoutError = class extends Error {
|
|
1746
|
+
constructor(url, budgetMs) {
|
|
1747
|
+
super(`memory sync request to ${url} exceeded ${budgetMs}ms`);
|
|
1748
|
+
this.name = "RequestTimeoutError";
|
|
1749
|
+
}
|
|
1750
|
+
};
|
|
1751
|
+
}
|
|
1752
|
+
});
|
|
1753
|
+
|
|
1754
|
+
// src/tools/memory/memory-push-cache.ts
|
|
1755
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
1756
|
+
import { readFileSync as readFileSync10, writeFileSync as writeFileSync5 } from "node:fs";
|
|
1757
|
+
import { join as join9 } from "node:path";
|
|
1758
|
+
function sha256(content) {
|
|
1759
|
+
return createHash3("sha256").update(content, "utf8").digest("hex");
|
|
1760
|
+
}
|
|
1761
|
+
function statePath(memoryDir) {
|
|
1762
|
+
return join9(memoryDir, MEMORY_SYNC_STATE_FILE);
|
|
1763
|
+
}
|
|
1764
|
+
function readPushCache(memoryDir, controlPlaneUrl) {
|
|
1765
|
+
const empty = { controlPlaneUrl, entries: /* @__PURE__ */ new Map(), knowledgeSweptAtMs: null };
|
|
1766
|
+
let raw;
|
|
1767
|
+
try {
|
|
1768
|
+
raw = readFileSync10(statePath(memoryDir), "utf8");
|
|
1769
|
+
} catch {
|
|
1770
|
+
return empty;
|
|
1771
|
+
}
|
|
1772
|
+
let parsed;
|
|
1773
|
+
try {
|
|
1774
|
+
parsed = JSON.parse(raw);
|
|
1775
|
+
} catch {
|
|
1776
|
+
return empty;
|
|
1777
|
+
}
|
|
1778
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return empty;
|
|
1779
|
+
const obj = parsed;
|
|
1780
|
+
if (obj["version"] !== STATE_VERSION) return empty;
|
|
1781
|
+
if (obj["controlPlaneUrl"] !== controlPlaneUrl) return empty;
|
|
1782
|
+
const files = obj["entries"];
|
|
1783
|
+
if (typeof files !== "object" || files === null || Array.isArray(files)) return empty;
|
|
1784
|
+
const entries = /* @__PURE__ */ new Map();
|
|
1785
|
+
for (const [name, value] of Object.entries(files)) {
|
|
1786
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) continue;
|
|
1787
|
+
const row = value;
|
|
1788
|
+
const memoryHash = typeof row["memoryHash"] === "string" ? row["memoryHash"] : void 0;
|
|
1789
|
+
const knowledgeHash = typeof row["knowledgeHash"] === "string" ? row["knowledgeHash"] : void 0;
|
|
1790
|
+
if (memoryHash === void 0 && knowledgeHash === void 0) continue;
|
|
1791
|
+
entries.set(name, {
|
|
1792
|
+
...memoryHash !== void 0 ? { memoryHash } : {},
|
|
1793
|
+
...knowledgeHash !== void 0 ? { knowledgeHash } : {}
|
|
1794
|
+
});
|
|
1795
|
+
}
|
|
1796
|
+
const sweptAt = obj["knowledgeSweptAtMs"];
|
|
1797
|
+
return {
|
|
1798
|
+
controlPlaneUrl,
|
|
1799
|
+
entries,
|
|
1800
|
+
// An unreadable/absent sweep stamp reads as NEVER SWEPT, which forces a full
|
|
1801
|
+
// sweep — the fail-closed direction (more upserts, never fewer).
|
|
1802
|
+
knowledgeSweptAtMs: typeof sweptAt === "number" && Number.isFinite(sweptAt) ? sweptAt : null
|
|
1803
|
+
};
|
|
1804
|
+
}
|
|
1805
|
+
function writePushCache(memoryDir, cache) {
|
|
1806
|
+
const entries = {};
|
|
1807
|
+
for (const [name, value] of cache.entries) entries[name] = value;
|
|
1808
|
+
try {
|
|
1809
|
+
writeFileSync5(
|
|
1810
|
+
statePath(memoryDir),
|
|
1811
|
+
`${JSON.stringify(
|
|
1812
|
+
{
|
|
1813
|
+
version: STATE_VERSION,
|
|
1814
|
+
controlPlaneUrl: cache.controlPlaneUrl,
|
|
1815
|
+
knowledgeSweptAtMs: cache.knowledgeSweptAtMs,
|
|
1816
|
+
entries
|
|
1817
|
+
},
|
|
1818
|
+
null,
|
|
1819
|
+
2
|
|
1820
|
+
)}
|
|
1821
|
+
`,
|
|
1822
|
+
"utf8"
|
|
1823
|
+
);
|
|
1824
|
+
} catch {
|
|
1825
|
+
}
|
|
1826
|
+
}
|
|
1827
|
+
function recordMemoryPush(cache, fileName, payloadHash) {
|
|
1828
|
+
cache.entries.set(fileName, { ...cache.entries.get(fileName), memoryHash: payloadHash });
|
|
1829
|
+
}
|
|
1830
|
+
function recordKnowledgePush(cache, fileName, contentHash) {
|
|
1831
|
+
cache.entries.set(fileName, { ...cache.entries.get(fileName), knowledgeHash: contentHash });
|
|
1832
|
+
}
|
|
1833
|
+
function pruneMissing(cache, presentFileNames) {
|
|
1834
|
+
const present = new Set(presentFileNames);
|
|
1835
|
+
for (const name of [...cache.entries.keys()]) {
|
|
1836
|
+
if (!present.has(name)) cache.entries.delete(name);
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1839
|
+
function needsMemoryPush(cache, fileName, payloadHash, serverHasEntry) {
|
|
1840
|
+
if (!serverHasEntry) return true;
|
|
1841
|
+
return cache.entries.get(fileName)?.memoryHash !== payloadHash;
|
|
1842
|
+
}
|
|
1843
|
+
function knowledgeSweepDue(cache, nowMs = Date.now()) {
|
|
1844
|
+
const swept = cache.knowledgeSweptAtMs;
|
|
1845
|
+
if (swept === null || !Number.isFinite(swept)) return true;
|
|
1846
|
+
const age = nowMs - swept;
|
|
1847
|
+
return !(age >= 0 && age < KNOWLEDGE_FULL_SWEEP_MS);
|
|
1848
|
+
}
|
|
1849
|
+
function needsKnowledgePush(cache, fileName, contentHash, sweepDue = false) {
|
|
1850
|
+
if (sweepDue) return true;
|
|
1851
|
+
return cache.entries.get(fileName)?.knowledgeHash !== contentHash;
|
|
1852
|
+
}
|
|
1853
|
+
var MEMORY_SYNC_STATE_FILE, STATE_VERSION, KNOWLEDGE_FULL_SWEEP_MS;
|
|
1854
|
+
var init_memory_push_cache = __esm({
|
|
1855
|
+
"src/tools/memory/memory-push-cache.ts"() {
|
|
1856
|
+
"use strict";
|
|
1857
|
+
MEMORY_SYNC_STATE_FILE = ".memory-sync-state.json";
|
|
1858
|
+
STATE_VERSION = 1;
|
|
1859
|
+
KNOWLEDGE_FULL_SWEEP_MS = 24 * 60 * 6e4;
|
|
1860
|
+
}
|
|
1861
|
+
});
|
|
1862
|
+
|
|
1863
|
+
// src/tools/memory/memory-sync-http.ts
|
|
1864
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync6, readFileSync as readFileSync11, writeFileSync as writeFileSync6, readdirSync as readdirSync4 } from "node:fs";
|
|
1865
|
+
async function pullMemory(controlPlaneUrl, token, memoryDir, fetchFn) {
|
|
1866
|
+
const url = `${controlPlaneUrl}/api/v1/agent-config/memory/me`;
|
|
1867
|
+
const response = await withRequestTimeout(
|
|
1868
|
+
url,
|
|
1869
|
+
() => fetchFn(url, {
|
|
1870
|
+
method: "GET",
|
|
1871
|
+
headers: {
|
|
1872
|
+
authorization: `Bearer ${token}`
|
|
1873
|
+
}
|
|
1874
|
+
})
|
|
1875
|
+
);
|
|
1876
|
+
if (response.status !== 200) {
|
|
1877
|
+
const text = await response.text();
|
|
1878
|
+
throw new Error(`GET /api/v1/agent-config/memory/me returned HTTP ${response.status}: ${text.slice(0, 200)}`);
|
|
1879
|
+
}
|
|
1880
|
+
const data = JSON.parse(await response.text());
|
|
1881
|
+
if (!data.ok || !Array.isArray(data.entries)) {
|
|
1882
|
+
throw new Error("GET /api/v1/agent-config/memory/me response missing ok=true or entries array");
|
|
1883
|
+
}
|
|
1884
|
+
const writes = data.entries.map((entry) => ({
|
|
1885
|
+
entry,
|
|
1886
|
+
filePath: resolveMemoryFilePath(memoryDir, entry.file_name)
|
|
1887
|
+
}));
|
|
1888
|
+
mkdirSync6(memoryDir, { recursive: true });
|
|
1889
|
+
const files = [];
|
|
1890
|
+
for (const { entry, filePath } of writes) {
|
|
1891
|
+
writeFileSync6(filePath, entry.content, "utf8");
|
|
1892
|
+
files.push(entry.file_name);
|
|
1893
|
+
}
|
|
1894
|
+
return { pulled: data.entries.length, files };
|
|
1895
|
+
}
|
|
1896
|
+
function listPushableFiles(memoryDir) {
|
|
1897
|
+
return readdirSync4(memoryDir).filter((f) => f.endsWith(".md") && f !== MEMORY_SYNC_LOCK_FILE);
|
|
1898
|
+
}
|
|
1899
|
+
async function uploadOne(item, controlPlaneUrl, token, sessionId, fetchFn, deadline) {
|
|
1900
|
+
deadline.check();
|
|
1901
|
+
if (item.memoryId !== null) {
|
|
1902
|
+
const updateUrl = `${controlPlaneUrl}/api/v1/agent-config/memory/${item.memoryId}`;
|
|
1903
|
+
const updateBody = { content: item.content, session_id: sessionId };
|
|
1904
|
+
const updateResponse = await withRequestTimeout(
|
|
1905
|
+
updateUrl,
|
|
1906
|
+
() => fetchFn(updateUrl, {
|
|
1907
|
+
method: "PUT",
|
|
1908
|
+
headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
|
|
1909
|
+
body: JSON.stringify(updateBody)
|
|
1910
|
+
})
|
|
1911
|
+
);
|
|
1912
|
+
if (updateResponse.status !== 200) {
|
|
1913
|
+
const text = await updateResponse.text();
|
|
1914
|
+
throw new Error(
|
|
1915
|
+
`PUT /api/v1/agent-config/memory/${item.memoryId} returned HTTP ${updateResponse.status}: ${text.slice(0, 200)}`
|
|
1916
|
+
);
|
|
1917
|
+
}
|
|
1918
|
+
const updateData = JSON.parse(await updateResponse.text());
|
|
1919
|
+
if (!updateData.ok) throw new Error(`PUT /api/v1/agent-config/memory/${item.memoryId} returned ok=false`);
|
|
1920
|
+
return "updated";
|
|
1921
|
+
}
|
|
1922
|
+
const createUrl = `${controlPlaneUrl}/api/v1/agent-config/memory/me`;
|
|
1923
|
+
const createBody = {
|
|
1924
|
+
entry_type: item.entryType,
|
|
1925
|
+
file_name: item.fileName,
|
|
1926
|
+
content: item.content,
|
|
1927
|
+
session_id: sessionId
|
|
1928
|
+
};
|
|
1929
|
+
const createResponse = await withRequestTimeout(
|
|
1930
|
+
createUrl,
|
|
1931
|
+
() => fetchFn(createUrl, {
|
|
1932
|
+
method: "POST",
|
|
1933
|
+
headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
|
|
1934
|
+
body: JSON.stringify(createBody)
|
|
1935
|
+
})
|
|
1936
|
+
);
|
|
1937
|
+
if (createResponse.status !== 200 && createResponse.status !== 201) {
|
|
1938
|
+
const text = await createResponse.text();
|
|
1939
|
+
throw new Error(
|
|
1940
|
+
`POST /api/v1/agent-config/memory/me returned HTTP ${createResponse.status}: ${text.slice(0, 200)}`
|
|
1941
|
+
);
|
|
1942
|
+
}
|
|
1943
|
+
const createData = JSON.parse(await createResponse.text());
|
|
1944
|
+
if (!createData.ok) throw new Error("POST /api/v1/agent-config/memory/me returned ok=false");
|
|
1945
|
+
return "created";
|
|
1946
|
+
}
|
|
1947
|
+
async function pushMemory(controlPlaneUrl, token, memoryDir, sessionId, fetchFn, options = {}) {
|
|
1948
|
+
const empty = { pushed: 0, created: 0, updated: 0, skipped: 0, indexRowsPreserved: 0 };
|
|
1949
|
+
if (!existsSync5(memoryDir)) {
|
|
1950
|
+
return empty;
|
|
1951
|
+
}
|
|
1952
|
+
const localFiles = listPushableFiles(memoryDir).map((f) => ({
|
|
1953
|
+
file_name: f,
|
|
1954
|
+
content: readFileSync11(resolveMemoryFilePath(memoryDir, f), "utf8"),
|
|
1955
|
+
entry_type: isMemoryIndexFile(f) ? "index" : "topic"
|
|
1956
|
+
}));
|
|
1957
|
+
if (localFiles.length === 0) {
|
|
1958
|
+
return empty;
|
|
1959
|
+
}
|
|
1960
|
+
const deadline = options.deadline ?? createSyncDeadline();
|
|
1961
|
+
const cache = options.cache ?? readPushCache(memoryDir, controlPlaneUrl);
|
|
1962
|
+
deadline.check();
|
|
1963
|
+
const getUrl = `${controlPlaneUrl}/api/v1/agent-config/memory/me`;
|
|
1964
|
+
const getResponse = await withRequestTimeout(
|
|
1965
|
+
getUrl,
|
|
1966
|
+
() => fetchFn(getUrl, { method: "GET", headers: { authorization: `Bearer ${token}` } })
|
|
1967
|
+
);
|
|
1968
|
+
const existingMap = /* @__PURE__ */ new Map();
|
|
1969
|
+
if (getResponse.status === 200) {
|
|
1970
|
+
const getData = JSON.parse(await getResponse.text());
|
|
1971
|
+
if (getData.ok && Array.isArray(getData.entries)) {
|
|
1972
|
+
for (const entry of getData.entries) {
|
|
1973
|
+
existingMap.set(entry.file_name, {
|
|
1974
|
+
memoryId: entry.memory_id,
|
|
1975
|
+
content: typeof entry.content === "string" ? entry.content : ""
|
|
1976
|
+
});
|
|
1977
|
+
}
|
|
1978
|
+
}
|
|
1979
|
+
}
|
|
1980
|
+
const toUpload = [];
|
|
1981
|
+
let skipped = 0;
|
|
1982
|
+
for (const localFile of localFiles) {
|
|
1983
|
+
const existing = existingMap.get(localFile.file_name);
|
|
1984
|
+
let content = localFile.content;
|
|
1985
|
+
let rowsPreserved = 0;
|
|
1986
|
+
if (localFile.entry_type === "index") {
|
|
1987
|
+
const merged = mergeMemoryIndex(localFile.content, existing?.content);
|
|
1988
|
+
content = merged.content;
|
|
1989
|
+
rowsPreserved = merged.addedFromCloud.length;
|
|
1990
|
+
}
|
|
1991
|
+
const payloadHash = sha256(content);
|
|
1992
|
+
if (!needsMemoryPush(cache, localFile.file_name, payloadHash, existing !== void 0)) {
|
|
1993
|
+
skipped++;
|
|
1994
|
+
continue;
|
|
1995
|
+
}
|
|
1996
|
+
toUpload.push({
|
|
1997
|
+
fileName: localFile.file_name,
|
|
1998
|
+
entryType: localFile.entry_type,
|
|
1999
|
+
content,
|
|
2000
|
+
payloadHash,
|
|
2001
|
+
memoryId: existing?.memoryId ?? null,
|
|
2002
|
+
rowsPreserved
|
|
2003
|
+
});
|
|
2004
|
+
}
|
|
2005
|
+
const outcomes = await mapWithConcurrency(
|
|
2006
|
+
toUpload,
|
|
2007
|
+
options.concurrency ?? PUSH_CONCURRENCY,
|
|
2008
|
+
(item) => uploadOne(item, controlPlaneUrl, token, sessionId, fetchFn, deadline)
|
|
2009
|
+
);
|
|
2010
|
+
let created = 0;
|
|
2011
|
+
let updated = 0;
|
|
2012
|
+
let indexRowsPreserved = 0;
|
|
2013
|
+
let firstError;
|
|
2014
|
+
for (let i = 0; i < outcomes.length; i++) {
|
|
2015
|
+
const outcome = outcomes[i];
|
|
2016
|
+
const item = toUpload[i];
|
|
2017
|
+
if (outcome.ok) {
|
|
2018
|
+
if (outcome.value === "created") created++;
|
|
2019
|
+
else updated++;
|
|
2020
|
+
indexRowsPreserved += item.rowsPreserved;
|
|
2021
|
+
recordMemoryPush(cache, item.fileName, item.payloadHash);
|
|
2022
|
+
} else if (firstError === void 0) {
|
|
2023
|
+
firstError = outcome.error;
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
2026
|
+
pruneMissing(cache, localFiles.map((f) => f.file_name));
|
|
2027
|
+
if (options.persistCache ?? options.cache === void 0) writePushCache(memoryDir, cache);
|
|
2028
|
+
if (firstError !== void 0) throw firstError;
|
|
2029
|
+
return { pushed: created + updated, created, updated, skipped, indexRowsPreserved };
|
|
2030
|
+
}
|
|
2031
|
+
var init_memory_sync_http = __esm({
|
|
2032
|
+
"src/tools/memory/memory-sync-http.ts"() {
|
|
2033
|
+
"use strict";
|
|
2034
|
+
init_safe_memory_file();
|
|
2035
|
+
init_sync_lock();
|
|
2036
|
+
init_memory_index_merge();
|
|
2037
|
+
init_bounded_sync();
|
|
2038
|
+
init_memory_push_cache();
|
|
2039
|
+
}
|
|
2040
|
+
});
|
|
2041
|
+
|
|
2042
|
+
// src/tools/memory/sync-kill-switch.ts
|
|
2043
|
+
import { existsSync as existsSync6, readFileSync as readFileSync12 } from "node:fs";
|
|
2044
|
+
import { homedir as homedir6 } from "node:os";
|
|
2045
|
+
import { join as join10 } from "node:path";
|
|
2046
|
+
function memorySyncSentinelPath(home) {
|
|
2047
|
+
return join10(home, ".claude", MEMORY_SYNC_DISABLE_SENTINEL);
|
|
2048
|
+
}
|
|
2049
|
+
function isKillSwitchValueOn(raw) {
|
|
2050
|
+
if (raw === void 0 || raw === null) return false;
|
|
2051
|
+
const v = raw.trim().toLowerCase();
|
|
2052
|
+
if (v === "") return false;
|
|
2053
|
+
return !NEGATIONS.has(v);
|
|
2054
|
+
}
|
|
2055
|
+
function clip(raw) {
|
|
2056
|
+
const v = raw.trim();
|
|
2057
|
+
return v.length > MAX_LOGGED_VALUE ? `${v.slice(0, MAX_LOGGED_VALUE)}\u2026` : v;
|
|
2058
|
+
}
|
|
2059
|
+
function evaluateMemorySyncKillSwitch(deps = {}) {
|
|
2060
|
+
const env = deps.env ?? process.env;
|
|
2061
|
+
const home = deps.home ?? homedir6();
|
|
2062
|
+
const fileExists = deps.fileExists ?? existsSync6;
|
|
2063
|
+
const readFile3 = deps.readFile ?? ((p) => readFileSync12(p, "utf8"));
|
|
2064
|
+
const fired = [];
|
|
2065
|
+
const rawEnv = env[MEMORY_SYNC_DISABLE_ENV];
|
|
2066
|
+
if (isKillSwitchValueOn(rawEnv)) {
|
|
2067
|
+
fired.push(`env ${MEMORY_SYNC_DISABLE_ENV}=${clip(rawEnv)}`);
|
|
2068
|
+
}
|
|
2069
|
+
const sentinel = memorySyncSentinelPath(home);
|
|
2070
|
+
let sentinelPresent;
|
|
2071
|
+
try {
|
|
2072
|
+
sentinelPresent = fileExists(sentinel);
|
|
2073
|
+
} catch {
|
|
2074
|
+
sentinelPresent = false;
|
|
2075
|
+
}
|
|
2076
|
+
if (sentinelPresent) {
|
|
2077
|
+
let contents = "";
|
|
2078
|
+
let readable = true;
|
|
2079
|
+
try {
|
|
2080
|
+
contents = readFile3(sentinel);
|
|
2081
|
+
} catch {
|
|
2082
|
+
readable = false;
|
|
2083
|
+
}
|
|
2084
|
+
if (!readable || isKillSwitchValueOn(contents) || contents.trim() === "") {
|
|
2085
|
+
fired.push(`sentinel file ${sentinel}`);
|
|
2086
|
+
}
|
|
2087
|
+
}
|
|
2088
|
+
if (fired.length === 0) return { disabled: false, reason: null };
|
|
2089
|
+
return { disabled: true, reason: `memory sync DISABLED by ${fired.join(" + ")}` };
|
|
2090
|
+
}
|
|
2091
|
+
var MEMORY_SYNC_DISABLE_ENV, MEMORY_SYNC_DISABLE_SENTINEL, NEGATIONS, MAX_LOGGED_VALUE;
|
|
2092
|
+
var init_sync_kill_switch = __esm({
|
|
2093
|
+
"src/tools/memory/sync-kill-switch.ts"() {
|
|
2094
|
+
"use strict";
|
|
2095
|
+
MEMORY_SYNC_DISABLE_ENV = "VO_MCP_DISABLE_MEMORY_SYNC";
|
|
2096
|
+
MEMORY_SYNC_DISABLE_SENTINEL = "vo-memory-sync-disabled";
|
|
2097
|
+
NEGATIONS = /* @__PURE__ */ new Set(["0", "false", "no"]);
|
|
2098
|
+
MAX_LOGGED_VALUE = 32;
|
|
2099
|
+
}
|
|
2100
|
+
});
|
|
2101
|
+
|
|
1402
2102
|
// src/tools/memory/memory-knowledge-bridge.ts
|
|
1403
2103
|
var memory_knowledge_bridge_exports = {};
|
|
1404
2104
|
__export(memory_knowledge_bridge_exports, {
|
|
1405
2105
|
extractMemoryTitle: () => extractMemoryTitle,
|
|
1406
2106
|
upsertMemoryFilesAsKnowledge: () => upsertMemoryFilesAsKnowledge
|
|
1407
2107
|
});
|
|
1408
|
-
import { existsSync as
|
|
2108
|
+
import { existsSync as existsSync7, readdirSync as readdirSync5, readFileSync as readFileSync13 } from "node:fs";
|
|
1409
2109
|
function extractMemoryTitle(fileName, content) {
|
|
1410
2110
|
const frontmatter = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
1411
2111
|
if (frontmatter) {
|
|
@@ -1417,13 +2117,13 @@ function extractMemoryTitle(fileName, content) {
|
|
|
1417
2117
|
return fileName;
|
|
1418
2118
|
}
|
|
1419
2119
|
async function upsertMemoryFilesAsKnowledge(options) {
|
|
1420
|
-
const { controlPlaneUrl, token, memoryDir, fetchFn } = options;
|
|
2120
|
+
const { controlPlaneUrl, token, memoryDir, fetchFn, cache, deadline } = options;
|
|
1421
2121
|
let files;
|
|
1422
2122
|
try {
|
|
1423
|
-
if (!
|
|
1424
|
-
return { attempted: 0, upserted: 0, failed: 0, failures: [] };
|
|
2123
|
+
if (!existsSync7(memoryDir)) {
|
|
2124
|
+
return { attempted: 0, upserted: 0, failed: 0, skipped: 0, failures: [] };
|
|
1425
2125
|
}
|
|
1426
|
-
files =
|
|
2126
|
+
files = readdirSync5(memoryDir).filter(
|
|
1427
2127
|
(f) => f.endsWith(".md") && f.toUpperCase() !== "MEMORY.MD"
|
|
1428
2128
|
);
|
|
1429
2129
|
} catch (err) {
|
|
@@ -1431,54 +2131,81 @@ async function upsertMemoryFilesAsKnowledge(options) {
|
|
|
1431
2131
|
attempted: 0,
|
|
1432
2132
|
upserted: 0,
|
|
1433
2133
|
failed: 1,
|
|
2134
|
+
skipped: 0,
|
|
1434
2135
|
failures: [`memory dir scan: ${err instanceof Error ? err.message : String(err)}`]
|
|
1435
2136
|
};
|
|
1436
2137
|
}
|
|
1437
|
-
|
|
2138
|
+
const sweepDue = cache ? knowledgeSweepDue(cache) : true;
|
|
2139
|
+
const candidates = [];
|
|
1438
2140
|
const failures = [];
|
|
2141
|
+
let skipped = 0;
|
|
1439
2142
|
for (const fileName of files) {
|
|
1440
2143
|
try {
|
|
1441
|
-
const content =
|
|
2144
|
+
const content = readFileSync13(resolveMemoryFilePath(memoryDir, fileName), "utf8");
|
|
1442
2145
|
if (content.length > CONTENT_HARD_LIMIT) {
|
|
1443
2146
|
failures.push(`${fileName}: ${content.length} chars exceeds the ${CONTENT_HARD_LIMIT} server limit \u2014 split the memory file`);
|
|
1444
2147
|
continue;
|
|
1445
2148
|
}
|
|
1446
|
-
const
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
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)}`);
|
|
2149
|
+
const hash = sha256(content);
|
|
2150
|
+
if (cache && !needsKnowledgePush(cache, fileName, hash, sweepDue)) {
|
|
2151
|
+
skipped += 1;
|
|
2152
|
+
continue;
|
|
1473
2153
|
}
|
|
2154
|
+
candidates.push({ fileName, content, hash });
|
|
1474
2155
|
} catch (err) {
|
|
1475
2156
|
failures.push(`${fileName}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1476
2157
|
}
|
|
1477
2158
|
}
|
|
2159
|
+
const url = `${controlPlaneUrl}/api/v1/knowledge/private`;
|
|
2160
|
+
const outcomes = await mapWithConcurrency(candidates, options.concurrency ?? PUSH_CONCURRENCY, async (candidate) => {
|
|
2161
|
+
deadline?.check();
|
|
2162
|
+
const base = {
|
|
2163
|
+
knowledge_class: "memory",
|
|
2164
|
+
source_path: `memory/${candidate.fileName}`,
|
|
2165
|
+
title: extractMemoryTitle(candidate.fileName, candidate.content),
|
|
2166
|
+
content: candidate.content
|
|
2167
|
+
};
|
|
2168
|
+
const post = (body) => withRequestTimeout(url, () => fetchFn(url, {
|
|
2169
|
+
method: "POST",
|
|
2170
|
+
headers: {
|
|
2171
|
+
authorization: `Bearer ${token}`,
|
|
2172
|
+
"content-type": "application/json"
|
|
2173
|
+
},
|
|
2174
|
+
body: JSON.stringify(body)
|
|
2175
|
+
}));
|
|
2176
|
+
let response = await post({
|
|
2177
|
+
...base,
|
|
2178
|
+
provenance: { written_by: "memory-bridge", source_kind: "operator_memory" }
|
|
2179
|
+
});
|
|
2180
|
+
if (response.status === 400) {
|
|
2181
|
+
response = await post(base);
|
|
2182
|
+
}
|
|
2183
|
+
if (response.status >= 200 && response.status < 300) return true;
|
|
2184
|
+
const text = await response.text();
|
|
2185
|
+
throw new Error(`HTTP ${response.status} ${text.slice(0, 80)}`);
|
|
2186
|
+
});
|
|
2187
|
+
let upserted = 0;
|
|
2188
|
+
for (let i = 0; i < outcomes.length; i++) {
|
|
2189
|
+
const outcome = outcomes[i];
|
|
2190
|
+
const candidate = candidates[i];
|
|
2191
|
+
if (outcome.ok) {
|
|
2192
|
+
upserted += 1;
|
|
2193
|
+
if (cache) recordKnowledgePush(cache, candidate.fileName, candidate.hash);
|
|
2194
|
+
} else {
|
|
2195
|
+
const error = outcome.error;
|
|
2196
|
+
failures.push(`${candidate.fileName}: ${error instanceof Error ? error.message : String(error)}`);
|
|
2197
|
+
}
|
|
2198
|
+
}
|
|
2199
|
+
if (cache && sweepDue && failures.length === 0) {
|
|
2200
|
+
cache.knowledgeSweptAtMs = Date.now();
|
|
2201
|
+
}
|
|
1478
2202
|
return {
|
|
2203
|
+
// Every memory file this run considered. `attempted === upserted + skipped
|
|
2204
|
+
// + failed` holds, so a caller can tell "nothing to do" from "nothing ran".
|
|
1479
2205
|
attempted: files.length,
|
|
1480
2206
|
upserted,
|
|
1481
2207
|
failed: failures.length,
|
|
2208
|
+
skipped,
|
|
1482
2209
|
failures: failures.slice(0, 5)
|
|
1483
2210
|
};
|
|
1484
2211
|
}
|
|
@@ -1487,6 +2214,8 @@ var init_memory_knowledge_bridge = __esm({
|
|
|
1487
2214
|
"src/tools/memory/memory-knowledge-bridge.ts"() {
|
|
1488
2215
|
"use strict";
|
|
1489
2216
|
init_safe_memory_file();
|
|
2217
|
+
init_bounded_sync();
|
|
2218
|
+
init_memory_push_cache();
|
|
1490
2219
|
CONTENT_HARD_LIMIT = 5e5;
|
|
1491
2220
|
}
|
|
1492
2221
|
});
|
|
@@ -1503,9 +2232,9 @@ __export(sync_config_exports, {
|
|
|
1503
2232
|
isNoopSyncReason: () => isNoopSyncReason,
|
|
1504
2233
|
runMemorySync: () => runMemorySync
|
|
1505
2234
|
});
|
|
1506
|
-
import {
|
|
1507
|
-
import {
|
|
1508
|
-
import {
|
|
2235
|
+
import { existsSync as existsSync8 } from "node:fs";
|
|
2236
|
+
import { homedir as homedir7 } from "node:os";
|
|
2237
|
+
import { join as join11 } from "node:path";
|
|
1509
2238
|
function isToolInput22(v) {
|
|
1510
2239
|
if (typeof v !== "object" || v === null) return false;
|
|
1511
2240
|
const o = v;
|
|
@@ -1518,129 +2247,17 @@ function deriveProjectSlug(cwd) {
|
|
|
1518
2247
|
}
|
|
1519
2248
|
function getMemoryDir(cwd) {
|
|
1520
2249
|
const slug = deriveProjectSlug(cwd);
|
|
1521
|
-
return
|
|
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 };
|
|
2250
|
+
return join11(homedir7(), ".claude", "projects", slug, "memory");
|
|
1638
2251
|
}
|
|
1639
2252
|
function isNoopSyncReason(reason) {
|
|
1640
2253
|
if (!reason) return false;
|
|
1641
|
-
return /not set|No auth configured|Failed to obtain auth token/.test(reason);
|
|
2254
|
+
return /not set|No auth configured|Failed to obtain auth token|DISABLED by/.test(reason);
|
|
1642
2255
|
}
|
|
1643
|
-
async function runMemorySync(action, cwd, sessionId, fetchFn = globalThis.fetch) {
|
|
2256
|
+
async function runMemorySync(action, cwd, sessionId, fetchFn = globalThis.fetch, lockOptions = {}) {
|
|
2257
|
+
const killSwitch = evaluateMemorySyncKillSwitch();
|
|
2258
|
+
if (killSwitch.disabled) {
|
|
2259
|
+
return { synced: false, reason: killSwitch.reason };
|
|
2260
|
+
}
|
|
1644
2261
|
const controlPlaneUrl = process.env["VO_CONTROL_PLANE_URL"];
|
|
1645
2262
|
if (!controlPlaneUrl) {
|
|
1646
2263
|
return { synced: false, reason: "VO_CONTROL_PLANE_URL not set \u2014 cloud mode disabled" };
|
|
@@ -1657,39 +2274,79 @@ async function runMemorySync(action, cwd, sessionId, fetchFn = globalThis.fetch)
|
|
|
1657
2274
|
}
|
|
1658
2275
|
const memoryDir = getMemoryDir(cwd);
|
|
1659
2276
|
const baseUrl = controlPlaneUrl.replace(/\/+$/, "");
|
|
1660
|
-
|
|
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
|
-
}
|
|
2277
|
+
if (action === "push" && !existsSync8(memoryDir)) {
|
|
1682
2278
|
return {
|
|
1683
2279
|
synced: true,
|
|
1684
2280
|
action: "push",
|
|
1685
|
-
pushed:
|
|
1686
|
-
created:
|
|
1687
|
-
updated:
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
2281
|
+
pushed: 0,
|
|
2282
|
+
created: 0,
|
|
2283
|
+
updated: 0,
|
|
2284
|
+
skipped: 0,
|
|
2285
|
+
index_rows_preserved: 0,
|
|
2286
|
+
knowledge_upserted: 0,
|
|
2287
|
+
knowledge_failed: 0,
|
|
2288
|
+
memory_dir: memoryDir
|
|
1692
2289
|
};
|
|
2290
|
+
}
|
|
2291
|
+
try {
|
|
2292
|
+
return await withMemorySyncLock({ ...lockOptions, memoryDir, sessionId, createDir: action === "pull" }, async (lock) => {
|
|
2293
|
+
const takeover = lock.tookOverFrom ? { lock_taken_over_from_pid: lock.tookOverFrom.pid } : {};
|
|
2294
|
+
if (action === "pull") {
|
|
2295
|
+
const result2 = await pullMemory(baseUrl, token, memoryDir, fetchFn);
|
|
2296
|
+
return {
|
|
2297
|
+
synced: true,
|
|
2298
|
+
action: "pull",
|
|
2299
|
+
pulled: result2.pulled,
|
|
2300
|
+
files: result2.files,
|
|
2301
|
+
memory_dir: memoryDir,
|
|
2302
|
+
...takeover
|
|
2303
|
+
};
|
|
2304
|
+
}
|
|
2305
|
+
const deadline = createSyncDeadline();
|
|
2306
|
+
const cache = readPushCache(memoryDir, baseUrl);
|
|
2307
|
+
let result;
|
|
2308
|
+
try {
|
|
2309
|
+
result = await pushMemory(baseUrl, token, memoryDir, sessionId, fetchFn, { cache, deadline });
|
|
2310
|
+
} catch (err) {
|
|
2311
|
+
writePushCache(memoryDir, cache);
|
|
2312
|
+
throw err;
|
|
2313
|
+
}
|
|
2314
|
+
let bridge;
|
|
2315
|
+
try {
|
|
2316
|
+
const { upsertMemoryFilesAsKnowledge: upsertMemoryFilesAsKnowledge2 } = await Promise.resolve().then(() => (init_memory_knowledge_bridge(), memory_knowledge_bridge_exports));
|
|
2317
|
+
bridge = await upsertMemoryFilesAsKnowledge2({
|
|
2318
|
+
controlPlaneUrl: baseUrl,
|
|
2319
|
+
token,
|
|
2320
|
+
memoryDir,
|
|
2321
|
+
fetchFn,
|
|
2322
|
+
cache,
|
|
2323
|
+
deadline
|
|
2324
|
+
});
|
|
2325
|
+
} catch (err) {
|
|
2326
|
+
bridge = {
|
|
2327
|
+
upserted: 0,
|
|
2328
|
+
failed: 1,
|
|
2329
|
+
skipped: 0,
|
|
2330
|
+
failures: [`bridge unavailable: ${err instanceof Error ? err.message : String(err)}`]
|
|
2331
|
+
};
|
|
2332
|
+
}
|
|
2333
|
+
writePushCache(memoryDir, cache);
|
|
2334
|
+
return {
|
|
2335
|
+
synced: true,
|
|
2336
|
+
action: "push",
|
|
2337
|
+
pushed: result.pushed,
|
|
2338
|
+
created: result.created,
|
|
2339
|
+
updated: result.updated,
|
|
2340
|
+
skipped: result.skipped,
|
|
2341
|
+
index_rows_preserved: result.indexRowsPreserved,
|
|
2342
|
+
memory_dir: memoryDir,
|
|
2343
|
+
knowledge_upserted: bridge.upserted,
|
|
2344
|
+
knowledge_failed: bridge.failed,
|
|
2345
|
+
knowledge_skipped: bridge.skipped,
|
|
2346
|
+
...bridge.failed > 0 ? { knowledge_failures: bridge.failures } : {},
|
|
2347
|
+
...takeover
|
|
2348
|
+
};
|
|
2349
|
+
});
|
|
1693
2350
|
} catch (err) {
|
|
1694
2351
|
const message = err instanceof Error ? err.message : String(err);
|
|
1695
2352
|
return { synced: false, reason: `Sync failed: ${message}` };
|
|
@@ -1711,7 +2368,11 @@ var init_sync_config = __esm({
|
|
|
1711
2368
|
"src/tools/memory/sync-config.ts"() {
|
|
1712
2369
|
"use strict";
|
|
1713
2370
|
init_common();
|
|
1714
|
-
|
|
2371
|
+
init_memory_sync_http();
|
|
2372
|
+
init_bounded_sync();
|
|
2373
|
+
init_memory_push_cache();
|
|
2374
|
+
init_sync_lock();
|
|
2375
|
+
init_sync_kill_switch();
|
|
1715
2376
|
TOOL_NAME22 = "vo_sync_config";
|
|
1716
2377
|
inputSchema22 = {
|
|
1717
2378
|
type: "object",
|
|
@@ -1729,19 +2390,19 @@ var init_sync_config = __esm({
|
|
|
1729
2390
|
required: ["action"],
|
|
1730
2391
|
additionalProperties: false
|
|
1731
2392
|
};
|
|
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.";
|
|
2393
|
+
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
2394
|
}
|
|
1734
2395
|
});
|
|
1735
2396
|
|
|
1736
2397
|
// src/cli.ts
|
|
1737
|
-
import { homedir as
|
|
1738
|
-
import { randomUUID as
|
|
1739
|
-
import { join as
|
|
2398
|
+
import { homedir as homedir8, hostname as hostname2 } from "node:os";
|
|
2399
|
+
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
2400
|
+
import { join as join14 } from "node:path";
|
|
1740
2401
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
1741
2402
|
|
|
1742
2403
|
// src/server.ts
|
|
1743
2404
|
init_common();
|
|
1744
|
-
import { randomUUID as
|
|
2405
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
1745
2406
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
1746
2407
|
import {
|
|
1747
2408
|
CallToolRequestSchema,
|
|
@@ -6319,12 +6980,12 @@ async function handleHqWhiteboardRead(_deps, rawInput, signal) {
|
|
|
6319
6980
|
}
|
|
6320
6981
|
|
|
6321
6982
|
// src/tools/skills/skill-corpus.ts
|
|
6322
|
-
import { existsSync as
|
|
6323
|
-
import { dirname as dirname5, isAbsolute, join as
|
|
6983
|
+
import { existsSync as existsSync9, statSync as statSync6 } from "node:fs";
|
|
6984
|
+
import { dirname as dirname5, isAbsolute, join as join13, resolve as resolve2 } from "node:path";
|
|
6324
6985
|
|
|
6325
6986
|
// ../skill-registry/src/loader.ts
|
|
6326
|
-
import { readdirSync as readdirSync6, readFileSync as
|
|
6327
|
-
import { join as
|
|
6987
|
+
import { readdirSync as readdirSync6, readFileSync as readFileSync14, statSync as statSync5 } from "node:fs";
|
|
6988
|
+
import { join as join12 } from "node:path";
|
|
6328
6989
|
var InvalidSkillFrontmatterError = class extends Error {
|
|
6329
6990
|
constructor(skillFile, reason) {
|
|
6330
6991
|
super(`Invalid frontmatter in ${skillFile}: ${reason}`);
|
|
@@ -6377,18 +7038,18 @@ function loadSkillsFromDir(skillsDir) {
|
|
|
6377
7038
|
const entries = readdirSync6(skillsDir);
|
|
6378
7039
|
const skills = [];
|
|
6379
7040
|
for (const entry of entries) {
|
|
6380
|
-
const entryPath =
|
|
7041
|
+
const entryPath = join12(skillsDir, entry);
|
|
6381
7042
|
let stat;
|
|
6382
7043
|
try {
|
|
6383
|
-
stat =
|
|
7044
|
+
stat = statSync5(entryPath);
|
|
6384
7045
|
} catch {
|
|
6385
7046
|
continue;
|
|
6386
7047
|
}
|
|
6387
7048
|
if (!stat.isDirectory()) continue;
|
|
6388
|
-
const skillFile =
|
|
7049
|
+
const skillFile = join12(entryPath, "SKILL.md");
|
|
6389
7050
|
let raw;
|
|
6390
7051
|
try {
|
|
6391
|
-
raw =
|
|
7052
|
+
raw = readFileSync14(skillFile, "utf8");
|
|
6392
7053
|
} catch {
|
|
6393
7054
|
continue;
|
|
6394
7055
|
}
|
|
@@ -6430,12 +7091,12 @@ function resolveSkillsDir(env = process.env, startDir = process.cwd()) {
|
|
|
6430
7091
|
const override = env.VO_SKILLS_DIR;
|
|
6431
7092
|
if (typeof override === "string" && override.length > 0) {
|
|
6432
7093
|
const abs = isAbsolute(override) ? override : resolve2(startDir, override);
|
|
6433
|
-
return
|
|
7094
|
+
return existsSync9(abs) && statSync6(abs).isDirectory() ? abs : null;
|
|
6434
7095
|
}
|
|
6435
7096
|
let dir = resolve2(startDir);
|
|
6436
7097
|
for (let i = 0; i < MAX_WALK_UP_LEVELS; i += 1) {
|
|
6437
|
-
const candidate =
|
|
6438
|
-
if (
|
|
7098
|
+
const candidate = join13(dir, ".claude", "skills");
|
|
7099
|
+
if (existsSync9(candidate) && statSync6(candidate).isDirectory()) return candidate;
|
|
6439
7100
|
const parent = dirname5(dir);
|
|
6440
7101
|
if (parent === dir) break;
|
|
6441
7102
|
dir = parent;
|
|
@@ -6746,7 +7407,7 @@ function buildToolRegistry() {
|
|
|
6746
7407
|
};
|
|
6747
7408
|
}
|
|
6748
7409
|
function createServer(options) {
|
|
6749
|
-
const sessionId = options.sessionId ??
|
|
7410
|
+
const sessionId = options.sessionId ?? randomUUID3();
|
|
6750
7411
|
const mode = createLocalMode();
|
|
6751
7412
|
const now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
6752
7413
|
const server = new Server(
|
|
@@ -6795,8 +7456,8 @@ function createServer(options) {
|
|
|
6795
7456
|
}
|
|
6796
7457
|
|
|
6797
7458
|
// src/cache/sqlite-cache.ts
|
|
6798
|
-
import { createHash as
|
|
6799
|
-
import { chmodSync as chmodSync3, mkdirSync as
|
|
7459
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
7460
|
+
import { chmodSync as chmodSync3, mkdirSync as mkdirSync7 } from "node:fs";
|
|
6800
7461
|
import { dirname as dirname6 } from "node:path";
|
|
6801
7462
|
import { DatabaseSync } from "node:sqlite";
|
|
6802
7463
|
|
|
@@ -6842,7 +7503,7 @@ function normalizeString(s) {
|
|
|
6842
7503
|
function createSqliteCache(options) {
|
|
6843
7504
|
const fileBacked = options.dbPath !== ":memory:";
|
|
6844
7505
|
if (fileBacked) {
|
|
6845
|
-
|
|
7506
|
+
mkdirSync7(dirname6(options.dbPath), { recursive: true, mode: 448 });
|
|
6846
7507
|
}
|
|
6847
7508
|
const versionNamespace = options.cacheVersionNamespace ?? "";
|
|
6848
7509
|
const db = new DatabaseSync(options.dbPath);
|
|
@@ -6873,7 +7534,7 @@ function createSqliteCache(options) {
|
|
|
6873
7534
|
return {
|
|
6874
7535
|
keyFor(toolName, input, opts) {
|
|
6875
7536
|
const canonical = canonicalize(input, opts);
|
|
6876
|
-
const hash =
|
|
7537
|
+
const hash = createHash4("sha256");
|
|
6877
7538
|
if (versionNamespace.length > 0) {
|
|
6878
7539
|
hash.update(versionNamespace);
|
|
6879
7540
|
hash.update("|");
|
|
@@ -6968,7 +7629,7 @@ function createStubRatchetClient() {
|
|
|
6968
7629
|
let m;
|
|
6969
7630
|
while ((m = pat.regex.exec(req.source)) !== null) {
|
|
6970
7631
|
findings.push({
|
|
6971
|
-
line_excerpt:
|
|
7632
|
+
line_excerpt: clip2(m[0], 80),
|
|
6972
7633
|
severity: pat.severity,
|
|
6973
7634
|
code: pat.code,
|
|
6974
7635
|
message: pat.message
|
|
@@ -7000,7 +7661,7 @@ function createStubRatchetClient() {
|
|
|
7000
7661
|
}
|
|
7001
7662
|
};
|
|
7002
7663
|
}
|
|
7003
|
-
function
|
|
7664
|
+
function clip2(s, n) {
|
|
7004
7665
|
return s.length <= n ? s : s.slice(0, n) + "\u2026";
|
|
7005
7666
|
}
|
|
7006
7667
|
function buildSummary2(args) {
|
|
@@ -7011,7 +7672,7 @@ function buildSummary2(args) {
|
|
|
7011
7672
|
// src/consensus/engine-client.ts
|
|
7012
7673
|
init_events_writer();
|
|
7013
7674
|
init_common();
|
|
7014
|
-
import { randomUUID as
|
|
7675
|
+
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
7015
7676
|
|
|
7016
7677
|
// src/consensus/null-client.ts
|
|
7017
7678
|
var NULL_CLIENT_DEFAULT_REASON = "consensus-engine-package-pending";
|
|
@@ -7471,7 +8132,7 @@ function tryCreateEngineConsensusClientFromEnv(options = {}) {
|
|
|
7471
8132
|
}
|
|
7472
8133
|
|
|
7473
8134
|
// src/consensus/moat-client.ts
|
|
7474
|
-
import { randomUUID as
|
|
8135
|
+
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
7475
8136
|
|
|
7476
8137
|
// src/consensus/client.ts
|
|
7477
8138
|
var CANCELLED_REASON = "cancelled";
|
|
@@ -7523,7 +8184,7 @@ function createMoatConsensusClient(opts) {
|
|
|
7523
8184
|
request.signal?.addEventListener("abort", onAbort, { once: true });
|
|
7524
8185
|
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
7525
8186
|
const body = JSON.stringify({
|
|
7526
|
-
task_id:
|
|
8187
|
+
task_id: randomUUID5(),
|
|
7527
8188
|
gate_type: request.gate_type,
|
|
7528
8189
|
excerpt: request.prompt,
|
|
7529
8190
|
...request.system_prompt ? { question: request.system_prompt } : {},
|
|
@@ -7844,7 +8505,7 @@ init_common();
|
|
|
7844
8505
|
function defaultCacheDbPath() {
|
|
7845
8506
|
const env = process.env["VO_MCP_DB_PATH"];
|
|
7846
8507
|
if (env && env.length > 0) return env;
|
|
7847
|
-
return
|
|
8508
|
+
return join14(homedir8(), ".claude", "vo-mcp-cache.db");
|
|
7848
8509
|
}
|
|
7849
8510
|
async function probeEngineVersion() {
|
|
7850
8511
|
try {
|
|
@@ -7971,7 +8632,7 @@ async function main() {
|
|
|
7971
8632
|
}
|
|
7972
8633
|
if (process.argv[2] === "login") {
|
|
7973
8634
|
const controlPlaneUrl = process.env["VO_CONTROL_PLANE_URL"]?.trim();
|
|
7974
|
-
const credentialLabel = `vo-mcp-cli@${
|
|
8635
|
+
const credentialLabel = `vo-mcp-cli@${hostname2()}`.slice(0, 200);
|
|
7975
8636
|
runLogin(
|
|
7976
8637
|
controlPlaneUrl ? {
|
|
7977
8638
|
exchange: (refreshToken, apiKey) => exchangeForVoCredential({ refreshToken, apiKey, controlPlaneUrl, label: credentialLabel })
|
|
@@ -7987,26 +8648,29 @@ if (process.argv[2] === "login") {
|
|
|
7987
8648
|
} else if (process.argv[2] === "sync") {
|
|
7988
8649
|
const action = process.argv[3];
|
|
7989
8650
|
if (action !== "push" && action !== "pull") {
|
|
7990
|
-
console.error("[vo-mcp] usage: vo-mcp sync <push|pull> [--cwd <path>]");
|
|
8651
|
+
console.error("[vo-mcp] usage: vo-mcp sync <push|pull> [--cwd <path>] [--lock-wait-ms <n>]");
|
|
7991
8652
|
process.exit(2);
|
|
7992
8653
|
}
|
|
7993
8654
|
const cwdFlag = process.argv.indexOf("--cwd");
|
|
7994
8655
|
const cwd = cwdFlag >= 0 && typeof process.argv[cwdFlag + 1] === "string" ? process.argv[cwdFlag + 1] : process.cwd();
|
|
7995
|
-
const
|
|
8656
|
+
const waitFlag = process.argv.indexOf("--lock-wait-ms");
|
|
8657
|
+
const parsedWait = waitFlag >= 0 ? Number(process.argv[waitFlag + 1]) : Number.NaN;
|
|
8658
|
+
const lockOptions = Number.isFinite(parsedWait) && parsedWait >= 0 ? { waitMs: parsedWait } : {};
|
|
8659
|
+
const sessionId = randomUUID6();
|
|
7996
8660
|
const appendSyncLog = async (line) => {
|
|
7997
8661
|
try {
|
|
7998
|
-
const { appendFileSync: appendFileSync2, mkdirSync:
|
|
7999
|
-
const { join:
|
|
8000
|
-
const { homedir:
|
|
8001
|
-
const dir =
|
|
8002
|
-
|
|
8003
|
-
appendFileSync2(
|
|
8662
|
+
const { appendFileSync: appendFileSync2, mkdirSync: mkdirSync8 } = await import("node:fs");
|
|
8663
|
+
const { join: join15 } = await import("node:path");
|
|
8664
|
+
const { homedir: homedir9 } = await import("node:os");
|
|
8665
|
+
const dir = join15(homedir9(), ".claude");
|
|
8666
|
+
mkdirSync8(dir, { recursive: true });
|
|
8667
|
+
appendFileSync2(join15(dir, "vo-mcp-sync.log"), `${line}
|
|
8004
8668
|
`, "utf8");
|
|
8005
8669
|
} catch {
|
|
8006
8670
|
}
|
|
8007
8671
|
};
|
|
8008
8672
|
Promise.resolve().then(() => (init_sync_config(), sync_config_exports)).then(async ({ runMemorySync: runMemorySync2, isNoopSyncReason: isNoopSyncReason2 }) => {
|
|
8009
|
-
const r = await runMemorySync2(action, cwd, sessionId);
|
|
8673
|
+
const r = await runMemorySync2(action, cwd, sessionId, void 0, lockOptions);
|
|
8010
8674
|
const stamp = `${sessionId} ${action}`;
|
|
8011
8675
|
if (r.synced) {
|
|
8012
8676
|
console.error(`[vo-mcp] sync ${action} ok: ${JSON.stringify(r)}`);
|