@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,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codex session-index title lookup — narrow port of the three
|
|
3
|
+
* `session_thread_titles` helpers in `participants/codex.py`.
|
|
4
|
+
*
|
|
5
|
+
* SCOPE (deliberately tiny): this is NOT the Codex participant/reader (the
|
|
6
|
+
* ~2.6k-line turn-compiler still lands in a follow-on slice). It ports ONLY the
|
|
7
|
+
* surface the tray/presence join needs — a `session_id -> human thread title`
|
|
8
|
+
* map read from `~/.codex/session_index.jsonl`.
|
|
9
|
+
*
|
|
10
|
+
* Why only the index: `session_thread_titles()` runs on EVERY tray refresh, so
|
|
11
|
+
* it must never touch rollout files. One small JSONL read, newest-first, done.
|
|
12
|
+
* It covers Codex CLI sessions AND ChatGPT-app threads — the app's Work and
|
|
13
|
+
* Codex modes share `~/.codex`, so `thread_name` is the human title for both.
|
|
14
|
+
*
|
|
15
|
+
* PRIVACY: titles are raw user text. Callers that EXPORT them MUST route them
|
|
16
|
+
* through the audited redaction pipeline (`redactField` in
|
|
17
|
+
* `mcp-server/src/agents-export.ts`, == Python's `_redact_field`), exactly like
|
|
18
|
+
* every other emitted string. This module deliberately does NOT redact — the
|
|
19
|
+
* redaction happens at the single audited egress point so it can't be
|
|
20
|
+
* double-applied or silently skipped.
|
|
21
|
+
*
|
|
22
|
+
* Best-effort by construction: a missing dir, an unreadable file, or a corrupt
|
|
23
|
+
* line degrades to fewer/no titles. Nothing here throws.
|
|
24
|
+
*/
|
|
25
|
+
import { readFileSync, statSync } from "node:fs";
|
|
26
|
+
import { homedir } from "node:os";
|
|
27
|
+
import { join } from "node:path";
|
|
28
|
+
/** Default number of index records considered (newest-first). */
|
|
29
|
+
export const DEFAULT_TITLE_LIMIT = 200;
|
|
30
|
+
/** Default number of threads on the tray codex card (newest-first). */
|
|
31
|
+
export const DEFAULT_RECENT_THREAD_LIMIT = 5;
|
|
32
|
+
/** Collapse internal whitespace runs — mirrors Python `_normalize_text`. */
|
|
33
|
+
function normalizeText(value) {
|
|
34
|
+
return value.trim().replace(/\s+/g, " ");
|
|
35
|
+
}
|
|
36
|
+
function isDir(path) {
|
|
37
|
+
try {
|
|
38
|
+
return statSync(path).isDirectory();
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Locate `~/.codex/` (the primary Codex store), or null.
|
|
46
|
+
*
|
|
47
|
+
* Resolution order, mirroring `_resolve_codex_home()` exactly:
|
|
48
|
+
* 1. `$CODEX_HOME` — **only if that directory already exists**
|
|
49
|
+
* 2. `~/.codex` — only if it exists
|
|
50
|
+
* 3. `null`
|
|
51
|
+
*
|
|
52
|
+
* ⚠ FOOTGUN (preserved for parity, do not "fix" without changing Python too):
|
|
53
|
+
* step 1 is a silent fallback, not a pin. If `$CODEX_HOME` points at a path that
|
|
54
|
+
* does not exist yet, the env var is IGNORED and this returns the developer's
|
|
55
|
+
* REAL `~/.codex`. A sandbox/test that sets `CODEX_HOME` to a temp path without
|
|
56
|
+
* `mkdir`-ing it first looks isolated but reads live data. Always create the
|
|
57
|
+
* directory before setting the variable.
|
|
58
|
+
*/
|
|
59
|
+
export function resolveCodexHome(baseHome) {
|
|
60
|
+
const env = process.env.CODEX_HOME;
|
|
61
|
+
if (env) {
|
|
62
|
+
// Existence check FIRST — see the footgun note above.
|
|
63
|
+
if (isDir(env))
|
|
64
|
+
return env;
|
|
65
|
+
}
|
|
66
|
+
const home = baseHome ? baseHome : homedir();
|
|
67
|
+
const candidate = join(home, ".codex");
|
|
68
|
+
return isDir(candidate) ? candidate : null;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Read `session_index.jsonl`, newest-first by `updated_at`.
|
|
72
|
+
*
|
|
73
|
+
* Port of `_parse_session_index()`. Per-line JSON parse: a malformed line is
|
|
74
|
+
* WARNED and SKIPPED — it never aborts the scan (one bad append must not blind
|
|
75
|
+
* the tray to every other session). An unreadable file warns and yields `[]`.
|
|
76
|
+
* Only records that are objects with a string `id` are kept.
|
|
77
|
+
*/
|
|
78
|
+
export function parseSessionIndex(path, limit) {
|
|
79
|
+
let text;
|
|
80
|
+
try {
|
|
81
|
+
if (!statSync(path).isFile())
|
|
82
|
+
return [];
|
|
83
|
+
text = readFileSync(path, "utf8");
|
|
84
|
+
}
|
|
85
|
+
catch (exc) {
|
|
86
|
+
// Covers both "missing" (Python's `is_file()` guard) and OSError-on-read.
|
|
87
|
+
if (existsQuiet(path))
|
|
88
|
+
console.warn(`Failed to read ${path}: ${String(exc)}`);
|
|
89
|
+
return [];
|
|
90
|
+
}
|
|
91
|
+
const entries = [];
|
|
92
|
+
const lines = text.split(/\r?\n/);
|
|
93
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
94
|
+
const line = lines[i].trim();
|
|
95
|
+
if (!line)
|
|
96
|
+
continue;
|
|
97
|
+
let record;
|
|
98
|
+
try {
|
|
99
|
+
record = JSON.parse(line);
|
|
100
|
+
}
|
|
101
|
+
catch (exc) {
|
|
102
|
+
console.warn(`Skipping malformed JSONL in ${path}:${i + 1} -- ${String(exc)}`);
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (record !== null &&
|
|
106
|
+
typeof record === "object" &&
|
|
107
|
+
!Array.isArray(record) &&
|
|
108
|
+
typeof record.id === "string") {
|
|
109
|
+
entries.push(record);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
// Descending by `updated_at` (missing key sorts as ""), stable on ties —
|
|
113
|
+
// mirrors Python's `sort(key=..., reverse=True)` on a stable sort.
|
|
114
|
+
entries.sort((a, b) => {
|
|
115
|
+
const ua = String(a.updated_at ?? "");
|
|
116
|
+
const ub = String(b.updated_at ?? "");
|
|
117
|
+
return ua < ub ? 1 : ua > ub ? -1 : 0;
|
|
118
|
+
});
|
|
119
|
+
return limit !== undefined && limit !== null ? entries.slice(0, limit) : entries;
|
|
120
|
+
}
|
|
121
|
+
/** True if the path exists at all (distinguishes "missing" from "unreadable"). */
|
|
122
|
+
function existsQuiet(path) {
|
|
123
|
+
try {
|
|
124
|
+
statSync(path);
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Map full Codex session id → human thread title, newest-first.
|
|
133
|
+
*
|
|
134
|
+
* Port of `session_thread_titles()`. Reads ONLY `session_index.jsonl` — no
|
|
135
|
+
* rollout I/O, because this runs on every tray refresh. A missing/malformed
|
|
136
|
+
* index yields `{}`.
|
|
137
|
+
*
|
|
138
|
+
* NEWEST WINS: entries are already sorted newest-first and the insert is a
|
|
139
|
+
* `setdefault` equivalent, so the freshest `thread_name` for a repeated id is
|
|
140
|
+
* the one kept.
|
|
141
|
+
*
|
|
142
|
+
* Titles are RAW USER TEXT — see the module note; the exporter redacts.
|
|
143
|
+
*/
|
|
144
|
+
export function sessionThreadTitles(codexHome, opts = {}) {
|
|
145
|
+
const limit = opts.limit ?? DEFAULT_TITLE_LIMIT;
|
|
146
|
+
const home = codexHome === undefined || codexHome === null ? resolveCodexHome() : codexHome;
|
|
147
|
+
if (home === null)
|
|
148
|
+
return {};
|
|
149
|
+
const titles = {};
|
|
150
|
+
for (const entry of parseSessionIndex(join(home, "session_index.jsonl"), limit)) {
|
|
151
|
+
const sid = entry.id;
|
|
152
|
+
const name = entry.thread_name;
|
|
153
|
+
if (typeof sid === "string" &&
|
|
154
|
+
sid &&
|
|
155
|
+
typeof name === "string" &&
|
|
156
|
+
name.trim() &&
|
|
157
|
+
!(sid in titles) // setdefault: first (== newest) wins
|
|
158
|
+
) {
|
|
159
|
+
titles[sid] = normalizeText(name);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return titles;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Newest-first `[{instance, title, updated_at}]` for the tray codex card.
|
|
166
|
+
*
|
|
167
|
+
* Port of `recent_session_threads()`. Independent of PRESENCE: ChatGPT-app
|
|
168
|
+
* threads fire no presence hooks, so this is the only way they surface.
|
|
169
|
+
*
|
|
170
|
+
* Emits only the 8-char `instance` prefix — parity with `presence.summarize`:
|
|
171
|
+
* a full session id never leaves the local read path. Titles are RAW USER TEXT;
|
|
172
|
+
* the exporter redacts them (see `sessionThreadTitles`).
|
|
173
|
+
*/
|
|
174
|
+
export function recentSessionThreads(codexHome, opts = {}) {
|
|
175
|
+
const limit = opts.limit ?? DEFAULT_RECENT_THREAD_LIMIT;
|
|
176
|
+
const home = codexHome === undefined || codexHome === null ? resolveCodexHome() : codexHome;
|
|
177
|
+
if (home === null)
|
|
178
|
+
return [];
|
|
179
|
+
const threads = [];
|
|
180
|
+
for (const entry of parseSessionIndex(join(home, "session_index.jsonl"), limit)) {
|
|
181
|
+
const sid = entry.id;
|
|
182
|
+
if (typeof sid !== "string" || !sid)
|
|
183
|
+
continue;
|
|
184
|
+
const name = entry.thread_name;
|
|
185
|
+
const title = typeof name === "string" && name.trim() ? normalizeText(name) : null;
|
|
186
|
+
const updated = String(entry.updated_at ?? "");
|
|
187
|
+
threads.push({
|
|
188
|
+
instance: sid.slice(0, 8),
|
|
189
|
+
title,
|
|
190
|
+
updated_at: updated || null,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
return threads;
|
|
194
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
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 { type L5ManifestModel, type SessionModel } from "@getbourdon/l5";
|
|
30
|
+
import { type AgentStore, type BourdonParticipant, type HealthStatus } from "../base.js";
|
|
31
|
+
/**
|
|
32
|
+
* Return the conventional `~/.copilot-bourdon/automations/` directory. Respects
|
|
33
|
+
* the `COPILOT_AUTOMATIONS_HOME` env-var override (returned verbatim — the
|
|
34
|
+
* override IS the automations dir, no `/automations` suffix is appended).
|
|
35
|
+
*/
|
|
36
|
+
export declare function defaultCopilotAutomationsDir(copilotBourdonDir?: string | null): string;
|
|
37
|
+
/**
|
|
38
|
+
* Convention-based Bourdon participant for Copilot automation memory artifacts.
|
|
39
|
+
* Users or CI scripts maintain `automation.toml` + `memory.md` files at
|
|
40
|
+
* `~/.copilot-bourdon/automations/<id>/`.
|
|
41
|
+
*/
|
|
42
|
+
export declare class CopilotAutomationsParticipant implements BourdonParticipant {
|
|
43
|
+
agentId: string;
|
|
44
|
+
agentType: string;
|
|
45
|
+
displayName: string;
|
|
46
|
+
private readonly automationsDir;
|
|
47
|
+
private readonly policy;
|
|
48
|
+
/** Conventional automations dir (`~/.copilot-bourdon/automations`). Does NOT
|
|
49
|
+
* consult `COPILOT_AUTOMATIONS_HOME` (matches Python `default_native_path`). */
|
|
50
|
+
static defaultNativePath(home?: string): string;
|
|
51
|
+
constructor(automationsDir?: string | null);
|
|
52
|
+
get nativePath(): string;
|
|
53
|
+
discover(): AgentStore;
|
|
54
|
+
exportSessions(since?: Date, limit?: number): SessionModel[];
|
|
55
|
+
exportL5(since?: Date): L5ManifestModel;
|
|
56
|
+
healthCheck(): HealthStatus;
|
|
57
|
+
private runs;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Create an automation scaffold at `<automationsDir>/<id>/`. Returns the path
|
|
61
|
+
* of the created automation directory. Throws when `automation.toml` already
|
|
62
|
+
* exists unless `force` is true.
|
|
63
|
+
*/
|
|
64
|
+
export declare function initAutomation(automationsDir?: string | null, automationId?: string, name?: string, force?: boolean): string;
|