@getbourdon/participants 0.1.0 → 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/index.cjs ADDED
@@ -0,0 +1,3292 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ CONTRACT_VERSION: () => CONTRACT_VERSION,
24
+ CacheEntry: () => CacheEntry,
25
+ CascadeParticipant: () => CascadeParticipant,
26
+ ClaudeCodeParticipant: () => ClaudeCodeParticipant,
27
+ CopilotCliParticipant: () => CopilotCliParticipant,
28
+ CopilotParticipant: () => CopilotParticipant,
29
+ CursorParticipant: () => CursorParticipant,
30
+ DEFAULT_CACHE_TTL_SECONDS: () => DEFAULT_CACHE_TTL_SECONDS,
31
+ FIRST_PARTY: () => FIRST_PARTY,
32
+ GitHubCopilotParticipant: () => GitHubCopilotParticipant,
33
+ HermesParticipant: () => HermesParticipant,
34
+ MIN_PATCHED_VERSION: () => MIN_PATCHED_VERSION,
35
+ NETWORK_CONTRACT_VERSION: () => NETWORK_CONTRACT_VERSION,
36
+ NetworkParticipant: () => NetworkParticipant,
37
+ NetworkUnavailable: () => NetworkUnavailable,
38
+ OpenClawApiClient: () => OpenClawApiClient,
39
+ OpenClawParticipant: () => OpenClawParticipant,
40
+ ParticipantAuthError: () => ParticipantAuthError,
41
+ ParticipantDiscoveryError: () => ParticipantDiscoveryError,
42
+ ParticipantError: () => ParticipantError,
43
+ ParticipantExportError: () => ParticipantExportError,
44
+ ParticipantVersionMismatchError: () => ParticipantVersionMismatchError,
45
+ PayloadCache: () => PayloadCache,
46
+ SPEC_VERSION: () => SPEC_VERSION,
47
+ Visibility: () => import_l59.Visibility,
48
+ applyVisibility: () => import_l59.applyVisibility,
49
+ defaultCacheRoot: () => defaultCacheRoot,
50
+ discoverParticipants: () => discoverParticipants,
51
+ envAuthProvider: () => envAuthProvider,
52
+ epochToIsoDate: () => epochToIsoDate,
53
+ epochToIsoDatetime: () => epochToIsoDatetime,
54
+ filterForFederation: () => import_l59.filterForFederation,
55
+ friendlyLabel: () => friendlyLabel,
56
+ ghTokenProvider: () => ghTokenProvider,
57
+ isParticipant: () => isParticipant,
58
+ openReadonly: () => openReadonly,
59
+ projectKeyFromCwd: () => projectKeyFromCwd,
60
+ resolvePluginParticipants: () => resolvePluginParticipants,
61
+ sqliteAvailable: () => sqliteAvailable,
62
+ tableColumns: () => tableColumns,
63
+ tableExists: () => tableExists,
64
+ toDict: () => import_l59.toDict,
65
+ tryOpenReadonly: () => tryOpenReadonly,
66
+ validateManifest: () => import_l59.validateManifest,
67
+ verifyInstance: () => verifyInstance
68
+ });
69
+ module.exports = __toCommonJS(index_exports);
70
+
71
+ // src/base.ts
72
+ var CONTRACT_VERSION = "0.1";
73
+ var SPEC_VERSION = "0.1";
74
+ var ParticipantError = class extends Error {
75
+ constructor(message) {
76
+ super(message);
77
+ this.name = new.target.name;
78
+ }
79
+ };
80
+ var ParticipantDiscoveryError = class extends ParticipantError {
81
+ };
82
+ var ParticipantExportError = class extends ParticipantError {
83
+ };
84
+ var ParticipantVersionMismatchError = class extends ParticipantDiscoveryError {
85
+ };
86
+ function isParticipant(obj) {
87
+ if (obj === null || typeof obj !== "object") return false;
88
+ const o = obj;
89
+ return typeof o["agentId"] === "string" && typeof o["agentType"] === "string" && typeof o["exportL5"] === "function" && typeof o["healthCheck"] === "function";
90
+ }
91
+
92
+ // src/sqlite-base.ts
93
+ var import_node_fs = require("fs");
94
+ var import_node_module = require("module");
95
+ var import_meta = {};
96
+ var moduleUrl = typeof import_meta !== "undefined" && import_meta.url || (typeof __filename !== "undefined" ? __filename : process.cwd());
97
+ var nodeRequire = (0, import_node_module.createRequire)(moduleUrl);
98
+ var driver;
99
+ function loadDriver() {
100
+ if (driver !== void 0) return driver;
101
+ try {
102
+ driver = nodeRequire("better-sqlite3");
103
+ } catch {
104
+ driver = null;
105
+ }
106
+ return driver;
107
+ }
108
+ function sqliteAvailable() {
109
+ return loadDriver() !== null;
110
+ }
111
+ function openReadonly(dbPath, opts = {}) {
112
+ const Ctor = loadDriver();
113
+ if (!Ctor) {
114
+ throw new Error("better-sqlite3 native addon is not available");
115
+ }
116
+ return new Ctor(dbPath, {
117
+ readonly: true,
118
+ fileMustExist: true,
119
+ timeout: opts.timeoutMs ?? 2e3
120
+ });
121
+ }
122
+ function tryOpenReadonly(dbPath, opts = {}) {
123
+ try {
124
+ if (!(0, import_node_fs.statSync)(dbPath).isFile()) return null;
125
+ } catch {
126
+ return null;
127
+ }
128
+ try {
129
+ return openReadonly(dbPath, opts);
130
+ } catch {
131
+ return null;
132
+ }
133
+ }
134
+ function tableExists(db, name) {
135
+ try {
136
+ const row = db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name=? LIMIT 1").get(name);
137
+ return row !== void 0;
138
+ } catch {
139
+ return false;
140
+ }
141
+ }
142
+ function tableColumns(db, table) {
143
+ try {
144
+ const rows = db.prepare(`PRAGMA table_info(${table})`).all();
145
+ return new Set(rows.map((r) => r.name));
146
+ } catch {
147
+ return /* @__PURE__ */ new Set();
148
+ }
149
+ }
150
+ function toFiniteNumber(value) {
151
+ if (value === null || value === void 0) return null;
152
+ const n = typeof value === "number" ? value : Number(value);
153
+ return Number.isFinite(n) ? n : null;
154
+ }
155
+ function epochToIsoDate(value) {
156
+ const n = toFiniteNumber(value);
157
+ if (n === null) return null;
158
+ const d = new Date(n * 1e3);
159
+ if (Number.isNaN(d.getTime())) return null;
160
+ return d.toISOString().slice(0, 10);
161
+ }
162
+ function epochToIsoDatetime(value) {
163
+ const n = toFiniteNumber(value);
164
+ if (n === null) return null;
165
+ const d = new Date(n * 1e3);
166
+ if (Number.isNaN(d.getTime())) return null;
167
+ return d.toISOString().replace(/\.\d{3}Z$/, "+00:00").replace(/Z$/, "+00:00");
168
+ }
169
+ function basenameOf(p) {
170
+ const trimmed = p.replace(/[\\/]+$/, "");
171
+ const parts = trimmed.split(/[\\/]/);
172
+ return parts[parts.length - 1] ?? "";
173
+ }
174
+ function projectKeyFromCwd(cwd, genericNames = /* @__PURE__ */ new Set()) {
175
+ if (!cwd) return null;
176
+ const name = basenameOf(cwd).trim().toLowerCase();
177
+ if (!name || genericNames.has(name)) return null;
178
+ return name;
179
+ }
180
+ function friendlyLabel(key) {
181
+ return key.replace(/[-_]+/g, " ").trim().replace(/\S+/g, (w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase());
182
+ }
183
+
184
+ // src/network-base.ts
185
+ var import_node_fs2 = require("fs");
186
+ var import_node_os = require("os");
187
+ var import_node_path = require("path");
188
+ var NETWORK_CONTRACT_VERSION = "0.3";
189
+ var DEFAULT_CACHE_TTL_SECONDS = 15 * 60;
190
+ var NetworkUnavailable = class extends ParticipantError {
191
+ };
192
+ var ParticipantAuthError = class extends ParticipantError {
193
+ };
194
+ function envAuthProvider(varName) {
195
+ return () => process.env[varName] || null;
196
+ }
197
+ function defaultCacheRoot() {
198
+ const xdg = process.env.XDG_CACHE_HOME;
199
+ const base = xdg ? xdg : (0, import_node_path.join)((0, import_node_os.homedir)(), ".cache");
200
+ return (0, import_node_path.join)(base, "bourdon");
201
+ }
202
+ var CacheEntry = class {
203
+ constructor(fetchedAt, payload) {
204
+ this.fetchedAt = fetchedAt;
205
+ this.payload = payload;
206
+ }
207
+ fetchedAt;
208
+ payload;
209
+ get ageSeconds() {
210
+ return Math.max(0, Date.now() / 1e3 - this.fetchedAt);
211
+ }
212
+ isFresh(ttlSeconds) {
213
+ return this.ageSeconds < ttlSeconds;
214
+ }
215
+ };
216
+ var PayloadCache = class {
217
+ slug;
218
+ root;
219
+ path;
220
+ constructor(slug, rootDir) {
221
+ this.slug = slug;
222
+ this.root = (0, import_node_path.join)(rootDir ?? defaultCacheRoot(), slug);
223
+ this.path = (0, import_node_path.join)(this.root, "payload.json");
224
+ }
225
+ read() {
226
+ let raw;
227
+ try {
228
+ raw = JSON.parse((0, import_node_fs2.readFileSync)(this.path, "utf8"));
229
+ } catch {
230
+ return null;
231
+ }
232
+ if (typeof raw !== "object" || raw === null || !("payload" in raw)) return null;
233
+ const obj = raw;
234
+ let fetchedAt = Number(obj["fetched_at"] ?? 0);
235
+ if (!Number.isFinite(fetchedAt)) fetchedAt = 0;
236
+ const payload = obj["payload"];
237
+ if (typeof payload !== "object" || payload === null || Array.isArray(payload)) return null;
238
+ return new CacheEntry(fetchedAt, payload);
239
+ }
240
+ write(payload) {
241
+ try {
242
+ (0, import_node_fs2.mkdirSync)(this.root, { recursive: true });
243
+ const tmp = `${this.path}.tmp`;
244
+ (0, import_node_fs2.writeFileSync)(tmp, JSON.stringify({ fetched_at: Date.now() / 1e3, payload }), "utf8");
245
+ (0, import_node_fs2.renameSync)(tmp, this.path);
246
+ } catch {
247
+ }
248
+ }
249
+ };
250
+ var NetworkParticipant = class {
251
+ participantSlug = "network";
252
+ agentId = "network";
253
+ agentType = "other";
254
+ cacheTtlSeconds = DEFAULT_CACHE_TTL_SECONDS;
255
+ nativePath = "network://";
256
+ authProvider;
257
+ cacheRoot;
258
+ cacheInstance;
259
+ constructor(opts = {}) {
260
+ this.authProvider = opts.authProvider ?? null;
261
+ this.cacheRoot = opts.cacheRoot;
262
+ }
263
+ /** Lazily built so it reads the SUBCLASS's `participantSlug` — subclass field
264
+ * initializers run after `super()`, so building the cache in the constructor
265
+ * would capture the base "network" slug instead. */
266
+ get cache() {
267
+ if (!this.cacheInstance) {
268
+ this.cacheInstance = new PayloadCache(this.participantSlug, this.cacheRoot);
269
+ }
270
+ return this.cacheInstance;
271
+ }
272
+ // -- Core fetch-with-cache-and-degrade -------------------------------------
273
+ /**
274
+ * Resolve (payload, source) where source is 'cache' | 'network' | 'stale-cache'.
275
+ * 1. Fresh cache hit → 'cache' (no network)
276
+ * 2. Miss/expired + token → 'network' (fetch + refresh cache)
277
+ * 3. Net fails + any cache → 'stale-cache' (serve last good payload)
278
+ * 4. Net fails + no cache → raise
279
+ * Auth errors are never swallowed — they propagate so health surfaces
280
+ * 'blocked' rather than serving stale data behind a dead token.
281
+ */
282
+ getPayload() {
283
+ const cached = this.cache.read();
284
+ if (cached !== null && cached.isFresh(this.cacheTtlSeconds)) {
285
+ return { payload: cached.payload, source: "cache" };
286
+ }
287
+ const token = this.authProvider ? this.authProvider() : null;
288
+ if (!token) {
289
+ if (cached !== null) return { payload: cached.payload, source: "stale-cache" };
290
+ throw new ParticipantAuthError(
291
+ `${this.participantSlug}: no auth token available and no cache`
292
+ );
293
+ }
294
+ let payload;
295
+ try {
296
+ payload = this.fetchPayload(token);
297
+ } catch (err) {
298
+ if (err instanceof ParticipantAuthError) throw err;
299
+ if (err instanceof NetworkUnavailable) {
300
+ if (cached !== null) return { payload: cached.payload, source: "stale-cache" };
301
+ throw err;
302
+ }
303
+ throw err;
304
+ }
305
+ this.cache.write(payload);
306
+ return { payload, source: "network" };
307
+ }
308
+ // -- Participant protocol ---------------------------------------------------
309
+ discover() {
310
+ const { source } = this.getPayload();
311
+ return {
312
+ path: this.nativePath,
313
+ version: `${this.participantSlug}-network-v1`,
314
+ metadata: { source, contract_version: NETWORK_CONTRACT_VERSION }
315
+ };
316
+ }
317
+ exportL5(_since) {
318
+ const { payload } = this.getPayload();
319
+ return this.payloadToL5(payload);
320
+ }
321
+ exportSessions(since, limit = 100) {
322
+ const manifest = this.exportL5(since);
323
+ let sessions = manifest.recent_sessions ?? [];
324
+ if (since) {
325
+ const cutoff = since.toISOString().slice(0, 10);
326
+ sessions = sessions.filter((s) => (s.date || "") >= cutoff);
327
+ }
328
+ return sessions.slice(0, limit);
329
+ }
330
+ healthCheck() {
331
+ let source;
332
+ try {
333
+ source = this.getPayload().source;
334
+ } catch (err) {
335
+ if (err instanceof ParticipantAuthError) {
336
+ return {
337
+ status: "blocked",
338
+ reason: String(err.message),
339
+ details: { participant: this.participantSlug },
340
+ proposedFix: `Provide a valid credential for ${this.participantSlug} (see the adapter docs for the auth path) and re-run \`bourdon export-all\`.`
341
+ };
342
+ }
343
+ if (err instanceof NetworkUnavailable) {
344
+ return {
345
+ status: "degraded",
346
+ reason: `network unavailable and no cache: ${err.message}`,
347
+ details: { participant: this.participantSlug },
348
+ proposedFix: "Check connectivity, then re-run `bourdon export-all`."
349
+ };
350
+ }
351
+ return {
352
+ status: "blocked",
353
+ reason: `unexpected error resolving payload: ${String(err)}`,
354
+ details: { participant: this.participantSlug },
355
+ proposedFix: `${this.participantSlug} raised an unexpected error during healthCheck; its fetchPayload should only raise ParticipantAuthError / NetworkUnavailable.`
356
+ };
357
+ }
358
+ const cached = this.cache.read();
359
+ const details = { participant: this.participantSlug, source };
360
+ if (cached !== null) {
361
+ details["cache_age_seconds"] = Math.round(cached.ageSeconds * 10) / 10;
362
+ details["cache_fetched_at"] = new Date(cached.fetchedAt * 1e3).toISOString().replace(/\.\d{3}Z$/, "+00:00");
363
+ }
364
+ if (source === "stale-cache") {
365
+ return {
366
+ status: "degraded",
367
+ reason: "serving stale cache (network unavailable or no token)",
368
+ details,
369
+ proposedFix: "Restore connectivity / refresh the token to update."
370
+ };
371
+ }
372
+ return { status: "ok", details };
373
+ }
374
+ };
375
+
376
+ // src/readers/hermes.ts
377
+ var import_node_fs3 = require("fs");
378
+ var import_node_os2 = require("os");
379
+ var import_node_path2 = require("path");
380
+ var import_l5 = require("@getbourdon/l5");
381
+ var import_redaction = require("@getbourdon/redaction");
382
+ var AGENT_ID = "hermes";
383
+ var AGENT_TYPE = "code-assistant";
384
+ var DISPLAY_NAME = "Hermes Agent";
385
+ var ROLE_NARRATIVE = "General-purpose tool-calling assistant (Nous Research). Operates across CLI, TUI, and messaging gateways (Slack/Telegram/Discord/WhatsApp); runs terminal, file, web, and delegation toolsets. Carries durable cross-session memory and a curated skill library. Federates session + memory context, not vendor account.";
386
+ var DEFAULT_SESSION_LIMIT = 100;
387
+ var MEMORY_FILENAMES = ["memory.md", "user.md"];
388
+ var DEFAULT_POLICY = (0, import_l5.makeVisibilityPolicy)({
389
+ default: import_l5.Visibility.TEAM,
390
+ private_tags: ["personal", "financial", "credential", "secret", "health", "family", "legal"],
391
+ team_tags: ["hermes-memory", "hermes-session", "hermes-project"]
392
+ });
393
+ var GENERIC_PROJECT_NAMES = /* @__PURE__ */ new Set([
394
+ "",
395
+ "root",
396
+ "home",
397
+ "tmp",
398
+ "temp",
399
+ "desktop",
400
+ "documents",
401
+ "downloads",
402
+ "src"
403
+ ]);
404
+ function defaultNativePath(home) {
405
+ if (home) return (0, import_node_path2.join)(home, ".hermes");
406
+ const env = process.env.HERMES_HOME;
407
+ if (env) return env;
408
+ return (0, import_node_path2.join)((0, import_node_os2.homedir)(), ".hermes");
409
+ }
410
+ function isDir(p) {
411
+ try {
412
+ return (0, import_node_fs3.statSync)(p).isDirectory();
413
+ } catch {
414
+ return false;
415
+ }
416
+ }
417
+ function isFile(p) {
418
+ try {
419
+ return (0, import_node_fs3.statSync)(p).isFile();
420
+ } catch {
421
+ return false;
422
+ }
423
+ }
424
+ function bounded(value, limit = 240) {
425
+ const cleaned = (0, import_redaction.redactText)((value || "").replace(/\s+/g, " ").trim(), limit);
426
+ return cleaned.trim();
427
+ }
428
+ function projectKey(cwd) {
429
+ return projectKeyFromCwd(cwd, GENERIC_PROJECT_NAMES);
430
+ }
431
+ function collectSessionRows(stateDb, limit = DEFAULT_SESSION_LIMIT) {
432
+ if (!isFile(stateDb)) return [];
433
+ const conn = tryOpenReadonly(stateDb);
434
+ if (!conn) return [];
435
+ const out = [];
436
+ try {
437
+ if (!tableExists(conn, "sessions")) return [];
438
+ const cols = tableColumns(conn, "sessions");
439
+ const archivedClause = cols.has("archived") ? "WHERE COALESCE(archived, 0) = 0" : "";
440
+ const rows = conn.prepare(
441
+ `SELECT id, source, model, title, cwd, started_at, ended_at,
442
+ message_count, tool_call_count
443
+ FROM sessions
444
+ ${archivedClause}
445
+ ORDER BY started_at DESC
446
+ LIMIT ?`
447
+ ).all(limit);
448
+ for (const r of rows) {
449
+ const day = epochToIsoDate(r["started_at"]);
450
+ if (!day) continue;
451
+ out.push({
452
+ id: String(r["id"]),
453
+ source: String(r["source"] ?? "").toLowerCase(),
454
+ model: r["model"],
455
+ title: r["title"] ?? null,
456
+ cwd: r["cwd"] ?? null,
457
+ date: day,
458
+ messageCount: Number(r["message_count"] ?? 0) || 0,
459
+ toolCallCount: Number(r["tool_call_count"] ?? 0) || 0
460
+ });
461
+ }
462
+ } catch {
463
+ } finally {
464
+ conn.close();
465
+ }
466
+ return out;
467
+ }
468
+ function toolNamesForSession(conn, sessionId) {
469
+ if (!tableExists(conn, "messages")) return [];
470
+ try {
471
+ const rows = conn.prepare(
472
+ `SELECT DISTINCT tool_name FROM messages
473
+ WHERE session_id = ? AND tool_name IS NOT NULL AND tool_name != ''`
474
+ ).all(sessionId);
475
+ const names = /* @__PURE__ */ new Set();
476
+ for (const r of rows) {
477
+ if (r.tool_name) names.add(r.tool_name);
478
+ }
479
+ return [...names].sort();
480
+ } catch {
481
+ return [];
482
+ }
483
+ }
484
+ var BULLET_RE = /^\s*[-*]\s+(.*)$/;
485
+ function parseMemoryFile(path) {
486
+ let text;
487
+ try {
488
+ text = (0, import_node_fs3.readFileSync)(path, "utf8");
489
+ } catch {
490
+ return [];
491
+ }
492
+ const entries = [];
493
+ for (const line of text.split(/\r?\n/)) {
494
+ const m = BULLET_RE.exec(line);
495
+ const candidate = m ? m[1] ?? "" : line.trim();
496
+ if (candidate && !candidate.startsWith("#")) entries.push(candidate);
497
+ }
498
+ return entries;
499
+ }
500
+ function collectMemoryEntries(home) {
501
+ const memDir = (0, import_node_path2.join)(home, "memories");
502
+ const result = [];
503
+ if (!isDir(memDir)) return result;
504
+ for (const fname of MEMORY_FILENAMES) {
505
+ const path = (0, import_node_path2.join)(memDir, fname);
506
+ if (isFile(path)) {
507
+ const entries = parseMemoryFile(path);
508
+ if (entries.length) {
509
+ const stem = fname.replace(/\.md$/, "");
510
+ result.push([stem, entries]);
511
+ }
512
+ }
513
+ }
514
+ return result;
515
+ }
516
+ function buildSession(record, toolNames) {
517
+ const key = projectKey(record.cwd);
518
+ const focus = [];
519
+ if (key) focus.push(key);
520
+ const keyActions = [];
521
+ if (record.title) keyActions.push(bounded(record.title, 160));
522
+ if (toolNames.length) {
523
+ keyActions.push("Tools: " + toolNames.slice(0, 8).join(", "));
524
+ } else if (record.toolCallCount) {
525
+ keyActions.push(`${record.toolCallCount} tool call(s)`);
526
+ }
527
+ return (0, import_l5.makeSession)({
528
+ date: record.date,
529
+ cwd: record.cwd || void 0,
530
+ project_focus: focus,
531
+ key_actions: keyActions,
532
+ visibility: import_l5.Visibility.TEAM
533
+ });
534
+ }
535
+ function memoryEntity(store, entry) {
536
+ const tag = store === "user" ? "hermes-user-memory" : "hermes-memory";
537
+ return (0, import_l5.makeEntity)({
538
+ name: bounded(entry, 80),
539
+ type: store === "user" ? "preference" : "fact",
540
+ summary: bounded(entry, 240),
541
+ tags: [tag],
542
+ visibility: import_l5.Visibility.TEAM
543
+ });
544
+ }
545
+ function projectEntities(records) {
546
+ const counts = /* @__PURE__ */ new Map();
547
+ for (const r of records) {
548
+ const key = projectKey(r.cwd);
549
+ if (key) counts.set(key, (counts.get(key) ?? 0) + 1);
550
+ }
551
+ const entities = [];
552
+ for (const [key, count] of counts) {
553
+ let last;
554
+ for (const r of records) {
555
+ if (projectKey(r.cwd) === key) {
556
+ if (last === void 0 || r.date > last) last = r.date;
557
+ }
558
+ }
559
+ entities.push(
560
+ (0, import_l5.makeEntity)({
561
+ name: friendlyLabel(key),
562
+ type: "project",
563
+ summary: `Workspace observed across ${count} Hermes session(s).`,
564
+ aliases: [key],
565
+ last_touched: last,
566
+ tags: ["hermes-project"],
567
+ visibility: import_l5.Visibility.TEAM
568
+ })
569
+ );
570
+ }
571
+ return entities;
572
+ }
573
+ var HermesParticipant = class {
574
+ agentId = AGENT_ID;
575
+ agentType = AGENT_TYPE;
576
+ displayName = DISPLAY_NAME;
577
+ nativePath;
578
+ home;
579
+ static defaultNativePath(home) {
580
+ return defaultNativePath(home);
581
+ }
582
+ /** `hermesHome` is the `.hermes` directory itself (matching the Python ctor). */
583
+ constructor(hermesHome) {
584
+ if (hermesHome) {
585
+ this.home = hermesHome;
586
+ } else {
587
+ const path = defaultNativePath();
588
+ this.home = isDir(path) ? path : null;
589
+ }
590
+ this.nativePath = this.home ?? (0, import_node_path2.join)((0, import_node_os2.homedir)(), ".hermes");
591
+ }
592
+ sources() {
593
+ const home = this.home;
594
+ const stateDb = home ? (0, import_node_path2.join)(home, "state.db") : null;
595
+ const memDir = home ? (0, import_node_path2.join)(home, "memories") : null;
596
+ const skillsDir = home ? (0, import_node_path2.join)(home, "skills") : null;
597
+ return {
598
+ hermes_home: home,
599
+ state_db: stateDb && isFile(stateDb) ? stateDb : null,
600
+ memories_dir: memDir && isDir(memDir) ? memDir : null,
601
+ skills_dir: skillsDir && isDir(skillsDir) ? skillsDir : null
602
+ };
603
+ }
604
+ discover() {
605
+ const sources = this.sources();
606
+ if (!Object.values(sources).some((v) => v)) {
607
+ throw new ParticipantDiscoveryError(
608
+ "No Hermes memory sources found. Expected ~/.hermes/ (set $HERMES_HOME to override)."
609
+ );
610
+ }
611
+ if (!sources["state_db"] && !sources["memories_dir"]) {
612
+ throw new ParticipantDiscoveryError(
613
+ `Hermes home '${this.nativePath}' exists but has no state.db or memories/ \u2014 nothing to federate yet.`
614
+ );
615
+ }
616
+ return {
617
+ path: this.nativePath,
618
+ version: "hermes-home-v1",
619
+ metadata: { sources }
620
+ };
621
+ }
622
+ exportSessions(since, limit = DEFAULT_SESSION_LIMIT) {
623
+ if (this.home === null) return [];
624
+ const stateDb = (0, import_node_path2.join)(this.home, "state.db");
625
+ const cutoff = since ? since.toISOString().slice(0, 10) : null;
626
+ const records = collectSessionRows(stateDb, limit);
627
+ if (!records.length) return [];
628
+ const conn = tryOpenReadonly(stateDb);
629
+ const out = [];
630
+ try {
631
+ for (const rec of records) {
632
+ if (cutoff && rec.date < cutoff) continue;
633
+ const tools = conn ? toolNamesForSession(conn, rec.id) : [];
634
+ out.push(buildSession(rec, tools));
635
+ }
636
+ } finally {
637
+ if (conn) conn.close();
638
+ }
639
+ return out;
640
+ }
641
+ exportL5(since) {
642
+ const store = this.discover();
643
+ const sources = store.metadata["sources"] ?? {};
644
+ const capabilities = Object.entries(sources).filter(([k, v]) => v && k !== "hermes_home").map(([k]) => k).sort();
645
+ const home = this.home;
646
+ const records = home ? collectSessionRows((0, import_node_path2.join)(home, "state.db"), DEFAULT_SESSION_LIMIT) : [];
647
+ const entities = projectEntities(records);
648
+ const seen = /* @__PURE__ */ new Set();
649
+ for (const e of entities) {
650
+ seen.add(`${e.type || "topic"}\0${e.name.toLowerCase()}`);
651
+ }
652
+ if (home) {
653
+ for (const [storeName, entries] of collectMemoryEntries(home)) {
654
+ for (const entry of entries) {
655
+ const ent = memoryEntity(storeName, entry);
656
+ const key = `${ent.type || "topic"}\0${ent.name.toLowerCase()}`;
657
+ if (seen.has(key)) continue;
658
+ seen.add(key);
659
+ entities.push(ent);
660
+ }
661
+ }
662
+ }
663
+ const visible = (0, import_l5.filterForFederation)(entities, DEFAULT_POLICY);
664
+ const sessions = this.exportSessions(
665
+ since ?? /* @__PURE__ */ new Date(0),
666
+ DEFAULT_SESSION_LIMIT
667
+ );
668
+ return (0, import_l5.makeManifest)({
669
+ spec_version: SPEC_VERSION,
670
+ agent: (0, import_l5.makeAgentInfo)({
671
+ id: this.agentId,
672
+ type: this.agentType,
673
+ role_narrative: ROLE_NARRATIVE,
674
+ spec_version_compat: CONTRACT_VERSION
675
+ }),
676
+ last_updated: (/* @__PURE__ */ new Date()).toISOString(),
677
+ capabilities,
678
+ recent_sessions: sessions,
679
+ known_entities: visible,
680
+ visibility_policy: DEFAULT_POLICY
681
+ });
682
+ }
683
+ healthCheck() {
684
+ const details = {
685
+ hermes_home: this.nativePath,
686
+ state_db: "missing",
687
+ memories_dir: "missing",
688
+ skills_dir: "missing"
689
+ };
690
+ try {
691
+ if (this.home === null) {
692
+ return {
693
+ status: "blocked",
694
+ reason: "~/.hermes/ not found \u2014 Hermes Agent not installed here",
695
+ details,
696
+ proposedFix: "Install Hermes Agent and run a session once, then `bourdon export-all`."
697
+ };
698
+ }
699
+ const sources = this.sources();
700
+ for (const key of ["state_db", "memories_dir", "skills_dir"]) {
701
+ if (sources[key]) details[key] = sources[key];
702
+ }
703
+ if (sources["state_db"]) {
704
+ const records = collectSessionRows((0, import_node_path2.join)(this.home, "state.db"), 1);
705
+ if (records.length) return { status: "ok", details };
706
+ return {
707
+ status: "degraded",
708
+ reason: "state.db present but no readable sessions yet",
709
+ details,
710
+ proposedFix: "Run at least one Hermes session, then `bourdon export-all`."
711
+ };
712
+ }
713
+ if (sources["memories_dir"]) {
714
+ return {
715
+ status: "degraded",
716
+ reason: "memories/ present but state.db missing \u2014 session history unavailable",
717
+ details
718
+ };
719
+ }
720
+ return {
721
+ status: "blocked",
722
+ reason: "Hermes home has neither state.db nor memories/",
723
+ details,
724
+ proposedFix: "Run a Hermes session to populate ~/.hermes/state.db."
725
+ };
726
+ } catch (err) {
727
+ return {
728
+ status: "degraded",
729
+ reason: `unexpected error: ${String(err)}`,
730
+ details
731
+ };
732
+ }
733
+ }
734
+ };
735
+
736
+ // src/readers/claude-code.ts
737
+ var import_node_fs4 = require("fs");
738
+ var import_node_os3 = require("os");
739
+ var import_node_path3 = require("path");
740
+ var import_yaml = require("yaml");
741
+ var import_l52 = require("@getbourdon/l5");
742
+ var import_redaction2 = require("@getbourdon/redaction");
743
+ var AGENT_ID2 = "claude-code";
744
+ var AGENT_TYPE2 = "code-assistant";
745
+ var ROLE_NARRATIVE2 = "Agentic manager and code-assistant. Coordinates the RADLAB agent fleet, reviews PRs, and consults on architectural decisions. Capable of authoring code but typically reviews + delegates to specialised code-assistants like Codex for prime-code execution.";
746
+ var DEFAULT_POLICY2 = (0, import_l52.makeVisibilityPolicy)({
747
+ default: import_l52.Visibility.PUBLIC,
748
+ private_tags: ["personal", "financial", "credential", "health", "family", "legal"],
749
+ team_tags: ["internal-roadmap"]
750
+ });
751
+ var PRIVATE_BY_DEFAULT_TYPES = /* @__PURE__ */ new Set([
752
+ "person",
753
+ "user",
754
+ "individual",
755
+ "contact",
756
+ "family-member",
757
+ "family_member"
758
+ ]);
759
+ var MAX_SUMMARY_CHARS = 500;
760
+ var MAX_OBSERVATIONS_IN_SUMMARY = 2;
761
+ function isDir2(p) {
762
+ try {
763
+ return (0, import_node_fs4.statSync)(p).isDirectory();
764
+ } catch {
765
+ return false;
766
+ }
767
+ }
768
+ function isFile2(p) {
769
+ try {
770
+ return (0, import_node_fs4.statSync)(p).isFile();
771
+ } catch {
772
+ return false;
773
+ }
774
+ }
775
+ function resolveClaudeBrainPath() {
776
+ const envOverride = process.env.CLAUDE_BRAIN;
777
+ if (envOverride && isDir2(envOverride)) return envOverride;
778
+ const homeCandidate = (0, import_node_path3.join)(homeDir(), "claude-brain");
779
+ if (isDir2(homeCandidate)) return homeCandidate;
780
+ const cwdCandidate = (0, import_node_path3.join)(process.cwd(), "claude-brain");
781
+ if (isDir2(cwdCandidate)) return cwdCandidate;
782
+ return null;
783
+ }
784
+ function homeDir() {
785
+ try {
786
+ return (0, import_node_os3.homedir)();
787
+ } catch {
788
+ return process.env.HOME || process.env.USERPROFILE || ".";
789
+ }
790
+ }
791
+ function resolveAutoMemoryPath() {
792
+ try {
793
+ const base = (0, import_node_path3.join)(homeDir(), ".claude", "projects");
794
+ if (!isDir2(base)) return null;
795
+ for (const child of (0, import_node_fs4.readdirSync)(base)) {
796
+ const memMarker = (0, import_node_path3.join)(base, child, "memory", "MEMORY.md");
797
+ if (isFile2(memMarker)) return (0, import_node_path3.join)(base, child, "memory");
798
+ }
799
+ } catch {
800
+ return null;
801
+ }
802
+ return null;
803
+ }
804
+ function resolveKnowledgeGraphPath() {
805
+ const candidate = (0, import_node_path3.join)(homeDir(), "claude-memory", "memory.jsonl");
806
+ return isFile2(candidate) ? candidate : null;
807
+ }
808
+ function isPrivateType(entityType) {
809
+ if (!entityType) return false;
810
+ const normalized = entityType.split("/").pop()?.trim().toLowerCase() ?? "";
811
+ return PRIVATE_BY_DEFAULT_TYPES.has(normalized);
812
+ }
813
+ var FRONTMATTER_OPEN = "---\n";
814
+ var FRONTMATTER_CLOSE = "\n---\n";
815
+ function parseFrontmatter(text) {
816
+ if (!text.startsWith(FRONTMATTER_OPEN)) return { frontmatter: {}, body: text };
817
+ const end = text.indexOf(FRONTMATTER_CLOSE, FRONTMATTER_OPEN.length);
818
+ if (end === -1) return { frontmatter: {}, body: text };
819
+ const fmText = text.slice(FRONTMATTER_OPEN.length, end);
820
+ const body = text.slice(end + FRONTMATTER_CLOSE.length);
821
+ let parsed;
822
+ try {
823
+ parsed = (0, import_yaml.parse)(fmText);
824
+ } catch {
825
+ return { frontmatter: {}, body: text };
826
+ }
827
+ const fm = typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed : {};
828
+ return { frontmatter: fm, body };
829
+ }
830
+ var H1_RE = /^#[ \t]+(.+?)[ \t]*$/m;
831
+ var STATUS_SECTION_RE = /^##[ \t]+(?:Current[ \t]+)?Status[^\n]*$/im;
832
+ var STATUS_DATE_RE = /\b(\d{4}-\d{2}-\d{2})\b/;
833
+ var TITLE_SPLIT_RE = /\s*[-—–:]+\s+/;
834
+ function extractH1Title(body) {
835
+ const match = H1_RE.exec(body);
836
+ if (!match) return null;
837
+ const title = (match[1] ?? "").trim();
838
+ const head = title.split(TITLE_SPLIT_RE)[0] ?? title;
839
+ return head.trim();
840
+ }
841
+ function extractFirstParagraph(body, maxChars = MAX_SUMMARY_CHARS) {
842
+ const paragraph = [];
843
+ for (const line of body.split(/\r?\n/)) {
844
+ const stripped = line.trim();
845
+ if (!stripped) {
846
+ if (paragraph.length) break;
847
+ continue;
848
+ }
849
+ if (stripped.startsWith("#")) continue;
850
+ paragraph.push(stripped);
851
+ if (paragraph.reduce((acc, s) => acc + s.length + 1, 0) > maxChars) break;
852
+ }
853
+ return paragraph.join(" ").trim().slice(0, maxChars).trim();
854
+ }
855
+ function extractStatusTag(body) {
856
+ const match = STATUS_SECTION_RE.exec(body);
857
+ if (!match) return [];
858
+ const region = body.slice(match.index, match.index + 400).toLowerCase();
859
+ const tags = [];
860
+ for (const keyword of ["archived", "canceled", "cancelled", "active", "shipped", "blocked"]) {
861
+ if (region.includes(keyword)) tags.push(keyword.replace("cancelled", "canceled"));
862
+ }
863
+ return [...new Set(tags)];
864
+ }
865
+ var END_OF_LIFE_TAGS = /* @__PURE__ */ new Set(["archived", "canceled"]);
866
+ function extractStatusDate(body) {
867
+ const match = STATUS_SECTION_RE.exec(body);
868
+ if (!match) return null;
869
+ const region = body.slice(match.index, match.index + 400);
870
+ const dateMatch = STATUS_DATE_RE.exec(region);
871
+ return dateMatch ? dateMatch[1] ?? null : null;
872
+ }
873
+ function parseProjectOverview(overviewPath) {
874
+ let text;
875
+ try {
876
+ text = (0, import_node_fs4.readFileSync)(overviewPath, "utf8");
877
+ } catch {
878
+ return null;
879
+ }
880
+ const { body } = parseFrontmatter(text);
881
+ const title = extractH1Title(body) || (0, import_node_path3.basename)((0, import_node_path3.join)(overviewPath, ".."));
882
+ const summary = extractFirstParagraph(body);
883
+ const tags = extractStatusTag(body);
884
+ let validTo = null;
885
+ if (tags.some((t) => END_OF_LIFE_TAGS.has(t))) {
886
+ validTo = extractStatusDate(body);
887
+ if (validTo === null) {
888
+ try {
889
+ const mtime = (0, import_node_fs4.statSync)(overviewPath).mtime;
890
+ validTo = mtime.toISOString().slice(0, 10);
891
+ } catch {
892
+ validTo = null;
893
+ }
894
+ }
895
+ }
896
+ return (0, import_l52.makeEntity)({
897
+ name: title,
898
+ type: "project",
899
+ summary: summary || void 0,
900
+ tags,
901
+ valid_to: validTo ?? void 0
902
+ });
903
+ }
904
+ function parseProjectsDir(brainPath) {
905
+ const projectsDir = (0, import_node_path3.join)(brainPath, "PROJECTS");
906
+ if (!isDir2(projectsDir)) return [];
907
+ const entities = [];
908
+ for (const name of (0, import_node_fs4.readdirSync)(projectsDir).sort()) {
909
+ const projectDir = (0, import_node_path3.join)(projectsDir, name);
910
+ if (!isDir2(projectDir)) continue;
911
+ const overview = (0, import_node_path3.join)(projectDir, "OVERVIEW.md");
912
+ if (!isFile2(overview)) continue;
913
+ const entity = parseProjectOverview(overview);
914
+ if (entity) entities.push(entity);
915
+ }
916
+ return entities;
917
+ }
918
+ var LOG_FILENAME_RE = /^(\d{4}-\d{2}-\d{2})(?:-(\w+?))?(?:-.*)?\.md$/;
919
+ function parseLogFile(logPath) {
920
+ const m = LOG_FILENAME_RE.exec((0, import_node_path3.basename)(logPath));
921
+ if (!m) return null;
922
+ const sessionDate = m[1] ?? "";
923
+ let text;
924
+ try {
925
+ text = (0, import_node_fs4.readFileSync)(logPath, "utf8");
926
+ } catch {
927
+ return null;
928
+ }
929
+ const { body } = parseFrontmatter(text);
930
+ const headline = extractFirstParagraph(body, 250);
931
+ return (0, import_l52.makeSession)({
932
+ date: sessionDate,
933
+ key_actions: headline ? [headline] : [],
934
+ files_touched: [`LOG/${(0, import_node_path3.basename)(logPath)}`]
935
+ });
936
+ }
937
+ function parseLogsDir(brainPath, since, limit = 100) {
938
+ const logDir = (0, import_node_path3.join)(brainPath, "LOG");
939
+ if (!isDir2(logDir)) return [];
940
+ const candidates = [];
941
+ for (const fname of (0, import_node_fs4.readdirSync)(logDir)) {
942
+ if (!fname.endsWith(".md")) continue;
943
+ const m = LOG_FILENAME_RE.exec(fname);
944
+ if (!m) continue;
945
+ const d = m[1] ?? "";
946
+ if (since) {
947
+ if (d < since.toISOString().slice(0, 10)) continue;
948
+ }
949
+ candidates.push([d, (0, import_node_path3.join)(logDir, fname)]);
950
+ }
951
+ candidates.sort((a, b) => a[0] < b[0] ? 1 : a[0] > b[0] ? -1 : 0);
952
+ const sessions = [];
953
+ for (const [, logFile] of candidates.slice(0, limit)) {
954
+ const session = parseLogFile(logFile);
955
+ if (session) sessions.push(session);
956
+ }
957
+ return sessions;
958
+ }
959
+ function parseAutoMemoryEntity(mdPath) {
960
+ let text;
961
+ try {
962
+ text = (0, import_node_fs4.readFileSync)(mdPath, "utf8");
963
+ } catch {
964
+ return null;
965
+ }
966
+ const { frontmatter, body } = parseFrontmatter(text);
967
+ const name = frontmatter["name"] || extractH1Title(body) || (0, import_node_path3.basename)(mdPath).replace(/\.md$/, "");
968
+ const entityType = frontmatter["type"];
969
+ const summaryRaw = frontmatter["description"] || extractFirstParagraph(body);
970
+ const rawTags = frontmatter["tags"];
971
+ const tags = Array.isArray(rawTags) ? rawTags.map((t) => String(t)) : [];
972
+ const visibility = isPrivateType(entityType) ? import_l52.Visibility.PRIVATE : void 0;
973
+ return (0, import_l52.makeEntity)({
974
+ name: String(name),
975
+ type: entityType ? String(entityType) : void 0,
976
+ summary: summaryRaw ? String(summaryRaw).slice(0, MAX_SUMMARY_CHARS).trim() : void 0,
977
+ tags,
978
+ visibility
979
+ });
980
+ }
981
+ function parseAutoMemory(memoryPath) {
982
+ if (!isDir2(memoryPath)) return [];
983
+ const entities = [];
984
+ for (const fname of (0, import_node_fs4.readdirSync)(memoryPath).sort()) {
985
+ if (!fname.endsWith(".md") || fname === "MEMORY.md") continue;
986
+ const entity = parseAutoMemoryEntity((0, import_node_path3.join)(memoryPath, fname));
987
+ if (entity) entities.push(entity);
988
+ }
989
+ return entities;
990
+ }
991
+ function graphEntityToBourdonEntity(record) {
992
+ const name = record["name"];
993
+ if (!name || typeof name !== "string") return null;
994
+ const entityTypeSlug = record["entityType"] || "";
995
+ const normalizedType = entityTypeSlug ? entityTypeSlug.split("/").pop() ?? null : null;
996
+ const observations = Array.isArray(record["observations"]) ? record["observations"] : [];
997
+ const safeObs = [];
998
+ for (const obs of observations.slice(0, MAX_OBSERVATIONS_IN_SUMMARY)) {
999
+ if (typeof obs !== "string") continue;
1000
+ safeObs.push((0, import_redaction2.containsSecret)(obs) ? "[redacted -- contains credential-like content]" : obs);
1001
+ }
1002
+ const joined = safeObs.join(" | ").slice(0, MAX_SUMMARY_CHARS).trim();
1003
+ const summary = joined || void 0;
1004
+ const hasCredObs = observations.some((o) => typeof o === "string" && (0, import_redaction2.containsSecret)(o));
1005
+ const visibility = isPrivateType(entityTypeSlug) || hasCredObs ? import_l52.Visibility.PRIVATE : void 0;
1006
+ const tags = normalizedType && normalizedType !== name.toLowerCase() ? [normalizedType] : [];
1007
+ return (0, import_l52.makeEntity)({
1008
+ name,
1009
+ type: normalizedType ?? void 0,
1010
+ summary,
1011
+ tags,
1012
+ visibility
1013
+ });
1014
+ }
1015
+ function parseKnowledgeGraph(graphPath) {
1016
+ if (!isFile2(graphPath)) return [];
1017
+ let raw;
1018
+ try {
1019
+ raw = (0, import_node_fs4.readFileSync)(graphPath, "utf8");
1020
+ } catch {
1021
+ return [];
1022
+ }
1023
+ const entities = [];
1024
+ for (const lineRaw of raw.split(/\r?\n/)) {
1025
+ const line = lineRaw.trim();
1026
+ if (!line) continue;
1027
+ let record;
1028
+ try {
1029
+ record = JSON.parse(line);
1030
+ } catch {
1031
+ continue;
1032
+ }
1033
+ if (typeof record !== "object" || record === null) continue;
1034
+ const rec = record;
1035
+ if (rec["type"] !== "entity") continue;
1036
+ const entity = graphEntityToBourdonEntity(rec);
1037
+ if (entity) entities.push(entity);
1038
+ }
1039
+ return entities;
1040
+ }
1041
+ var VIS_ORDER = {
1042
+ [import_l52.Visibility.PRIVATE]: 0,
1043
+ [import_l52.Visibility.TEAM]: 1,
1044
+ [import_l52.Visibility.PUBLIC]: 2,
1045
+ none: 3
1046
+ };
1047
+ function visRank(v) {
1048
+ return v == null ? VIS_ORDER["none"] : VIS_ORDER[v];
1049
+ }
1050
+ function mergeEntities(into, other) {
1051
+ if (!into.type && other.type) into.type = other.type;
1052
+ if (other.summary && other.summary.trim() && other.summary.length > (into.summary?.length ?? 0)) {
1053
+ into.summary = other.summary;
1054
+ }
1055
+ into.tags = [.../* @__PURE__ */ new Set([...into.tags ?? [], ...other.tags ?? []])];
1056
+ if (visRank(other.visibility) < visRank(into.visibility)) {
1057
+ into.visibility = other.visibility;
1058
+ }
1059
+ return into;
1060
+ }
1061
+ function dedupeEntities(sourceLists) {
1062
+ const byKey = /* @__PURE__ */ new Map();
1063
+ for (const source of sourceLists) {
1064
+ for (const entity of source) {
1065
+ const key = entity.name.trim().toLowerCase();
1066
+ if (!key) continue;
1067
+ const existing = byKey.get(key);
1068
+ if (existing) {
1069
+ mergeEntities(existing, entity);
1070
+ } else {
1071
+ byKey.set(
1072
+ key,
1073
+ (0, import_l52.makeEntity)({
1074
+ name: entity.name,
1075
+ type: entity.type,
1076
+ aliases: [...entity.aliases ?? []],
1077
+ summary: entity.summary,
1078
+ last_touched: entity.last_touched,
1079
+ tags: [...entity.tags ?? []],
1080
+ visibility: entity.visibility
1081
+ // valid_from / valid_to intentionally dropped (oracle behavior).
1082
+ })
1083
+ );
1084
+ }
1085
+ }
1086
+ }
1087
+ return [...byKey.values()].sort((a, b) => {
1088
+ const an = a.name.toLowerCase();
1089
+ const bn = b.name.toLowerCase();
1090
+ return an < bn ? -1 : an > bn ? 1 : 0;
1091
+ });
1092
+ }
1093
+ var ClaudeCodeParticipant = class {
1094
+ agentId = AGENT_ID2;
1095
+ agentType = AGENT_TYPE2;
1096
+ nativePath;
1097
+ /** Public so a hermetic test can seed the discovered sources directly (path
1098
+ * RESOLUTION is not the parity contract — only the export output shape is). */
1099
+ brainPath;
1100
+ autoMemoryPath;
1101
+ knowledgeGraphPath;
1102
+ static defaultNativePath(home) {
1103
+ return (0, import_node_path3.join)(home ?? homeDir(), ".claude");
1104
+ }
1105
+ constructor() {
1106
+ this.nativePath = (0, import_node_path3.join)(homeDir(), "claude-brain");
1107
+ this.brainPath = resolveClaudeBrainPath();
1108
+ this.autoMemoryPath = resolveAutoMemoryPath();
1109
+ this.knowledgeGraphPath = resolveKnowledgeGraphPath();
1110
+ }
1111
+ discover() {
1112
+ const sources = {
1113
+ claude_brain: this.brainPath,
1114
+ auto_memory: this.autoMemoryPath,
1115
+ knowledge_graph: this.knowledgeGraphPath
1116
+ };
1117
+ if (!Object.values(sources).some((v) => v)) {
1118
+ throw new ParticipantDiscoveryError(
1119
+ "No Claude Code memory sources found. Expected one of: ~/claude-brain/, ~/.claude/projects/*/memory/, ~/claude-memory/memory.jsonl."
1120
+ );
1121
+ }
1122
+ return {
1123
+ path: this.nativePath,
1124
+ version: "claude-code-memory-v1",
1125
+ metadata: { sources }
1126
+ };
1127
+ }
1128
+ exportL5(since) {
1129
+ const store = this.discover();
1130
+ const sources = store.metadata["sources"];
1131
+ const capabilities = Object.entries(sources).filter(([, v]) => v).map(([k]) => k).sort();
1132
+ const autoMemoryEntities = this.autoMemoryPath ? parseAutoMemory(this.autoMemoryPath) : [];
1133
+ const graphEntities = this.knowledgeGraphPath ? parseKnowledgeGraph(this.knowledgeGraphPath) : [];
1134
+ const projectEntities2 = this.brainPath ? parseProjectsDir(this.brainPath) : [];
1135
+ const allEntities = dedupeEntities([autoMemoryEntities, graphEntities, projectEntities2]);
1136
+ const visibleEntities = (0, import_l52.filterForFederation)(allEntities, DEFAULT_POLICY2);
1137
+ const sessions = this.brainPath ? parseLogsDir(this.brainPath, since) : [];
1138
+ return (0, import_l52.makeManifest)({
1139
+ spec_version: SPEC_VERSION,
1140
+ agent: (0, import_l52.makeAgentInfo)({
1141
+ id: this.agentId,
1142
+ type: this.agentType,
1143
+ instance: (0, import_node_os3.hostname)() || "unknown",
1144
+ spec_version_compat: `>=${SPEC_VERSION}`,
1145
+ role_narrative: ROLE_NARRATIVE2
1146
+ }),
1147
+ last_updated: (/* @__PURE__ */ new Date()).toISOString(),
1148
+ capabilities,
1149
+ recent_sessions: sessions,
1150
+ known_entities: visibleEntities,
1151
+ visibility_policy: DEFAULT_POLICY2
1152
+ });
1153
+ }
1154
+ exportSessions(since, limit = 100) {
1155
+ if (!this.brainPath) return [];
1156
+ return parseLogsDir(this.brainPath, since, limit);
1157
+ }
1158
+ healthCheck() {
1159
+ try {
1160
+ const present = [this.brainPath, this.autoMemoryPath, this.knowledgeGraphPath].filter(
1161
+ (p) => p !== null
1162
+ ).length;
1163
+ const details = {
1164
+ claude_brain: this.brainPath ?? "missing",
1165
+ auto_memory: this.autoMemoryPath ?? "missing",
1166
+ knowledge_graph: this.knowledgeGraphPath ?? "missing"
1167
+ };
1168
+ if (present === 3) return { status: "ok", details };
1169
+ if (present === 0) {
1170
+ return {
1171
+ status: "blocked",
1172
+ reason: "No Claude Code memory sources found on this machine",
1173
+ details,
1174
+ proposedFix: "Run `bourdon setup` to wire the SessionEnd hook and bootstrap ~/agent-library/, then open Claude Code once to populate ~/.claude/projects/."
1175
+ };
1176
+ }
1177
+ return {
1178
+ status: "degraded",
1179
+ reason: `${present}/3 Claude Code memory sources found`,
1180
+ details,
1181
+ proposedFix: "Run `bourdon setup` to (re-)wire missing sources, or set the CLAUDE_BRAIN env var to point at your claude-brain checkout."
1182
+ };
1183
+ } catch (err) {
1184
+ return {
1185
+ status: "degraded",
1186
+ reason: `unexpected error: ${String(err)}`,
1187
+ details: {}
1188
+ };
1189
+ }
1190
+ }
1191
+ };
1192
+
1193
+ // src/readers/github-copilot.ts
1194
+ var import_node_child_process = require("child_process");
1195
+ var import_l53 = require("@getbourdon/l5");
1196
+ var import_redaction3 = require("@getbourdon/redaction");
1197
+ var AGENT_ID3 = "github-copilot";
1198
+ var AGENT_TYPE3 = "code-assistant";
1199
+ var PARTICIPANT_SLUG = "github-copilot";
1200
+ var DISPLAY_NAME2 = "GitHub Copilot (cloud)";
1201
+ var ROLE_NARRATIVE3 = "Cloud-side GitHub Copilot: PR review and @copilot-authored comments/suggestions that live on github.com, not on the user's machine. Federated via the GitHub REST API rather than a local artifact.";
1202
+ var GITHUB_API_ROOT = "https://api.github.com";
1203
+ var DEFAULT_POLICY3 = (0, import_l53.makeVisibilityPolicy)({
1204
+ default: import_l53.Visibility.TEAM,
1205
+ private_tags: ["personal", "financial", "credential", "secret"],
1206
+ team_tags: ["github-copilot", "pull-request"]
1207
+ });
1208
+ function ghTokenProvider() {
1209
+ for (const varName of ["GITHUB_TOKEN", "GH_TOKEN"]) {
1210
+ const val = process.env[varName];
1211
+ if (val) return val;
1212
+ }
1213
+ try {
1214
+ const out = (0, import_node_child_process.execFileSync)("gh", ["auth", "token"], {
1215
+ encoding: "utf8",
1216
+ timeout: 5e3,
1217
+ stdio: ["ignore", "pipe", "ignore"]
1218
+ });
1219
+ const token = out.trim();
1220
+ if (token) return token;
1221
+ } catch {
1222
+ }
1223
+ return null;
1224
+ }
1225
+ function githubGet(path, token, timeoutSec = 10) {
1226
+ const url = path.startsWith("http") ? path : `${GITHUB_API_ROOT}${path}`;
1227
+ let resp;
1228
+ try {
1229
+ resp = curlGet(url, token, timeoutSec);
1230
+ } catch (err) {
1231
+ throw new NetworkUnavailable(`GitHub API unreachable: ${String(err)}`);
1232
+ }
1233
+ const remaining = resp.headers["x-ratelimit-remaining"];
1234
+ if (resp.status === 429 || resp.status === 403 && String(remaining) === "0") {
1235
+ throw new NetworkUnavailable(`GitHub API ${resp.status}: rate-limited`);
1236
+ }
1237
+ if (resp.status === 401 || resp.status === 403) {
1238
+ throw new ParticipantAuthError(`GitHub API ${resp.status}: token invalid or lacks scope`);
1239
+ }
1240
+ if (resp.status >= 500) {
1241
+ throw new NetworkUnavailable(`GitHub API ${resp.status}`);
1242
+ }
1243
+ if (resp.status >= 400) {
1244
+ throw new NetworkUnavailable(`GitHub API ${resp.status}`);
1245
+ }
1246
+ try {
1247
+ return JSON.parse(resp.body);
1248
+ } catch (err) {
1249
+ throw new NetworkUnavailable(`GitHub API returned a non-JSON body: ${String(err)}`);
1250
+ }
1251
+ }
1252
+ function curlGet(url, token, timeoutSec) {
1253
+ const raw = (0, import_node_child_process.execFileSync)(
1254
+ "curl",
1255
+ [
1256
+ "-sS",
1257
+ "-D",
1258
+ "-",
1259
+ "-o",
1260
+ "-",
1261
+ "--max-time",
1262
+ String(timeoutSec),
1263
+ "-H",
1264
+ `Authorization: Bearer ${token}`,
1265
+ "-H",
1266
+ "Accept: application/vnd.github+json",
1267
+ "-H",
1268
+ "X-GitHub-Api-Version: 2022-11-28",
1269
+ "-H",
1270
+ "User-Agent: bourdon-github-copilot-participant",
1271
+ "-w",
1272
+ "\n__BOURDON_STATUS__%{http_code}",
1273
+ url
1274
+ ],
1275
+ { encoding: "utf8", timeout: (timeoutSec + 2) * 1e3, maxBuffer: 8 * 1024 * 1024 }
1276
+ );
1277
+ const statusMatch = /\n__BOURDON_STATUS__(\d+)$/.exec(raw);
1278
+ const status = statusMatch ? Number(statusMatch[1]) : 0;
1279
+ const withoutStatus = statusMatch ? raw.slice(0, statusMatch.index) : raw;
1280
+ const headerBodySplit = withoutStatus.indexOf("\r\n\r\n");
1281
+ let headerBlock = "";
1282
+ let body = withoutStatus;
1283
+ if (headerBodySplit >= 0) {
1284
+ headerBlock = withoutStatus.slice(0, headerBodySplit);
1285
+ body = withoutStatus.slice(headerBodySplit + 4);
1286
+ } else {
1287
+ const lfSplit = withoutStatus.indexOf("\n\n");
1288
+ if (lfSplit >= 0) {
1289
+ headerBlock = withoutStatus.slice(0, lfSplit);
1290
+ body = withoutStatus.slice(lfSplit + 2);
1291
+ }
1292
+ }
1293
+ const headers = {};
1294
+ for (const line of headerBlock.split(/\r?\n/)) {
1295
+ const idx = line.indexOf(":");
1296
+ if (idx > 0) {
1297
+ headers[line.slice(0, idx).trim().toLowerCase()] = line.slice(idx + 1).trim();
1298
+ }
1299
+ }
1300
+ return { status, headers, body };
1301
+ }
1302
+ var GitHubCopilotParticipant = class extends NetworkParticipant {
1303
+ participantSlug = PARTICIPANT_SLUG;
1304
+ agentId = AGENT_ID3;
1305
+ agentType = AGENT_TYPE3;
1306
+ nativePath = GITHUB_API_ROOT;
1307
+ displayName = DISPLAY_NAME2;
1308
+ maxResults;
1309
+ constructor(opts = {}) {
1310
+ super({
1311
+ authProvider: opts.authProvider ?? ghTokenProvider,
1312
+ cacheRoot: opts.cacheRoot
1313
+ });
1314
+ this.maxResults = opts.maxResults ?? 50;
1315
+ }
1316
+ static defaultNativePath() {
1317
+ return GITHUB_API_ROOT;
1318
+ }
1319
+ fetchPayload(token) {
1320
+ const me = githubGet("/user", token);
1321
+ const login = me && typeof me === "object" ? me["login"] : null;
1322
+ const q = "is:pr involves:@me commenter:copilot";
1323
+ const encoded = encodeURIComponent(q);
1324
+ const search = githubGet(
1325
+ `/search/issues?q=${encoded}&sort=updated&per_page=${this.maxResults}`,
1326
+ token
1327
+ );
1328
+ const items = search && typeof search === "object" ? search["items"] ?? [] : [];
1329
+ return {
1330
+ fetched_user: login ?? null,
1331
+ fetched_at: (/* @__PURE__ */ new Date()).toISOString(),
1332
+ items
1333
+ };
1334
+ }
1335
+ payloadToL5(payload) {
1336
+ const items = Array.isArray(payload["items"]) ? payload["items"] : [];
1337
+ const sessions = [];
1338
+ const entities = /* @__PURE__ */ new Map();
1339
+ for (const item of items) {
1340
+ if (typeof item !== "object" || item === null) continue;
1341
+ const it = item;
1342
+ const repo = repoFromItem(it);
1343
+ const title = (0, import_redaction3.redactText)(String(it["title"] ?? ""), 160);
1344
+ const number = it["number"];
1345
+ const updated = isoDate(it["updated_at"]);
1346
+ const focus = repo ? [repo] : [];
1347
+ sessions.push(
1348
+ (0, import_l53.makeSession)({
1349
+ date: updated || "1970-01-01",
1350
+ cwd: repo ? `github.com/${repo}` : void 0,
1351
+ project_focus: focus,
1352
+ key_actions: number != null ? [`PR #${String(number)}: ${title}`] : [title],
1353
+ visibility: import_l53.Visibility.TEAM
1354
+ })
1355
+ );
1356
+ if (repo && !entities.has(repo)) {
1357
+ entities.set(
1358
+ repo,
1359
+ (0, import_l53.makeEntity)({
1360
+ name: repo,
1361
+ type: "repository",
1362
+ summary: "GitHub repo with Copilot PR activity.",
1363
+ aliases: repo.includes("/") ? [repo.split("/").pop() ?? ""] : [],
1364
+ last_touched: updated ?? void 0,
1365
+ tags: ["github-copilot", "pull-request"],
1366
+ visibility: import_l53.Visibility.TEAM
1367
+ })
1368
+ );
1369
+ }
1370
+ }
1371
+ sessions.sort((a, b) => a.date < b.date ? 1 : a.date > b.date ? -1 : 0);
1372
+ return (0, import_l53.makeManifest)({
1373
+ spec_version: SPEC_VERSION,
1374
+ agent: (0, import_l53.makeAgentInfo)({
1375
+ id: this.agentId,
1376
+ type: this.agentType,
1377
+ role_narrative: ROLE_NARRATIVE3,
1378
+ instance: payload["fetched_user"] ?? void 0
1379
+ }),
1380
+ last_updated: (/* @__PURE__ */ new Date()).toISOString(),
1381
+ capabilities: ["github-search", "pull-request-comments"],
1382
+ recent_sessions: sessions.slice(0, this.maxResults),
1383
+ known_entities: [...entities.values()],
1384
+ visibility_policy: DEFAULT_POLICY3
1385
+ });
1386
+ }
1387
+ };
1388
+ function repoFromItem(item) {
1389
+ const repoUrl = item["repository_url"];
1390
+ if (typeof repoUrl === "string" && repoUrl.includes("/repos/")) {
1391
+ return repoUrl.split("/repos/")[1] ?? null;
1392
+ }
1393
+ return null;
1394
+ }
1395
+ function isoDate(value) {
1396
+ if (typeof value === "string" && value.length >= 10) return value.slice(0, 10);
1397
+ return null;
1398
+ }
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
+
3202
+ // src/registry.ts
3203
+ var FIRST_PARTY = [
3204
+ CascadeParticipant,
3205
+ ClaudeCodeParticipant,
3206
+ CopilotParticipant,
3207
+ CopilotCliParticipant,
3208
+ CursorParticipant,
3209
+ GitHubCopilotParticipant,
3210
+ HermesParticipant,
3211
+ OpenClawParticipant
3212
+ ];
3213
+ var logger3 = {
3214
+ warn(msg) {
3215
+ console.warn(`[bourdon.participants] ${msg}`);
3216
+ }
3217
+ };
3218
+ function discoverParticipants(ctors = FIRST_PARTY) {
3219
+ const found = /* @__PURE__ */ new Map();
3220
+ for (const Ctor of [...ctors, ...resolvePluginParticipants()]) {
3221
+ let instance;
3222
+ try {
3223
+ instance = new Ctor();
3224
+ } catch (err) {
3225
+ logger3.warn(
3226
+ `Skipping participant ${Ctor.name || "<anonymous>"}: construction failed: ${String(err)}`
3227
+ );
3228
+ continue;
3229
+ }
3230
+ if (!isParticipant(instance)) continue;
3231
+ if (!found.has(instance.agentId)) found.set(instance.agentId, instance);
3232
+ }
3233
+ return [...found.values()].sort(
3234
+ (a, b) => a.agentId < b.agentId ? -1 : a.agentId > b.agentId ? 1 : 0
3235
+ );
3236
+ }
3237
+ function resolvePluginParticipants() {
3238
+ if (process.env.BOURDON_PLUGINS !== "1") return [];
3239
+ return [];
3240
+ }
3241
+
3242
+ // src/index.ts
3243
+ var import_l59 = require("@getbourdon/l5");
3244
+ // Annotate the CommonJS export names for ESM import in node:
3245
+ 0 && (module.exports = {
3246
+ CONTRACT_VERSION,
3247
+ CacheEntry,
3248
+ CascadeParticipant,
3249
+ ClaudeCodeParticipant,
3250
+ CopilotCliParticipant,
3251
+ CopilotParticipant,
3252
+ CursorParticipant,
3253
+ DEFAULT_CACHE_TTL_SECONDS,
3254
+ FIRST_PARTY,
3255
+ GitHubCopilotParticipant,
3256
+ HermesParticipant,
3257
+ MIN_PATCHED_VERSION,
3258
+ NETWORK_CONTRACT_VERSION,
3259
+ NetworkParticipant,
3260
+ NetworkUnavailable,
3261
+ OpenClawApiClient,
3262
+ OpenClawParticipant,
3263
+ ParticipantAuthError,
3264
+ ParticipantDiscoveryError,
3265
+ ParticipantError,
3266
+ ParticipantExportError,
3267
+ ParticipantVersionMismatchError,
3268
+ PayloadCache,
3269
+ SPEC_VERSION,
3270
+ Visibility,
3271
+ applyVisibility,
3272
+ defaultCacheRoot,
3273
+ discoverParticipants,
3274
+ envAuthProvider,
3275
+ epochToIsoDate,
3276
+ epochToIsoDatetime,
3277
+ filterForFederation,
3278
+ friendlyLabel,
3279
+ ghTokenProvider,
3280
+ isParticipant,
3281
+ openReadonly,
3282
+ projectKeyFromCwd,
3283
+ resolvePluginParticipants,
3284
+ sqliteAvailable,
3285
+ tableColumns,
3286
+ tableExists,
3287
+ toDict,
3288
+ tryOpenReadonly,
3289
+ validateManifest,
3290
+ verifyInstance
3291
+ });
3292
+ //# sourceMappingURL=index.cjs.map