@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.
Files changed (48) hide show
  1. package/README.md +7 -1
  2. package/dist/base.d.ts +109 -0
  3. package/dist/base.js +88 -0
  4. package/dist/index.cjs +171 -82
  5. package/dist/index.cjs.map +1 -1
  6. package/dist/index.d.cts +117 -4
  7. package/dist/index.d.ts +42 -970
  8. package/dist/index.js +53 -6509
  9. package/dist/index.js.map +1 -1
  10. package/dist/network-base.d.ts +108 -0
  11. package/dist/network-base.js +248 -0
  12. package/dist/readers/cascade.d.ts +49 -0
  13. package/dist/readers/cascade.js +428 -0
  14. package/dist/readers/claude-code-automations.d.ts +62 -0
  15. package/dist/readers/claude-code-automations.js +693 -0
  16. package/dist/readers/claude-code.d.ts +50 -0
  17. package/dist/readers/claude-code.js +583 -0
  18. package/dist/readers/claude-desktop-code.d.ts +54 -0
  19. package/dist/readers/claude-desktop-code.js +526 -0
  20. package/dist/readers/claude-desktop-cowork.d.ts +47 -0
  21. package/dist/readers/claude-desktop-cowork.js +659 -0
  22. package/dist/readers/codex-automations.d.ts +55 -0
  23. package/dist/readers/codex-automations.js +519 -0
  24. package/dist/readers/codex-sessions.d.ts +88 -0
  25. package/dist/readers/codex-sessions.js +194 -0
  26. package/dist/readers/copilot-automations.d.ts +64 -0
  27. package/dist/readers/copilot-automations.js +530 -0
  28. package/dist/readers/copilot-cli.d.ts +45 -0
  29. package/dist/readers/copilot-cli.js +416 -0
  30. package/dist/readers/copilot-vscode.d.ts +54 -0
  31. package/dist/readers/copilot-vscode.js +553 -0
  32. package/dist/readers/copilot.d.ts +59 -0
  33. package/dist/readers/copilot.js +413 -0
  34. package/dist/readers/cursor-automations.d.ts +57 -0
  35. package/dist/readers/cursor-automations.js +685 -0
  36. package/dist/readers/cursor.d.ts +50 -0
  37. package/dist/readers/cursor.js +670 -0
  38. package/dist/readers/github-copilot.d.ts +34 -0
  39. package/dist/readers/github-copilot.js +242 -0
  40. package/dist/readers/hermes.d.ts +37 -0
  41. package/dist/readers/hermes.js +426 -0
  42. package/dist/readers/openclaw.d.ts +78 -0
  43. package/dist/readers/openclaw.js +386 -0
  44. package/dist/registry.d.ts +39 -0
  45. package/dist/registry.js +98 -0
  46. package/dist/sqlite-base.d.ts +65 -0
  47. package/dist/sqlite-base.js +163 -0
  48. package/package.json +5 -5
package/README.md CHANGED
@@ -14,12 +14,18 @@ Python (`pip install bourdon`) is the oracle; this TS mirror asserts against the
14
14
  | Reader | Category | Native store |
15
15
  |---|---|---|
16
16
  | `HermesParticipant` | SQLite (read-only) | `~/.hermes/` — `state.db` sessions + `memories/*.md` |
17
- | `ClaudeCodeParticipant` | file / convention | claude-brain + `~/.claude/projects/*/memory/` + KG JSONL |
17
+ | `ClaudeCodeParticipant` | file / convention | `~/agent-brain/` + `~/.claude/projects/*/memory/` + KG JSONL |
18
18
  | `GitHubCopilotParticipant` | network (TTL cache) | `api.github.com` PR activity → `~/.cache/bourdon/github-copilot/` |
19
19
 
20
20
  The heavier SQLite readers (`cursor`, `codex`, `copilot_cli`) are **deferred to
21
21
  the follow-on slice**; `openclaw` ships as the `@getbourdon/openclaw` plugin.
22
22
 
23
+ `ClaudeCodeParticipant` resolves its knowledge repo in this order: the
24
+ `AGENT_BRAIN` env var, then `$HOME/agent-brain/`, then `./agent-brain/`. The
25
+ pre-rename env var (`CLAUDE_BRAIN`) and the pre-rename directory name are still
26
+ accepted as **legacy aliases** at each tier, so a checkout that predates the
27
+ rename keeps resolving with no operator action.
28
+
23
29
  ## The four invariants (enforced in code, not by trust)
24
30
 
25
31
  1. **Visibility filter BEFORE emission** — every entity passes
