@getbourdon/participants 0.2.2 → 0.2.4
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/README.md +7 -1
- package/dist/base.d.ts +109 -0
- package/dist/base.js +88 -0
- package/dist/index.cjs +171 -82
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +117 -4
- package/dist/index.d.ts +42 -970
- package/dist/index.js +53 -6509
- package/dist/index.js.map +1 -1
- package/dist/network-base.d.ts +108 -0
- package/dist/network-base.js +248 -0
- package/dist/readers/cascade.d.ts +49 -0
- package/dist/readers/cascade.js +428 -0
- package/dist/readers/claude-code-automations.d.ts +62 -0
- package/dist/readers/claude-code-automations.js +693 -0
- package/dist/readers/claude-code.d.ts +50 -0
- package/dist/readers/claude-code.js +583 -0
- package/dist/readers/claude-desktop-code.d.ts +54 -0
- package/dist/readers/claude-desktop-code.js +526 -0
- package/dist/readers/claude-desktop-cowork.d.ts +47 -0
- package/dist/readers/claude-desktop-cowork.js +659 -0
- package/dist/readers/codex-automations.d.ts +55 -0
- package/dist/readers/codex-automations.js +519 -0
- package/dist/readers/codex-sessions.d.ts +88 -0
- package/dist/readers/codex-sessions.js +194 -0
- package/dist/readers/copilot-automations.d.ts +64 -0
- package/dist/readers/copilot-automations.js +530 -0
- package/dist/readers/copilot-cli.d.ts +45 -0
- package/dist/readers/copilot-cli.js +416 -0
- package/dist/readers/copilot-vscode.d.ts +54 -0
- package/dist/readers/copilot-vscode.js +553 -0
- package/dist/readers/copilot.d.ts +59 -0
- package/dist/readers/copilot.js +413 -0
- package/dist/readers/cursor-automations.d.ts +57 -0
- package/dist/readers/cursor-automations.js +685 -0
- package/dist/readers/cursor.d.ts +50 -0
- package/dist/readers/cursor.js +670 -0
- package/dist/readers/github-copilot.d.ts +34 -0
- package/dist/readers/github-copilot.js +242 -0
- package/dist/readers/hermes.d.ts +37 -0
- package/dist/readers/hermes.js +426 -0
- package/dist/readers/openclaw.d.ts +78 -0
- package/dist/readers/openclaw.js +386 -0
- package/dist/registry.d.ts +39 -0
- package/dist/registry.js +98 -0
- package/dist/sqlite-base.d.ts +65 -0
- package/dist/sqlite-base.js +163 -0
- package/package.json +5 -5
|
@@ -0,0 +1,530 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub Copilot · Automations participant — convention-file reader. Port of
|
|
3
|
+
* `participants/copilot_automations.py`.
|
|
4
|
+
*
|
|
5
|
+
* GitHub Copilot can be triggered by GitHub Actions workflows, scheduled tasks,
|
|
6
|
+
* and event-driven automations (issue triage, PR review, etc.). These run on a
|
|
7
|
+
* separate memory surface from both the CLI agent and the VS Code extension.
|
|
8
|
+
* Copilot automations have no standardized local state dump, so — mirroring the
|
|
9
|
+
* codex-automations reader — this participant uses a CONVENTION directory:
|
|
10
|
+
*
|
|
11
|
+
* ~/.copilot-bourdon/automations/<automation-id>/
|
|
12
|
+
* automation.toml — id, name, status, trigger, kind, repos
|
|
13
|
+
* memory.md — dated bullet entries, one block per run
|
|
14
|
+
*
|
|
15
|
+
* Each `automation.toml` describes a recurring automation; each dated section of
|
|
16
|
+
* `memory.md` captures what happened in that run. Users or CI scripts maintain
|
|
17
|
+
* these files.
|
|
18
|
+
*
|
|
19
|
+
* Path resolution (matches the oracle):
|
|
20
|
+
* 1. `COPILOT_AUTOMATIONS_HOME` env-var override (verbatim, → /automations skipped:
|
|
21
|
+
* the override IS the automations dir).
|
|
22
|
+
* 2. `~/.copilot-bourdon/automations/` (default convention path).
|
|
23
|
+
*
|
|
24
|
+
* Defensive throughout: a missing automations directory raises
|
|
25
|
+
* {@link ParticipantDiscoveryError} from `discover()`; a malformed
|
|
26
|
+
* `automation.toml` or unreadable `memory.md` is logged and skipped rather than
|
|
27
|
+
* raising; `healthCheck` never throws.
|
|
28
|
+
*/
|
|
29
|
+
import { mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
30
|
+
import { homedir, hostname } from "node:os";
|
|
31
|
+
import { dirname, join } from "node:path";
|
|
32
|
+
import { parse as parseToml } from "smol-toml";
|
|
33
|
+
import { Visibility, filterForFederation, makeAgentInfo, makeEntity, makeManifest, makeSession, makeVisibilityPolicy, } from "@getbourdon/l5";
|
|
34
|
+
import { redactText } from "@getbourdon/redaction";
|
|
35
|
+
import { ParticipantDiscoveryError, SPEC_VERSION, } from "../base.js";
|
|
36
|
+
// -- Constants -----------------------------------------------------------------
|
|
37
|
+
const AGENT_ID = "copilot-automations";
|
|
38
|
+
const AGENT_TYPE = "other";
|
|
39
|
+
const DISPLAY_NAME = "GitHub Copilot · Automations";
|
|
40
|
+
const ROLE_NARRATIVE = "Publishes read-only Copilot automation run memory into Bourdon so " +
|
|
41
|
+
"GitHub Actions-triggered Copilot tasks, scheduled PR reviews, issue " +
|
|
42
|
+
"triage, and other event-driven Copilot work are visible alongside " +
|
|
43
|
+
"interactive agent sessions.";
|
|
44
|
+
const DEFAULT_POLICY = makeVisibilityPolicy({
|
|
45
|
+
default: Visibility.TEAM,
|
|
46
|
+
private_tags: ["personal", "financial", "credential", "health", "family", "legal"],
|
|
47
|
+
team_tags: ["copilot-automation", "automation", "github-actions", "workspace"],
|
|
48
|
+
});
|
|
49
|
+
const CONVENTION_DIR_NAME = ".copilot-bourdon";
|
|
50
|
+
const AUTOMATIONS_DIR = "automations";
|
|
51
|
+
const AUTOMATION_TOML = "automation.toml";
|
|
52
|
+
const MEMORY_MD = "memory.md";
|
|
53
|
+
const MAX_MEMORY_CHARS = 160_000;
|
|
54
|
+
const MAX_KEY_ACTIONS_PER_RUN = 6;
|
|
55
|
+
const MAX_KEY_ACTION_CHARS = 280;
|
|
56
|
+
/** `^(\d{4}-\d{2}-\d{2})(?:\b|$)(.*)$` — the dated run header. */
|
|
57
|
+
const RUN_HEADER_RE = /^(\d{4}-\d{2}-\d{2})(?:\b|$)(.*)$/;
|
|
58
|
+
/** Starter template for `bourdon copilot-automations init`. */
|
|
59
|
+
const AUTOMATION_TOML_TEMPLATE = `# Copilot Automation definition
|
|
60
|
+
# Edit this file to describe a recurring Copilot automation.
|
|
61
|
+
|
|
62
|
+
id = "{automation_id}"
|
|
63
|
+
name = "{name}"
|
|
64
|
+
status = "ACTIVE"
|
|
65
|
+
trigger = "workflow_dispatch" # or: schedule, issue_comment, pull_request, etc.
|
|
66
|
+
kind = "pr-review" # or: issue-triage, code-generation, test-review, etc.
|
|
67
|
+
repos = [] # list of repositories this automation targets
|
|
68
|
+
`;
|
|
69
|
+
const MEMORY_MD_TEMPLATE = `# {name} — Run Memory
|
|
70
|
+
|
|
71
|
+
Record automation run outcomes here. Each dated section is parsed by Bourdon.
|
|
72
|
+
|
|
73
|
+
# Format:
|
|
74
|
+
# YYYY-MM-DD <optional title>
|
|
75
|
+
# - bullet point of what happened
|
|
76
|
+
# - another action or outcome
|
|
77
|
+
|
|
78
|
+
`;
|
|
79
|
+
/** Minimal logger so a degraded parse is visible without pulling in a dep
|
|
80
|
+
* (matches the sibling readers' logging idiom). */
|
|
81
|
+
const logger = {
|
|
82
|
+
warn(msg) {
|
|
83
|
+
// eslint-disable-next-line no-console
|
|
84
|
+
console.warn(`[bourdon.participants] ${msg}`);
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
// -- fs helpers ----------------------------------------------------------------
|
|
88
|
+
function homeDir() {
|
|
89
|
+
// os.homedir() can throw under some sandboxes; stay defensive so import +
|
|
90
|
+
// construction never crash even when HOME points at an unreadable path.
|
|
91
|
+
try {
|
|
92
|
+
return homedir();
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
return process.env.HOME || process.env.USERPROFILE || ".";
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function isDir(p) {
|
|
99
|
+
try {
|
|
100
|
+
return statSync(p).isDirectory();
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function isFile(p) {
|
|
107
|
+
try {
|
|
108
|
+
return statSync(p).isFile();
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
function isRecord(v) {
|
|
115
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
116
|
+
}
|
|
117
|
+
/** Python truthiness for the scalar/array types TOML yields. */
|
|
118
|
+
function pyTruthy(v) {
|
|
119
|
+
if (v === null || v === undefined || v === false)
|
|
120
|
+
return false;
|
|
121
|
+
if (typeof v === "string")
|
|
122
|
+
return v.length > 0;
|
|
123
|
+
if (typeof v === "number")
|
|
124
|
+
return v !== 0;
|
|
125
|
+
if (Array.isArray(v))
|
|
126
|
+
return v.length > 0;
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
/** Mirror Python `str(value)` for the value types encountered here. */
|
|
130
|
+
function pyStr(v) {
|
|
131
|
+
if (typeof v === "string")
|
|
132
|
+
return v;
|
|
133
|
+
if (typeof v === "boolean")
|
|
134
|
+
return v ? "True" : "False";
|
|
135
|
+
if (v === null || v === undefined)
|
|
136
|
+
return "None";
|
|
137
|
+
return String(v);
|
|
138
|
+
}
|
|
139
|
+
// -- Path helpers --------------------------------------------------------------
|
|
140
|
+
/**
|
|
141
|
+
* Return the conventional `~/.copilot-bourdon/automations/` directory. Respects
|
|
142
|
+
* the `COPILOT_AUTOMATIONS_HOME` env-var override (returned verbatim — the
|
|
143
|
+
* override IS the automations dir, no `/automations` suffix is appended).
|
|
144
|
+
*/
|
|
145
|
+
export function defaultCopilotAutomationsDir(copilotBourdonDir) {
|
|
146
|
+
const env = process.env.COPILOT_AUTOMATIONS_HOME;
|
|
147
|
+
if (env)
|
|
148
|
+
return env;
|
|
149
|
+
const base = copilotBourdonDir || join(homeDir(), CONVENTION_DIR_NAME);
|
|
150
|
+
return join(base, AUTOMATIONS_DIR);
|
|
151
|
+
}
|
|
152
|
+
// -- Config / memory parsing ---------------------------------------------------
|
|
153
|
+
function readAutomationToml(path) {
|
|
154
|
+
let text;
|
|
155
|
+
try {
|
|
156
|
+
text = readFileSync(path, "utf8");
|
|
157
|
+
}
|
|
158
|
+
catch (exc) {
|
|
159
|
+
logger.warn(`CopilotAutomationsParticipant: cannot parse ${path}: ${String(exc)}`);
|
|
160
|
+
return {};
|
|
161
|
+
}
|
|
162
|
+
try {
|
|
163
|
+
const parsed = parseToml(text);
|
|
164
|
+
return isRecord(parsed) ? parsed : {};
|
|
165
|
+
}
|
|
166
|
+
catch (exc) {
|
|
167
|
+
logger.warn(`CopilotAutomationsParticipant: cannot parse ${path}: ${String(exc)}`);
|
|
168
|
+
return {};
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
function buildConfig(tomlPath) {
|
|
172
|
+
const raw = readAutomationToml(tomlPath);
|
|
173
|
+
const parentDir = dirname(tomlPath);
|
|
174
|
+
const parentName = parentDir.split(/[\\/]/).filter(Boolean).pop() ?? "";
|
|
175
|
+
const automationId = pyStr(pyTruthy(raw["id"]) ? raw["id"] : parentName).trim();
|
|
176
|
+
if (!automationId)
|
|
177
|
+
return null;
|
|
178
|
+
const name = pyStr(pyTruthy(raw["name"]) ? raw["name"] : automationId).trim();
|
|
179
|
+
const status = pyStr(pyTruthy(raw["status"]) ? raw["status"] : "UNKNOWN")
|
|
180
|
+
.trim()
|
|
181
|
+
.toUpperCase();
|
|
182
|
+
const trigger = pyStr(pyTruthy(raw["trigger"]) ? raw["trigger"] : "").trim();
|
|
183
|
+
const kind = pyStr(pyTruthy(raw["kind"]) ? raw["kind"] : "").trim();
|
|
184
|
+
const reposRaw = pyTruthy(raw["repos"]) ? raw["repos"] : [];
|
|
185
|
+
const reposIter = Array.isArray(reposRaw)
|
|
186
|
+
? reposRaw
|
|
187
|
+
: typeof reposRaw === "string"
|
|
188
|
+
? Array.from(reposRaw)
|
|
189
|
+
: [];
|
|
190
|
+
const repos = reposIter.filter((r) => typeof r === "string" && r.trim() !== "");
|
|
191
|
+
const memoryPath = join(parentDir, MEMORY_MD);
|
|
192
|
+
return {
|
|
193
|
+
automationId,
|
|
194
|
+
name,
|
|
195
|
+
status,
|
|
196
|
+
trigger,
|
|
197
|
+
kind,
|
|
198
|
+
repos,
|
|
199
|
+
path: tomlPath,
|
|
200
|
+
memoryPath: isFile(memoryPath) ? memoryPath : null,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
function iterConfigs(automationsDir) {
|
|
204
|
+
const configs = [];
|
|
205
|
+
if (!isDir(automationsDir))
|
|
206
|
+
return configs;
|
|
207
|
+
let entries;
|
|
208
|
+
try {
|
|
209
|
+
entries = readdirSync(automationsDir);
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
return configs;
|
|
213
|
+
}
|
|
214
|
+
// Mirror `sorted(Path.glob("*/automation.toml"))`: one directory level deep,
|
|
215
|
+
// sorted by path. NOTE pathlib glob MATCHES dot-prefixed dirs (unlike shell
|
|
216
|
+
// glob / glob.glob) — do NOT skip them, or dot-named automations under-report.
|
|
217
|
+
const tomlPaths = [];
|
|
218
|
+
for (const entry of entries) {
|
|
219
|
+
const candidate = join(automationsDir, entry, AUTOMATION_TOML);
|
|
220
|
+
if (isFile(candidate))
|
|
221
|
+
tomlPaths.push(candidate);
|
|
222
|
+
}
|
|
223
|
+
tomlPaths.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
|
|
224
|
+
for (const tomlPath of tomlPaths) {
|
|
225
|
+
const config = buildConfig(tomlPath);
|
|
226
|
+
if (config !== null)
|
|
227
|
+
configs.push(config);
|
|
228
|
+
}
|
|
229
|
+
return configs;
|
|
230
|
+
}
|
|
231
|
+
/** Python `str.splitlines()` for the common line boundaries. Empty input → []. */
|
|
232
|
+
function splitLines(text) {
|
|
233
|
+
if (text.length === 0)
|
|
234
|
+
return [];
|
|
235
|
+
// Python splitlines includes ASCII record separators; this explicit set preserves parity.
|
|
236
|
+
return text.split(
|
|
237
|
+
// eslint-disable-next-line no-control-regex
|
|
238
|
+
/\r\n|\r|\n|\u000B|\u000C|\u001C|\u001D|\u001E|\u0085|\u2028|\u2029/u);
|
|
239
|
+
}
|
|
240
|
+
/** Python `str.rstrip()` — strip trailing whitespace. */
|
|
241
|
+
function rstrip(s) {
|
|
242
|
+
return s.replace(/\s+$/u, "");
|
|
243
|
+
}
|
|
244
|
+
/** Python `str.strip(chars)` — strip the given char set from both ends. */
|
|
245
|
+
function stripChars(s, chars) {
|
|
246
|
+
let start = 0;
|
|
247
|
+
let end = s.length;
|
|
248
|
+
while (start < end && chars.includes(s[start]))
|
|
249
|
+
start++;
|
|
250
|
+
while (end > start && chars.includes(s[end - 1]))
|
|
251
|
+
end--;
|
|
252
|
+
return s.slice(start, end);
|
|
253
|
+
}
|
|
254
|
+
function extractMemoryRuns(config) {
|
|
255
|
+
if (config.memoryPath === null)
|
|
256
|
+
return [];
|
|
257
|
+
let text;
|
|
258
|
+
try {
|
|
259
|
+
text = readFileSync(config.memoryPath, "utf8");
|
|
260
|
+
}
|
|
261
|
+
catch (exc) {
|
|
262
|
+
logger.warn(`CopilotAutomationsParticipant: cannot read ${config.memoryPath}: ${String(exc)}`);
|
|
263
|
+
return [];
|
|
264
|
+
}
|
|
265
|
+
text = text.slice(-MAX_MEMORY_CHARS);
|
|
266
|
+
const chunks = [];
|
|
267
|
+
let currentDate = "";
|
|
268
|
+
let currentLines = [];
|
|
269
|
+
for (const rawLine of splitLines(text)) {
|
|
270
|
+
const line = rstrip(rawLine);
|
|
271
|
+
const match = RUN_HEADER_RE.exec(line.trim());
|
|
272
|
+
if (match) {
|
|
273
|
+
if (currentDate)
|
|
274
|
+
chunks.push([currentDate, currentLines]);
|
|
275
|
+
currentDate = match[1];
|
|
276
|
+
const suffix = stripChars((match[2] ?? "").trim(), " -:—");
|
|
277
|
+
currentLines = suffix ? [suffix] : [];
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
if (currentDate)
|
|
281
|
+
currentLines.push(line);
|
|
282
|
+
}
|
|
283
|
+
if (currentDate)
|
|
284
|
+
chunks.push([currentDate, currentLines]);
|
|
285
|
+
const runs = [];
|
|
286
|
+
for (const [runDate, lines] of chunks) {
|
|
287
|
+
const actions = actionsFromLines(lines);
|
|
288
|
+
if (actions.length === 0)
|
|
289
|
+
continue;
|
|
290
|
+
const title = titleFromActions(config, actions);
|
|
291
|
+
runs.push({
|
|
292
|
+
automation: config,
|
|
293
|
+
date: runDate,
|
|
294
|
+
title,
|
|
295
|
+
keyActions: actions,
|
|
296
|
+
repos: config.repos,
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
return runs;
|
|
300
|
+
}
|
|
301
|
+
function actionsFromLines(lines) {
|
|
302
|
+
const actions = [];
|
|
303
|
+
for (const line of lines) {
|
|
304
|
+
let cleaned = line.trim();
|
|
305
|
+
if (!cleaned)
|
|
306
|
+
continue;
|
|
307
|
+
cleaned = (cleaned.startsWith("- ") ? cleaned.slice(2) : cleaned).trim();
|
|
308
|
+
if (!cleaned || cleaned.toLowerCase().startsWith("runtime"))
|
|
309
|
+
continue;
|
|
310
|
+
const safe = bounded(safeNativeMemoryText(cleaned), MAX_KEY_ACTION_CHARS);
|
|
311
|
+
if (safe && !actions.includes(safe))
|
|
312
|
+
actions.push(safe);
|
|
313
|
+
if (actions.length >= MAX_KEY_ACTIONS_PER_RUN)
|
|
314
|
+
break;
|
|
315
|
+
}
|
|
316
|
+
return actions;
|
|
317
|
+
}
|
|
318
|
+
function titleFromActions(config, actions) {
|
|
319
|
+
if (actions.length === 0)
|
|
320
|
+
return config.name;
|
|
321
|
+
const first = actions[0];
|
|
322
|
+
const prefix = `${config.name}: `;
|
|
323
|
+
if (first.startsWith(prefix))
|
|
324
|
+
return bounded(first, 120);
|
|
325
|
+
return bounded(prefix + first, 120);
|
|
326
|
+
}
|
|
327
|
+
/** `" ".join(value.split())` + ellipsis truncation. */
|
|
328
|
+
function bounded(value, limit) {
|
|
329
|
+
const normalized = value
|
|
330
|
+
.split(/\s+/u)
|
|
331
|
+
.filter((x) => x.length > 0)
|
|
332
|
+
.join(" ");
|
|
333
|
+
if (normalized.length <= limit)
|
|
334
|
+
return normalized;
|
|
335
|
+
return rstrip(normalized.slice(0, limit - 1)) + "…";
|
|
336
|
+
}
|
|
337
|
+
/** Python `_safe_native_memory_text(v)` == `redactText(v, 180)`. */
|
|
338
|
+
function safeNativeMemoryText(value) {
|
|
339
|
+
return redactText(value, 180);
|
|
340
|
+
}
|
|
341
|
+
// -- Conversion to Bourdon types ----------------------------------------------
|
|
342
|
+
function sessionFromRun(run) {
|
|
343
|
+
const config = run.automation;
|
|
344
|
+
const keyActions = [
|
|
345
|
+
`automation: ${config.automationId}`,
|
|
346
|
+
config.trigger ? `trigger: ${config.trigger}` : `kind: ${config.kind}`,
|
|
347
|
+
`run: ${run.title}`,
|
|
348
|
+
...run.keyActions,
|
|
349
|
+
];
|
|
350
|
+
return makeSession({
|
|
351
|
+
date: run.date,
|
|
352
|
+
cwd: dirname(config.path),
|
|
353
|
+
project_focus: [...run.repos],
|
|
354
|
+
key_actions: keyActions.slice(0, MAX_KEY_ACTIONS_PER_RUN + 3),
|
|
355
|
+
files_touched: [config.path],
|
|
356
|
+
visibility: Visibility.TEAM,
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
function entitiesFromConfigsAndRuns(configs, runs) {
|
|
360
|
+
const entities = new Map();
|
|
361
|
+
for (const config of configs) {
|
|
362
|
+
entities.set(config.automationId, makeEntity({
|
|
363
|
+
name: config.automationId,
|
|
364
|
+
type: "automation",
|
|
365
|
+
summary: bounded(`Copilot automation '${config.name}' (${config.status}). ` +
|
|
366
|
+
`Trigger: ${config.trigger || "unspecified"}. ` +
|
|
367
|
+
`Kind: ${config.kind || "unspecified"}.`, 260),
|
|
368
|
+
last_touched: undefined,
|
|
369
|
+
tags: ["copilot-automation", "automation", config.status.toLowerCase()],
|
|
370
|
+
visibility: Visibility.TEAM,
|
|
371
|
+
}));
|
|
372
|
+
}
|
|
373
|
+
// Repos mentioned across all runs (setdefault: first-write-wins).
|
|
374
|
+
for (const run of runs) {
|
|
375
|
+
for (const repo of run.repos) {
|
|
376
|
+
if (!entities.has(repo)) {
|
|
377
|
+
entities.set(repo, makeEntity({
|
|
378
|
+
name: repo,
|
|
379
|
+
type: "project",
|
|
380
|
+
summary: "Repository targeted by Copilot automation.",
|
|
381
|
+
last_touched: run.date,
|
|
382
|
+
tags: ["copilot-automation", "project"],
|
|
383
|
+
visibility: Visibility.TEAM,
|
|
384
|
+
}));
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
return [...entities.values()];
|
|
389
|
+
}
|
|
390
|
+
// -- Participant ---------------------------------------------------------------
|
|
391
|
+
/**
|
|
392
|
+
* Convention-based Bourdon participant for Copilot automation memory artifacts.
|
|
393
|
+
* Users or CI scripts maintain `automation.toml` + `memory.md` files at
|
|
394
|
+
* `~/.copilot-bourdon/automations/<id>/`.
|
|
395
|
+
*/
|
|
396
|
+
export class CopilotAutomationsParticipant {
|
|
397
|
+
agentId = AGENT_ID;
|
|
398
|
+
agentType = AGENT_TYPE;
|
|
399
|
+
displayName = DISPLAY_NAME;
|
|
400
|
+
automationsDir;
|
|
401
|
+
policy;
|
|
402
|
+
/** Conventional automations dir (`~/.copilot-bourdon/automations`). Does NOT
|
|
403
|
+
* consult `COPILOT_AUTOMATIONS_HOME` (matches Python `default_native_path`). */
|
|
404
|
+
static defaultNativePath(home) {
|
|
405
|
+
const base = join(home ?? homeDir(), CONVENTION_DIR_NAME);
|
|
406
|
+
return join(base, AUTOMATIONS_DIR);
|
|
407
|
+
}
|
|
408
|
+
constructor(automationsDir) {
|
|
409
|
+
this.automationsDir = automationsDir ?? defaultCopilotAutomationsDir();
|
|
410
|
+
this.policy = DEFAULT_POLICY;
|
|
411
|
+
}
|
|
412
|
+
get nativePath() {
|
|
413
|
+
return this.automationsDir;
|
|
414
|
+
}
|
|
415
|
+
// -- Protocol surface -------------------------------------------------------
|
|
416
|
+
discover() {
|
|
417
|
+
if (!isDir(this.automationsDir)) {
|
|
418
|
+
throw new ParticipantDiscoveryError(`Copilot automations directory not found at ${this.automationsDir}. ` +
|
|
419
|
+
"Run `bourdon copilot-automations init <name>` to create one.");
|
|
420
|
+
}
|
|
421
|
+
const configs = iterConfigs(this.automationsDir);
|
|
422
|
+
return {
|
|
423
|
+
path: this.automationsDir,
|
|
424
|
+
version: "convention-v1",
|
|
425
|
+
metadata: {
|
|
426
|
+
automations: configs.length,
|
|
427
|
+
with_memory: configs.filter((c) => c.memoryPath !== null).length,
|
|
428
|
+
},
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
exportSessions(since, limit = 100) {
|
|
432
|
+
const sessions = this.runs(undefined, since).map(sessionFromRun);
|
|
433
|
+
sessions.sort((a, b) => (a.date < b.date ? 1 : a.date > b.date ? -1 : 0));
|
|
434
|
+
return sessions.slice(0, limit);
|
|
435
|
+
}
|
|
436
|
+
exportL5(since) {
|
|
437
|
+
const configs = iterConfigs(this.automationsDir);
|
|
438
|
+
const runs = this.runs(configs, since);
|
|
439
|
+
const sessions = runs.map(sessionFromRun);
|
|
440
|
+
sessions.sort((a, b) => (a.date < b.date ? 1 : a.date > b.date ? -1 : 0));
|
|
441
|
+
const entities = entitiesFromConfigsAndRuns(configs, runs);
|
|
442
|
+
const visibleEntities = filterForFederation(entities, this.policy);
|
|
443
|
+
return makeManifest({
|
|
444
|
+
spec_version: SPEC_VERSION,
|
|
445
|
+
agent: makeAgentInfo({
|
|
446
|
+
id: AGENT_ID,
|
|
447
|
+
type: AGENT_TYPE,
|
|
448
|
+
instance: hostname() || "unknown",
|
|
449
|
+
spec_version_compat: `>=${SPEC_VERSION}`,
|
|
450
|
+
role_narrative: ROLE_NARRATIVE,
|
|
451
|
+
}),
|
|
452
|
+
last_updated: new Date().toISOString(),
|
|
453
|
+
capabilities: ["copilot-automation-memory", "run-summary-publication"],
|
|
454
|
+
recent_sessions: sessions,
|
|
455
|
+
known_entities: visibleEntities,
|
|
456
|
+
visibility_policy: this.policy,
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
healthCheck() {
|
|
460
|
+
if (!isDir(this.automationsDir)) {
|
|
461
|
+
return {
|
|
462
|
+
status: "blocked",
|
|
463
|
+
reason: `Copilot automations directory not found at ${this.automationsDir}.`,
|
|
464
|
+
details: { automations_dir: this.automationsDir },
|
|
465
|
+
proposedFix: "Run `bourdon copilot-automations init <name>` to create an automation.",
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
const configs = iterConfigs(this.automationsDir);
|
|
469
|
+
const runs = this.runs(configs);
|
|
470
|
+
const hasConfigs = configs.length > 0;
|
|
471
|
+
return {
|
|
472
|
+
status: hasConfigs ? "ok" : "degraded",
|
|
473
|
+
reason: hasConfigs ? undefined : "No automation.toml files found.",
|
|
474
|
+
details: {
|
|
475
|
+
automations_dir: this.automationsDir,
|
|
476
|
+
automation_count: configs.length,
|
|
477
|
+
memory_files: configs.filter((c) => c.memoryPath !== null).length,
|
|
478
|
+
runs_extracted: runs.length,
|
|
479
|
+
active_automations: configs.filter((c) => c.status === "ACTIVE").length,
|
|
480
|
+
},
|
|
481
|
+
proposedFix: hasConfigs
|
|
482
|
+
? undefined
|
|
483
|
+
: "Run `bourdon copilot-automations init <name>` to create an automation, " +
|
|
484
|
+
"or add automation.toml files manually.",
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
// -- Internal ---------------------------------------------------------------
|
|
488
|
+
runs(configs, since) {
|
|
489
|
+
const runCutoff = since ? since.toISOString().slice(0, 10) : null;
|
|
490
|
+
const out = [];
|
|
491
|
+
for (const config of configs ?? iterConfigs(this.automationsDir)) {
|
|
492
|
+
for (const run of extractMemoryRuns(config)) {
|
|
493
|
+
if (runCutoff && run.date < runCutoff)
|
|
494
|
+
continue;
|
|
495
|
+
out.push(run);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
// sorted(key=(date, automation_id), reverse=True)
|
|
499
|
+
out.sort((a, b) => {
|
|
500
|
+
if (a.date !== b.date)
|
|
501
|
+
return a.date < b.date ? 1 : -1;
|
|
502
|
+
const ai = a.automation.automationId;
|
|
503
|
+
const bi = b.automation.automationId;
|
|
504
|
+
if (ai !== bi)
|
|
505
|
+
return ai < bi ? 1 : -1;
|
|
506
|
+
return 0;
|
|
507
|
+
});
|
|
508
|
+
return out;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
// -- Init helper ---------------------------------------------------------------
|
|
512
|
+
/**
|
|
513
|
+
* Create an automation scaffold at `<automationsDir>/<id>/`. Returns the path
|
|
514
|
+
* of the created automation directory. Throws when `automation.toml` already
|
|
515
|
+
* exists unless `force` is true.
|
|
516
|
+
*/
|
|
517
|
+
export function initAutomation(automationsDir, automationId = "my-automation", name = "My Copilot Automation", force = false) {
|
|
518
|
+
const targetDir = join(automationsDir || defaultCopilotAutomationsDir(), automationId);
|
|
519
|
+
const tomlPath = join(targetDir, AUTOMATION_TOML);
|
|
520
|
+
const memoryPath = join(targetDir, MEMORY_MD);
|
|
521
|
+
if (isFile(tomlPath) && !force) {
|
|
522
|
+
throw new Error(`${tomlPath} already exists. Pass --force to overwrite.`);
|
|
523
|
+
}
|
|
524
|
+
mkdirSync(targetDir, { recursive: true });
|
|
525
|
+
writeFileSync(tomlPath, AUTOMATION_TOML_TEMPLATE.replace("{automation_id}", automationId).replace("{name}", name), "utf8");
|
|
526
|
+
if (!isFile(memoryPath) || force) {
|
|
527
|
+
writeFileSync(memoryPath, MEMORY_MD_TEMPLATE.replace("{name}", name), "utf8");
|
|
528
|
+
}
|
|
529
|
+
return targetDir;
|
|
530
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub Copilot CLI participant — normalize the `copilot` terminal agent's
|
|
3
|
+
* SQLite session store into L5. Port of `participants/copilot_cli.py`.
|
|
4
|
+
*
|
|
5
|
+
* Copilot CLI stores session history in a SQLite database at
|
|
6
|
+
* `~/.copilot/session-store.db` (override `$COPILOT_CLI_HOME`). This is distinct
|
|
7
|
+
* from:
|
|
8
|
+
* - the VS Code Copilot Chat extension (`github-copilot` network reader)
|
|
9
|
+
* - the `~/.copilot-bourdon/memory.md` convention-file participant (`copilot`)
|
|
10
|
+
* - GitHub-embedded Copilot (no local state)
|
|
11
|
+
*
|
|
12
|
+
* The schema (schema_version 4) carries `sessions` / `turns` / `checkpoints`
|
|
13
|
+
* plus `session_files` / `session_refs` / `dynamic_context_items`. The reader
|
|
14
|
+
* opens the DB read-only (`mode=ro` via {@link tryOpenReadonly}) so a live
|
|
15
|
+
* writer's lock can never block the export, probes each table defensively (a
|
|
16
|
+
* missing table degrades to "no rows" rather than raising), redacts every native
|
|
17
|
+
* string, applies the visibility filter BEFORE emission, and is deterministic.
|
|
18
|
+
* `healthCheck` never throws.
|
|
19
|
+
*/
|
|
20
|
+
import { type L5ManifestModel, type SessionModel } from "@getbourdon/l5";
|
|
21
|
+
import { type AgentStore, type BourdonParticipant, type HealthStatus } from "../base.js";
|
|
22
|
+
/**
|
|
23
|
+
* Conventional `~/.copilot/` directory. Respects the `COPILOT_CLI_HOME`
|
|
24
|
+
* environment-variable override (mirrors `default_copilot_cli_dir`).
|
|
25
|
+
*/
|
|
26
|
+
export declare function defaultCopilotCliDir(): string;
|
|
27
|
+
/**
|
|
28
|
+
* External participant for GitHub Copilot CLI (`~/.copilot/session-store.db`).
|
|
29
|
+
* Reads the SQLite database read-only (never locks the live db).
|
|
30
|
+
*/
|
|
31
|
+
export declare class CopilotCliParticipant implements BourdonParticipant {
|
|
32
|
+
agentId: string;
|
|
33
|
+
agentType: string;
|
|
34
|
+
displayName: string;
|
|
35
|
+
nativePath: string;
|
|
36
|
+
private readonly copilotDir;
|
|
37
|
+
/** Conventional `~/.copilot` dir used by the setup wizard's detection. */
|
|
38
|
+
static defaultNativePath(home?: string): string;
|
|
39
|
+
constructor(copilotDir?: string);
|
|
40
|
+
discover(): AgentStore;
|
|
41
|
+
exportSessions(since?: Date, limit?: number): SessionModel[];
|
|
42
|
+
exportL5(since?: Date): L5ManifestModel;
|
|
43
|
+
healthCheck(): HealthStatus;
|
|
44
|
+
private emptyManifest;
|
|
45
|
+
}
|