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