package/dist/base.d.ts ADDED
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Bourdon participant base — the Protocol → TS interface, errors, and the
3
+ * AgentStore / HealthStatus types. Faithful port of `participants/base.py`.
4
+ *
5
+ * The L5 dataclass model (AgentInfo/Entity/Session/VisibilityPolicy/L5Manifest),
6
+ * the `toDict` omission rules, and the visibility helpers all live in
7
+ * `@getbourdon/l5` and are reused verbatim — only THAT output shape is the parity
8
+ * contract. This module owns the participant CONTRACT surface around them.
9
+ *
10
+ * Contract semantics (enforced by every reader, asserted by the conformance
11
+ * fixtures, never by trust):
12
+ * 1. visibility filter BEFORE emission (`filterForFederation`) — private wins.
13
+ * 2. deterministic / idempotent `exportL5` — same store → identical `toDict`.
14
+ * 3. `healthCheck` NEVER throws — catch-all → degraded/blocked.
15
+ * 4. every native string through `@getbourdon/redaction` before it enters L5.
16
+ */
17
+ import type { L5ManifestModel, SessionModel } from "@getbourdon/l5";
18
+ /** Tied to Bourdon spec v0.1 — same literals as the Python oracle. */
19
+ export declare const CONTRACT_VERSION = "0.1";
20
+ export declare const SPEC_VERSION = "0.1";
21
+ /** Base class for participant errors. */
22
+ export declare class ParticipantError extends Error {
23
+ constructor(message?: string);
24
+ }
25
+ /** Raised by `discover()` when the native store cannot be found or read.
26
+ * Non-fatal: L6 skips this agent rather than aborting the whole export. */
27
+ export declare class ParticipantDiscoveryError extends ParticipantError {
28
+ }
29
+ /** Raised by `exportL5()` / `exportSessions()` when an export fails mid-op
30
+ * (the store WAS present — distinct from a discovery miss). */
31
+ export declare class ParticipantExportError extends ParticipantError {
32
+ }
33
+ /** Raised when the native store's version is outside the supported range.
34
+ * Subtype of discovery error (a version mismatch is a discovery-time miss). */
35
+ export declare class ParticipantVersionMismatchError extends ParticipantDiscoveryError {
36
+ }
37
+ /** Metadata describing the native agent store. Returned by `discover()`. */
38
+ export interface AgentStore {
39
+ path: string;
40
+ version: string;
41
+ metadata: Record<string, unknown>;
42
+ }
43
+ /** Returned by `healthCheck()`. Consumed by the `bourdon doctor` CLI. */
44
+ export interface HealthStatus {
45
+ status: "ok" | "degraded" | "blocked";
46
+ reason?: string;
47
+ details: Record<string, unknown>;
48
+ /** A human-runnable command to remedy a non-ok status. */
49
+ proposedFix?: string;
50
+ }
51
+ /**
52
+ * Structural interface every participant must satisfy (the Python
53
+ * `@runtime_checkable Protocol`). Duck-typed at registration: a class is a
54
+ * participant iff it has `agentId` + `agentType` + `exportL5` + `healthCheck`.
55
+ *
56
+ * Optional members (`displayName`, a `defaultNativePath` static) are documented
57
+ * here as in the Python Protocol; discovery + the setup wizard fall back to
58
+ * sensible defaults when absent.
59
+ */
60
+ export interface BourdonParticipant {
61
+ agentId: string;
62
+ agentType: string;
63
+ nativePath: string;
64
+ /** Human-friendly label (e.g. "GitHub Copilot"). Optional. */
65
+ displayName?: string;
66
+ /** Quarantined-class marker (Python `QUARANTINED_CLASS`). When true, the CLI
67
+ * requires `--i-understand-the-risk` to register the agent trusted, and its
68
+ * exports are staged for operator review rather than written to the live
69
+ * store (spec D6: quarantine follows the content, not the invoker). Absent =
70
+ * a normal, non-quarantined participant. Read by `bourdon agent add/set-tier`
71
+ * (cli/src/commands/federation.ts). */
72
+ quarantinedClass?: boolean;
73
+ /** Confirm the native store exists; return metadata. Throws
74
+ * {@link ParticipantDiscoveryError} when missing/unreadable. */
75
+ discover(): AgentStore;
76
+ /** Build the L5 manifest from native memory, applying the visibility filter
77
+ * BEFORE return. Deterministic — same store yields the same manifest. */
78
+ exportL5(since?: Date): L5ManifestModel;
79
+ /** Export recent sessions in normalized schema. */
80
+ exportSessions(since?: Date, limit?: number): SessionModel[];
81
+ /** Return ok / degraded / blocked with a reason. MUST NOT throw. */
82
+ healthCheck(): HealthStatus;
83
+ }
84
+ /**
85
+ * Tiny structural guard mirroring Python's `_PARTICIPANT_MARKER_ATTRS`
86
+ * duck-type: a value is a participant iff it carries the four marker members.
87
+ */
88
+ export declare function isParticipant(obj: unknown): obj is BourdonParticipant;
89
+ /**
90
+ * Environment variable holding the deployment's own project-name recognition
91
+ * vocabulary, as a comma-separated list (e.g.
92
+ * `BOURDON_PROJECT_HINTS="Acme Portal,Widget API,internal-tools"`).
93
+ */
94
+ export declare const PROJECT_HINTS_ENV = "BOURDON_PROJECT_HINTS";
95
+ /**
96
+ * Project-name hints used to tag sessions with a project focus.
97
+ *
98
+ * Bourdon ships **no** project roster of its own — the `defaults` a reader
99
+ * passes are limited to product-neutral names (this product, plus the agent
100
+ * surfaces it federates). A deployment that wants its own projects recognized
101
+ * sets {@link PROJECT_HINTS_ENV}; those entries are appended to `defaults`.
102
+ *
103
+ * Matching downstream is case-insensitive substring matching, so entries are
104
+ * de-duplicated case-insensitively here while preserving first-seen order and
105
+ * the original casing (which is what gets emitted as the project name).
106
+ *
107
+ * Mirrors Python `participants.base.load_project_hints`.
108
+ */
109
+ export declare function loadProjectHints(defaults?: readonly string[]): readonly string[];
package/dist/base.js ADDED
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Bourdon participant base — the Protocol → TS interface, errors, and the
3
+ * AgentStore / HealthStatus types. Faithful port of `participants/base.py`.
4
+ *
5
+ * The L5 dataclass model (AgentInfo/Entity/Session/VisibilityPolicy/L5Manifest),
6
+ * the `toDict` omission rules, and the visibility helpers all live in
7
+ * `@getbourdon/l5` and are reused verbatim — only THAT output shape is the parity
8
+ * contract. This module owns the participant CONTRACT surface around them.
9
+ *
10
+ * Contract semantics (enforced by every reader, asserted by the conformance
11
+ * fixtures, never by trust):
12
+ * 1. visibility filter BEFORE emission (`filterForFederation`) — private wins.
13
+ * 2. deterministic / idempotent `exportL5` — same store → identical `toDict`.
14
+ * 3. `healthCheck` NEVER throws — catch-all → degraded/blocked.
15
+ * 4. every native string through `@getbourdon/redaction` before it enters L5.
16
+ */
17
+ /** Tied to Bourdon spec v0.1 — same literals as the Python oracle. */
18
+ export const CONTRACT_VERSION = "0.1";
19
+ export const SPEC_VERSION = "0.1";
20
+ // -- Errors -------------------------------------------------------------------
21
+ /** Base class for participant errors. */
22
+ export class ParticipantError extends Error {
23
+ constructor(message) {
24
+ super(message);
25
+ this.name = new.target.name;
26
+ }
27
+ }
28
+ /** Raised by `discover()` when the native store cannot be found or read.
29
+ * Non-fatal: L6 skips this agent rather than aborting the whole export. */
30
+ export class ParticipantDiscoveryError extends ParticipantError {
31
+ }
32
+ /** Raised by `exportL5()` / `exportSessions()` when an export fails mid-op
33
+ * (the store WAS present — distinct from a discovery miss). */
34
+ export class ParticipantExportError extends ParticipantError {
35
+ }
36
+ /** Raised when the native store's version is outside the supported range.
37
+ * Subtype of discovery error (a version mismatch is a discovery-time miss). */
38
+ export class ParticipantVersionMismatchError extends ParticipantDiscoveryError {
39
+ }
40
+ /**
41
+ * Tiny structural guard mirroring Python's `_PARTICIPANT_MARKER_ATTRS`
42
+ * duck-type: a value is a participant iff it carries the four marker members.
43
+ */
44
+ export function isParticipant(obj) {
45
+ if (obj === null || typeof obj !== "object")
46
+ return false;
47
+ const o = obj;
48
+ return (typeof o["agentId"] === "string" &&
49
+ typeof o["agentType"] === "string" &&
50
+ typeof o["exportL5"] === "function" &&
51
+ typeof o["healthCheck"] === "function");
52
+ }
53
+ /**
54
+ * Environment variable holding the deployment's own project-name recognition
55
+ * vocabulary, as a comma-separated list (e.g.
56
+ * `BOURDON_PROJECT_HINTS="Acme Portal,Widget API,internal-tools"`).
57
+ */
58
+ export const PROJECT_HINTS_ENV = "BOURDON_PROJECT_HINTS";
59
+ /**
60
+ * Project-name hints used to tag sessions with a project focus.
61
+ *
62
+ * Bourdon ships **no** project roster of its own — the `defaults` a reader
63
+ * passes are limited to product-neutral names (this product, plus the agent
64
+ * surfaces it federates). A deployment that wants its own projects recognized
65
+ * sets {@link PROJECT_HINTS_ENV}; those entries are appended to `defaults`.
66
+ *
67
+ * Matching downstream is case-insensitive substring matching, so entries are
68
+ * de-duplicated case-insensitively here while preserving first-seen order and
69
+ * the original casing (which is what gets emitted as the project name).
70
+ *
71
+ * Mirrors Python `participants.base.load_project_hints`.
72
+ */
73
+ export function loadProjectHints(defaults = []) {
74
+ const hints = [];
75
+ const seen = new Set();
76
+ const raw = process.env[PROJECT_HINTS_ENV] ?? "";
77
+ for (const candidate of [...defaults, ...raw.split(",")]) {
78
+ const name = candidate.trim();
79
+ if (!name)
80
+ continue;
81
+ const key = name.toLowerCase();
82
+ if (seen.has(key))
83
+ continue;
84
+ seen.add(key);
85
+ hints.push(name);
86
+ }
87
+ return hints;
88
+ }
package/dist/index.cjs CHANGED
@@ -35,6 +35,8 @@ __export(index_exports, {
35
35
  CursorAutomationsParticipant: () => CursorAutomationsParticipant,
36
36
  CursorParticipant: () => CursorParticipant,
37
37
  DEFAULT_CACHE_TTL_SECONDS: () => DEFAULT_CACHE_TTL_SECONDS,
38
+ DEFAULT_RECENT_THREAD_LIMIT: () => DEFAULT_RECENT_THREAD_LIMIT,
39
+ DEFAULT_TITLE_LIMIT: () => DEFAULT_TITLE_LIMIT,
38
40
  FIRST_PARTY: () => FIRST_PARTY,
39
41
  GitHubCopilotParticipant: () => GitHubCopilotParticipant,
40
42
  HermesParticipant: () => HermesParticipant,
@@ -44,6 +46,7 @@ __export(index_exports, {
44
46
  NetworkUnavailable: () => NetworkUnavailable,
45
47
  OpenClawApiClient: () => OpenClawApiClient,
46
48
  OpenClawParticipant: () => OpenClawParticipant,
49
+ PROJECT_HINTS_ENV: () => PROJECT_HINTS_ENV,
47
50
  ParticipantAuthError: () => ParticipantAuthError,
48
51
  ParticipantDiscoveryError: () => ParticipantDiscoveryError,
49
52
  ParticipantError: () => ParticipantError,
@@ -62,9 +65,14 @@ __export(index_exports, {
62
65
  friendlyLabel: () => friendlyLabel,
63
66
  ghTokenProvider: () => ghTokenProvider,
64
67
  isParticipant: () => isParticipant,
68
+ loadProjectHints: () => loadProjectHints,
65
69
  openReadonly: () => openReadonly,
70
+ parseSessionIndex: () => parseSessionIndex,
66
71
  projectKeyFromCwd: () => projectKeyFromCwd,
72
+ recentSessionThreads: () => recentSessionThreads,
73
+ resolveCodexHome: () => resolveCodexHome,
67
74
  resolvePluginParticipants: () => resolvePluginParticipants,
75
+ sessionThreadTitles: () => sessionThreadTitles,
68
76
  sqliteAvailable: () => sqliteAvailable,
69
77
  tableColumns: () => tableColumns,
70
78
  tableExists: () => tableExists,
@@ -95,6 +103,21 @@ function isParticipant(obj) {
95
103
  const o = obj;
96
104
  return typeof o["agentId"] === "string" && typeof o["agentType"] === "string" && typeof o["exportL5"] === "function" && typeof o["healthCheck"] === "function";
97
105
  }
106
+ var PROJECT_HINTS_ENV = "BOURDON_PROJECT_HINTS";
107
+ function loadProjectHints(defaults = []) {
108
+ const hints = [];
109
+ const seen = /* @__PURE__ */ new Set();
110
+ const raw = process.env[PROJECT_HINTS_ENV] ?? "";
111
+ for (const candidate of [...defaults, ...raw.split(",")]) {
112
+ const name = candidate.trim();
113
+ if (!name) continue;
114
+ const key = name.toLowerCase();
115
+ if (seen.has(key)) continue;
116
+ seen.add(key);
117
+ hints.push(name);
118
+ }
119
+ return hints;
120
+ }
98
121
 
99
122
  // src/sqlite-base.ts
100
123
  var import_node_fs = require("fs");
@@ -749,7 +772,7 @@ var import_l52 = require("@getbourdon/l5");
749
772
  var import_redaction2 = require("@getbourdon/redaction");
750
773
  var AGENT_ID2 = "claude-code";
751
774
  var AGENT_TYPE2 = "code-assistant";
752
- 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.";
775
+ var ROLE_NARRATIVE2 = "Agentic manager and code-assistant. Coordinates the 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.";
753
776
  var DEFAULT_POLICY2 = (0, import_l52.makeVisibilityPolicy)({
754
777
  default: import_l52.Visibility.PUBLIC,
755
778
  private_tags: ["personal", "financial", "credential", "health", "family", "legal"],
@@ -779,13 +802,22 @@ function isFile2(p) {
779
802
  return false;
780
803
  }
781
804
  }
782
- function resolveClaudeBrainPath() {
783
- const envOverride = process.env.CLAUDE_BRAIN;
784
- if (envOverride && isDir2(envOverride)) return envOverride;
785
- const homeCandidate = (0, import_node_path3.join)(homeDir(), "claude-brain");
786
- if (isDir2(homeCandidate)) return homeCandidate;
787
- const cwdCandidate = (0, import_node_path3.join)(process.cwd(), "claude-brain");
788
- if (isDir2(cwdCandidate)) return cwdCandidate;
805
+ var BRAIN_DIR_NAMES = [
806
+ "agent-brain",
807
+ ["c", "laude-brain"].join("")
808
+ ];
809
+ var BRAIN_ENV_VARS = ["AGENT_BRAIN", "CLAUDE_BRAIN"];
810
+ function resolveBrainPath() {
811
+ for (const varName of BRAIN_ENV_VARS) {
812
+ const envOverride = process.env[varName];
813
+ if (envOverride && isDir2(envOverride)) return envOverride;
814
+ }
815
+ for (const base of [homeDir(), process.cwd()]) {
816
+ for (const name of BRAIN_DIR_NAMES) {
817
+ const candidate = (0, import_node_path3.join)(base, name);
818
+ if (isDir2(candidate)) return candidate;
819
+ }
820
+ }
789
821
  return null;
790
822
  }
791
823
  function homeDir() {
@@ -1110,8 +1142,8 @@ var ClaudeCodeParticipant = class {
1110
1142
  return (0, import_node_path3.join)(home ?? homeDir(), ".claude");
1111
1143
  }
1112
1144
  constructor() {
1113
- this.nativePath = (0, import_node_path3.join)(homeDir(), "claude-brain");
1114
- this.brainPath = resolveClaudeBrainPath();
1145
+ this.nativePath = (0, import_node_path3.join)(homeDir(), BRAIN_DIR_NAMES[0]);
1146
+ this.brainPath = resolveBrainPath();
1115
1147
  this.autoMemoryPath = resolveAutoMemoryPath();
1116
1148
  this.knowledgeGraphPath = resolveKnowledgeGraphPath();
1117
1149
  }
@@ -1123,7 +1155,7 @@ var ClaudeCodeParticipant = class {
1123
1155
  };
1124
1156
  if (!Object.values(sources).some((v) => v)) {
1125
1157
  throw new ParticipantDiscoveryError(
1126
- "No Claude Code memory sources found. Expected one of: ~/claude-brain/, ~/.claude/projects/*/memory/, ~/claude-memory/memory.jsonl."
1158
+ "No Claude Code memory sources found. Expected one of: ~/agent-brain/, ~/.claude/projects/*/memory/, ~/claude-memory/memory.jsonl."
1127
1159
  );
1128
1160
  }
1129
1161
  return {
@@ -1185,7 +1217,7 @@ var ClaudeCodeParticipant = class {
1185
1217
  status: "degraded",
1186
1218
  reason: `${present}/3 Claude Code memory sources found`,
1187
1219
  details,
1188
- proposedFix: "Run `bourdon setup` to (re-)wire missing sources, or set the CLAUDE_BRAIN env var to point at your claude-brain checkout."
1220
+ proposedFix: "Run `bourdon setup` to (re-)wire missing sources, or set the AGENT_BRAIN env var to point at your agent-brain checkout."
1189
1221
  };
1190
1222
  } catch (err) {
1191
1223
  return {
@@ -3229,24 +3261,15 @@ var MAX_KEY_ACTIONS_PER_RUN = 6;
3229
3261
  var MAX_KEY_ACTION_CHARS2 = 280;
3230
3262
  var RUN_HEADER_RE = /^(\d{4}-\d{2}-\d{2})(?:\b|$)(.*)$/;
3231
3263
  var HEADER_SUFFIX_STRIP = " -:\u2014";
3232
- var PROJECT_HINTS = [
3233
- "ShipStable",
3234
- "ILTT",
3235
- "Prun",
3236
- "PRUN",
3237
- "OMNIvour",
3238
- "Castmore",
3264
+ var DEFAULT_PROJECT_HINTS = [
3239
3265
  "Bourdon",
3240
- "RADLAB",
3241
- "CHIP",
3242
- "Claude Brain",
3243
3266
  "Cursor",
3244
3267
  "Copilot",
3245
3268
  "Codex",
3246
3269
  "Cascade"
3247
3270
  ];
3248
3271
  var SIGNAL_PATTERNS = [
3249
- ["human-dashboard-action", /\b(human|ryan|dashboard|manual)\b/i],
3272
+ ["human-dashboard-action", /\b(human|dashboard|manual)\b/i],
3250
3273
  ["release-gate", /\b(release|store|app review|play console|testflight)\b/i],
3251
3274
  ["billing-drift", /\b(billing|stripe|revenuecat|iap|subscription)\b/i],
3252
3275
  ["memory-coverage-gap", /\b(memory|l5|manifest|federated|bourdon)\b/i],
@@ -3470,7 +3493,7 @@ function inferProjects(text) {
3470
3493
  const projects = [];
3471
3494
  const lowered = text.toLowerCase();
3472
3495
  const seen = /* @__PURE__ */ new Set();
3473
- for (const project of PROJECT_HINTS) {
3496
+ for (const project of loadProjectHints(DEFAULT_PROJECT_HINTS)) {
3474
3497
  const key = project.toLowerCase();
3475
3498
  if (lowered.includes(key) && !seen.has(key)) {
3476
3499
  projects.push(project);
@@ -3701,22 +3724,9 @@ var MAX_MEMORY_CHARS2 = 16e4;
3701
3724
  var MAX_KEY_ACTIONS_PER_RUN2 = 6;
3702
3725
  var MAX_KEY_ACTION_CHARS3 = 280;
3703
3726
  var RUN_HEADER_RE2 = /^(\d{4}-\d{2}-\d{2})(?:\b|$)(.*)$/;
3704
- var PROJECT_HINTS2 = [
3705
- "ShipStable",
3706
- "ILTT",
3707
- "Prun",
3708
- "PRUN",
3709
- "OMNIvour",
3710
- "Castmore",
3711
- "Bourdon",
3712
- "RADLAB",
3713
- "CHIP",
3714
- "Claude Brain",
3715
- "Cursor",
3716
- "Copilot"
3717
- ];
3727
+ var DEFAULT_PROJECT_HINTS2 = ["Bourdon", "Cursor", "Copilot"];
3718
3728
  var SIGNAL_PATTERNS2 = [
3719
- ["human-dashboard-action", /\b(human|ryan|dashboard|manual)\b/i],
3729
+ ["human-dashboard-action", /\b(human|dashboard|manual)\b/i],
3720
3730
  ["release-gate", /\b(release|store|app review|play console|testflight)\b/i],
3721
3731
  ["billing-drift", /\b(billing|stripe|revenuecat|iap|subscription)\b/i],
3722
3732
  ["memory-coverage-gap", /\b(memory|l5|manifest|federated|bourdon)\b/i],
@@ -3834,7 +3844,7 @@ function readMemoryText2(path) {
3834
3844
  return text.length > MAX_MEMORY_CHARS2 ? text.slice(-MAX_MEMORY_CHARS2) : text;
3835
3845
  }
3836
3846
  function splitLines2(text) {
3837
- return text.split(/\r\n|[\n\r\v\f\x1c\x1d\x1e\x85\u2028\u2029]/);
3847
+ return text.split(/\r\n|[\n\r\u000B\u000C\u001C\u001D\u001E\u0085\u2028\u2029]/u);
3838
3848
  }
3839
3849
  function rstrip2(s) {
3840
3850
  return s.replace(/\s+$/, "");
@@ -3874,7 +3884,7 @@ function inferProjects2(text) {
3874
3884
  const projects = [];
3875
3885
  const lowered = text.toLowerCase();
3876
3886
  const seen = /* @__PURE__ */ new Set();
3877
- for (const project of PROJECT_HINTS2) {
3887
+ for (const project of loadProjectHints(DEFAULT_PROJECT_HINTS2)) {
3878
3888
  const key = project.toLowerCase();
3879
3889
  if (lowered.includes(key) && !seen.has(key)) {
3880
3890
  projects.push(project);
@@ -4213,9 +4223,7 @@ function buildConfig3(tomlPath) {
4213
4223
  const kind = pyStr2(pyTruthy(raw["kind"]) ? raw["kind"] : "").trim();
4214
4224
  const reposRaw = pyTruthy(raw["repos"]) ? raw["repos"] : [];
4215
4225
  const reposIter = Array.isArray(reposRaw) ? reposRaw : typeof reposRaw === "string" ? Array.from(reposRaw) : [];
4216
- const repos = reposIter.filter(
4217
- (r) => typeof r === "string" && r.trim() !== ""
4218
- );
4226
+ const repos = reposIter.filter((r) => typeof r === "string" && r.trim() !== "");
4219
4227
  const memoryPath = (0, import_node_path11.join)(parentDir, MEMORY_MD3);
4220
4228
  return {
4221
4229
  automationId,
@@ -4251,7 +4259,10 @@ function iterConfigs3(automationsDir) {
4251
4259
  }
4252
4260
  function splitLines3(text) {
4253
4261
  if (text.length === 0) return [];
4254
- return text.split(/\r\n|\r|\n|\v|\f|\x1c|\x1d|\x1e|\x85|\u2028|\u2029/);
4262
+ return text.split(
4263
+ // eslint-disable-next-line no-control-regex
4264
+ /\r\n|\r|\n|\u000B|\u000C|\u001C|\u001D|\u001E|\u0085|\u2028|\u2029/u
4265
+ );
4255
4266
  }
4256
4267
  function rstrip3(s) {
4257
4268
  return s.replace(/\s+$/u, "");
@@ -4269,9 +4280,7 @@ function extractMemoryRuns3(config) {
4269
4280
  try {
4270
4281
  text = (0, import_node_fs11.readFileSync)(config.memoryPath, "utf8");
4271
4282
  } catch (exc) {
4272
- logger5.warn(
4273
- `CopilotAutomationsParticipant: cannot read ${config.memoryPath}: ${String(exc)}`
4274
- );
4283
+ logger5.warn(`CopilotAutomationsParticipant: cannot read ${config.memoryPath}: ${String(exc)}`);
4275
4284
  return [];
4276
4285
  }
4277
4286
  text = text.slice(-MAX_MEMORY_CHARS3);
@@ -4521,24 +4530,15 @@ var MAX_KEY_ACTIONS_PER_RUN4 = 6;
4521
4530
  var MAX_KEY_ACTION_CHARS5 = 280;
4522
4531
  var RUN_HEADER_RE4 = /^(\d{4}-\d{2}-\d{2})(?:\b|$)(.*)$/;
4523
4532
  var HEADER_SUFFIX_STRIP2 = " -:\u2014";
4524
- var PROJECT_HINTS3 = [
4525
- "ShipStable",
4526
- "ILTT",
4527
- "Prun",
4528
- "PRUN",
4529
- "OMNIvour",
4530
- "Castmore",
4533
+ var DEFAULT_PROJECT_HINTS3 = [
4531
4534
  "Bourdon",
4532
- "RADLAB",
4533
- "CHIP",
4534
- "Claude Brain",
4535
4535
  "Cursor",
4536
4536
  "Copilot",
4537
4537
  "Codex",
4538
4538
  "Cascade"
4539
4539
  ];
4540
4540
  var SIGNAL_PATTERNS3 = [
4541
- ["human-dashboard-action", /\b(human|ryan|dashboard|manual)\b/i],
4541
+ ["human-dashboard-action", /\b(human|dashboard|manual)\b/i],
4542
4542
  ["release-gate", /\b(release|store|app review|play console|testflight)\b/i],
4543
4543
  ["billing-drift", /\b(billing|stripe|revenuecat|iap|subscription)\b/i],
4544
4544
  ["memory-coverage-gap", /\b(memory|l5|manifest|federated|bourdon)\b/i],
@@ -4751,7 +4751,7 @@ function inferProjects3(text) {
4751
4751
  const projects = [];
4752
4752
  const lowered = text.toLowerCase();
4753
4753
  const seen = /* @__PURE__ */ new Set();
4754
- for (const project of PROJECT_HINTS3) {
4754
+ for (const project of loadProjectHints(DEFAULT_PROJECT_HINTS3)) {
4755
4755
  const key = project.toLowerCase();
4756
4756
  if (lowered.includes(key) && !seen.has(key)) {
4757
4757
  projects.push(project);
@@ -4962,17 +4962,8 @@ var STATE_FILE_RE = /^local_.*\.json$/;
4962
4962
  var MAX_KEY_ACTION_CHARS6 = 280;
4963
4963
  var MAX_PROJECTS = 6;
4964
4964
  var MAX_KEY_ACTIONS2 = 6;
4965
- var PROJECT_HINTS4 = [
4966
- "ShipStable",
4967
- "ILTT",
4968
- "Prun",
4969
- "PRUN",
4970
- "OMNIvour",
4971
- "Castmore",
4965
+ var DEFAULT_PROJECT_HINTS4 = [
4972
4966
  "Bourdon",
4973
- "RADLAB",
4974
- "CHIP",
4975
- "Claude Brain",
4976
4967
  "Cursor",
4977
4968
  "Copilot",
4978
4969
  "Codex",
@@ -5142,7 +5133,7 @@ function inferProjects4(state) {
5142
5133
  if (base) add(base);
5143
5134
  }
5144
5135
  const haystack = pathStrings.join(" ").toLowerCase();
5145
- for (const hint of PROJECT_HINTS4) {
5136
+ for (const hint of loadProjectHints(DEFAULT_PROJECT_HINTS4)) {
5146
5137
  if (haystack.includes(hint.toLowerCase())) add(hint);
5147
5138
  }
5148
5139
  return projects.slice(0, MAX_PROJECTS);
@@ -5389,17 +5380,8 @@ var STATE_PREFIX2 = "local_";
5389
5380
  var MAX_PROJECTS2 = 6;
5390
5381
  var MAX_KEY_ACTIONS3 = 8;
5391
5382
  var MAX_AUDIT_LINES = 5e4;
5392
- var PROJECT_HINTS5 = [
5393
- "ShipStable",
5394
- "ILTT",
5395
- "Prun",
5396
- "PRUN",
5397
- "OMNIvour",
5398
- "Castmore",
5383
+ var DEFAULT_PROJECT_HINTS5 = [
5399
5384
  "Bourdon",
5400
- "RADLAB",
5401
- "CHIP",
5402
- "Claude Brain",
5403
5385
  "Cursor",
5404
5386
  "Copilot",
5405
5387
  "Codex",
@@ -5579,7 +5561,7 @@ function inferProjects5(state) {
5579
5561
  if (base) add(base);
5580
5562
  }
5581
5563
  const haystack = pathStrings.join(" ").toLowerCase();
5582
- for (const hint of PROJECT_HINTS5) {
5564
+ for (const hint of loadProjectHints(DEFAULT_PROJECT_HINTS5)) {
5583
5565
  if (haystack.includes(hint.toLowerCase())) add(hint);
5584
5566
  }
5585
5567
  return projects.slice(0, MAX_PROJECTS2);
@@ -6350,6 +6332,105 @@ var CopilotVscodeParticipant = class {
6350
6332
  }
6351
6333
  };
6352
6334
 
6335
+ // src/readers/codex-sessions.ts
6336
+ var import_node_fs16 = require("fs");
6337
+ var import_node_os16 = require("os");
6338
+ var import_node_path16 = require("path");
6339
+ var DEFAULT_TITLE_LIMIT = 200;
6340
+ var DEFAULT_RECENT_THREAD_LIMIT = 5;
6341
+ function normalizeText(value) {
6342
+ return value.trim().replace(/\s+/g, " ");
6343
+ }
6344
+ function isDir14(path) {
6345
+ try {
6346
+ return (0, import_node_fs16.statSync)(path).isDirectory();
6347
+ } catch {
6348
+ return false;
6349
+ }
6350
+ }
6351
+ function resolveCodexHome(baseHome) {
6352
+ const env = process.env.CODEX_HOME;
6353
+ if (env) {
6354
+ if (isDir14(env)) return env;
6355
+ }
6356
+ const home = baseHome ? baseHome : (0, import_node_os16.homedir)();
6357
+ const candidate = (0, import_node_path16.join)(home, ".codex");
6358
+ return isDir14(candidate) ? candidate : null;
6359
+ }
6360
+ function parseSessionIndex(path, limit) {
6361
+ let text;
6362
+ try {
6363
+ if (!(0, import_node_fs16.statSync)(path).isFile()) return [];
6364
+ text = (0, import_node_fs16.readFileSync)(path, "utf8");
6365
+ } catch (exc) {
6366
+ if (existsQuiet(path)) console.warn(`Failed to read ${path}: ${String(exc)}`);
6367
+ return [];
6368
+ }
6369
+ const entries = [];
6370
+ const lines = text.split(/\r?\n/);
6371
+ for (let i = 0; i < lines.length; i += 1) {
6372
+ const line = lines[i].trim();
6373
+ if (!line) continue;
6374
+ let record;
6375
+ try {
6376
+ record = JSON.parse(line);
6377
+ } catch (exc) {
6378
+ console.warn(`Skipping malformed JSONL in ${path}:${i + 1} -- ${String(exc)}`);
6379
+ continue;
6380
+ }
6381
+ if (record !== null && typeof record === "object" && !Array.isArray(record) && typeof record.id === "string") {
6382
+ entries.push(record);
6383
+ }
6384
+ }
6385
+ entries.sort((a, b) => {
6386
+ const ua = String(a.updated_at ?? "");
6387
+ const ub = String(b.updated_at ?? "");
6388
+ return ua < ub ? 1 : ua > ub ? -1 : 0;
6389
+ });
6390
+ return limit !== void 0 && limit !== null ? entries.slice(0, limit) : entries;
6391
+ }
6392
+ function existsQuiet(path) {
6393
+ try {
6394
+ (0, import_node_fs16.statSync)(path);
6395
+ return true;
6396
+ } catch {
6397
+ return false;
6398
+ }
6399
+ }
6400
+ function sessionThreadTitles(codexHome, opts = {}) {
6401
+ const limit = opts.limit ?? DEFAULT_TITLE_LIMIT;
6402
+ const home = codexHome === void 0 || codexHome === null ? resolveCodexHome() : codexHome;
6403
+ if (home === null) return {};
6404
+ const titles = {};
6405
+ for (const entry of parseSessionIndex((0, import_node_path16.join)(home, "session_index.jsonl"), limit)) {
6406
+ const sid = entry.id;
6407
+ const name = entry.thread_name;
6408
+ if (typeof sid === "string" && sid && typeof name === "string" && name.trim() && !(sid in titles)) {
6409
+ titles[sid] = normalizeText(name);
6410
+ }
6411
+ }
6412
+ return titles;
6413
+ }
6414
+ function recentSessionThreads(codexHome, opts = {}) {
6415
+ const limit = opts.limit ?? DEFAULT_RECENT_THREAD_LIMIT;
6416
+ const home = codexHome === void 0 || codexHome === null ? resolveCodexHome() : codexHome;
6417
+ if (home === null) return [];
6418
+ const threads = [];
6419
+ for (const entry of parseSessionIndex((0, import_node_path16.join)(home, "session_index.jsonl"), limit)) {
6420
+ const sid = entry.id;
6421
+ if (typeof sid !== "string" || !sid) continue;
6422
+ const name = entry.thread_name;
6423
+ const title = typeof name === "string" && name.trim() ? normalizeText(name) : null;
6424
+ const updated = String(entry.updated_at ?? "");
6425
+ threads.push({
6426
+ instance: sid.slice(0, 8),
6427
+ title,
6428
+ updated_at: updated || null
6429
+ });
6430
+ }
6431
+ return threads;
6432
+ }
6433
+
6353
6434
  // src/registry.ts
6354
6435
  var FIRST_PARTY = [
6355
6436
  CascadeParticipant,
@@ -6416,6 +6497,8 @@ var import_l516 = require("@getbourdon/l5");
6416
6497
  CursorAutomationsParticipant,
6417
6498
  CursorParticipant,
6418
6499
  DEFAULT_CACHE_TTL_SECONDS,
6500
+ DEFAULT_RECENT_THREAD_LIMIT,
6501
+ DEFAULT_TITLE_LIMIT,
6419
6502
  FIRST_PARTY,
6420
6503
  GitHubCopilotParticipant,
6421
6504
  HermesParticipant,
@@ -6425,6 +6508,7 @@ var import_l516 = require("@getbourdon/l5");
6425
6508
  NetworkUnavailable,
6426
6509
  OpenClawApiClient,
6427
6510
  OpenClawParticipant,
6511
+ PROJECT_HINTS_ENV,
6428
6512
  ParticipantAuthError,
6429
6513
  ParticipantDiscoveryError,
6430
6514
  ParticipantError,
@@ -6443,9 +6527,14 @@ var import_l516 = require("@getbourdon/l5");
6443
6527
  friendlyLabel,
6444
6528
  ghTokenProvider,
6445
6529
  isParticipant,
6530
+ loadProjectHints,
6446
6531
  openReadonly,
6532
+ parseSessionIndex,
6447
6533
  projectKeyFromCwd,
6534
+ recentSessionThreads,
6535
+ resolveCodexHome,
6448
6536
  resolvePluginParticipants,
6537
+ sessionThreadTitles,
6449
6538
  sqliteAvailable,
6450
6539
  tableColumns,
6451
6540
  tableExists,