@getbourdon/participants 0.1.1 → 0.2.0
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/base.d.ts +7 -0
- package/dist/index.cjs +1833 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +255 -4
- package/dist/index.d.ts +13 -5
- package/dist/index.js +13 -5
- package/dist/index.js.map +1 -1
- package/dist/readers/cascade.d.ts +49 -0
- package/dist/readers/cascade.js +428 -0
- package/dist/readers/copilot-cli.d.ts +45 -0
- package/dist/readers/copilot-cli.js +416 -0
- package/dist/readers/copilot.d.ts +59 -0
- package/dist/readers/copilot.js +413 -0
- package/dist/readers/cursor.d.ts +50 -0
- package/dist/readers/cursor.js +670 -0
- package/dist/readers/openclaw.d.ts +78 -0
- package/dist/readers/openclaw.js +386 -0
- package/dist/registry.d.ts +4 -2
- package/dist/registry.js +14 -2
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -22,14 +22,21 @@ var index_exports = {};
|
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
CONTRACT_VERSION: () => CONTRACT_VERSION,
|
|
24
24
|
CacheEntry: () => CacheEntry,
|
|
25
|
+
CascadeParticipant: () => CascadeParticipant,
|
|
25
26
|
ClaudeCodeParticipant: () => ClaudeCodeParticipant,
|
|
27
|
+
CopilotCliParticipant: () => CopilotCliParticipant,
|
|
28
|
+
CopilotParticipant: () => CopilotParticipant,
|
|
29
|
+
CursorParticipant: () => CursorParticipant,
|
|
26
30
|
DEFAULT_CACHE_TTL_SECONDS: () => DEFAULT_CACHE_TTL_SECONDS,
|
|
27
31
|
FIRST_PARTY: () => FIRST_PARTY,
|
|
28
32
|
GitHubCopilotParticipant: () => GitHubCopilotParticipant,
|
|
29
33
|
HermesParticipant: () => HermesParticipant,
|
|
34
|
+
MIN_PATCHED_VERSION: () => MIN_PATCHED_VERSION,
|
|
30
35
|
NETWORK_CONTRACT_VERSION: () => NETWORK_CONTRACT_VERSION,
|
|
31
36
|
NetworkParticipant: () => NetworkParticipant,
|
|
32
37
|
NetworkUnavailable: () => NetworkUnavailable,
|
|
38
|
+
OpenClawApiClient: () => OpenClawApiClient,
|
|
39
|
+
OpenClawParticipant: () => OpenClawParticipant,
|
|
33
40
|
ParticipantAuthError: () => ParticipantAuthError,
|
|
34
41
|
ParticipantDiscoveryError: () => ParticipantDiscoveryError,
|
|
35
42
|
ParticipantError: () => ParticipantError,
|
|
@@ -37,14 +44,14 @@ __export(index_exports, {
|
|
|
37
44
|
ParticipantVersionMismatchError: () => ParticipantVersionMismatchError,
|
|
38
45
|
PayloadCache: () => PayloadCache,
|
|
39
46
|
SPEC_VERSION: () => SPEC_VERSION,
|
|
40
|
-
Visibility: () =>
|
|
41
|
-
applyVisibility: () =>
|
|
47
|
+
Visibility: () => import_l59.Visibility,
|
|
48
|
+
applyVisibility: () => import_l59.applyVisibility,
|
|
42
49
|
defaultCacheRoot: () => defaultCacheRoot,
|
|
43
50
|
discoverParticipants: () => discoverParticipants,
|
|
44
51
|
envAuthProvider: () => envAuthProvider,
|
|
45
52
|
epochToIsoDate: () => epochToIsoDate,
|
|
46
53
|
epochToIsoDatetime: () => epochToIsoDatetime,
|
|
47
|
-
filterForFederation: () =>
|
|
54
|
+
filterForFederation: () => import_l59.filterForFederation,
|
|
48
55
|
friendlyLabel: () => friendlyLabel,
|
|
49
56
|
ghTokenProvider: () => ghTokenProvider,
|
|
50
57
|
isParticipant: () => isParticipant,
|
|
@@ -54,9 +61,10 @@ __export(index_exports, {
|
|
|
54
61
|
sqliteAvailable: () => sqliteAvailable,
|
|
55
62
|
tableColumns: () => tableColumns,
|
|
56
63
|
tableExists: () => tableExists,
|
|
57
|
-
toDict: () =>
|
|
64
|
+
toDict: () => import_l59.toDict,
|
|
58
65
|
tryOpenReadonly: () => tryOpenReadonly,
|
|
59
|
-
validateManifest: () =>
|
|
66
|
+
validateManifest: () => import_l59.validateManifest,
|
|
67
|
+
verifyInstance: () => verifyInstance
|
|
60
68
|
});
|
|
61
69
|
module.exports = __toCommonJS(index_exports);
|
|
62
70
|
|
|
@@ -1389,13 +1397,1820 @@ function isoDate(value) {
|
|
|
1389
1397
|
return null;
|
|
1390
1398
|
}
|
|
1391
1399
|
|
|
1400
|
+
// src/readers/cursor.ts
|
|
1401
|
+
var import_node_fs5 = require("fs");
|
|
1402
|
+
var import_node_os4 = require("os");
|
|
1403
|
+
var import_node_path4 = require("path");
|
|
1404
|
+
var import_l54 = require("@getbourdon/l5");
|
|
1405
|
+
var import_redaction4 = require("@getbourdon/redaction");
|
|
1406
|
+
var AGENT_ID4 = "cursor";
|
|
1407
|
+
var AGENT_TYPE4 = "code-assistant";
|
|
1408
|
+
var ROLE_NARRATIVE4 = "AI-first IDE. Bourdon reads the SQLite-backed composer/workspace state to surface recent sessions and project entities to other agents.";
|
|
1409
|
+
var DEFAULT_POLICY4 = (0, import_l54.makeVisibilityPolicy)({
|
|
1410
|
+
default: import_l54.Visibility.TEAM,
|
|
1411
|
+
private_tags: ["personal", "credential", "secret"],
|
|
1412
|
+
team_tags: ["cursor", "workspace", "sqlite"]
|
|
1413
|
+
});
|
|
1414
|
+
var TOPIC_MIN_MENTIONS = 3;
|
|
1415
|
+
function homeDir2() {
|
|
1416
|
+
try {
|
|
1417
|
+
return (0, import_node_os4.homedir)();
|
|
1418
|
+
} catch {
|
|
1419
|
+
return process.env.HOME || process.env.USERPROFILE || ".";
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
function isDir3(p) {
|
|
1423
|
+
try {
|
|
1424
|
+
return (0, import_node_fs5.statSync)(p).isDirectory();
|
|
1425
|
+
} catch {
|
|
1426
|
+
return false;
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
function isFile3(p) {
|
|
1430
|
+
try {
|
|
1431
|
+
return (0, import_node_fs5.statSync)(p).isFile();
|
|
1432
|
+
} catch {
|
|
1433
|
+
return false;
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
function scrubText(value, limit = 256) {
|
|
1437
|
+
return (0, import_redaction4.redactText)(value, limit);
|
|
1438
|
+
}
|
|
1439
|
+
function defaultCursorDir() {
|
|
1440
|
+
const env = process.env.CURSOR_DIR;
|
|
1441
|
+
if (env) return env;
|
|
1442
|
+
switch (process.platform) {
|
|
1443
|
+
case "darwin":
|
|
1444
|
+
return (0, import_node_path4.join)(homeDir2(), "Library", "Application Support", "Cursor");
|
|
1445
|
+
case "linux":
|
|
1446
|
+
return (0, import_node_path4.join)(homeDir2(), ".config", "Cursor");
|
|
1447
|
+
case "win32": {
|
|
1448
|
+
const appdata = process.env.APPDATA;
|
|
1449
|
+
return appdata ? (0, import_node_path4.join)(appdata, "Cursor") : null;
|
|
1450
|
+
}
|
|
1451
|
+
default:
|
|
1452
|
+
return null;
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
function iterStateDbs(root) {
|
|
1456
|
+
const candidates = [
|
|
1457
|
+
(0, import_node_path4.join)(root, "state.vscdb"),
|
|
1458
|
+
(0, import_node_path4.join)(root, "User", "globalStorage", "state.vscdb")
|
|
1459
|
+
];
|
|
1460
|
+
const workspaceStorage = (0, import_node_path4.join)(root, "User", "workspaceStorage");
|
|
1461
|
+
if (isDir3(workspaceStorage)) {
|
|
1462
|
+
let children = [];
|
|
1463
|
+
try {
|
|
1464
|
+
children = (0, import_node_fs5.readdirSync)(workspaceStorage).sort();
|
|
1465
|
+
} catch {
|
|
1466
|
+
children = [];
|
|
1467
|
+
}
|
|
1468
|
+
for (const child of children) {
|
|
1469
|
+
candidates.push((0, import_node_path4.join)(workspaceStorage, child, "state.vscdb"));
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
return candidates.filter((p) => isFile3(p));
|
|
1473
|
+
}
|
|
1474
|
+
function readItemTable(dbPath) {
|
|
1475
|
+
const records = [];
|
|
1476
|
+
let malformed = 0;
|
|
1477
|
+
const conn = tryOpenReadonly(dbPath);
|
|
1478
|
+
if (!conn) return { records, malformed };
|
|
1479
|
+
let rows;
|
|
1480
|
+
try {
|
|
1481
|
+
if (!tableExists(conn, "ItemTable")) return { records, malformed };
|
|
1482
|
+
rows = conn.prepare("SELECT key, value FROM ItemTable").all();
|
|
1483
|
+
} catch {
|
|
1484
|
+
return { records, malformed };
|
|
1485
|
+
} finally {
|
|
1486
|
+
conn.close();
|
|
1487
|
+
}
|
|
1488
|
+
for (const row of rows) {
|
|
1489
|
+
const raw = row.value;
|
|
1490
|
+
let text = null;
|
|
1491
|
+
if (typeof raw === "string") text = raw;
|
|
1492
|
+
else if (raw instanceof Uint8Array) text = Buffer.from(raw).toString("utf8");
|
|
1493
|
+
if (text === null) {
|
|
1494
|
+
malformed += 1;
|
|
1495
|
+
continue;
|
|
1496
|
+
}
|
|
1497
|
+
let value;
|
|
1498
|
+
try {
|
|
1499
|
+
value = JSON.parse(text);
|
|
1500
|
+
} catch {
|
|
1501
|
+
malformed += 1;
|
|
1502
|
+
continue;
|
|
1503
|
+
}
|
|
1504
|
+
records.push([String(row.key), value]);
|
|
1505
|
+
}
|
|
1506
|
+
return { records, malformed };
|
|
1507
|
+
}
|
|
1508
|
+
function isRecord(value) {
|
|
1509
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1510
|
+
}
|
|
1511
|
+
function firstString(value, keys) {
|
|
1512
|
+
for (const key of keys) {
|
|
1513
|
+
const candidate = value[key];
|
|
1514
|
+
if (typeof candidate === "string" && candidate.trim()) return candidate.trim();
|
|
1515
|
+
}
|
|
1516
|
+
return "";
|
|
1517
|
+
}
|
|
1518
|
+
function firstMessageText(messages) {
|
|
1519
|
+
if (!Array.isArray(messages)) return "";
|
|
1520
|
+
for (const message of messages) {
|
|
1521
|
+
if (!isRecord(message)) continue;
|
|
1522
|
+
const content = message["content"] || message["text"];
|
|
1523
|
+
if (typeof content === "string" && content.trim()) return content.trim();
|
|
1524
|
+
}
|
|
1525
|
+
return "";
|
|
1526
|
+
}
|
|
1527
|
+
function looksLikeComposerRecord(key, value) {
|
|
1528
|
+
const loweredKey = key.toLowerCase();
|
|
1529
|
+
if (loweredKey.includes("composer") || loweredKey.includes("aichat") || loweredKey.includes("chat")) {
|
|
1530
|
+
return true;
|
|
1531
|
+
}
|
|
1532
|
+
if (isRecord(value)) {
|
|
1533
|
+
const joinedKeys = Object.keys(value).join(" ").toLowerCase();
|
|
1534
|
+
return joinedKeys.includes("message") && (joinedKeys.includes("workspace") || joinedKeys.includes("file"));
|
|
1535
|
+
}
|
|
1536
|
+
return false;
|
|
1537
|
+
}
|
|
1538
|
+
function parseDate(value) {
|
|
1539
|
+
if (typeof value === "string") {
|
|
1540
|
+
const normalized = value.replaceAll("Z", "+00:00");
|
|
1541
|
+
const m = /^(\d{4}-\d{2}-\d{2})([T ].*)?$/.exec(normalized);
|
|
1542
|
+
if (!m) return "";
|
|
1543
|
+
const datePart = m[1] ?? "";
|
|
1544
|
+
const dayProbe = /* @__PURE__ */ new Date(`${datePart}T00:00:00Z`);
|
|
1545
|
+
if (Number.isNaN(dayProbe.getTime()) || dayProbe.toISOString().slice(0, 10) !== datePart) {
|
|
1546
|
+
return "";
|
|
1547
|
+
}
|
|
1548
|
+
const probe = new Date(m[2] ? normalized.replace(" ", "T") : `${datePart}T00:00:00`);
|
|
1549
|
+
if (Number.isNaN(probe.getTime())) return "";
|
|
1550
|
+
return datePart;
|
|
1551
|
+
}
|
|
1552
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
1553
|
+
const timestamp = value > 1e10 ? value / 1e3 : value;
|
|
1554
|
+
const dt = new Date(timestamp * 1e3);
|
|
1555
|
+
if (Number.isNaN(dt.getTime())) return "";
|
|
1556
|
+
return dt.toISOString().slice(0, 10);
|
|
1557
|
+
}
|
|
1558
|
+
return "";
|
|
1559
|
+
}
|
|
1560
|
+
function dateFromRecord(value) {
|
|
1561
|
+
for (const key of ["createdAt", "timestamp", "updatedAt", "lastUpdatedAt", "time"]) {
|
|
1562
|
+
const parsed = parseDate(value[key]);
|
|
1563
|
+
if (parsed) return parsed;
|
|
1564
|
+
}
|
|
1565
|
+
return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
1566
|
+
}
|
|
1567
|
+
function stringsFromList(values) {
|
|
1568
|
+
const strings = [];
|
|
1569
|
+
for (const item of values) {
|
|
1570
|
+
if (typeof item === "string" && item.trim()) {
|
|
1571
|
+
strings.push(item.trim());
|
|
1572
|
+
} else if (isRecord(item)) {
|
|
1573
|
+
const path = firstString(item, ["path", "file", "uri", "relativePath"]);
|
|
1574
|
+
if (path) strings.push(path);
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
return strings;
|
|
1578
|
+
}
|
|
1579
|
+
function dedupe(values) {
|
|
1580
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1581
|
+
const out = [];
|
|
1582
|
+
for (const value of values) {
|
|
1583
|
+
if (!seen.has(value)) {
|
|
1584
|
+
seen.add(value);
|
|
1585
|
+
out.push(value);
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
return out;
|
|
1589
|
+
}
|
|
1590
|
+
function extractFiles(value) {
|
|
1591
|
+
const files = [];
|
|
1592
|
+
for (const key of ["files", "filePaths", "filesTouched", "references"]) {
|
|
1593
|
+
const candidate = value[key];
|
|
1594
|
+
if (Array.isArray(candidate)) files.push(...stringsFromList(candidate));
|
|
1595
|
+
}
|
|
1596
|
+
const messages = value["messages"];
|
|
1597
|
+
if (Array.isArray(messages)) {
|
|
1598
|
+
for (const message of messages) {
|
|
1599
|
+
if (isRecord(message)) files.push(...extractFiles(message));
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
return dedupe(files);
|
|
1603
|
+
}
|
|
1604
|
+
function recordToSession(key, value) {
|
|
1605
|
+
if (!looksLikeComposerRecord(key, value)) return null;
|
|
1606
|
+
if (!isRecord(value)) return null;
|
|
1607
|
+
let cwd = firstString(value, ["workspacePath", "workspace", "cwd", "folder", "rootPath"]);
|
|
1608
|
+
if (!cwd && isRecord(value["workspace"])) {
|
|
1609
|
+
cwd = firstString(value["workspace"], ["path", "folder", "cwd"]);
|
|
1610
|
+
}
|
|
1611
|
+
if (!cwd) cwd = "";
|
|
1612
|
+
let action = firstString(value, ["title", "text", "prompt", "summary", "name"]);
|
|
1613
|
+
if (!action) action = firstMessageText(value["messages"]);
|
|
1614
|
+
if (!action) return null;
|
|
1615
|
+
return {
|
|
1616
|
+
date: dateFromRecord(value),
|
|
1617
|
+
cwd,
|
|
1618
|
+
keyActions: [scrubText(action)],
|
|
1619
|
+
filesTouched: extractFiles(value)
|
|
1620
|
+
};
|
|
1621
|
+
}
|
|
1622
|
+
function projectName(cwd) {
|
|
1623
|
+
if (!cwd) return "";
|
|
1624
|
+
const name = (0, import_node_path4.basename)(cwd).trim();
|
|
1625
|
+
return name && name !== "." && name !== "/" ? name : "";
|
|
1626
|
+
}
|
|
1627
|
+
var STRIP_CHARS = /* @__PURE__ */ new Set([
|
|
1628
|
+
".",
|
|
1629
|
+
",",
|
|
1630
|
+
";",
|
|
1631
|
+
":",
|
|
1632
|
+
"!",
|
|
1633
|
+
"?",
|
|
1634
|
+
'"',
|
|
1635
|
+
"'",
|
|
1636
|
+
"`",
|
|
1637
|
+
"(",
|
|
1638
|
+
")",
|
|
1639
|
+
"[",
|
|
1640
|
+
"]",
|
|
1641
|
+
"{",
|
|
1642
|
+
"}",
|
|
1643
|
+
"#",
|
|
1644
|
+
"-",
|
|
1645
|
+
"/"
|
|
1646
|
+
]);
|
|
1647
|
+
function stripChars(token) {
|
|
1648
|
+
let start = 0;
|
|
1649
|
+
let end = token.length;
|
|
1650
|
+
while (start < end && STRIP_CHARS.has(token[start])) start += 1;
|
|
1651
|
+
while (end > start && STRIP_CHARS.has(token[end - 1])) end -= 1;
|
|
1652
|
+
return token.slice(start, end);
|
|
1653
|
+
}
|
|
1654
|
+
var TOPIC_STOPWORDS = /* @__PURE__ */ new Set([
|
|
1655
|
+
"the",
|
|
1656
|
+
"and",
|
|
1657
|
+
"for",
|
|
1658
|
+
"with",
|
|
1659
|
+
"this",
|
|
1660
|
+
"that",
|
|
1661
|
+
"from",
|
|
1662
|
+
"have",
|
|
1663
|
+
"will",
|
|
1664
|
+
"are",
|
|
1665
|
+
"was",
|
|
1666
|
+
"been",
|
|
1667
|
+
"not",
|
|
1668
|
+
"but",
|
|
1669
|
+
"all",
|
|
1670
|
+
"can",
|
|
1671
|
+
"has",
|
|
1672
|
+
"its",
|
|
1673
|
+
"add",
|
|
1674
|
+
"use",
|
|
1675
|
+
"set",
|
|
1676
|
+
"get",
|
|
1677
|
+
"new",
|
|
1678
|
+
"fix",
|
|
1679
|
+
"run",
|
|
1680
|
+
"now",
|
|
1681
|
+
"also",
|
|
1682
|
+
"into",
|
|
1683
|
+
"make",
|
|
1684
|
+
"just",
|
|
1685
|
+
"like",
|
|
1686
|
+
"need",
|
|
1687
|
+
"want",
|
|
1688
|
+
"work",
|
|
1689
|
+
"code",
|
|
1690
|
+
"file",
|
|
1691
|
+
"test",
|
|
1692
|
+
"data",
|
|
1693
|
+
"type",
|
|
1694
|
+
"should",
|
|
1695
|
+
"would",
|
|
1696
|
+
"could"
|
|
1697
|
+
]);
|
|
1698
|
+
function addTopicEntities(sessions, entities) {
|
|
1699
|
+
const wordCounts = /* @__PURE__ */ new Map();
|
|
1700
|
+
const wordLatest = /* @__PURE__ */ new Map();
|
|
1701
|
+
for (const session of sessions) {
|
|
1702
|
+
for (const action of session.keyActions) {
|
|
1703
|
+
const words = /* @__PURE__ */ new Set();
|
|
1704
|
+
for (const token of action.toLowerCase().split(/\s+/)) {
|
|
1705
|
+
const cleaned = stripChars(token);
|
|
1706
|
+
if (cleaned.length >= 4 && !TOPIC_STOPWORDS.has(cleaned) && new RegExp("^\\p{L}+$", "u").test(cleaned)) {
|
|
1707
|
+
words.add(cleaned);
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1710
|
+
for (const word of words) {
|
|
1711
|
+
wordCounts.set(word, (wordCounts.get(word) ?? 0) + 1);
|
|
1712
|
+
if (session.date) {
|
|
1713
|
+
const prev = wordLatest.get(word);
|
|
1714
|
+
if (prev === void 0 || session.date > prev) wordLatest.set(word, session.date);
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
}
|
|
1719
|
+
for (const [word, count] of wordCounts) {
|
|
1720
|
+
if (count >= TOPIC_MIN_MENTIONS && !entities.has(word)) {
|
|
1721
|
+
entities.set(word, {
|
|
1722
|
+
name: word,
|
|
1723
|
+
entityType: "topic",
|
|
1724
|
+
aliases: [],
|
|
1725
|
+
summary: `Recurring topic across ${count} Cursor sessions.`,
|
|
1726
|
+
tags: ["cursor", "topic", "inferred"],
|
|
1727
|
+
lastUpdated: wordLatest.get(word) ?? ""
|
|
1728
|
+
});
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
}
|
|
1732
|
+
function extractCursorMemories(cursorDir = null) {
|
|
1733
|
+
const root = cursorDir ?? defaultCursorDir();
|
|
1734
|
+
if (root === null || !isDir3(root)) {
|
|
1735
|
+
return { sessions: [], entities: [], databasesScanned: [], malformedRecords: 0 };
|
|
1736
|
+
}
|
|
1737
|
+
const sessions = [];
|
|
1738
|
+
const entitiesByName = /* @__PURE__ */ new Map();
|
|
1739
|
+
const databasesScanned = [];
|
|
1740
|
+
let malformedRecords = 0;
|
|
1741
|
+
const projectLatest = /* @__PURE__ */ new Map();
|
|
1742
|
+
for (const dbPath of iterStateDbs(root)) {
|
|
1743
|
+
databasesScanned.push(dbPath);
|
|
1744
|
+
const { records, malformed } = readItemTable(dbPath);
|
|
1745
|
+
malformedRecords += malformed;
|
|
1746
|
+
for (const [key, value] of records) {
|
|
1747
|
+
const parsedSession = recordToSession(key, value);
|
|
1748
|
+
if (parsedSession === null) continue;
|
|
1749
|
+
sessions.push(parsedSession);
|
|
1750
|
+
const name = projectName(parsedSession.cwd);
|
|
1751
|
+
if (!name) continue;
|
|
1752
|
+
const prev = projectLatest.get(name) ?? "";
|
|
1753
|
+
if (!projectLatest.has(name) || parsedSession.date && parsedSession.date > prev) {
|
|
1754
|
+
projectLatest.set(name, parsedSession.date);
|
|
1755
|
+
}
|
|
1756
|
+
const existing = entitiesByName.get(name);
|
|
1757
|
+
const aliasSet = new Set(parsedSession.cwd ? [parsedSession.cwd] : []);
|
|
1758
|
+
if (existing) {
|
|
1759
|
+
for (const a of existing.aliases) aliasSet.add(a);
|
|
1760
|
+
}
|
|
1761
|
+
entitiesByName.set(name, {
|
|
1762
|
+
name: scrubText(name, 120),
|
|
1763
|
+
entityType: "project",
|
|
1764
|
+
aliases: [...aliasSet].sort(),
|
|
1765
|
+
summary: scrubText(`Cursor workspace inferred from ${parsedSession.cwd}.`),
|
|
1766
|
+
tags: ["cursor", "workspace", "sqlite"],
|
|
1767
|
+
lastUpdated: projectLatest.get(name) ?? ""
|
|
1768
|
+
});
|
|
1769
|
+
}
|
|
1770
|
+
}
|
|
1771
|
+
addTopicEntities(sessions, entitiesByName);
|
|
1772
|
+
sessions.sort((a, b) => a.date < b.date ? 1 : a.date > b.date ? -1 : 0);
|
|
1773
|
+
return {
|
|
1774
|
+
sessions,
|
|
1775
|
+
entities: [...entitiesByName.values()],
|
|
1776
|
+
databasesScanned,
|
|
1777
|
+
malformedRecords
|
|
1778
|
+
};
|
|
1779
|
+
}
|
|
1780
|
+
function readShortIndex(path) {
|
|
1781
|
+
if (!isFile3(path)) return [];
|
|
1782
|
+
let payload;
|
|
1783
|
+
try {
|
|
1784
|
+
const raw = (0, import_node_fs5.readFileSync)(path, "utf8").replace(/^\uFEFF/, "");
|
|
1785
|
+
payload = JSON.parse(raw);
|
|
1786
|
+
} catch {
|
|
1787
|
+
return [];
|
|
1788
|
+
}
|
|
1789
|
+
if (!isRecord(payload)) return [];
|
|
1790
|
+
const entries = payload["entries"];
|
|
1791
|
+
if (!Array.isArray(entries)) return [];
|
|
1792
|
+
return entries.filter(
|
|
1793
|
+
(e) => isRecord(e) && Boolean(e["topic_key"])
|
|
1794
|
+
);
|
|
1795
|
+
}
|
|
1796
|
+
function shortIndexToEntities(workspaceRoot, _cursorDir) {
|
|
1797
|
+
const paths = [];
|
|
1798
|
+
if (workspaceRoot) {
|
|
1799
|
+
paths.push((0, import_node_path4.join)(workspaceRoot, ".cursor", "memory", "short-index.json"));
|
|
1800
|
+
}
|
|
1801
|
+
const cursorHome = process.env.CURSOR_DIR ? process.env.CURSOR_DIR : null;
|
|
1802
|
+
const homeBase = cursorHome ?? (0, import_node_path4.join)(homeDir2(), ".cursor");
|
|
1803
|
+
paths.push((0, import_node_path4.join)(homeBase, "memory", "short-index.json"));
|
|
1804
|
+
const merged = /* @__PURE__ */ new Map();
|
|
1805
|
+
for (const path of paths) {
|
|
1806
|
+
for (const entry of readShortIndex(path)) {
|
|
1807
|
+
const key = String(entry["topic_key"]).trim().toLowerCase();
|
|
1808
|
+
merged.set(key, entry);
|
|
1809
|
+
}
|
|
1810
|
+
}
|
|
1811
|
+
const entities = [];
|
|
1812
|
+
for (const entry of merged.values()) {
|
|
1813
|
+
const nameRaw = entry["topic_name"] !== void 0 ? entry["topic_name"] : entry["topic_key"] ?? "";
|
|
1814
|
+
const name = String(nameRaw).trim();
|
|
1815
|
+
if (!name) continue;
|
|
1816
|
+
const aliasesRaw = "triggers" in entry ? entry["triggers"] : "aliases" in entry ? entry["aliases"] : [];
|
|
1817
|
+
const aliases = Array.isArray(aliasesRaw) ? aliasesRaw.map((a) => String(a).trim()).filter((s) => s.length > 0) : [];
|
|
1818
|
+
const summary = String(entry["summary"] ?? "").trim();
|
|
1819
|
+
const tagsRaw = entry["tags"] ?? [];
|
|
1820
|
+
const tags = Array.isArray(tagsRaw) ? tagsRaw.map((t) => String(t).trim()).filter((s) => s.length > 0) : [];
|
|
1821
|
+
tags.push("short-index");
|
|
1822
|
+
const access = String(entry["access_level"] ?? "team").trim().toLowerCase();
|
|
1823
|
+
const visibility = access === "private" ? import_l54.Visibility.PRIVATE : access === "public" ? import_l54.Visibility.PUBLIC : import_l54.Visibility.TEAM;
|
|
1824
|
+
const lastTouched = String(entry["last_updated"] ?? "");
|
|
1825
|
+
entities.push(
|
|
1826
|
+
(0, import_l54.makeEntity)({
|
|
1827
|
+
name,
|
|
1828
|
+
type: "topic",
|
|
1829
|
+
aliases,
|
|
1830
|
+
summary: summary || void 0,
|
|
1831
|
+
tags,
|
|
1832
|
+
last_touched: lastTouched || void 0,
|
|
1833
|
+
visibility
|
|
1834
|
+
})
|
|
1835
|
+
);
|
|
1836
|
+
}
|
|
1837
|
+
return entities;
|
|
1838
|
+
}
|
|
1839
|
+
function toSession(raw) {
|
|
1840
|
+
return (0, import_l54.makeSession)({
|
|
1841
|
+
date: raw.date || "",
|
|
1842
|
+
cwd: raw.cwd || void 0,
|
|
1843
|
+
project_focus: [],
|
|
1844
|
+
key_actions: [...raw.keyActions],
|
|
1845
|
+
files_touched: [...raw.filesTouched]
|
|
1846
|
+
});
|
|
1847
|
+
}
|
|
1848
|
+
function toEntity(raw) {
|
|
1849
|
+
return (0, import_l54.makeEntity)({
|
|
1850
|
+
name: raw.name,
|
|
1851
|
+
type: raw.entityType || void 0,
|
|
1852
|
+
aliases: [...raw.aliases],
|
|
1853
|
+
summary: raw.summary || void 0,
|
|
1854
|
+
tags: [...raw.tags],
|
|
1855
|
+
last_touched: raw.lastUpdated || void 0
|
|
1856
|
+
});
|
|
1857
|
+
}
|
|
1858
|
+
function nowIsoSeconds() {
|
|
1859
|
+
return (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "+00:00");
|
|
1860
|
+
}
|
|
1861
|
+
var CursorParticipant = class {
|
|
1862
|
+
agentId = AGENT_ID4;
|
|
1863
|
+
agentType = AGENT_TYPE4;
|
|
1864
|
+
cursorDir;
|
|
1865
|
+
workspaceRoot;
|
|
1866
|
+
policy = DEFAULT_POLICY4;
|
|
1867
|
+
/**
|
|
1868
|
+
* Conventional Cursor config dir (`~/.cursor`) used by the setup wizard. The
|
|
1869
|
+
* wizard probes `~/.cursor` for presence; the SQLite workspace state that
|
|
1870
|
+
* `exportL5` actually reads lives at {@link defaultCursorDir}. Detection and
|
|
1871
|
+
* extraction intentionally use different anchors.
|
|
1872
|
+
*/
|
|
1873
|
+
static defaultNativePath(home) {
|
|
1874
|
+
return (0, import_node_path4.join)(home ?? homeDir2(), ".cursor");
|
|
1875
|
+
}
|
|
1876
|
+
constructor(cursorDir, workspaceRoot) {
|
|
1877
|
+
this.cursorDir = cursorDir ?? null;
|
|
1878
|
+
this.workspaceRoot = workspaceRoot ?? null;
|
|
1879
|
+
}
|
|
1880
|
+
get nativePath() {
|
|
1881
|
+
const path = this.cursorDir ?? defaultCursorDir();
|
|
1882
|
+
return path ?? "";
|
|
1883
|
+
}
|
|
1884
|
+
discover() {
|
|
1885
|
+
const path = this.cursorDir ?? defaultCursorDir();
|
|
1886
|
+
if (path === null || !isDir3(path)) {
|
|
1887
|
+
throw new ParticipantDiscoveryError(
|
|
1888
|
+
`Cursor data directory not found at ${path === null ? "None" : `'${path}'`}. Pass an explicit cursor_dir to CursorParticipant() if Cursor stores its state somewhere non-standard.`
|
|
1889
|
+
);
|
|
1890
|
+
}
|
|
1891
|
+
const dbs = iterStateDbs(path);
|
|
1892
|
+
const dbDetails = dbs.map((db) => {
|
|
1893
|
+
let size;
|
|
1894
|
+
try {
|
|
1895
|
+
size = (0, import_node_fs5.statSync)(db).size;
|
|
1896
|
+
} catch {
|
|
1897
|
+
size = -1;
|
|
1898
|
+
}
|
|
1899
|
+
return { path: db, size_bytes: size };
|
|
1900
|
+
});
|
|
1901
|
+
return {
|
|
1902
|
+
path,
|
|
1903
|
+
version: "unknown",
|
|
1904
|
+
metadata: {
|
|
1905
|
+
platform_default: defaultCursorDir() ?? "None",
|
|
1906
|
+
databases_found: dbs.length,
|
|
1907
|
+
databases: dbDetails
|
|
1908
|
+
}
|
|
1909
|
+
};
|
|
1910
|
+
}
|
|
1911
|
+
exportSessions(since, limit = 100) {
|
|
1912
|
+
const memories = this.extract();
|
|
1913
|
+
const out = [];
|
|
1914
|
+
const sinceIso = since ? since.toISOString().slice(0, 10) : null;
|
|
1915
|
+
for (const raw of memories.sessions) {
|
|
1916
|
+
if (sinceIso && raw.date && raw.date < sinceIso) continue;
|
|
1917
|
+
out.push(toSession(raw));
|
|
1918
|
+
if (out.length >= limit) break;
|
|
1919
|
+
}
|
|
1920
|
+
return out;
|
|
1921
|
+
}
|
|
1922
|
+
exportL5(since) {
|
|
1923
|
+
const memories = this.extract();
|
|
1924
|
+
let sessions = memories.sessions.map(toSession);
|
|
1925
|
+
if (since) {
|
|
1926
|
+
const sinceIso = since.toISOString().slice(0, 10);
|
|
1927
|
+
sessions = sessions.filter((s) => !s.date || s.date >= sinceIso);
|
|
1928
|
+
}
|
|
1929
|
+
const entities = memories.entities.map(toEntity);
|
|
1930
|
+
const shortIndexEntities = shortIndexToEntities(this.workspaceRoot, this.cursorDir);
|
|
1931
|
+
const seenNames = new Set(entities.map((e) => e.name.toLowerCase()));
|
|
1932
|
+
for (const si of shortIndexEntities) {
|
|
1933
|
+
const key = si.name.toLowerCase();
|
|
1934
|
+
if (!seenNames.has(key)) {
|
|
1935
|
+
entities.push(si);
|
|
1936
|
+
seenNames.add(key);
|
|
1937
|
+
}
|
|
1938
|
+
}
|
|
1939
|
+
const visibleEntities = (0, import_l54.filterForFederation)(entities, this.policy);
|
|
1940
|
+
return (0, import_l54.makeManifest)({
|
|
1941
|
+
spec_version: SPEC_VERSION,
|
|
1942
|
+
agent: (0, import_l54.makeAgentInfo)({
|
|
1943
|
+
id: AGENT_ID4,
|
|
1944
|
+
type: AGENT_TYPE4,
|
|
1945
|
+
role_narrative: ROLE_NARRATIVE4,
|
|
1946
|
+
spec_version_compat: SPEC_VERSION
|
|
1947
|
+
}),
|
|
1948
|
+
last_updated: nowIsoSeconds(),
|
|
1949
|
+
capabilities: ["composer-history", "workspace-state"],
|
|
1950
|
+
recent_sessions: sessions,
|
|
1951
|
+
known_entities: visibleEntities,
|
|
1952
|
+
visibility_policy: this.policy
|
|
1953
|
+
});
|
|
1954
|
+
}
|
|
1955
|
+
healthCheck() {
|
|
1956
|
+
const path = this.cursorDir ?? defaultCursorDir();
|
|
1957
|
+
if (path === null) {
|
|
1958
|
+
return {
|
|
1959
|
+
status: "blocked",
|
|
1960
|
+
reason: "Cursor data directory not resolvable on this platform.",
|
|
1961
|
+
details: {},
|
|
1962
|
+
proposedFix: "Install Cursor (https://cursor.sh) and open it once. Set CURSOR_DIR if Cursor stores state outside the default location."
|
|
1963
|
+
};
|
|
1964
|
+
}
|
|
1965
|
+
if (!isDir3(path)) {
|
|
1966
|
+
return {
|
|
1967
|
+
status: "blocked",
|
|
1968
|
+
reason: `Cursor data directory not present at ${path}.`,
|
|
1969
|
+
details: { expected_path: path },
|
|
1970
|
+
proposedFix: "Install Cursor and open it once to create the data directory, or set CURSOR_DIR to the actual location."
|
|
1971
|
+
};
|
|
1972
|
+
}
|
|
1973
|
+
let memories;
|
|
1974
|
+
try {
|
|
1975
|
+
memories = this.extract();
|
|
1976
|
+
} catch (err) {
|
|
1977
|
+
return {
|
|
1978
|
+
status: "degraded",
|
|
1979
|
+
reason: "Cursor data directory present but extraction failed.",
|
|
1980
|
+
details: { error: String(err) },
|
|
1981
|
+
proposedFix: "Close Cursor (its SQLite stores may be locked) then re-run `bourdon cursor export`. If extraction still fails, file an issue with the error above."
|
|
1982
|
+
};
|
|
1983
|
+
}
|
|
1984
|
+
if (memories.databasesScanned.length === 0) {
|
|
1985
|
+
return {
|
|
1986
|
+
status: "degraded",
|
|
1987
|
+
reason: "No Cursor SQLite stores found under the data directory.",
|
|
1988
|
+
details: { path },
|
|
1989
|
+
proposedFix: "Open Cursor and use it for at least one chat session, then re-run `bourdon cursor export`."
|
|
1990
|
+
};
|
|
1991
|
+
}
|
|
1992
|
+
return {
|
|
1993
|
+
status: "ok",
|
|
1994
|
+
details: {
|
|
1995
|
+
databases_scanned: memories.databasesScanned.length,
|
|
1996
|
+
sessions_extracted: memories.sessions.length,
|
|
1997
|
+
entities_extracted: memories.entities.length,
|
|
1998
|
+
malformed_records: memories.malformedRecords
|
|
1999
|
+
}
|
|
2000
|
+
};
|
|
2001
|
+
}
|
|
2002
|
+
extract() {
|
|
2003
|
+
return extractCursorMemories(this.cursorDir);
|
|
2004
|
+
}
|
|
2005
|
+
};
|
|
2006
|
+
|
|
2007
|
+
// src/readers/copilot.ts
|
|
2008
|
+
var import_node_fs6 = require("fs");
|
|
2009
|
+
var import_node_os5 = require("os");
|
|
2010
|
+
var import_node_path5 = require("path");
|
|
2011
|
+
var import_yaml2 = require("yaml");
|
|
2012
|
+
var import_l55 = require("@getbourdon/l5");
|
|
2013
|
+
var import_redaction5 = require("@getbourdon/redaction");
|
|
2014
|
+
var AGENT_ID5 = "copilot";
|
|
2015
|
+
var AGENT_TYPE5 = "code-assistant";
|
|
2016
|
+
var DISPLAY_NAME3 = "GitHub Copilot";
|
|
2017
|
+
var ROLE_NARRATIVE5 = "Inline completion and chat assistant present across every IDE the team uses. Works alongside the human at the keystroke level -- the ambient layer that recognises what is being typed before a full turn is even formed. Bourdon gives Copilot cross-session entity awareness it would otherwise lack entirely.";
|
|
2018
|
+
var CONVENTION_DIR_NAME = ".copilot-bourdon";
|
|
2019
|
+
var MEMORY_FILENAME = "memory.md";
|
|
2020
|
+
var FRONTMATTER_OPEN2 = "---\n";
|
|
2021
|
+
var FRONTMATTER_CLOSE2 = "\n---\n";
|
|
2022
|
+
var DEFAULT_POLICY5 = (0, import_l55.makeVisibilityPolicy)({
|
|
2023
|
+
default: import_l55.Visibility.TEAM,
|
|
2024
|
+
private_tags: ["personal", "financial", "credential", "health", "family", "legal"],
|
|
2025
|
+
team_tags: ["copilot-memory", "workspace", "copilot"]
|
|
2026
|
+
});
|
|
2027
|
+
var logger = {
|
|
2028
|
+
warn(msg) {
|
|
2029
|
+
console.warn(`[bourdon.participants] ${msg}`);
|
|
2030
|
+
}
|
|
2031
|
+
};
|
|
2032
|
+
function homeDir3() {
|
|
2033
|
+
try {
|
|
2034
|
+
return (0, import_node_os5.homedir)();
|
|
2035
|
+
} catch {
|
|
2036
|
+
return process.env.HOME || process.env.USERPROFILE || ".";
|
|
2037
|
+
}
|
|
2038
|
+
}
|
|
2039
|
+
function isDir4(p) {
|
|
2040
|
+
try {
|
|
2041
|
+
return (0, import_node_fs6.statSync)(p).isDirectory();
|
|
2042
|
+
} catch {
|
|
2043
|
+
return false;
|
|
2044
|
+
}
|
|
2045
|
+
}
|
|
2046
|
+
function isFile4(p) {
|
|
2047
|
+
try {
|
|
2048
|
+
return (0, import_node_fs6.statSync)(p).isFile();
|
|
2049
|
+
} catch {
|
|
2050
|
+
return false;
|
|
2051
|
+
}
|
|
2052
|
+
}
|
|
2053
|
+
function defaultCopilotBourdonDir() {
|
|
2054
|
+
const env = process.env.COPILOT_BOURDON_HOME;
|
|
2055
|
+
if (env) return env;
|
|
2056
|
+
return (0, import_node_path5.join)(homeDir3(), CONVENTION_DIR_NAME);
|
|
2057
|
+
}
|
|
2058
|
+
function defaultCopilotMemoryPath(copilotDir) {
|
|
2059
|
+
return (0, import_node_path5.join)(copilotDir || defaultCopilotBourdonDir(), MEMORY_FILENAME);
|
|
2060
|
+
}
|
|
2061
|
+
function coerceStr(v) {
|
|
2062
|
+
if (v instanceof Date) return v.toISOString().slice(0, 10);
|
|
2063
|
+
return String(v);
|
|
2064
|
+
}
|
|
2065
|
+
function strOrUndef(v) {
|
|
2066
|
+
const s = coerceStr(v || "");
|
|
2067
|
+
return s || void 0;
|
|
2068
|
+
}
|
|
2069
|
+
function strList(v) {
|
|
2070
|
+
if (!Array.isArray(v)) return [];
|
|
2071
|
+
return v.filter((x) => typeof x === "string" && x.trim() !== "");
|
|
2072
|
+
}
|
|
2073
|
+
function isRecord2(v) {
|
|
2074
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
2075
|
+
}
|
|
2076
|
+
function asArray(v) {
|
|
2077
|
+
return Array.isArray(v) ? v : [];
|
|
2078
|
+
}
|
|
2079
|
+
function parseFrontmatter2(text, source) {
|
|
2080
|
+
if (!text.startsWith(FRONTMATTER_OPEN2)) return {};
|
|
2081
|
+
const end = text.indexOf(FRONTMATTER_CLOSE2, FRONTMATTER_OPEN2.length);
|
|
2082
|
+
if (end === -1) return {};
|
|
2083
|
+
const fmText = text.slice(FRONTMATTER_OPEN2.length, end);
|
|
2084
|
+
let parsed;
|
|
2085
|
+
try {
|
|
2086
|
+
parsed = (0, import_yaml2.parse)(fmText);
|
|
2087
|
+
} catch (err) {
|
|
2088
|
+
const where = source ? ` in ${source}` : "";
|
|
2089
|
+
const detail = String(err).replace(/\n/g, " ").slice(0, 200);
|
|
2090
|
+
logger.warn(
|
|
2091
|
+
`CopilotParticipant: malformed YAML frontmatter${where}; treating as no-frontmatter (${detail})`
|
|
2092
|
+
);
|
|
2093
|
+
return {};
|
|
2094
|
+
}
|
|
2095
|
+
return isRecord2(parsed) ? parsed : {};
|
|
2096
|
+
}
|
|
2097
|
+
function readMemoryFile(path) {
|
|
2098
|
+
if (!isFile4(path)) return {};
|
|
2099
|
+
let text;
|
|
2100
|
+
try {
|
|
2101
|
+
text = (0, import_node_fs6.readFileSync)(path, "utf8");
|
|
2102
|
+
} catch (err) {
|
|
2103
|
+
logger.warn(`CopilotParticipant: cannot read ${path}: ${String(err)}`);
|
|
2104
|
+
return {};
|
|
2105
|
+
}
|
|
2106
|
+
return parseFrontmatter2(text, path);
|
|
2107
|
+
}
|
|
2108
|
+
function buildEntity(raw) {
|
|
2109
|
+
if (!isRecord2(raw)) return null;
|
|
2110
|
+
const name = raw["name"];
|
|
2111
|
+
if (typeof name !== "string" || name.trim() === "") return null;
|
|
2112
|
+
const summaryRaw = raw["summary"] || "";
|
|
2113
|
+
const summary = summaryRaw ? (0, import_redaction5.redactText)(coerceStr(summaryRaw)) : void 0;
|
|
2114
|
+
return (0, import_l55.makeEntity)({
|
|
2115
|
+
name: name.trim(),
|
|
2116
|
+
type: strOrUndef(raw["type"]),
|
|
2117
|
+
aliases: strList(raw["aliases"]),
|
|
2118
|
+
summary,
|
|
2119
|
+
last_touched: strOrUndef(raw["last_touched"]),
|
|
2120
|
+
tags: strList(raw["tags"]),
|
|
2121
|
+
valid_from: strOrUndef(raw["valid_from"]),
|
|
2122
|
+
valid_to: strOrUndef(raw["valid_to"])
|
|
2123
|
+
});
|
|
2124
|
+
}
|
|
2125
|
+
function buildSession2(raw) {
|
|
2126
|
+
if (!isRecord2(raw)) return null;
|
|
2127
|
+
const dateVal = raw["date"];
|
|
2128
|
+
if (!dateVal) return null;
|
|
2129
|
+
const dateStr = coerceStr(dateVal).slice(0, 10);
|
|
2130
|
+
return (0, import_l55.makeSession)({
|
|
2131
|
+
date: dateStr,
|
|
2132
|
+
cwd: strOrUndef(raw["cwd"]),
|
|
2133
|
+
project_focus: strList(raw["project_focus"]),
|
|
2134
|
+
key_actions: strList(raw["key_actions"]),
|
|
2135
|
+
files_touched: strList(raw["files_touched"])
|
|
2136
|
+
});
|
|
2137
|
+
}
|
|
2138
|
+
var CopilotParticipant = class {
|
|
2139
|
+
agentId = AGENT_ID5;
|
|
2140
|
+
agentType = AGENT_TYPE5;
|
|
2141
|
+
displayName = DISPLAY_NAME3;
|
|
2142
|
+
nativePath;
|
|
2143
|
+
/** Conventional `~/.copilot-bourdon` dir used by the setup wizard's detection. */
|
|
2144
|
+
static defaultNativePath(home) {
|
|
2145
|
+
if (home !== void 0) return (0, import_node_path5.join)(home, CONVENTION_DIR_NAME);
|
|
2146
|
+
return defaultCopilotBourdonDir();
|
|
2147
|
+
}
|
|
2148
|
+
copilotDir;
|
|
2149
|
+
constructor(copilotDir) {
|
|
2150
|
+
this.copilotDir = copilotDir ?? null;
|
|
2151
|
+
this.nativePath = this.copilotDir || defaultCopilotBourdonDir();
|
|
2152
|
+
}
|
|
2153
|
+
discover() {
|
|
2154
|
+
const path = this.copilotDir || defaultCopilotBourdonDir();
|
|
2155
|
+
if (!isDir4(path)) {
|
|
2156
|
+
throw new ParticipantDiscoveryError(
|
|
2157
|
+
`Copilot convention directory not found at '${path}'. Run \`bourdon copilot init\` to create it with a starter template, or create ~/.copilot-bourdon/memory.md manually.`
|
|
2158
|
+
);
|
|
2159
|
+
}
|
|
2160
|
+
const memPath = (0, import_node_path5.join)(path, MEMORY_FILENAME);
|
|
2161
|
+
return {
|
|
2162
|
+
path,
|
|
2163
|
+
version: "convention-v1",
|
|
2164
|
+
metadata: {
|
|
2165
|
+
memory_file: memPath,
|
|
2166
|
+
memory_file_present: isFile4(memPath)
|
|
2167
|
+
}
|
|
2168
|
+
};
|
|
2169
|
+
}
|
|
2170
|
+
exportSessions(since, limit = 100) {
|
|
2171
|
+
const data = this.read();
|
|
2172
|
+
const sinceIso = since ? since.toISOString().slice(0, 10) : null;
|
|
2173
|
+
const sessions = [];
|
|
2174
|
+
for (const raw of asArray(data["sessions"])) {
|
|
2175
|
+
const session = buildSession2(raw);
|
|
2176
|
+
if (session === null) continue;
|
|
2177
|
+
if (sinceIso && session.date && session.date < sinceIso) continue;
|
|
2178
|
+
sessions.push(session);
|
|
2179
|
+
if (sessions.length >= limit) break;
|
|
2180
|
+
}
|
|
2181
|
+
return sessions;
|
|
2182
|
+
}
|
|
2183
|
+
exportL5(since) {
|
|
2184
|
+
const data = this.read();
|
|
2185
|
+
const entities = [];
|
|
2186
|
+
for (const raw of asArray(data["entities"])) {
|
|
2187
|
+
const entity = buildEntity(raw);
|
|
2188
|
+
if (entity !== null) entities.push(entity);
|
|
2189
|
+
}
|
|
2190
|
+
const visibleEntities = (0, import_l55.filterForFederation)(entities, DEFAULT_POLICY5);
|
|
2191
|
+
const sinceIso = since ? since.toISOString().slice(0, 10) : null;
|
|
2192
|
+
const sessions = [];
|
|
2193
|
+
for (const raw of asArray(data["sessions"])) {
|
|
2194
|
+
const session = buildSession2(raw);
|
|
2195
|
+
if (session === null) continue;
|
|
2196
|
+
if (sinceIso && session.date && session.date < sinceIso) continue;
|
|
2197
|
+
sessions.push(session);
|
|
2198
|
+
}
|
|
2199
|
+
return (0, import_l55.makeManifest)({
|
|
2200
|
+
spec_version: SPEC_VERSION,
|
|
2201
|
+
agent: (0, import_l55.makeAgentInfo)({
|
|
2202
|
+
id: AGENT_ID5,
|
|
2203
|
+
type: AGENT_TYPE5,
|
|
2204
|
+
instance: (0, import_node_os5.hostname)() || "unknown",
|
|
2205
|
+
spec_version_compat: `>=${SPEC_VERSION}`,
|
|
2206
|
+
role_narrative: ROLE_NARRATIVE5
|
|
2207
|
+
}),
|
|
2208
|
+
last_updated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2209
|
+
capabilities: ["inline-completion", "chat", "pr-review", "convention-memory"],
|
|
2210
|
+
recent_sessions: sessions,
|
|
2211
|
+
known_entities: visibleEntities,
|
|
2212
|
+
visibility_policy: DEFAULT_POLICY5
|
|
2213
|
+
});
|
|
2214
|
+
}
|
|
2215
|
+
healthCheck() {
|
|
2216
|
+
const path = this.copilotDir || defaultCopilotBourdonDir();
|
|
2217
|
+
if (!isDir4(path)) {
|
|
2218
|
+
return {
|
|
2219
|
+
status: "blocked",
|
|
2220
|
+
reason: `Convention directory ${path} not found. Run \`bourdon copilot init\` to create it.`,
|
|
2221
|
+
details: { expected_path: path },
|
|
2222
|
+
proposedFix: "Run `bourdon copilot init` to create the convention directory."
|
|
2223
|
+
};
|
|
2224
|
+
}
|
|
2225
|
+
const memPath = (0, import_node_path5.join)(path, MEMORY_FILENAME);
|
|
2226
|
+
if (!isFile4(memPath)) {
|
|
2227
|
+
return {
|
|
2228
|
+
status: "degraded",
|
|
2229
|
+
reason: `Memory file ${memPath} not found. Run \`bourdon copilot init\` to write a starter template.`,
|
|
2230
|
+
details: { expected_memory_file: memPath },
|
|
2231
|
+
proposedFix: "Run `bourdon copilot init` to write the starter memory.md template."
|
|
2232
|
+
};
|
|
2233
|
+
}
|
|
2234
|
+
let data;
|
|
2235
|
+
try {
|
|
2236
|
+
data = this.read();
|
|
2237
|
+
} catch (err) {
|
|
2238
|
+
logger.warn(`CopilotParticipant health_check failed: ${String(err)}`);
|
|
2239
|
+
return {
|
|
2240
|
+
status: "degraded",
|
|
2241
|
+
reason: "Memory file present but could not be parsed.",
|
|
2242
|
+
details: { error: String(err) },
|
|
2243
|
+
proposedFix: `Inspect ${memPath} for malformed YAML front-matter. The opening and closing \`---\` fences must wrap a valid YAML block. See docs/agent-integration-status.md for the schema.`
|
|
2244
|
+
};
|
|
2245
|
+
}
|
|
2246
|
+
return {
|
|
2247
|
+
status: "ok",
|
|
2248
|
+
details: {
|
|
2249
|
+
memory_file: memPath,
|
|
2250
|
+
entity_count: asArray(data["entities"]).length,
|
|
2251
|
+
session_count: asArray(data["sessions"]).length
|
|
2252
|
+
}
|
|
2253
|
+
};
|
|
2254
|
+
}
|
|
2255
|
+
// -- Internal ---------------------------------------------------------------
|
|
2256
|
+
read() {
|
|
2257
|
+
return readMemoryFile(defaultCopilotMemoryPath(this.copilotDir));
|
|
2258
|
+
}
|
|
2259
|
+
};
|
|
2260
|
+
|
|
2261
|
+
// src/readers/copilot-cli.ts
|
|
2262
|
+
var import_node_fs7 = require("fs");
|
|
2263
|
+
var import_node_os6 = require("os");
|
|
2264
|
+
var import_node_path6 = require("path");
|
|
2265
|
+
var import_l56 = require("@getbourdon/l5");
|
|
2266
|
+
var import_redaction6 = require("@getbourdon/redaction");
|
|
2267
|
+
var AGENT_ID6 = "copilot-cli";
|
|
2268
|
+
var AGENT_TYPE6 = "code-assistant";
|
|
2269
|
+
var DISPLAY_NAME4 = "GitHub Copilot CLI";
|
|
2270
|
+
var ROLE_NARRATIVE6 = "Terminal-native Copilot agent with full filesystem + tool access. Runs multi-turn sessions with checkpoints, file edits, and git operations. The CLI surface has the deepest tool integration of all Copilot surfaces \u2014 shell, grep, edit, git \u2014 and persists rich session history locally in SQLite.";
|
|
2271
|
+
var DEFAULT_POLICY6 = (0, import_l56.makeVisibilityPolicy)({
|
|
2272
|
+
default: import_l56.Visibility.TEAM,
|
|
2273
|
+
private_tags: ["personal", "financial", "credential", "health", "family", "legal"],
|
|
2274
|
+
team_tags: ["copilot-cli", "copilot", "terminal", "workspace"]
|
|
2275
|
+
});
|
|
2276
|
+
var DB_FILENAME = "session-store.db";
|
|
2277
|
+
var COPILOT_DIR_NAME = ".copilot";
|
|
2278
|
+
var MAX_SUMMARY_CHARS2 = 260;
|
|
2279
|
+
var MAX_KEY_ACTIONS = 6;
|
|
2280
|
+
var MAX_KEY_ACTION_CHARS = 280;
|
|
2281
|
+
function defaultCopilotCliDir() {
|
|
2282
|
+
const env = process.env.COPILOT_CLI_HOME;
|
|
2283
|
+
if (env) return env;
|
|
2284
|
+
return (0, import_node_path6.join)((0, import_node_os6.homedir)(), COPILOT_DIR_NAME);
|
|
2285
|
+
}
|
|
2286
|
+
function defaultCopilotCliDbPath(copilotDir) {
|
|
2287
|
+
return (0, import_node_path6.join)(copilotDir ?? defaultCopilotCliDir(), DB_FILENAME);
|
|
2288
|
+
}
|
|
2289
|
+
function isFile5(p) {
|
|
2290
|
+
try {
|
|
2291
|
+
return (0, import_node_fs7.statSync)(p).isFile();
|
|
2292
|
+
} catch {
|
|
2293
|
+
return false;
|
|
2294
|
+
}
|
|
2295
|
+
}
|
|
2296
|
+
function isDir5(p) {
|
|
2297
|
+
try {
|
|
2298
|
+
return (0, import_node_fs7.statSync)(p).isDirectory();
|
|
2299
|
+
} catch {
|
|
2300
|
+
return false;
|
|
2301
|
+
}
|
|
2302
|
+
}
|
|
2303
|
+
function safeNativeMemoryText(value, limit = 180) {
|
|
2304
|
+
return (0, import_redaction6.redactText)(value, limit);
|
|
2305
|
+
}
|
|
2306
|
+
function bounded2(value, limit) {
|
|
2307
|
+
const normalized = value.split(/\s+/).filter(Boolean).join(" ");
|
|
2308
|
+
if (normalized.length <= limit) return normalized;
|
|
2309
|
+
return normalized.slice(0, limit - 1).replace(/\s+$/, "") + "\u2026";
|
|
2310
|
+
}
|
|
2311
|
+
function queryDb(db, sql, params = []) {
|
|
2312
|
+
try {
|
|
2313
|
+
return db.prepare(sql).all(...params);
|
|
2314
|
+
} catch {
|
|
2315
|
+
return [];
|
|
2316
|
+
}
|
|
2317
|
+
}
|
|
2318
|
+
function extractSessions(db, since) {
|
|
2319
|
+
let sql = "SELECT id, cwd, repository, branch, summary, created_at, updated_at FROM sessions";
|
|
2320
|
+
const params = [];
|
|
2321
|
+
if (since) {
|
|
2322
|
+
sql += " WHERE created_at >= ?";
|
|
2323
|
+
params.push(since);
|
|
2324
|
+
}
|
|
2325
|
+
sql += " ORDER BY created_at DESC LIMIT 200";
|
|
2326
|
+
return queryDb(db, sql, params);
|
|
2327
|
+
}
|
|
2328
|
+
function extractSessionFiles(db, sessionId) {
|
|
2329
|
+
const rows = queryDb(
|
|
2330
|
+
db,
|
|
2331
|
+
"SELECT file_path FROM session_files WHERE session_id = ? LIMIT 20",
|
|
2332
|
+
[sessionId]
|
|
2333
|
+
);
|
|
2334
|
+
const out = [];
|
|
2335
|
+
for (const r of rows) {
|
|
2336
|
+
const fp = r["file_path"];
|
|
2337
|
+
if (fp) out.push(String(fp));
|
|
2338
|
+
}
|
|
2339
|
+
return out;
|
|
2340
|
+
}
|
|
2341
|
+
function extractSessionRefs(db, sessionId) {
|
|
2342
|
+
const rows = queryDb(
|
|
2343
|
+
db,
|
|
2344
|
+
"SELECT ref_type, ref_value FROM session_refs WHERE session_id = ?",
|
|
2345
|
+
[sessionId]
|
|
2346
|
+
);
|
|
2347
|
+
return rows.map((r) => ({
|
|
2348
|
+
ref_type: String(r["ref_type"] ?? ""),
|
|
2349
|
+
ref_value: String(r["ref_value"] ?? "")
|
|
2350
|
+
}));
|
|
2351
|
+
}
|
|
2352
|
+
function extractCheckpoints(db, sessionId) {
|
|
2353
|
+
return queryDb(
|
|
2354
|
+
db,
|
|
2355
|
+
"SELECT checkpoint_number, title, overview FROM checkpoints WHERE session_id = ? ORDER BY checkpoint_number",
|
|
2356
|
+
[sessionId]
|
|
2357
|
+
);
|
|
2358
|
+
}
|
|
2359
|
+
function extractDynamicContext(db) {
|
|
2360
|
+
return queryDb(
|
|
2361
|
+
db,
|
|
2362
|
+
"SELECT repository, branch, src, name, description, content, read_count, count FROM dynamic_context_items ORDER BY count DESC LIMIT 50"
|
|
2363
|
+
);
|
|
2364
|
+
}
|
|
2365
|
+
function extractTurnCount(db, sessionId) {
|
|
2366
|
+
const rows = queryDb(db, "SELECT COUNT(*) as cnt FROM turns WHERE session_id = ?", [sessionId]);
|
|
2367
|
+
const first = rows[0];
|
|
2368
|
+
return first ? Number(first["cnt"] ?? 0) || 0 : 0;
|
|
2369
|
+
}
|
|
2370
|
+
function sessionToBourdon(raw, files, refs, checkpoints, turnCount) {
|
|
2371
|
+
const created = raw["created_at"] ? String(raw["created_at"]) : "";
|
|
2372
|
+
const dateStr = created ? created.slice(0, 10) : "";
|
|
2373
|
+
const keyActions = [];
|
|
2374
|
+
if (raw["summary"]) {
|
|
2375
|
+
keyActions.push(bounded2(safeNativeMemoryText(String(raw["summary"])), MAX_KEY_ACTION_CHARS));
|
|
2376
|
+
}
|
|
2377
|
+
for (const cp of checkpoints.slice(0, 3)) {
|
|
2378
|
+
if (cp["title"]) {
|
|
2379
|
+
keyActions.push(
|
|
2380
|
+
bounded2(`checkpoint: ${safeNativeMemoryText(String(cp["title"]))}`, MAX_KEY_ACTION_CHARS)
|
|
2381
|
+
);
|
|
2382
|
+
}
|
|
2383
|
+
}
|
|
2384
|
+
if (turnCount) {
|
|
2385
|
+
keyActions.push(`turns: ${turnCount}`);
|
|
2386
|
+
}
|
|
2387
|
+
for (const ref of refs.slice(0, 3)) {
|
|
2388
|
+
const refLabel = `${ref.ref_type}: ${ref.ref_value}`;
|
|
2389
|
+
keyActions.push(bounded2(refLabel, MAX_KEY_ACTION_CHARS));
|
|
2390
|
+
}
|
|
2391
|
+
const projectFocus = [];
|
|
2392
|
+
if (raw["repository"]) projectFocus.push(String(raw["repository"]));
|
|
2393
|
+
return (0, import_l56.makeSession)({
|
|
2394
|
+
date: dateStr,
|
|
2395
|
+
cwd: raw["cwd"] ? String(raw["cwd"]) : void 0,
|
|
2396
|
+
project_focus: projectFocus,
|
|
2397
|
+
key_actions: keyActions.slice(0, MAX_KEY_ACTIONS),
|
|
2398
|
+
files_touched: files.slice(0, 20),
|
|
2399
|
+
visibility: import_l56.Visibility.TEAM
|
|
2400
|
+
});
|
|
2401
|
+
}
|
|
2402
|
+
function entitiesFromSessionsAndContext(sessions, dynamicContext) {
|
|
2403
|
+
const entities = /* @__PURE__ */ new Map();
|
|
2404
|
+
for (const raw of sessions) {
|
|
2405
|
+
const repo = raw["repository"] ? String(raw["repository"]) : "";
|
|
2406
|
+
if (repo && !entities.has(repo)) {
|
|
2407
|
+
const created = raw["created_at"] ? String(raw["created_at"]) : "";
|
|
2408
|
+
const lastTouched = created.slice(0, 10) || null;
|
|
2409
|
+
entities.set(
|
|
2410
|
+
repo,
|
|
2411
|
+
(0, import_l56.makeEntity)({
|
|
2412
|
+
name: repo,
|
|
2413
|
+
type: "project",
|
|
2414
|
+
summary: "Repository worked on via Copilot CLI.",
|
|
2415
|
+
last_touched: lastTouched ?? void 0,
|
|
2416
|
+
tags: ["copilot-cli", "project"],
|
|
2417
|
+
visibility: import_l56.Visibility.TEAM
|
|
2418
|
+
})
|
|
2419
|
+
);
|
|
2420
|
+
}
|
|
2421
|
+
}
|
|
2422
|
+
for (const ctx of dynamicContext) {
|
|
2423
|
+
const name = ctx["name"] ? String(ctx["name"]) : "";
|
|
2424
|
+
if (!name || entities.has(name)) continue;
|
|
2425
|
+
const desc = ctx["description"] ? String(ctx["description"]) : "";
|
|
2426
|
+
const content = ctx["content"] ? String(ctx["content"]) : "";
|
|
2427
|
+
let summaryText = desc || content;
|
|
2428
|
+
if (summaryText) {
|
|
2429
|
+
summaryText = bounded2(safeNativeMemoryText(summaryText), MAX_SUMMARY_CHARS2);
|
|
2430
|
+
}
|
|
2431
|
+
entities.set(
|
|
2432
|
+
name,
|
|
2433
|
+
(0, import_l56.makeEntity)({
|
|
2434
|
+
name,
|
|
2435
|
+
type: "dynamic-context",
|
|
2436
|
+
summary: summaryText || void 0,
|
|
2437
|
+
last_touched: void 0,
|
|
2438
|
+
tags: ["copilot-cli", "dynamic-context", ctx["src"] ? String(ctx["src"]) : "unknown"],
|
|
2439
|
+
visibility: import_l56.Visibility.TEAM
|
|
2440
|
+
})
|
|
2441
|
+
);
|
|
2442
|
+
}
|
|
2443
|
+
return [...entities.values()];
|
|
2444
|
+
}
|
|
2445
|
+
function toUtcIso(d) {
|
|
2446
|
+
return d.toISOString().replace(/\.\d{3}Z$/, "+00:00").replace(/Z$/, "+00:00");
|
|
2447
|
+
}
|
|
2448
|
+
var CopilotCliParticipant = class {
|
|
2449
|
+
agentId = AGENT_ID6;
|
|
2450
|
+
agentType = AGENT_TYPE6;
|
|
2451
|
+
displayName = DISPLAY_NAME4;
|
|
2452
|
+
nativePath;
|
|
2453
|
+
copilotDir;
|
|
2454
|
+
/** Conventional `~/.copilot` dir used by the setup wizard's detection. */
|
|
2455
|
+
static defaultNativePath(home) {
|
|
2456
|
+
if (home) return (0, import_node_path6.join)(home, COPILOT_DIR_NAME);
|
|
2457
|
+
return defaultCopilotCliDir();
|
|
2458
|
+
}
|
|
2459
|
+
constructor(copilotDir) {
|
|
2460
|
+
this.copilotDir = copilotDir ?? null;
|
|
2461
|
+
this.nativePath = this.copilotDir ?? defaultCopilotCliDir();
|
|
2462
|
+
}
|
|
2463
|
+
// -- Protocol surface -------------------------------------------------------
|
|
2464
|
+
discover() {
|
|
2465
|
+
const dbPath = defaultCopilotCliDbPath(this.copilotDir);
|
|
2466
|
+
if (!isFile5(dbPath)) {
|
|
2467
|
+
throw new ParticipantDiscoveryError(
|
|
2468
|
+
`Copilot CLI session-store.db not found at ${dbPath}. The Copilot CLI agent must be run at least once to create this database.`
|
|
2469
|
+
);
|
|
2470
|
+
}
|
|
2471
|
+
let dbSize = 0;
|
|
2472
|
+
try {
|
|
2473
|
+
dbSize = (0, import_node_fs7.statSync)(dbPath).size;
|
|
2474
|
+
} catch {
|
|
2475
|
+
dbSize = 0;
|
|
2476
|
+
}
|
|
2477
|
+
return {
|
|
2478
|
+
path: (0, import_node_path6.dirname)(dbPath),
|
|
2479
|
+
version: "schema-v4",
|
|
2480
|
+
metadata: { db_path: dbPath, db_size_bytes: dbSize }
|
|
2481
|
+
};
|
|
2482
|
+
}
|
|
2483
|
+
exportSessions(since, limit = 100) {
|
|
2484
|
+
const dbPath = defaultCopilotCliDbPath(this.copilotDir);
|
|
2485
|
+
const db = tryOpenReadonly(dbPath);
|
|
2486
|
+
if (!db) return [];
|
|
2487
|
+
try {
|
|
2488
|
+
const sinceIso = since ? toUtcIso(since) : null;
|
|
2489
|
+
const rawSessions = extractSessions(db, sinceIso);
|
|
2490
|
+
const sessions = [];
|
|
2491
|
+
for (const raw of rawSessions.slice(0, limit)) {
|
|
2492
|
+
const sid = String(raw["id"]);
|
|
2493
|
+
const files = extractSessionFiles(db, sid);
|
|
2494
|
+
const refs = extractSessionRefs(db, sid);
|
|
2495
|
+
const checkpoints = extractCheckpoints(db, sid);
|
|
2496
|
+
const turnCount = extractTurnCount(db, sid);
|
|
2497
|
+
sessions.push(sessionToBourdon(raw, files, refs, checkpoints, turnCount));
|
|
2498
|
+
}
|
|
2499
|
+
return sessions;
|
|
2500
|
+
} finally {
|
|
2501
|
+
db.close();
|
|
2502
|
+
}
|
|
2503
|
+
}
|
|
2504
|
+
exportL5(since) {
|
|
2505
|
+
const dbPath = defaultCopilotCliDbPath(this.copilotDir);
|
|
2506
|
+
const db = tryOpenReadonly(dbPath);
|
|
2507
|
+
if (!db) return this.emptyManifest();
|
|
2508
|
+
try {
|
|
2509
|
+
const sinceIso = since ? toUtcIso(since) : null;
|
|
2510
|
+
const rawSessions = extractSessions(db, sinceIso);
|
|
2511
|
+
const dynamicContext = extractDynamicContext(db);
|
|
2512
|
+
const sessions = [];
|
|
2513
|
+
for (const raw of rawSessions.slice(0, 100)) {
|
|
2514
|
+
const sid = String(raw["id"]);
|
|
2515
|
+
const files = extractSessionFiles(db, sid);
|
|
2516
|
+
const refs = extractSessionRefs(db, sid);
|
|
2517
|
+
const checkpoints = extractCheckpoints(db, sid);
|
|
2518
|
+
const turnCount = extractTurnCount(db, sid);
|
|
2519
|
+
sessions.push(sessionToBourdon(raw, files, refs, checkpoints, turnCount));
|
|
2520
|
+
}
|
|
2521
|
+
const entities = entitiesFromSessionsAndContext(rawSessions, dynamicContext);
|
|
2522
|
+
const visibleEntities = (0, import_l56.filterForFederation)(entities, DEFAULT_POLICY6);
|
|
2523
|
+
return (0, import_l56.makeManifest)({
|
|
2524
|
+
spec_version: SPEC_VERSION,
|
|
2525
|
+
agent: (0, import_l56.makeAgentInfo)({
|
|
2526
|
+
id: AGENT_ID6,
|
|
2527
|
+
type: AGENT_TYPE6,
|
|
2528
|
+
instance: (0, import_node_os6.hostname)() || "unknown",
|
|
2529
|
+
spec_version_compat: `>=${SPEC_VERSION}`,
|
|
2530
|
+
role_narrative: ROLE_NARRATIVE6
|
|
2531
|
+
}),
|
|
2532
|
+
last_updated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2533
|
+
capabilities: [
|
|
2534
|
+
"terminal-agent",
|
|
2535
|
+
"file-edit",
|
|
2536
|
+
"shell-execution",
|
|
2537
|
+
"git-operations",
|
|
2538
|
+
"multi-turn-sessions",
|
|
2539
|
+
"checkpoints",
|
|
2540
|
+
"dynamic-context"
|
|
2541
|
+
],
|
|
2542
|
+
recent_sessions: sessions,
|
|
2543
|
+
known_entities: visibleEntities,
|
|
2544
|
+
visibility_policy: DEFAULT_POLICY6
|
|
2545
|
+
});
|
|
2546
|
+
} finally {
|
|
2547
|
+
db.close();
|
|
2548
|
+
}
|
|
2549
|
+
}
|
|
2550
|
+
healthCheck() {
|
|
2551
|
+
try {
|
|
2552
|
+
const copilotDir = this.copilotDir ?? defaultCopilotCliDir();
|
|
2553
|
+
if (!isDir5(copilotDir)) {
|
|
2554
|
+
return {
|
|
2555
|
+
status: "blocked",
|
|
2556
|
+
reason: `Copilot CLI directory not found at ${copilotDir}.`,
|
|
2557
|
+
details: { expected_path: copilotDir },
|
|
2558
|
+
proposedFix: "Run the Copilot CLI agent (`copilot` or `gh copilot`) at least once to create the session store."
|
|
2559
|
+
};
|
|
2560
|
+
}
|
|
2561
|
+
const dbPath = defaultCopilotCliDbPath(copilotDir);
|
|
2562
|
+
if (!isFile5(dbPath)) {
|
|
2563
|
+
return {
|
|
2564
|
+
status: "blocked",
|
|
2565
|
+
reason: `session-store.db not found at ${dbPath}.`,
|
|
2566
|
+
details: { expected_db: dbPath },
|
|
2567
|
+
proposedFix: "Run the Copilot CLI agent at least once. The session-store.db is created on first use."
|
|
2568
|
+
};
|
|
2569
|
+
}
|
|
2570
|
+
const db = tryOpenReadonly(dbPath);
|
|
2571
|
+
if (!db) {
|
|
2572
|
+
return {
|
|
2573
|
+
status: "degraded",
|
|
2574
|
+
reason: "Cannot copy session-store.db for reading.",
|
|
2575
|
+
details: { db_path: dbPath },
|
|
2576
|
+
proposedFix: "Check file permissions on the session-store.db."
|
|
2577
|
+
};
|
|
2578
|
+
}
|
|
2579
|
+
try {
|
|
2580
|
+
const rawSessions = extractSessions(db);
|
|
2581
|
+
const dynamicContext = extractDynamicContext(db);
|
|
2582
|
+
const totalTurns = queryDb(db, "SELECT COUNT(*) as cnt FROM turns");
|
|
2583
|
+
const first = totalTurns[0];
|
|
2584
|
+
let dbSize = 0;
|
|
2585
|
+
try {
|
|
2586
|
+
dbSize = (0, import_node_fs7.statSync)(dbPath).size;
|
|
2587
|
+
} catch {
|
|
2588
|
+
dbSize = 0;
|
|
2589
|
+
}
|
|
2590
|
+
return {
|
|
2591
|
+
status: "ok",
|
|
2592
|
+
details: {
|
|
2593
|
+
db_path: dbPath,
|
|
2594
|
+
db_size_bytes: dbSize,
|
|
2595
|
+
session_count: rawSessions.length,
|
|
2596
|
+
total_turns: first ? Number(first["cnt"] ?? 0) || 0 : 0,
|
|
2597
|
+
dynamic_context_items: dynamicContext.length
|
|
2598
|
+
}
|
|
2599
|
+
};
|
|
2600
|
+
} finally {
|
|
2601
|
+
db.close();
|
|
2602
|
+
}
|
|
2603
|
+
} catch (err) {
|
|
2604
|
+
return {
|
|
2605
|
+
status: "degraded",
|
|
2606
|
+
reason: `unexpected error: ${String(err)}`,
|
|
2607
|
+
details: {}
|
|
2608
|
+
};
|
|
2609
|
+
}
|
|
2610
|
+
}
|
|
2611
|
+
// -- Internal ---------------------------------------------------------------
|
|
2612
|
+
emptyManifest() {
|
|
2613
|
+
return (0, import_l56.makeManifest)({
|
|
2614
|
+
spec_version: SPEC_VERSION,
|
|
2615
|
+
agent: (0, import_l56.makeAgentInfo)({
|
|
2616
|
+
id: AGENT_ID6,
|
|
2617
|
+
type: AGENT_TYPE6,
|
|
2618
|
+
instance: (0, import_node_os6.hostname)() || "unknown",
|
|
2619
|
+
spec_version_compat: `>=${SPEC_VERSION}`,
|
|
2620
|
+
role_narrative: ROLE_NARRATIVE6
|
|
2621
|
+
}),
|
|
2622
|
+
last_updated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2623
|
+
capabilities: [],
|
|
2624
|
+
recent_sessions: [],
|
|
2625
|
+
known_entities: [],
|
|
2626
|
+
visibility_policy: DEFAULT_POLICY6
|
|
2627
|
+
});
|
|
2628
|
+
}
|
|
2629
|
+
};
|
|
2630
|
+
|
|
2631
|
+
// src/readers/cascade.ts
|
|
2632
|
+
var import_node_fs8 = require("fs");
|
|
2633
|
+
var import_node_os7 = require("os");
|
|
2634
|
+
var import_node_path7 = require("path");
|
|
2635
|
+
var import_yaml3 = require("yaml");
|
|
2636
|
+
var import_l57 = require("@getbourdon/l5");
|
|
2637
|
+
var import_redaction7 = require("@getbourdon/redaction");
|
|
2638
|
+
var AGENT_ID7 = "cascade";
|
|
2639
|
+
var AGENT_TYPE7 = "code-assistant";
|
|
2640
|
+
var DISPLAY_NAME5 = "Cascade (Windsurf)";
|
|
2641
|
+
var ROLE_NARRATIVE7 = "Agentic AI coding assistant embedded in Windsurf IDE. Operates with multi-step planning, tool use (file editing, terminal, browser preview, code search), persistent memory, and workspace-level context awareness. Specializes in pair-programming workflows with concurrent read-plan-execute cycles.";
|
|
2642
|
+
var CONVENTION_DIR_NAME2 = ".cascade-bourdon";
|
|
2643
|
+
var MEMORY_FILENAME2 = "memory.md";
|
|
2644
|
+
var DEFAULT_POLICY7 = (0, import_l57.makeVisibilityPolicy)({
|
|
2645
|
+
default: import_l57.Visibility.PUBLIC,
|
|
2646
|
+
private_tags: ["personal", "credential", "financial", "secret", "private"]
|
|
2647
|
+
});
|
|
2648
|
+
var CASCADE_SENSITIVE_PATTERNS = [
|
|
2649
|
+
...import_redaction7.SENSITIVE_PATTERNS,
|
|
2650
|
+
/\bsecret\b/i,
|
|
2651
|
+
/sk[_-]test[_-]/i
|
|
2652
|
+
];
|
|
2653
|
+
var logger2 = {
|
|
2654
|
+
warn(msg) {
|
|
2655
|
+
console.warn(`[bourdon.participants] ${msg}`);
|
|
2656
|
+
}
|
|
2657
|
+
};
|
|
2658
|
+
function homeDir4() {
|
|
2659
|
+
try {
|
|
2660
|
+
return (0, import_node_os7.homedir)();
|
|
2661
|
+
} catch {
|
|
2662
|
+
return process.env.HOME || process.env.USERPROFILE || ".";
|
|
2663
|
+
}
|
|
2664
|
+
}
|
|
2665
|
+
function isDir6(p) {
|
|
2666
|
+
try {
|
|
2667
|
+
return (0, import_node_fs8.statSync)(p).isDirectory();
|
|
2668
|
+
} catch {
|
|
2669
|
+
return false;
|
|
2670
|
+
}
|
|
2671
|
+
}
|
|
2672
|
+
function isFile6(p) {
|
|
2673
|
+
try {
|
|
2674
|
+
return (0, import_node_fs8.statSync)(p).isFile();
|
|
2675
|
+
} catch {
|
|
2676
|
+
return false;
|
|
2677
|
+
}
|
|
2678
|
+
}
|
|
2679
|
+
function defaultCascadeDir() {
|
|
2680
|
+
return (0, import_node_path7.join)(homeDir4(), CONVENTION_DIR_NAME2);
|
|
2681
|
+
}
|
|
2682
|
+
function parseFrontmatter3(text, source) {
|
|
2683
|
+
if (!text.startsWith("---")) return {};
|
|
2684
|
+
const end = text.indexOf("---", 3);
|
|
2685
|
+
if (end === -1) return {};
|
|
2686
|
+
const yamlBlock = text.slice(3, end).trim();
|
|
2687
|
+
if (!yamlBlock) return {};
|
|
2688
|
+
let data;
|
|
2689
|
+
try {
|
|
2690
|
+
data = (0, import_yaml3.parse)(yamlBlock);
|
|
2691
|
+
} catch (exc) {
|
|
2692
|
+
const where = source ? ` in ${source}` : "";
|
|
2693
|
+
const detail = String(exc).replace(/\n/g, " ").slice(0, 200);
|
|
2694
|
+
logger2.warn(
|
|
2695
|
+
`CascadeParticipant: malformed YAML frontmatter${where}; treating as no-frontmatter (${detail})`
|
|
2696
|
+
);
|
|
2697
|
+
return {};
|
|
2698
|
+
}
|
|
2699
|
+
return typeof data === "object" && data !== null && !Array.isArray(data) ? data : {};
|
|
2700
|
+
}
|
|
2701
|
+
function scrubCredential(text) {
|
|
2702
|
+
if (!text) return text;
|
|
2703
|
+
if (CASCADE_SENSITIVE_PATTERNS.some((p) => p.test(text))) {
|
|
2704
|
+
return import_redaction7.REDACTED;
|
|
2705
|
+
}
|
|
2706
|
+
return (0, import_redaction7.redactText)(text);
|
|
2707
|
+
}
|
|
2708
|
+
function asList(v) {
|
|
2709
|
+
if (!v) return [];
|
|
2710
|
+
if (Array.isArray(v)) return v;
|
|
2711
|
+
if (typeof v === "string") return Array.from(v);
|
|
2712
|
+
return [];
|
|
2713
|
+
}
|
|
2714
|
+
function isRecord3(v) {
|
|
2715
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
2716
|
+
}
|
|
2717
|
+
function buildEntity2(raw) {
|
|
2718
|
+
if (!isRecord3(raw)) return null;
|
|
2719
|
+
const name = raw["name"];
|
|
2720
|
+
if (typeof name !== "string" || !name.trim()) return null;
|
|
2721
|
+
let summary;
|
|
2722
|
+
const rawSummary = raw["summary"];
|
|
2723
|
+
if (typeof rawSummary === "string") summary = scrubCredential(rawSummary);
|
|
2724
|
+
return (0, import_l57.makeEntity)({
|
|
2725
|
+
name: name.trim(),
|
|
2726
|
+
type: typeof raw["type"] === "string" ? raw["type"] : void 0,
|
|
2727
|
+
summary,
|
|
2728
|
+
aliases: asList(raw["aliases"]),
|
|
2729
|
+
tags: asList(raw["tags"]),
|
|
2730
|
+
last_touched: raw["last_touched"] ? String(raw["last_touched"]) : void 0,
|
|
2731
|
+
valid_from: raw["valid_from"] ? String(raw["valid_from"]) : void 0,
|
|
2732
|
+
valid_to: raw["valid_to"] ? String(raw["valid_to"]) : void 0,
|
|
2733
|
+
visibility: void 0
|
|
2734
|
+
});
|
|
2735
|
+
}
|
|
2736
|
+
function buildSession3(raw) {
|
|
2737
|
+
if (!isRecord3(raw)) return null;
|
|
2738
|
+
const dateVal = raw["date"];
|
|
2739
|
+
if (!dateVal) return null;
|
|
2740
|
+
const dateStr = String(dateVal).slice(0, 10);
|
|
2741
|
+
return (0, import_l57.makeSession)({
|
|
2742
|
+
date: dateStr,
|
|
2743
|
+
cwd: raw["cwd"] == null ? void 0 : raw["cwd"],
|
|
2744
|
+
key_actions: asList(raw["key_actions"]),
|
|
2745
|
+
files_touched: asList(raw["files_touched"]),
|
|
2746
|
+
project_focus: asList(raw["project_focus"]),
|
|
2747
|
+
visibility: void 0
|
|
2748
|
+
});
|
|
2749
|
+
}
|
|
2750
|
+
function parseSessionDate(date) {
|
|
2751
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) return null;
|
|
2752
|
+
const ms = Date.parse(`${date}T00:00:00Z`);
|
|
2753
|
+
return Number.isNaN(ms) ? null : new Date(ms);
|
|
2754
|
+
}
|
|
2755
|
+
function buildSessions(rawSessions, since) {
|
|
2756
|
+
const list = Array.isArray(rawSessions) ? rawSessions : [];
|
|
2757
|
+
const sessions = [];
|
|
2758
|
+
for (const raw of list) {
|
|
2759
|
+
const session = buildSession3(raw);
|
|
2760
|
+
if (session === null) continue;
|
|
2761
|
+
if (since !== void 0) {
|
|
2762
|
+
const d = parseSessionDate(session.date);
|
|
2763
|
+
if (d !== null && d.getTime() < since.getTime()) continue;
|
|
2764
|
+
}
|
|
2765
|
+
sessions.push(session);
|
|
2766
|
+
}
|
|
2767
|
+
return sessions;
|
|
2768
|
+
}
|
|
2769
|
+
function inspectCascadeMemory(cascadeDir) {
|
|
2770
|
+
const memoryPath = (0, import_node_path7.join)(cascadeDir, MEMORY_FILENAME2);
|
|
2771
|
+
if (!isFile6(memoryPath)) return { present: false, error: "missing" };
|
|
2772
|
+
let text;
|
|
2773
|
+
try {
|
|
2774
|
+
text = (0, import_node_fs8.readFileSync)(memoryPath, "utf8");
|
|
2775
|
+
} catch (e) {
|
|
2776
|
+
return { present: true, readable: false, error: String(e) };
|
|
2777
|
+
}
|
|
2778
|
+
const data = parseFrontmatter3(text, memoryPath);
|
|
2779
|
+
if (Object.keys(data).length === 0) {
|
|
2780
|
+
return {
|
|
2781
|
+
present: true,
|
|
2782
|
+
readable: true,
|
|
2783
|
+
frontmatter_valid: false,
|
|
2784
|
+
entity_count: 0,
|
|
2785
|
+
session_count: 0
|
|
2786
|
+
};
|
|
2787
|
+
}
|
|
2788
|
+
const entities = data["entities"];
|
|
2789
|
+
const sessions = data["sessions"];
|
|
2790
|
+
return {
|
|
2791
|
+
present: true,
|
|
2792
|
+
readable: true,
|
|
2793
|
+
frontmatter_valid: true,
|
|
2794
|
+
entity_count: Array.isArray(entities) ? entities.length : 0,
|
|
2795
|
+
session_count: Array.isArray(sessions) ? sessions.length : 0
|
|
2796
|
+
};
|
|
2797
|
+
}
|
|
2798
|
+
var CascadeParticipant = class {
|
|
2799
|
+
agentId = AGENT_ID7;
|
|
2800
|
+
agentType = AGENT_TYPE7;
|
|
2801
|
+
displayName = DISPLAY_NAME5;
|
|
2802
|
+
dir;
|
|
2803
|
+
policy;
|
|
2804
|
+
static defaultNativePath(home) {
|
|
2805
|
+
if (home !== void 0) return (0, import_node_path7.join)(home, CONVENTION_DIR_NAME2);
|
|
2806
|
+
return defaultCascadeDir();
|
|
2807
|
+
}
|
|
2808
|
+
constructor(cascadeDir, policy) {
|
|
2809
|
+
this.dir = cascadeDir ?? defaultCascadeDir();
|
|
2810
|
+
this.policy = policy ?? DEFAULT_POLICY7;
|
|
2811
|
+
}
|
|
2812
|
+
/** Path to the Cascade-Bourdon convention directory. */
|
|
2813
|
+
get nativePath() {
|
|
2814
|
+
return this.dir;
|
|
2815
|
+
}
|
|
2816
|
+
memoryPath() {
|
|
2817
|
+
return (0, import_node_path7.join)(this.dir, MEMORY_FILENAME2);
|
|
2818
|
+
}
|
|
2819
|
+
readFrontmatter() {
|
|
2820
|
+
const path = this.memoryPath();
|
|
2821
|
+
if (!isFile6(path)) return {};
|
|
2822
|
+
let text;
|
|
2823
|
+
try {
|
|
2824
|
+
text = (0, import_node_fs8.readFileSync)(path, "utf8");
|
|
2825
|
+
} catch {
|
|
2826
|
+
return {};
|
|
2827
|
+
}
|
|
2828
|
+
return parseFrontmatter3(text, path);
|
|
2829
|
+
}
|
|
2830
|
+
discover() {
|
|
2831
|
+
if (!isDir6(this.dir)) {
|
|
2832
|
+
throw new ParticipantDiscoveryError(
|
|
2833
|
+
`Cascade-Bourdon directory not found: ${this.dir}`
|
|
2834
|
+
);
|
|
2835
|
+
}
|
|
2836
|
+
const memoryPresent = isFile6(this.memoryPath());
|
|
2837
|
+
return {
|
|
2838
|
+
path: this.dir,
|
|
2839
|
+
version: "unknown",
|
|
2840
|
+
metadata: {
|
|
2841
|
+
memory_file: this.memoryPath(),
|
|
2842
|
+
memory_file_present: memoryPresent
|
|
2843
|
+
}
|
|
2844
|
+
};
|
|
2845
|
+
}
|
|
2846
|
+
exportL5(since) {
|
|
2847
|
+
const data = this.readFrontmatter();
|
|
2848
|
+
const rawEntities = data["entities"];
|
|
2849
|
+
const entityList = Array.isArray(rawEntities) ? rawEntities : [];
|
|
2850
|
+
const entities = [];
|
|
2851
|
+
for (const raw of entityList) {
|
|
2852
|
+
const entity = buildEntity2(raw);
|
|
2853
|
+
if (entity !== null) entities.push(entity);
|
|
2854
|
+
}
|
|
2855
|
+
const sessions = buildSessions(data["sessions"], since);
|
|
2856
|
+
const visibleEntities = (0, import_l57.filterForFederation)(entities, this.policy);
|
|
2857
|
+
return (0, import_l57.makeManifest)({
|
|
2858
|
+
spec_version: SPEC_VERSION,
|
|
2859
|
+
agent: (0, import_l57.makeAgentInfo)({
|
|
2860
|
+
id: AGENT_ID7,
|
|
2861
|
+
type: AGENT_TYPE7,
|
|
2862
|
+
role_narrative: ROLE_NARRATIVE7
|
|
2863
|
+
}),
|
|
2864
|
+
last_updated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2865
|
+
known_entities: visibleEntities,
|
|
2866
|
+
recent_sessions: sessions,
|
|
2867
|
+
capabilities: ["chat", "code-editing", "terminal", "planning", "search"]
|
|
2868
|
+
});
|
|
2869
|
+
}
|
|
2870
|
+
exportSessions(since, limit) {
|
|
2871
|
+
const data = this.readFrontmatter();
|
|
2872
|
+
const sessions = buildSessions(data["sessions"], since);
|
|
2873
|
+
sessions.sort((a, b) => a.date < b.date ? 1 : a.date > b.date ? -1 : 0);
|
|
2874
|
+
if (limit !== void 0) return sessions.slice(0, limit);
|
|
2875
|
+
return sessions;
|
|
2876
|
+
}
|
|
2877
|
+
healthCheck() {
|
|
2878
|
+
if (!isDir6(this.dir)) {
|
|
2879
|
+
return {
|
|
2880
|
+
status: "blocked",
|
|
2881
|
+
reason: "Cascade-Bourdon directory not found",
|
|
2882
|
+
details: { expected_path: this.dir },
|
|
2883
|
+
proposedFix: "Run `bourdon cascade init` to create the convention directory + starter memory.md."
|
|
2884
|
+
};
|
|
2885
|
+
}
|
|
2886
|
+
const report = inspectCascadeMemory(this.dir);
|
|
2887
|
+
if (!report["present"]) {
|
|
2888
|
+
return {
|
|
2889
|
+
status: "degraded",
|
|
2890
|
+
reason: "Memory file not found; run `bourdon cascade init` to create it",
|
|
2891
|
+
details: report,
|
|
2892
|
+
proposedFix: "Run `bourdon cascade init` to write the starter memory.md template."
|
|
2893
|
+
};
|
|
2894
|
+
}
|
|
2895
|
+
if (!report["readable"]) {
|
|
2896
|
+
return {
|
|
2897
|
+
status: "degraded",
|
|
2898
|
+
reason: `Memory file not readable: ${String(report["error"])}`,
|
|
2899
|
+
details: report,
|
|
2900
|
+
proposedFix: `Check filesystem permissions on ${(0, import_node_path7.join)(this.dir, "memory.md")} (should be readable by your user).`
|
|
2901
|
+
};
|
|
2902
|
+
}
|
|
2903
|
+
if (!report["frontmatter_valid"]) {
|
|
2904
|
+
return {
|
|
2905
|
+
status: "degraded",
|
|
2906
|
+
reason: "Memory file has no valid YAML front-matter",
|
|
2907
|
+
details: report,
|
|
2908
|
+
proposedFix: `Inspect ${(0, import_node_path7.join)(this.dir, "memory.md")} -- the opening and closing \`---\` fences must wrap a valid YAML block. Run \`bourdon cascade init --force\` to reset to the template (WARNING: this overwrites existing content).`
|
|
2909
|
+
};
|
|
2910
|
+
}
|
|
2911
|
+
return {
|
|
2912
|
+
status: "ok",
|
|
2913
|
+
details: report
|
|
2914
|
+
};
|
|
2915
|
+
}
|
|
2916
|
+
};
|
|
2917
|
+
|
|
2918
|
+
// src/readers/openclaw.ts
|
|
2919
|
+
var import_node_child_process2 = require("child_process");
|
|
2920
|
+
var import_node_os8 = require("os");
|
|
2921
|
+
var import_node_path8 = require("path");
|
|
2922
|
+
var import_l58 = require("@getbourdon/l5");
|
|
2923
|
+
var import_redaction8 = require("@getbourdon/redaction");
|
|
2924
|
+
var AGENT_ID8 = "openclaw";
|
|
2925
|
+
var AGENT_TYPE8 = "other";
|
|
2926
|
+
var DISPLAY_NAME6 = "OpenClaw (quarantined)";
|
|
2927
|
+
var ROLE_NARRATIVE8 = "OpenClaw personal AI assistant \u2014 federated as a QUARANTINED member: its reads are limited to granted namespaces and its writes are staged for operator review.";
|
|
2928
|
+
var MIN_PATCHED_VERSION = "2026.1.29";
|
|
2929
|
+
var DEFAULT_OPENCLAW_URL = "http://127.0.0.1:8080";
|
|
2930
|
+
var SESSION_LIMIT = 50;
|
|
2931
|
+
var ENTITY_LIMIT = 100;
|
|
2932
|
+
function isRecord4(v) {
|
|
2933
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
2934
|
+
}
|
|
2935
|
+
function safeNativeMemoryText2(value) {
|
|
2936
|
+
return (0, import_redaction8.redactText)(value, 180);
|
|
2937
|
+
}
|
|
2938
|
+
function parseVersion(value) {
|
|
2939
|
+
const parts = [];
|
|
2940
|
+
for (const chunk of String(value ?? "").trim().split(".")) {
|
|
2941
|
+
const match = /^(\d+)/.exec(chunk);
|
|
2942
|
+
if (!match) break;
|
|
2943
|
+
parts.push(Number(match[1]));
|
|
2944
|
+
}
|
|
2945
|
+
return parts.length > 0 ? parts : null;
|
|
2946
|
+
}
|
|
2947
|
+
function versionLessThan(a, b) {
|
|
2948
|
+
const n = Math.max(a.length, b.length);
|
|
2949
|
+
for (let i = 0; i < n; i += 1) {
|
|
2950
|
+
const av = i < a.length ? a[i] : void 0;
|
|
2951
|
+
const bv = i < b.length ? b[i] : void 0;
|
|
2952
|
+
if (av === void 0) return true;
|
|
2953
|
+
if (bv === void 0) return false;
|
|
2954
|
+
if (av < bv) return true;
|
|
2955
|
+
if (av > bv) return false;
|
|
2956
|
+
}
|
|
2957
|
+
return false;
|
|
2958
|
+
}
|
|
2959
|
+
function statusVersion(status) {
|
|
2960
|
+
for (const key of ["version", "openclaw_version", "app_version"]) {
|
|
2961
|
+
const value = status[key];
|
|
2962
|
+
if (typeof value === "string" && value.trim()) return value.trim();
|
|
2963
|
+
}
|
|
2964
|
+
return "";
|
|
2965
|
+
}
|
|
2966
|
+
function statusAuthEnabled(status) {
|
|
2967
|
+
for (const key of ["auth_enabled", "authEnabled"]) {
|
|
2968
|
+
if (key in status) return Boolean(status[key]);
|
|
2969
|
+
}
|
|
2970
|
+
const auth = status["auth"];
|
|
2971
|
+
if (isRecord4(auth)) return Boolean(auth["enabled"]);
|
|
2972
|
+
return false;
|
|
2973
|
+
}
|
|
2974
|
+
function verifyInstance(status, url) {
|
|
2975
|
+
const version = statusVersion(status);
|
|
2976
|
+
const parsed = parseVersion(version);
|
|
2977
|
+
const minimum = parseVersion(MIN_PATCHED_VERSION);
|
|
2978
|
+
if (parsed === null) {
|
|
2979
|
+
throw new ParticipantDiscoveryError(
|
|
2980
|
+
`OpenClaw at ${url} did not report a parseable version (got ${JSON.stringify(version)}). Refusing handshake: cannot verify the CVE-2026-25253 patch level. Fix: upgrade OpenClaw to >= ${MIN_PATCHED_VERSION} and ensure /api/status reports it.`
|
|
2981
|
+
);
|
|
2982
|
+
}
|
|
2983
|
+
if (versionLessThan(parsed, minimum)) {
|
|
2984
|
+
throw new ParticipantDiscoveryError(
|
|
2985
|
+
`OpenClaw at ${url} runs ${version}, which predates the CVE-2026-25253 patch (one-click RCE, CVSS 8.8). Refusing handshake. Fix: upgrade OpenClaw to >= ${MIN_PATCHED_VERSION}.`
|
|
2986
|
+
);
|
|
2987
|
+
}
|
|
2988
|
+
if (!statusAuthEnabled(status)) {
|
|
2989
|
+
throw new ParticipantDiscoveryError(
|
|
2990
|
+
`OpenClaw at ${url} has authentication DISABLED (the exposed-instance default). Refusing handshake. Fix: enable auth in your OpenClaw config (set auth.enabled=true / OPENCLAW_AUTH=1), restart the instance, and set OPENCLAW_TOKEN for Bourdon.`
|
|
2991
|
+
);
|
|
2992
|
+
}
|
|
2993
|
+
return version;
|
|
2994
|
+
}
|
|
2995
|
+
function before(dateText, since) {
|
|
2996
|
+
const parsed = fromIsoformatUtc(dateText.replace(/Z/g, "+00:00"));
|
|
2997
|
+
if (parsed === null) return false;
|
|
2998
|
+
return parsed.getTime() < since.getTime();
|
|
2999
|
+
}
|
|
3000
|
+
function fromIsoformatUtc(text) {
|
|
3001
|
+
const value = text.trim();
|
|
3002
|
+
if (!value) return null;
|
|
3003
|
+
const hasOffset = /[+-]\d{2}:?\d{2}$/.test(value);
|
|
3004
|
+
const normalized = hasOffset || !value.includes("T") ? value : `${value}Z`;
|
|
3005
|
+
const ms = Date.parse(normalized);
|
|
3006
|
+
return Number.isNaN(ms) ? null : new Date(ms);
|
|
3007
|
+
}
|
|
3008
|
+
function nowIsoSeconds2() {
|
|
3009
|
+
return (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
3010
|
+
}
|
|
3011
|
+
function curlGet2(url, token, timeoutSec) {
|
|
3012
|
+
const args = ["-sS", "-o", "-", "--max-time", String(timeoutSec)];
|
|
3013
|
+
if (token) {
|
|
3014
|
+
args.push("-H", `Authorization: Bearer ${token}`);
|
|
3015
|
+
}
|
|
3016
|
+
args.push("-w", "\n__BOURDON_STATUS__%{http_code}", url);
|
|
3017
|
+
const raw = (0, import_node_child_process2.execFileSync)("curl", args, {
|
|
3018
|
+
encoding: "utf8",
|
|
3019
|
+
timeout: (timeoutSec + 2) * 1e3,
|
|
3020
|
+
maxBuffer: 8 * 1024 * 1024
|
|
3021
|
+
});
|
|
3022
|
+
const statusMatch = /\n__BOURDON_STATUS__(\d+)$/.exec(raw);
|
|
3023
|
+
const status = statusMatch ? Number(statusMatch[1]) : 0;
|
|
3024
|
+
const body = statusMatch ? raw.slice(0, statusMatch.index) : raw;
|
|
3025
|
+
return { status, body };
|
|
3026
|
+
}
|
|
3027
|
+
var OpenClawApiClient = class {
|
|
3028
|
+
url;
|
|
3029
|
+
token;
|
|
3030
|
+
timeout;
|
|
3031
|
+
constructor(url, token, timeout = 5) {
|
|
3032
|
+
this.url = url.replace(/\/+$/, "");
|
|
3033
|
+
this.token = token;
|
|
3034
|
+
this.timeout = timeout;
|
|
3035
|
+
}
|
|
3036
|
+
get(path) {
|
|
3037
|
+
let resp;
|
|
3038
|
+
try {
|
|
3039
|
+
resp = curlGet2(this.url + path, this.token, this.timeout);
|
|
3040
|
+
} catch (err) {
|
|
3041
|
+
throw new ParticipantDiscoveryError(
|
|
3042
|
+
`OpenClaw instance unreachable at ${this.url} (${String(err)})`
|
|
3043
|
+
);
|
|
3044
|
+
}
|
|
3045
|
+
if (resp.status >= 400) {
|
|
3046
|
+
throw new ParticipantDiscoveryError(
|
|
3047
|
+
`OpenClaw API ${path} returned HTTP ${resp.status}`
|
|
3048
|
+
);
|
|
3049
|
+
}
|
|
3050
|
+
if (resp.status === 0) {
|
|
3051
|
+
throw new ParticipantDiscoveryError(
|
|
3052
|
+
`OpenClaw instance unreachable at ${this.url} (no HTTP response)`
|
|
3053
|
+
);
|
|
3054
|
+
}
|
|
3055
|
+
try {
|
|
3056
|
+
return JSON.parse(resp.body);
|
|
3057
|
+
} catch (err) {
|
|
3058
|
+
throw new ParticipantDiscoveryError(
|
|
3059
|
+
`OpenClaw instance unreachable at ${this.url} (${String(err)})`
|
|
3060
|
+
);
|
|
3061
|
+
}
|
|
3062
|
+
}
|
|
3063
|
+
status() {
|
|
3064
|
+
const data = this.get("/api/status");
|
|
3065
|
+
return isRecord4(data) ? data : {};
|
|
3066
|
+
}
|
|
3067
|
+
sessions() {
|
|
3068
|
+
return this.readCollection("/api/sessions", "sessions");
|
|
3069
|
+
}
|
|
3070
|
+
memories() {
|
|
3071
|
+
return this.readCollection("/api/memories", "memories");
|
|
3072
|
+
}
|
|
3073
|
+
/** Shared `sessions()`/`memories()` body: try/except-returns-[], unwrap the
|
|
3074
|
+
* `{ "<key>": [...] }` envelope, and keep only object rows. */
|
|
3075
|
+
readCollection(path, key) {
|
|
3076
|
+
let data;
|
|
3077
|
+
try {
|
|
3078
|
+
data = this.get(path);
|
|
3079
|
+
} catch (err) {
|
|
3080
|
+
if (err instanceof ParticipantDiscoveryError) return [];
|
|
3081
|
+
throw err;
|
|
3082
|
+
}
|
|
3083
|
+
if (isRecord4(data)) {
|
|
3084
|
+
data = data[key] || [];
|
|
3085
|
+
}
|
|
3086
|
+
if (!Array.isArray(data)) return [];
|
|
3087
|
+
return data.filter(isRecord4);
|
|
3088
|
+
}
|
|
3089
|
+
};
|
|
3090
|
+
var OpenClawParticipant = class {
|
|
3091
|
+
agentId = AGENT_ID8;
|
|
3092
|
+
agentType = AGENT_TYPE8;
|
|
3093
|
+
displayName = DISPLAY_NAME6;
|
|
3094
|
+
/** Trust marker consumed by `bourdon agent add/set-tier` + export-all:
|
|
3095
|
+
* registering as trusted needs --i-understand-the-risk; exports stage. */
|
|
3096
|
+
quarantinedClass = true;
|
|
3097
|
+
url;
|
|
3098
|
+
client;
|
|
3099
|
+
static defaultNativePath(home) {
|
|
3100
|
+
return (0, import_node_path8.join)(home ?? (0, import_node_os8.homedir)(), ".openclaw");
|
|
3101
|
+
}
|
|
3102
|
+
constructor(url, token, client) {
|
|
3103
|
+
this.url = (url || process.env.OPENCLAW_URL || DEFAULT_OPENCLAW_URL).replace(/\/+$/, "");
|
|
3104
|
+
this.client = client ?? new OpenClawApiClient(this.url, token ?? process.env.OPENCLAW_TOKEN);
|
|
3105
|
+
}
|
|
3106
|
+
get nativePath() {
|
|
3107
|
+
return this.url;
|
|
3108
|
+
}
|
|
3109
|
+
// -- protocol ----------------------------------------------------------------
|
|
3110
|
+
discover() {
|
|
3111
|
+
const status = this.client.status();
|
|
3112
|
+
const version = verifyInstance(status, this.url);
|
|
3113
|
+
return {
|
|
3114
|
+
path: this.url,
|
|
3115
|
+
version,
|
|
3116
|
+
metadata: { auth_enabled: true, transport: "http" }
|
|
3117
|
+
};
|
|
3118
|
+
}
|
|
3119
|
+
exportSessions(since, limit = SESSION_LIMIT) {
|
|
3120
|
+
this.discover();
|
|
3121
|
+
const sessions = [];
|
|
3122
|
+
for (const row of this.client.sessions()) {
|
|
3123
|
+
const date = String(row["updated_at"] || row["started_at"] || row["date"] || "").trim();
|
|
3124
|
+
if (!date) continue;
|
|
3125
|
+
if (since !== void 0 && before(date, since)) continue;
|
|
3126
|
+
const title = safeNativeMemoryText2(String(row["title"] || row["summary"] || ""));
|
|
3127
|
+
const projects = Array.isArray(row["projects"]) ? row["projects"].slice(0, 5) : [];
|
|
3128
|
+
sessions.push(
|
|
3129
|
+
(0, import_l58.makeSession)({
|
|
3130
|
+
date,
|
|
3131
|
+
cwd: void 0,
|
|
3132
|
+
project_focus: projects.map((p) => safeNativeMemoryText2(String(p))),
|
|
3133
|
+
key_actions: title ? [title] : []
|
|
3134
|
+
})
|
|
3135
|
+
);
|
|
3136
|
+
if (sessions.length >= limit) break;
|
|
3137
|
+
}
|
|
3138
|
+
return sessions;
|
|
3139
|
+
}
|
|
3140
|
+
exportL5(since) {
|
|
3141
|
+
const store = this.discover();
|
|
3142
|
+
const entities = [];
|
|
3143
|
+
for (const row of this.client.memories().slice(0, ENTITY_LIMIT)) {
|
|
3144
|
+
const name = safeNativeMemoryText2(String(row["name"] || row["title"] || ""));
|
|
3145
|
+
if (!name) continue;
|
|
3146
|
+
const tags = Array.isArray(row["tags"]) ? row["tags"].slice(0, 8) : [];
|
|
3147
|
+
entities.push(
|
|
3148
|
+
(0, import_l58.makeEntity)({
|
|
3149
|
+
name,
|
|
3150
|
+
type: String(row["type"] || "topic"),
|
|
3151
|
+
summary: safeNativeMemoryText2(String(row["summary"] || "")),
|
|
3152
|
+
tags: tags.map((t) => safeNativeMemoryText2(String(t)))
|
|
3153
|
+
})
|
|
3154
|
+
);
|
|
3155
|
+
}
|
|
3156
|
+
const policy = (0, import_l58.makeVisibilityPolicy)({ default: import_l58.Visibility.TEAM });
|
|
3157
|
+
return (0, import_l58.makeManifest)({
|
|
3158
|
+
spec_version: SPEC_VERSION,
|
|
3159
|
+
agent: (0, import_l58.makeAgentInfo)({
|
|
3160
|
+
id: AGENT_ID8,
|
|
3161
|
+
type: AGENT_TYPE8,
|
|
3162
|
+
instance: this.url,
|
|
3163
|
+
role_narrative: ROLE_NARRATIVE8
|
|
3164
|
+
}),
|
|
3165
|
+
last_updated: nowIsoSeconds2(),
|
|
3166
|
+
capabilities: [`openclaw ${store.version}`],
|
|
3167
|
+
recent_sessions: this.exportSessions(since),
|
|
3168
|
+
known_entities: (0, import_l58.filterForFederation)(entities, policy),
|
|
3169
|
+
visibility_policy: policy
|
|
3170
|
+
});
|
|
3171
|
+
}
|
|
3172
|
+
healthCheck() {
|
|
3173
|
+
let store;
|
|
3174
|
+
try {
|
|
3175
|
+
store = this.discover();
|
|
3176
|
+
} catch (exc) {
|
|
3177
|
+
if (exc instanceof ParticipantDiscoveryError) {
|
|
3178
|
+
const reason = exc.message;
|
|
3179
|
+
let fix = "see the refusal reason above";
|
|
3180
|
+
if (reason.includes("upgrade OpenClaw")) {
|
|
3181
|
+
fix = `upgrade the OpenClaw instance to >= ${MIN_PATCHED_VERSION}`;
|
|
3182
|
+
} else if (reason.includes("authentication DISABLED")) {
|
|
3183
|
+
fix = "enable auth on the OpenClaw instance, then set OPENCLAW_TOKEN";
|
|
3184
|
+
} else if (reason.includes("unreachable")) {
|
|
3185
|
+
fix = "start OpenClaw locally or set OPENCLAW_URL to the instance address";
|
|
3186
|
+
}
|
|
3187
|
+
return { status: "blocked", reason, proposedFix: fix, details: {} };
|
|
3188
|
+
}
|
|
3189
|
+
return {
|
|
3190
|
+
status: "degraded",
|
|
3191
|
+
reason: exc instanceof Error ? exc.message : String(exc),
|
|
3192
|
+
details: {}
|
|
3193
|
+
};
|
|
3194
|
+
}
|
|
3195
|
+
return {
|
|
3196
|
+
status: "ok",
|
|
3197
|
+
details: { url: this.url, version: store.version, quarantined: true }
|
|
3198
|
+
};
|
|
3199
|
+
}
|
|
3200
|
+
};
|
|
3201
|
+
|
|
1392
3202
|
// src/registry.ts
|
|
1393
3203
|
var FIRST_PARTY = [
|
|
3204
|
+
CascadeParticipant,
|
|
1394
3205
|
ClaudeCodeParticipant,
|
|
3206
|
+
CopilotParticipant,
|
|
3207
|
+
CopilotCliParticipant,
|
|
3208
|
+
CursorParticipant,
|
|
1395
3209
|
GitHubCopilotParticipant,
|
|
1396
|
-
HermesParticipant
|
|
3210
|
+
HermesParticipant,
|
|
3211
|
+
OpenClawParticipant
|
|
1397
3212
|
];
|
|
1398
|
-
var
|
|
3213
|
+
var logger3 = {
|
|
1399
3214
|
warn(msg) {
|
|
1400
3215
|
console.warn(`[bourdon.participants] ${msg}`);
|
|
1401
3216
|
}
|
|
@@ -1407,7 +3222,7 @@ function discoverParticipants(ctors = FIRST_PARTY) {
|
|
|
1407
3222
|
try {
|
|
1408
3223
|
instance = new Ctor();
|
|
1409
3224
|
} catch (err) {
|
|
1410
|
-
|
|
3225
|
+
logger3.warn(
|
|
1411
3226
|
`Skipping participant ${Ctor.name || "<anonymous>"}: construction failed: ${String(err)}`
|
|
1412
3227
|
);
|
|
1413
3228
|
continue;
|
|
@@ -1425,19 +3240,26 @@ function resolvePluginParticipants() {
|
|
|
1425
3240
|
}
|
|
1426
3241
|
|
|
1427
3242
|
// src/index.ts
|
|
1428
|
-
var
|
|
3243
|
+
var import_l59 = require("@getbourdon/l5");
|
|
1429
3244
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1430
3245
|
0 && (module.exports = {
|
|
1431
3246
|
CONTRACT_VERSION,
|
|
1432
3247
|
CacheEntry,
|
|
3248
|
+
CascadeParticipant,
|
|
1433
3249
|
ClaudeCodeParticipant,
|
|
3250
|
+
CopilotCliParticipant,
|
|
3251
|
+
CopilotParticipant,
|
|
3252
|
+
CursorParticipant,
|
|
1434
3253
|
DEFAULT_CACHE_TTL_SECONDS,
|
|
1435
3254
|
FIRST_PARTY,
|
|
1436
3255
|
GitHubCopilotParticipant,
|
|
1437
3256
|
HermesParticipant,
|
|
3257
|
+
MIN_PATCHED_VERSION,
|
|
1438
3258
|
NETWORK_CONTRACT_VERSION,
|
|
1439
3259
|
NetworkParticipant,
|
|
1440
3260
|
NetworkUnavailable,
|
|
3261
|
+
OpenClawApiClient,
|
|
3262
|
+
OpenClawParticipant,
|
|
1441
3263
|
ParticipantAuthError,
|
|
1442
3264
|
ParticipantDiscoveryError,
|
|
1443
3265
|
ParticipantError,
|
|
@@ -1464,6 +3286,7 @@ var import_l54 = require("@getbourdon/l5");
|
|
|
1464
3286
|
tableExists,
|
|
1465
3287
|
toDict,
|
|
1466
3288
|
tryOpenReadonly,
|
|
1467
|
-
validateManifest
|
|
3289
|
+
validateManifest,
|
|
3290
|
+
verifyInstance
|
|
1468
3291
|
});
|
|
1469
3292
|
//# sourceMappingURL=index.cjs.map
|