@getbourdon/participants 0.1.1 → 0.2.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 +5011 -23
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +577 -4
- package/dist/index.d.ts +970 -25
- package/dist/index.js +6509 -33
- package/dist/index.js.map +1 -1
- package/package.json +13 -12
- package/dist/base.d.ts +0 -81
- package/dist/base.js +0 -52
- package/dist/network-base.d.ts +0 -108
- package/dist/network-base.js +0 -248
- package/dist/readers/claude-code.d.ts +0 -33
- package/dist/readers/claude-code.js +0 -552
- package/dist/readers/github-copilot.d.ts +0 -34
- package/dist/readers/github-copilot.js +0 -242
- package/dist/readers/hermes.d.ts +0 -37
- package/dist/readers/hermes.js +0 -426
- package/dist/registry.d.ts +0 -38
- package/dist/registry.js +0 -73
- package/dist/sqlite-base.d.ts +0 -65
- package/dist/sqlite-base.js +0 -163
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@getbourdon/participants",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Bourdon participants — the external-agent → L5 reader layer (BUSL-1.1). Each participant normalizes a foreign agent's native memory store into a visibility-filtered, redacted L5 manifest: read-only SQLite readers (better-sqlite3 mode=ro: hermes), file/convention readers (claude-code), and network readers (github-copilot) with a TTL JSON cache + lazy AuthProvider that never persists a token. Four invariants enforced in code, not by trust: visibility-filter-before-emission, deterministic exportL5, healthCheck never throws, redact every native string. Discovery is a static TS registry (agent_id-sorted, first-wins, log-and-skip) replacing Python's pkgutil scan. Python (pip install bourdon) is the oracle; this mirror asserts against @getbourdon/conformance native_stores fixtures.",
|
|
5
5
|
"license": "BUSL-1.1",
|
|
6
6
|
"type": "module",
|
|
@@ -29,23 +29,24 @@
|
|
|
29
29
|
"homepage": "https://bourdon.ai",
|
|
30
30
|
"repository": {
|
|
31
31
|
"type": "git",
|
|
32
|
-
"url": "git+https://
|
|
32
|
+
"url": "git+https://gitlab.com/getbourdon/bourdon-js.git",
|
|
33
33
|
"directory": "packages/participants"
|
|
34
34
|
},
|
|
35
|
+
"scripts": {
|
|
36
|
+
"build": "tsup",
|
|
37
|
+
"test": "vitest run"
|
|
38
|
+
},
|
|
35
39
|
"dependencies": {
|
|
36
|
-
"
|
|
37
|
-
"@getbourdon/
|
|
38
|
-
"
|
|
40
|
+
"@getbourdon/l5": "workspace:*",
|
|
41
|
+
"@getbourdon/redaction": "workspace:*",
|
|
42
|
+
"smol-toml": "^1.7.0",
|
|
43
|
+
"yaml": "^2.6.1"
|
|
39
44
|
},
|
|
40
45
|
"optionalDependencies": {
|
|
41
46
|
"better-sqlite3": "^12.2.0"
|
|
42
47
|
},
|
|
43
48
|
"devDependencies": {
|
|
44
|
-
"@
|
|
45
|
-
"@
|
|
46
|
-
},
|
|
47
|
-
"scripts": {
|
|
48
|
-
"build": "tsup",
|
|
49
|
-
"test": "vitest run"
|
|
49
|
+
"@getbourdon/conformance": "workspace:*",
|
|
50
|
+
"@types/better-sqlite3": "^7.6.12"
|
|
50
51
|
}
|
|
51
|
-
}
|
|
52
|
+
}
|
package/dist/base.d.ts
DELETED
|
@@ -1,81 +0,0 @@
|
|
|
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
|
-
/** Confirm the native store exists; return metadata. Throws
|
|
67
|
-
* {@link ParticipantDiscoveryError} when missing/unreadable. */
|
|
68
|
-
discover(): AgentStore;
|
|
69
|
-
/** Build the L5 manifest from native memory, applying the visibility filter
|
|
70
|
-
* BEFORE return. Deterministic — same store yields the same manifest. */
|
|
71
|
-
exportL5(since?: Date): L5ManifestModel;
|
|
72
|
-
/** Export recent sessions in normalized schema. */
|
|
73
|
-
exportSessions(since?: Date, limit?: number): SessionModel[];
|
|
74
|
-
/** Return ok / degraded / blocked with a reason. MUST NOT throw. */
|
|
75
|
-
healthCheck(): HealthStatus;
|
|
76
|
-
}
|
|
77
|
-
/**
|
|
78
|
-
* Tiny structural guard mirroring Python's `_PARTICIPANT_MARKER_ATTRS`
|
|
79
|
-
* duck-type: a value is a participant iff it carries the four marker members.
|
|
80
|
-
*/
|
|
81
|
-
export declare function isParticipant(obj: unknown): obj is BourdonParticipant;
|
package/dist/base.js
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
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
|
-
}
|
package/dist/network-base.d.ts
DELETED
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Network-shaped participant base — port of `participants/_network_base.py`
|
|
3
|
-
* (v0.3 adapter contract). Same `exportL5` / `exportSessions` half as a file
|
|
4
|
-
* participant; only the discovery half differs (fetch from an authenticated
|
|
5
|
-
* API). The base provides the three guarantees so adapters don't reimplement:
|
|
6
|
-
*
|
|
7
|
-
* 1. Local TTL cache under `~/.cache/bourdon/<slug>/payload.json` storing ONLY
|
|
8
|
-
* `{fetched_at, payload}` — NEVER the token. A cache hit = zero network.
|
|
9
|
-
* 2. Graceful degradation: network down → serve the most recent cached payload
|
|
10
|
-
* (even expired) and report `degraded` (not silence).
|
|
11
|
-
* 3. Auth boundary: an injected lazy {@link AuthProvider} (env / keychain).
|
|
12
|
-
* An invalid token (401/403 non-ratelimit) ALWAYS propagates as `blocked`
|
|
13
|
-
* and is NEVER masked by stale cache; a missing token serves stale when a
|
|
14
|
-
* cache exists, else blocks; rate-limit degrades.
|
|
15
|
-
*
|
|
16
|
-
* A subclass sets `participantSlug` / `agentId` / `agentType` (+ `nativePath`)
|
|
17
|
-
* and implements `fetchPayload(token)` + `payloadToL5(payload)` (pure, no I/O).
|
|
18
|
-
*/
|
|
19
|
-
import type { L5ManifestModel, SessionModel } from "@getbourdon/l5";
|
|
20
|
-
import { ParticipantError, type AgentStore, type BourdonParticipant, type HealthStatus } from "./base.js";
|
|
21
|
-
export declare const NETWORK_CONTRACT_VERSION = "0.3";
|
|
22
|
-
/** Default cache TTL — adapters override via `cacheTtlSeconds`. */
|
|
23
|
-
export declare const DEFAULT_CACHE_TTL_SECONDS: number;
|
|
24
|
-
/** Transport failure / timeout / 5xx / rate-limit — recoverable via cache fallback. */
|
|
25
|
-
export declare class NetworkUnavailable extends ParticipantError {
|
|
26
|
-
}
|
|
27
|
-
/** 401/403 / missing credential — NOT recoverable via cache. The user must fix
|
|
28
|
-
* auth; surfaced as `blocked`, never silently served from stale cache (stale
|
|
29
|
-
* data would mask a broken token indefinitely). */
|
|
30
|
-
export declare class ParticipantAuthError extends ParticipantError {
|
|
31
|
-
}
|
|
32
|
-
/** A zero-arg callable returning the token string, or null if no credential is
|
|
33
|
-
* available. Kept a callable (not a bare string) so the token is fetched lazily
|
|
34
|
-
* at call time and never sits in the participant's fields or the cache. */
|
|
35
|
-
export type AuthProvider = () => string | null;
|
|
36
|
-
/** Auth provider that reads a token from an environment variable. */
|
|
37
|
-
export declare function envAuthProvider(varName: string): AuthProvider;
|
|
38
|
-
/** `$XDG_CACHE_HOME/bourdon` or `~/.cache/bourdon`. */
|
|
39
|
-
export declare function defaultCacheRoot(): string;
|
|
40
|
-
/** A cached payload + the wall-clock time it was fetched (epoch seconds). */
|
|
41
|
-
export declare class CacheEntry {
|
|
42
|
-
readonly fetchedAt: number;
|
|
43
|
-
readonly payload: Record<string, unknown>;
|
|
44
|
-
constructor(fetchedAt: number, payload: Record<string, unknown>);
|
|
45
|
-
get ageSeconds(): number;
|
|
46
|
-
isFresh(ttlSeconds: number): boolean;
|
|
47
|
-
}
|
|
48
|
-
/**
|
|
49
|
-
* A tiny JSON file cache, one file per participant slug. Stores only
|
|
50
|
-
* `{fetched_at, payload}` — never the auth token. Read/write are best-effort: a
|
|
51
|
-
* corrupt or unwritable cache degrades to "no cache", never raises.
|
|
52
|
-
*/
|
|
53
|
-
export declare class PayloadCache {
|
|
54
|
-
readonly slug: string;
|
|
55
|
-
readonly root: string;
|
|
56
|
-
readonly path: string;
|
|
57
|
-
constructor(slug: string, rootDir?: string);
|
|
58
|
-
read(): CacheEntry | null;
|
|
59
|
-
write(payload: Record<string, unknown>): void;
|
|
60
|
-
}
|
|
61
|
-
export interface NetworkParticipantOptions {
|
|
62
|
-
authProvider?: AuthProvider | null;
|
|
63
|
-
cacheRoot?: string;
|
|
64
|
-
}
|
|
65
|
-
/**
|
|
66
|
-
* Base for participants whose native state lives behind an authenticated API.
|
|
67
|
-
* Caching, degradation, the auth boundary, and the
|
|
68
|
-
* `discover`/`exportL5`/`exportSessions`/`healthCheck` plumbing live here;
|
|
69
|
-
* subclasses provide only `fetchPayload` + `payloadToL5`.
|
|
70
|
-
*/
|
|
71
|
-
export declare abstract class NetworkParticipant implements BourdonParticipant {
|
|
72
|
-
participantSlug: string;
|
|
73
|
-
agentId: string;
|
|
74
|
-
agentType: string;
|
|
75
|
-
cacheTtlSeconds: number;
|
|
76
|
-
nativePath: string;
|
|
77
|
-
protected readonly authProvider: AuthProvider | null;
|
|
78
|
-
private readonly cacheRoot?;
|
|
79
|
-
private cacheInstance?;
|
|
80
|
-
constructor(opts?: NetworkParticipantOptions);
|
|
81
|
-
/** Lazily built so it reads the SUBCLASS's `participantSlug` — subclass field
|
|
82
|
-
* initializers run after `super()`, so building the cache in the constructor
|
|
83
|
-
* would capture the base "network" slug instead. */
|
|
84
|
-
protected get cache(): PayloadCache;
|
|
85
|
-
/** Do the authenticated API call; return a JSON-serializable dict. Raise
|
|
86
|
-
* {@link NetworkUnavailable} on transport/5xx/rate-limit (→ cache fallback) or
|
|
87
|
-
* {@link ParticipantAuthError} on 401/403 (→ blocked, no fallback). */
|
|
88
|
-
abstract fetchPayload(token: string): Record<string, unknown>;
|
|
89
|
-
/** Normalize a (fresh or cached) payload into an L5 manifest. Pure, no I/O. */
|
|
90
|
-
abstract payloadToL5(payload: Record<string, unknown>): L5ManifestModel;
|
|
91
|
-
/**
|
|
92
|
-
* Resolve (payload, source) where source is 'cache' | 'network' | 'stale-cache'.
|
|
93
|
-
* 1. Fresh cache hit → 'cache' (no network)
|
|
94
|
-
* 2. Miss/expired + token → 'network' (fetch + refresh cache)
|
|
95
|
-
* 3. Net fails + any cache → 'stale-cache' (serve last good payload)
|
|
96
|
-
* 4. Net fails + no cache → raise
|
|
97
|
-
* Auth errors are never swallowed — they propagate so health surfaces
|
|
98
|
-
* 'blocked' rather than serving stale data behind a dead token.
|
|
99
|
-
*/
|
|
100
|
-
protected getPayload(): {
|
|
101
|
-
payload: Record<string, unknown>;
|
|
102
|
-
source: string;
|
|
103
|
-
};
|
|
104
|
-
discover(): AgentStore;
|
|
105
|
-
exportL5(_since?: Date): L5ManifestModel;
|
|
106
|
-
exportSessions(since?: Date, limit?: number): SessionModel[];
|
|
107
|
-
healthCheck(): HealthStatus;
|
|
108
|
-
}
|
package/dist/network-base.js
DELETED
|
@@ -1,248 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Network-shaped participant base — port of `participants/_network_base.py`
|
|
3
|
-
* (v0.3 adapter contract). Same `exportL5` / `exportSessions` half as a file
|
|
4
|
-
* participant; only the discovery half differs (fetch from an authenticated
|
|
5
|
-
* API). The base provides the three guarantees so adapters don't reimplement:
|
|
6
|
-
*
|
|
7
|
-
* 1. Local TTL cache under `~/.cache/bourdon/<slug>/payload.json` storing ONLY
|
|
8
|
-
* `{fetched_at, payload}` — NEVER the token. A cache hit = zero network.
|
|
9
|
-
* 2. Graceful degradation: network down → serve the most recent cached payload
|
|
10
|
-
* (even expired) and report `degraded` (not silence).
|
|
11
|
-
* 3. Auth boundary: an injected lazy {@link AuthProvider} (env / keychain).
|
|
12
|
-
* An invalid token (401/403 non-ratelimit) ALWAYS propagates as `blocked`
|
|
13
|
-
* and is NEVER masked by stale cache; a missing token serves stale when a
|
|
14
|
-
* cache exists, else blocks; rate-limit degrades.
|
|
15
|
-
*
|
|
16
|
-
* A subclass sets `participantSlug` / `agentId` / `agentType` (+ `nativePath`)
|
|
17
|
-
* and implements `fetchPayload(token)` + `payloadToL5(payload)` (pure, no I/O).
|
|
18
|
-
*/
|
|
19
|
-
import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
20
|
-
import { homedir } from "node:os";
|
|
21
|
-
import { join } from "node:path";
|
|
22
|
-
import { ParticipantError, } from "./base.js";
|
|
23
|
-
export const NETWORK_CONTRACT_VERSION = "0.3";
|
|
24
|
-
/** Default cache TTL — adapters override via `cacheTtlSeconds`. */
|
|
25
|
-
export const DEFAULT_CACHE_TTL_SECONDS = 15 * 60; // 15 minutes
|
|
26
|
-
// -- Network-specific errors --------------------------------------------------
|
|
27
|
-
/** Transport failure / timeout / 5xx / rate-limit — recoverable via cache fallback. */
|
|
28
|
-
export class NetworkUnavailable extends ParticipantError {
|
|
29
|
-
}
|
|
30
|
-
/** 401/403 / missing credential — NOT recoverable via cache. The user must fix
|
|
31
|
-
* auth; surfaced as `blocked`, never silently served from stale cache (stale
|
|
32
|
-
* data would mask a broken token indefinitely). */
|
|
33
|
-
export class ParticipantAuthError extends ParticipantError {
|
|
34
|
-
}
|
|
35
|
-
/** Auth provider that reads a token from an environment variable. */
|
|
36
|
-
export function envAuthProvider(varName) {
|
|
37
|
-
return () => process.env[varName] || null;
|
|
38
|
-
}
|
|
39
|
-
// -- Cache --------------------------------------------------------------------
|
|
40
|
-
/** `$XDG_CACHE_HOME/bourdon` or `~/.cache/bourdon`. */
|
|
41
|
-
export function defaultCacheRoot() {
|
|
42
|
-
const xdg = process.env.XDG_CACHE_HOME;
|
|
43
|
-
const base = xdg ? xdg : join(homedir(), ".cache");
|
|
44
|
-
return join(base, "bourdon");
|
|
45
|
-
}
|
|
46
|
-
/** A cached payload + the wall-clock time it was fetched (epoch seconds). */
|
|
47
|
-
export class CacheEntry {
|
|
48
|
-
fetchedAt;
|
|
49
|
-
payload;
|
|
50
|
-
constructor(fetchedAt, payload) {
|
|
51
|
-
this.fetchedAt = fetchedAt;
|
|
52
|
-
this.payload = payload;
|
|
53
|
-
}
|
|
54
|
-
get ageSeconds() {
|
|
55
|
-
return Math.max(0, Date.now() / 1000 - this.fetchedAt);
|
|
56
|
-
}
|
|
57
|
-
isFresh(ttlSeconds) {
|
|
58
|
-
return this.ageSeconds < ttlSeconds;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
/**
|
|
62
|
-
* A tiny JSON file cache, one file per participant slug. Stores only
|
|
63
|
-
* `{fetched_at, payload}` — never the auth token. Read/write are best-effort: a
|
|
64
|
-
* corrupt or unwritable cache degrades to "no cache", never raises.
|
|
65
|
-
*/
|
|
66
|
-
export class PayloadCache {
|
|
67
|
-
slug;
|
|
68
|
-
root;
|
|
69
|
-
path;
|
|
70
|
-
constructor(slug, rootDir) {
|
|
71
|
-
this.slug = slug;
|
|
72
|
-
this.root = join(rootDir ?? defaultCacheRoot(), slug);
|
|
73
|
-
this.path = join(this.root, "payload.json");
|
|
74
|
-
}
|
|
75
|
-
read() {
|
|
76
|
-
let raw;
|
|
77
|
-
try {
|
|
78
|
-
raw = JSON.parse(readFileSync(this.path, "utf8"));
|
|
79
|
-
}
|
|
80
|
-
catch {
|
|
81
|
-
return null;
|
|
82
|
-
}
|
|
83
|
-
if (typeof raw !== "object" || raw === null || !("payload" in raw))
|
|
84
|
-
return null;
|
|
85
|
-
const obj = raw;
|
|
86
|
-
let fetchedAt = Number(obj["fetched_at"] ?? 0);
|
|
87
|
-
if (!Number.isFinite(fetchedAt))
|
|
88
|
-
fetchedAt = 0;
|
|
89
|
-
const payload = obj["payload"];
|
|
90
|
-
if (typeof payload !== "object" || payload === null || Array.isArray(payload))
|
|
91
|
-
return null;
|
|
92
|
-
return new CacheEntry(fetchedAt, payload);
|
|
93
|
-
}
|
|
94
|
-
write(payload) {
|
|
95
|
-
try {
|
|
96
|
-
mkdirSync(this.root, { recursive: true });
|
|
97
|
-
const tmp = `${this.path}.tmp`;
|
|
98
|
-
writeFileSync(tmp, JSON.stringify({ fetched_at: Date.now() / 1000, payload }), "utf8");
|
|
99
|
-
renameSync(tmp, this.path); // atomic
|
|
100
|
-
}
|
|
101
|
-
catch {
|
|
102
|
-
/* best-effort; a write failure degrades to "no cache" */
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
// -- Base participant ---------------------------------------------------------
|
|
107
|
-
/**
|
|
108
|
-
* Base for participants whose native state lives behind an authenticated API.
|
|
109
|
-
* Caching, degradation, the auth boundary, and the
|
|
110
|
-
* `discover`/`exportL5`/`exportSessions`/`healthCheck` plumbing live here;
|
|
111
|
-
* subclasses provide only `fetchPayload` + `payloadToL5`.
|
|
112
|
-
*/
|
|
113
|
-
export class NetworkParticipant {
|
|
114
|
-
participantSlug = "network";
|
|
115
|
-
agentId = "network";
|
|
116
|
-
agentType = "other";
|
|
117
|
-
cacheTtlSeconds = DEFAULT_CACHE_TTL_SECONDS;
|
|
118
|
-
nativePath = "network://";
|
|
119
|
-
authProvider;
|
|
120
|
-
cacheRoot;
|
|
121
|
-
cacheInstance;
|
|
122
|
-
constructor(opts = {}) {
|
|
123
|
-
this.authProvider = opts.authProvider ?? null;
|
|
124
|
-
this.cacheRoot = opts.cacheRoot;
|
|
125
|
-
}
|
|
126
|
-
/** Lazily built so it reads the SUBCLASS's `participantSlug` — subclass field
|
|
127
|
-
* initializers run after `super()`, so building the cache in the constructor
|
|
128
|
-
* would capture the base "network" slug instead. */
|
|
129
|
-
get cache() {
|
|
130
|
-
if (!this.cacheInstance) {
|
|
131
|
-
this.cacheInstance = new PayloadCache(this.participantSlug, this.cacheRoot);
|
|
132
|
-
}
|
|
133
|
-
return this.cacheInstance;
|
|
134
|
-
}
|
|
135
|
-
// -- Core fetch-with-cache-and-degrade -------------------------------------
|
|
136
|
-
/**
|
|
137
|
-
* Resolve (payload, source) where source is 'cache' | 'network' | 'stale-cache'.
|
|
138
|
-
* 1. Fresh cache hit → 'cache' (no network)
|
|
139
|
-
* 2. Miss/expired + token → 'network' (fetch + refresh cache)
|
|
140
|
-
* 3. Net fails + any cache → 'stale-cache' (serve last good payload)
|
|
141
|
-
* 4. Net fails + no cache → raise
|
|
142
|
-
* Auth errors are never swallowed — they propagate so health surfaces
|
|
143
|
-
* 'blocked' rather than serving stale data behind a dead token.
|
|
144
|
-
*/
|
|
145
|
-
getPayload() {
|
|
146
|
-
const cached = this.cache.read();
|
|
147
|
-
if (cached !== null && cached.isFresh(this.cacheTtlSeconds)) {
|
|
148
|
-
return { payload: cached.payload, source: "cache" };
|
|
149
|
-
}
|
|
150
|
-
const token = this.authProvider ? this.authProvider() : null;
|
|
151
|
-
if (!token) {
|
|
152
|
-
// No credential: if we have any cache, serve it stale; else blocked.
|
|
153
|
-
if (cached !== null)
|
|
154
|
-
return { payload: cached.payload, source: "stale-cache" };
|
|
155
|
-
throw new ParticipantAuthError(`${this.participantSlug}: no auth token available and no cache`);
|
|
156
|
-
}
|
|
157
|
-
let payload;
|
|
158
|
-
try {
|
|
159
|
-
payload = this.fetchPayload(token);
|
|
160
|
-
}
|
|
161
|
-
catch (err) {
|
|
162
|
-
if (err instanceof ParticipantAuthError)
|
|
163
|
-
throw err; // never mask a bad token
|
|
164
|
-
if (err instanceof NetworkUnavailable) {
|
|
165
|
-
if (cached !== null)
|
|
166
|
-
return { payload: cached.payload, source: "stale-cache" };
|
|
167
|
-
throw err;
|
|
168
|
-
}
|
|
169
|
-
throw err;
|
|
170
|
-
}
|
|
171
|
-
this.cache.write(payload);
|
|
172
|
-
return { payload, source: "network" };
|
|
173
|
-
}
|
|
174
|
-
// -- Participant protocol ---------------------------------------------------
|
|
175
|
-
discover() {
|
|
176
|
-
const { source } = this.getPayload();
|
|
177
|
-
return {
|
|
178
|
-
path: this.nativePath,
|
|
179
|
-
version: `${this.participantSlug}-network-v1`,
|
|
180
|
-
metadata: { source, contract_version: NETWORK_CONTRACT_VERSION },
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
|
-
exportL5(_since) {
|
|
184
|
-
const { payload } = this.getPayload();
|
|
185
|
-
return this.payloadToL5(payload);
|
|
186
|
-
}
|
|
187
|
-
exportSessions(since, limit = 100) {
|
|
188
|
-
const manifest = this.exportL5(since);
|
|
189
|
-
let sessions = manifest.recent_sessions ?? [];
|
|
190
|
-
if (since) {
|
|
191
|
-
const cutoff = since.toISOString().slice(0, 10);
|
|
192
|
-
sessions = sessions.filter((s) => (s.date || "") >= cutoff);
|
|
193
|
-
}
|
|
194
|
-
return sessions.slice(0, limit);
|
|
195
|
-
}
|
|
196
|
-
healthCheck() {
|
|
197
|
-
let source;
|
|
198
|
-
try {
|
|
199
|
-
source = this.getPayload().source;
|
|
200
|
-
}
|
|
201
|
-
catch (err) {
|
|
202
|
-
if (err instanceof ParticipantAuthError) {
|
|
203
|
-
return {
|
|
204
|
-
status: "blocked",
|
|
205
|
-
reason: String(err.message),
|
|
206
|
-
details: { participant: this.participantSlug },
|
|
207
|
-
proposedFix: `Provide a valid credential for ${this.participantSlug} ` +
|
|
208
|
-
"(see the adapter docs for the auth path) and re-run `bourdon export-all`.",
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
|
-
if (err instanceof NetworkUnavailable) {
|
|
212
|
-
return {
|
|
213
|
-
status: "degraded",
|
|
214
|
-
reason: `network unavailable and no cache: ${err.message}`,
|
|
215
|
-
details: { participant: this.participantSlug },
|
|
216
|
-
proposedFix: "Check connectivity, then re-run `bourdon export-all`.",
|
|
217
|
-
};
|
|
218
|
-
}
|
|
219
|
-
// Defense in depth: health_check must NEVER raise. An adapter's
|
|
220
|
-
// fetchPayload should only raise the two contract errors; anything else is
|
|
221
|
-
// reported as blocked rather than propagated.
|
|
222
|
-
return {
|
|
223
|
-
status: "blocked",
|
|
224
|
-
reason: `unexpected error resolving payload: ${String(err)}`,
|
|
225
|
-
details: { participant: this.participantSlug },
|
|
226
|
-
proposedFix: `${this.participantSlug} raised an unexpected error during healthCheck; ` +
|
|
227
|
-
"its fetchPayload should only raise ParticipantAuthError / NetworkUnavailable.",
|
|
228
|
-
};
|
|
229
|
-
}
|
|
230
|
-
const cached = this.cache.read();
|
|
231
|
-
const details = { participant: this.participantSlug, source };
|
|
232
|
-
if (cached !== null) {
|
|
233
|
-
details["cache_age_seconds"] = Math.round(cached.ageSeconds * 10) / 10;
|
|
234
|
-
details["cache_fetched_at"] = new Date(cached.fetchedAt * 1000)
|
|
235
|
-
.toISOString()
|
|
236
|
-
.replace(/\.\d{3}Z$/, "+00:00");
|
|
237
|
-
}
|
|
238
|
-
if (source === "stale-cache") {
|
|
239
|
-
return {
|
|
240
|
-
status: "degraded",
|
|
241
|
-
reason: "serving stale cache (network unavailable or no token)",
|
|
242
|
-
details,
|
|
243
|
-
proposedFix: "Restore connectivity / refresh the token to update.",
|
|
244
|
-
};
|
|
245
|
-
}
|
|
246
|
-
return { status: "ok", details };
|
|
247
|
-
}
|
|
248
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Claude Code participant — reads claude-brain + auto-memory + MCP knowledge
|
|
3
|
-
* graph. Port of `participants/claude_code.py`.
|
|
4
|
-
*
|
|
5
|
-
* Three optional sources (the participant degrades gracefully):
|
|
6
|
-
* 1. claude-brain/ — git-synced markdown records
|
|
7
|
-
* 2. ~/.claude/projects/<ws>/memory/ — per-machine auto-memory frontmatter
|
|
8
|
-
* 3. ~/claude-memory/memory.jsonl — MCP knowledge-graph JSONL
|
|
9
|
-
*
|
|
10
|
-
* Privacy: entities typed `person` (+ family/contact variants) and observations
|
|
11
|
-
* containing credential-like strings default to PRIVATE and are filtered before
|
|
12
|
-
* federation. Entities are deduped by case-insensitive name and sorted
|
|
13
|
-
* alphabetically.
|
|
14
|
-
*/
|
|
15
|
-
import { type L5ManifestModel, type SessionModel } from "@getbourdon/l5";
|
|
16
|
-
import { type AgentStore, type BourdonParticipant, type HealthStatus } from "../base.js";
|
|
17
|
-
/** External participant for Anthropic's Claude Code CLI. */
|
|
18
|
-
export declare class ClaudeCodeParticipant implements BourdonParticipant {
|
|
19
|
-
agentId: string;
|
|
20
|
-
agentType: string;
|
|
21
|
-
nativePath: string;
|
|
22
|
-
/** Public so a hermetic test can seed the discovered sources directly (path
|
|
23
|
-
* RESOLUTION is not the parity contract — only the export output shape is). */
|
|
24
|
-
brainPath: string | null;
|
|
25
|
-
autoMemoryPath: string | null;
|
|
26
|
-
knowledgeGraphPath: string | null;
|
|
27
|
-
static defaultNativePath(home?: string): string;
|
|
28
|
-
constructor();
|
|
29
|
-
discover(): AgentStore;
|
|
30
|
-
exportL5(since?: Date): L5ManifestModel;
|
|
31
|
-
exportSessions(since?: Date, limit?: number): SessionModel[];
|
|
32
|
-
healthCheck(): HealthStatus;
|
|
33
|
-
}
|