brainclaw 1.20.4 → 1.22.0

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 (51) hide show
  1. package/dist/brainclaw-vscode.vsix +0 -0
  2. package/dist/cli/register-cloud.js +63 -0
  3. package/dist/cli.js +2 -3
  4. package/dist/commands/cloud.js +198 -0
  5. package/dist/commands/export.js +3 -3
  6. package/dist/commands/init.js +11 -0
  7. package/dist/commands/mcp-write-claims.js +162 -46
  8. package/dist/commands/mcp-write-entities.js +67 -0
  9. package/dist/commands/mcp.js +64 -1
  10. package/dist/commands/session-end.js +0 -102
  11. package/dist/commands/session-start.js +0 -23
  12. package/dist/commands/switch.js +41 -12
  13. package/dist/core/actions.js +25 -1
  14. package/dist/core/agent-files.js +19 -0
  15. package/dist/core/agentruns.js +68 -10
  16. package/dist/core/assignments.js +94 -19
  17. package/dist/core/claims.js +13 -24
  18. package/dist/core/config.js +58 -0
  19. package/dist/core/context-diff.js +28 -11
  20. package/dist/core/coordination.js +1 -3
  21. package/dist/core/entity-locator.js +404 -0
  22. package/dist/core/federation-attestation.js +96 -0
  23. package/dist/core/federation-canonical.js +95 -0
  24. package/dist/core/federation-hpke.js +213 -0
  25. package/dist/core/federation-inbound.js +187 -0
  26. package/dist/core/federation-keyring.js +241 -0
  27. package/dist/core/federation-message.js +5 -5
  28. package/dist/core/federation-outbox-v2.js +125 -0
  29. package/dist/core/federation-pairing.js +213 -0
  30. package/dist/core/federation-projection.js +336 -0
  31. package/dist/core/federation-relay.js +223 -0
  32. package/dist/core/federation-state.js +270 -0
  33. package/dist/core/identity.js +9 -1
  34. package/dist/core/ids.js +5 -0
  35. package/dist/core/io.js +39 -1
  36. package/dist/core/operations/relocate.js +40 -10
  37. package/dist/core/schema.js +24 -17
  38. package/dist/core/sequence.js +47 -6
  39. package/dist/core/store-resolution.js +99 -26
  40. package/dist/core/workspace-projects.js +23 -2
  41. package/dist/core/worktree.js +59 -1
  42. package/dist/facts.js +7 -7
  43. package/dist/facts.json +6 -6
  44. package/docs/cli.md +73 -40
  45. package/docs/concepts/federation-v2-rfc.md +275 -0
  46. package/docs/index.md +1 -0
  47. package/package.json +2 -2
  48. package/dist/cli/register-federation.js +0 -258
  49. package/dist/core/federation-cloud.js +0 -245
  50. package/dist/core/federation-outbox.js +0 -292
  51. package/dist/core/federation-signing.js +0 -115
@@ -1,258 +0,0 @@
1
- export function registerFederationCommands(program) {
2
- // --- federation cloud ---
3
- const federationCmd = program
4
- .command('federation')
5
- .description('Cloud federation — sync signals with app.brainclaw.dev');
6
- federationCmd
7
- .command('push <message>')
8
- .description('Push a test signal to the cloud')
9
- .option('--type <type>', 'Signal type', 'runtime_note')
10
- .option('--to-project <project>', 'Target project name')
11
- .option('--to-agent <agent>', 'Target agent name')
12
- .action(async (message, options) => {
13
- const { pushSignalToCloud, isCloudConfigured } = await import('../core/federation-cloud.js');
14
- const { createFederationMessage } = await import('../core/federation-message.js');
15
- const { loadConfig } = await import('../core/config.js');
16
- const { resolveCurrentAgentName } = await import('../core/agent-registry.js');
17
- if (!isCloudConfigured()) {
18
- console.error('Error: cloud not configured. Set BRAINCLAW_CLOUD_API_KEY env var.');
19
- process.exit(1);
20
- }
21
- const config = loadConfig();
22
- const agent = resolveCurrentAgentName() ?? 'unknown';
23
- const msg = createFederationMessage({
24
- version: 1,
25
- from: { project_name: config.project_name, project_path: process.cwd(), agent_name: agent },
26
- to: {
27
- project_name: options.toProject ?? 'broadcast',
28
- project_path: '',
29
- // Wire --to-agent into the message (was declared but dropped, so every
30
- // push went out as a broadcast with to_agent NULL — found during the
31
- // cross-machine E2E, pln#365). Omitted → undefined → broadcast, as before.
32
- ...(options.toAgent ? { agent_name: options.toAgent } : {}),
33
- },
34
- type: options.type,
35
- payload: { text: message },
36
- });
37
- const ok = await pushSignalToCloud(msg);
38
- if (ok) {
39
- console.log(`✔ Signal pushed to cloud: [${msg.id}] ${message}`);
40
- }
41
- else {
42
- console.error('Error: failed to push signal to cloud.');
43
- process.exit(1);
44
- }
45
- });
46
- federationCmd
47
- .command('pull')
48
- .description('Pull signals from the cloud inbox')
49
- .option('--agent <name>', 'Agent name to pull for')
50
- .option('--since <date>', 'Only pull signals after this ISO date')
51
- .option('--limit <n>', 'Max signals to pull', '20')
52
- .action(async (options) => {
53
- const { pullSignalsFromCloud, isCloudConfigured } = await import('../core/federation-cloud.js');
54
- const { resolveCurrentAgentName } = await import('../core/agent-registry.js');
55
- if (!isCloudConfigured()) {
56
- console.error('Error: cloud not configured. Set BRAINCLAW_CLOUD_API_KEY env var.');
57
- process.exit(1);
58
- }
59
- const agent = options.agent ?? resolveCurrentAgentName() ?? 'unknown';
60
- const signals = await pullSignalsFromCloud(agent, {
61
- since: options.since,
62
- limit: parseInt(options.limit, 10),
63
- });
64
- if (signals.length === 0) {
65
- console.log('No signals in cloud inbox.');
66
- return;
67
- }
68
- console.log(`${signals.length} signal(s) from cloud:\n`);
69
- for (const s of signals) {
70
- const payload = typeof s.payload === 'object' && s.payload !== null ? s.payload.text ?? JSON.stringify(s.payload) : String(s.payload);
71
- console.log(` [${s.id}] ${s.type} from ${s.from.project_name}/${s.from.agent_name}`);
72
- console.log(` ${String(payload).slice(0, 120)}`);
73
- console.log(` ${s.created_at}\n`);
74
- }
75
- });
76
- federationCmd
77
- .command('status')
78
- .description('Diagnose cloud federation: config, health, signing identity, approved agent')
79
- .action(async () => {
80
- const { diagnoseCloudBridge } = await import('../core/federation-cloud.js');
81
- const d = await diagnoseCloudBridge();
82
- const yn = (b) => (b ? 'yes' : 'no');
83
- console.log(`Cloud URL: ${d.apiUrl}`);
84
- console.log(`API Key: ${process.env.BRAINCLAW_CLOUD_API_KEY ? '***configured***' : (d.configured ? 'from config' : 'NOT SET')}`);
85
- console.log(`Configured: ${yn(d.configured)}`);
86
- console.log(`Opted-in: ${yn(d.enabled)}`);
87
- console.log(`Project: ${d.projectId ?? '(none)'}`);
88
- console.log(`Require signed writes: ${yn(d.requireSigned)}`);
89
- if (d.health) {
90
- console.log(d.health.ok
91
- ? `Cloud status: ${d.health.status} (v${d.health.version})`
92
- : `Cloud unreachable: ${d.health.error ?? 'unknown error'}`);
93
- }
94
- if (d.signing.available) {
95
- console.log('\nSigning identity:');
96
- console.log(` Agent: ${d.signing.agentName} [${d.signing.cloudAgentId}]`);
97
- console.log(` Key present: yes`);
98
- console.log(` Fingerprint: ${d.signing.fingerprint.slice(0, 16)}…`);
99
- }
100
- else {
101
- console.log(`\nSigning identity: unavailable — ${d.signing.reason}`);
102
- }
103
- if (d.approvedAgent) {
104
- console.log('\nApproved agent (cloud):');
105
- if (d.approvedAgent.found) {
106
- console.log(` Status: ${d.approvedAgent.status ?? '(unknown)'}`);
107
- console.log(` Trust: ${d.approvedAgent.trustLevel ?? '(unknown)'}`);
108
- console.log(` Key match: ${d.approvedAgent.fingerprintMatch ? 'yes ✔' : 'NO ✗ (local key does not match the registered key)'}`);
109
- }
110
- else {
111
- console.log(` Not found${d.approvedAgent.error ? ` — ${d.approvedAgent.error}` : ''}`);
112
- }
113
- }
114
- if (d.requireSigned && !d.signing.available) {
115
- console.log('\n⚠ require_signed is set but no signing identity is available — the bridge will refuse to push (fail-closed).');
116
- }
117
- });
118
- federationCmd
119
- .command('identity')
120
- .description('Show this agent\'s federation signing identity (public key to approve in the cloud UI)')
121
- .option('--agent <name>', 'Agent name (defaults to the current agent)')
122
- .option('--json', 'Output as JSON')
123
- .action(async (options) => {
124
- const { resolveOrAutoRegisterAgentIdentity, ensureAgentSigningKey, resolveCurrentAgentName } = await import('../core/agent-registry.js');
125
- const agentName = options.agent ?? resolveCurrentAgentName();
126
- const { identity } = resolveOrAutoRegisterAgentIdentity({ agentName, cwd: process.cwd() });
127
- const key = ensureAgentSigningKey(identity.agent_id);
128
- if (options.json) {
129
- console.log(JSON.stringify({
130
- agent_name: identity.agent_name,
131
- local_agent_id: identity.agent_id,
132
- fingerprint: key.fingerprint,
133
- public_key_pem: key.publicKeyPem,
134
- }, null, 2));
135
- return;
136
- }
137
- console.log(`Agent name: ${identity.agent_name}`);
138
- console.log(`Local agent id: ${identity.agent_id}`);
139
- console.log(`Fingerprint: ${key.fingerprint}`);
140
- console.log('\nPublic key — paste into the cloud UI (project → Agents → Register / approve an agent):\n');
141
- console.log(key.publicKeyPem.trim());
142
- console.log('\nThen, on this machine, configure the bridge with the cloud agent id shown after approval:');
143
- console.log(` export BRAINCLAW_AGENT_NAME=${identity.agent_name}`);
144
- console.log(' export BRAINCLAW_CLOUD_AGENT_ID=<agt_... returned by the UI>');
145
- console.log(' (plus BRAINCLAW_CLOUD_API_KEY, BRAINCLAW_PROJECT_ID) — then run `brainclaw federation status`.');
146
- });
147
- federationCmd
148
- .command('sync')
149
- .description('Drain the federation outbox — push signed claim upserts to the cloud')
150
- .option('--entity <type>', 'Entity type to sync (increment 1: claim)', 'claim')
151
- .option('--limit <n>', 'Max records to push this run')
152
- .option('--dry-run', 'Reconcile + list pending records without any network calls')
153
- .option('--json', 'Output as JSON')
154
- .action(async (options) => {
155
- const outbox = await import('../core/federation-outbox.js');
156
- const { pushClaimToCloud, isCloudConfigured } = await import('../core/federation-cloud.js');
157
- const cwd = process.cwd();
158
- const PARK_AFTER = 5;
159
- const reconciled = outbox.reconcileOutbox(cwd);
160
- let records = outbox.listOutboxRecords(cwd);
161
- if (options.limit)
162
- records = records.slice(0, parseInt(options.limit, 10));
163
- const counts = { synced: 0, superseded: 0, parked: reconciled.parked, dropped: reconciled.dropped, retry: 0 };
164
- const lines = [];
165
- const emit = (line) => { lines.push(line); if (!options.json)
166
- console.log(line); };
167
- if (options.dryRun) {
168
- for (const r of records)
169
- emit(`pending ${r.record.entity_type} ${r.record.entity_id} r${r.record.rev} (${r.record.to_status})`);
170
- const out = { dry_run: true, reconciled, pending: records.length, records: lines };
171
- if (options.json)
172
- console.log(JSON.stringify(out, null, 2));
173
- else
174
- console.log(`\npending=${records.length} reconciled_dropped=${reconciled.dropped} reconciled_parked=${reconciled.parked}`);
175
- return;
176
- }
177
- if (!isCloudConfigured(cwd)) {
178
- console.error('Error: cloud not configured (set BRAINCLAW_CLOUD_API_KEY or cloud_sync). Records left in outbox.');
179
- process.exit(3);
180
- }
181
- let failClosed = false;
182
- for (const r of records) {
183
- const res = await pushClaimToCloud(r.record.payload, cwd);
184
- const tag = `${r.record.entity_id} r${r.record.rev}`;
185
- if (res.kind === 'not_configured' || res.kind === 'fail_closed') {
186
- failClosed = true;
187
- emit(`fail-closed ${tag} (${res.kind}) — not sent`);
188
- break; // same config for all remaining records
189
- }
190
- if (res.kind === 'network_error') {
191
- const attempts = r.record.attempts + 1;
192
- if (attempts >= PARK_AFTER) {
193
- outbox.parkRecord(r, `network error x${attempts}: ${res.error}`, cwd);
194
- counts.parked++;
195
- emit(`park ${tag} (network x${attempts}: ${res.error})`);
196
- }
197
- else {
198
- outbox.recordAttempt(r, { http_status: null, error: res.error }, cwd);
199
- counts.retry++;
200
- emit(`retry ${tag} (network: ${res.error})`);
201
- }
202
- continue;
203
- }
204
- const { httpStatus, code } = res;
205
- if (httpStatus === 200 || httpStatus === 201) {
206
- outbox.archiveToSent(r, { http_status: httpStatus }, cwd);
207
- counts.synced++;
208
- emit(`pushed ${tag} → ${httpStatus}`);
209
- }
210
- else if (httpStatus === 409 && (code === 'STALE' || code === 'stale_version')) {
211
- outbox.archiveToSent(r, { http_status: httpStatus }, cwd);
212
- counts.superseded++;
213
- emit(`superseded ${tag} → 409 ${code} (cloud has a newer rev)`);
214
- }
215
- else if (httpStatus === 409) {
216
- outbox.parkRecord(r, `409 ${code ?? 'conflict'}`, cwd);
217
- counts.parked++;
218
- emit(`PARK ${tag} → 409 ${code ?? 'conflict'} (divergence — inspect)`);
219
- }
220
- else if (httpStatus === 403) {
221
- outbox.parkRecord(r, `403 ${code ?? 'forbidden'}`, cwd);
222
- counts.parked++;
223
- emit(`PARK ${tag} → 403 ${code ?? 'forbidden'}`);
224
- }
225
- else if (httpStatus >= 500) {
226
- const attempts = r.record.attempts + 1;
227
- if (attempts >= PARK_AFTER) {
228
- outbox.parkRecord(r, `5xx x${attempts} (last ${httpStatus})`, cwd);
229
- counts.parked++;
230
- emit(`park ${tag} → ${httpStatus} (x${attempts})`);
231
- }
232
- else {
233
- outbox.recordAttempt(r, { http_status: httpStatus, error: null }, cwd);
234
- counts.retry++;
235
- emit(`retry ${tag} → ${httpStatus}`);
236
- }
237
- }
238
- else {
239
- outbox.parkRecord(r, `${httpStatus} ${code ?? 'client error'}`, cwd);
240
- counts.parked++;
241
- emit(`PARK ${tag} → ${httpStatus} ${code ?? ''}`.trim());
242
- }
243
- }
244
- const summary = `synced=${counts.synced} superseded=${counts.superseded} retry=${counts.retry} parked=${counts.parked} dropped=${counts.dropped}`;
245
- if (options.json)
246
- console.log(JSON.stringify({ ...counts, fail_closed: failClosed, records: lines }, null, 2));
247
- else
248
- console.log(`\n${summary}`);
249
- if (failClosed)
250
- process.exit(3);
251
- if (counts.parked > 0)
252
- process.exit(2);
253
- if (counts.retry > 0)
254
- process.exit(1);
255
- // exit 0
256
- });
257
- }
258
- //# sourceMappingURL=register-federation.js.map
@@ -1,245 +0,0 @@
1
- import { loadConfig } from './config.js';
2
- import { logger } from './logger.js';
3
- import { buildCloudWriteHeaders, resolveCloudSigningIdentity, } from './federation-signing.js';
4
- const DEFAULT_API_URL = 'https://app.brainclaw.dev';
5
- function envFlag(value) {
6
- if (!value)
7
- return false;
8
- const v = value.trim().toLowerCase();
9
- return v === '1' || v === 'true' || v === 'yes';
10
- }
11
- function resolveCloudConfig(cwd) {
12
- const envApiUrl = process.env.BRAINCLAW_CLOUD_URL;
13
- const envApiKey = process.env.BRAINCLAW_CLOUD_API_KEY;
14
- const envProjectId = process.env.BRAINCLAW_PROJECT_ID;
15
- const envRequireSigned = process.env.BRAINCLAW_CLOUD_REQUIRE_SIGNED;
16
- let configEnabled = false;
17
- let configEndpoint;
18
- let configApiKey;
19
- let configProjectId;
20
- let configRequireSigned = false;
21
- try {
22
- const config = loadConfig(cwd);
23
- if (config.cloud_sync) {
24
- configEnabled = config.cloud_sync.enabled === true;
25
- configEndpoint = config.cloud_sync.endpoint;
26
- configApiKey = config.cloud_sync.api_key;
27
- configProjectId = config.cloud_sync.project_id;
28
- configRequireSigned = config.cloud_sync.require_signed === true;
29
- }
30
- }
31
- catch {
32
- // No config available — fall back to env only
33
- }
34
- const apiKey = envApiKey ?? configApiKey;
35
- if (!apiKey)
36
- return undefined;
37
- // Env-supplied key implies explicit opt-in; config flag is the alternative
38
- const enabled = Boolean(envApiKey) || configEnabled;
39
- const apiUrl = envApiUrl ?? configEndpoint ?? DEFAULT_API_URL;
40
- const projectId = envProjectId?.trim() || configProjectId;
41
- const requireSigned = envRequireSigned !== undefined ? envFlag(envRequireSigned) : configRequireSigned;
42
- return { apiUrl, apiKey, enabled, projectId, requireSigned };
43
- }
44
- /**
45
- * Build the outgoing headers for a runtime write, signing when possible.
46
- * Returns undefined when `require_signed` is set but no signing identity is
47
- * available — the caller must NOT send the request (fail-closed).
48
- */
49
- function writeHeaders(body, cloud, cwd) {
50
- const signing = resolveCloudSigningIdentity(cwd);
51
- return buildCloudWriteHeaders(body, {
52
- apiKey: cloud.apiKey,
53
- signing,
54
- requireSigned: cloud.requireSigned,
55
- });
56
- }
57
- export async function pushSignalToCloud(message, cwd) {
58
- const cloud = resolveCloudConfig(cwd);
59
- if (!cloud) {
60
- logger.debug('Cloud not configured — skipping push');
61
- return false;
62
- }
63
- const body = JSON.stringify(message);
64
- const headers = writeHeaders(body, cloud, cwd);
65
- if (!headers) {
66
- logger.warn('Cloud push refused: require_signed is set but no approved signing identity/key is available (fail-closed).');
67
- return false;
68
- }
69
- try {
70
- const response = await fetch(`${cloud.apiUrl}/api/v1/messages`, {
71
- method: 'POST',
72
- headers,
73
- body,
74
- });
75
- if (!response.ok) {
76
- logger.debug(`Cloud push failed: ${response.status} ${response.statusText}`);
77
- return false;
78
- }
79
- return true;
80
- }
81
- catch (err) {
82
- logger.debug('Cloud push error:', err);
83
- return false;
84
- }
85
- }
86
- export async function pullSignalsFromCloud(agentName, options, cwd) {
87
- const cloud = resolveCloudConfig(cwd);
88
- if (!cloud) {
89
- return [];
90
- }
91
- try {
92
- const params = new URLSearchParams();
93
- if (options?.since)
94
- params.set('since', options.since);
95
- if (options?.limit)
96
- params.set('limit', String(options.limit));
97
- const url = `${cloud.apiUrl}/api/v1/inbox/${encodeURIComponent(agentName)}?${params}`;
98
- const response = await fetch(url, {
99
- headers: { 'X-API-Key': cloud.apiKey },
100
- });
101
- if (!response.ok) {
102
- logger.debug(`Cloud pull failed: ${response.status}`);
103
- return [];
104
- }
105
- const data = (await response.json());
106
- return data.messages ?? [];
107
- }
108
- catch (err) {
109
- logger.debug('Cloud pull error:', err);
110
- return [];
111
- }
112
- }
113
- export async function pushBoardToCloud(projectName, boardData, cwd) {
114
- const cloud = resolveCloudConfig(cwd);
115
- if (!cloud)
116
- return false;
117
- const body = JSON.stringify(boardData);
118
- const headers = writeHeaders(body, cloud, cwd);
119
- if (!headers) {
120
- logger.warn('Cloud board push refused: require_signed is set but no approved signing identity/key is available (fail-closed).');
121
- return false;
122
- }
123
- try {
124
- const response = await fetch(`${cloud.apiUrl}/api/v1/board/${encodeURIComponent(projectName)}`, {
125
- method: 'POST',
126
- headers,
127
- body,
128
- });
129
- return response.ok;
130
- }
131
- catch {
132
- return false;
133
- }
134
- }
135
- export async function pushClaimToCloud(payload, cwd) {
136
- const cloud = resolveCloudConfig(cwd);
137
- if (!cloud)
138
- return { kind: 'not_configured' };
139
- const body = JSON.stringify(payload);
140
- const headers = writeHeaders(body, cloud, cwd);
141
- if (!headers)
142
- return { kind: 'fail_closed' };
143
- try {
144
- const response = await fetch(`${cloud.apiUrl}/api/v1/claims/${encodeURIComponent(payload.id)}`, {
145
- method: 'PUT',
146
- headers,
147
- body,
148
- });
149
- let code = null;
150
- try {
151
- const data = (await response.json());
152
- const raw = data.code ?? data.status;
153
- code = typeof raw === 'string' ? raw : null;
154
- }
155
- catch {
156
- // Non-JSON body — leave code null; the HTTP status still classifies it.
157
- }
158
- return { kind: 'response', httpStatus: response.status, code };
159
- }
160
- catch (err) {
161
- return { kind: 'network_error', error: err.message };
162
- }
163
- }
164
- export function isCloudConfigured(cwd) {
165
- return resolveCloudConfig(cwd) !== undefined;
166
- }
167
- /**
168
- * Returns true when cloud sync is both configured AND explicitly opted-in.
169
- * Use this gate for automatic lifecycle hooks (session-start pull, session-end push).
170
- * `isCloudConfigured` alone does NOT imply opt-in — a stale config api_key without
171
- * `cloud_sync.enabled=true` and without the BRAINCLAW_CLOUD_API_KEY env var stays inert.
172
- */
173
- export function isCloudSyncEnabled(cwd) {
174
- const cloud = resolveCloudConfig(cwd);
175
- return cloud !== undefined && cloud.enabled;
176
- }
177
- /**
178
- * Startup diagnostics for the cloud bridge: remote health, resolved signing
179
- * identity, approved-agent lookup, and a local↔remote key fingerprint match.
180
- * Never throws — every probe degrades to an error field so the CLI can print a
181
- * complete report.
182
- */
183
- export async function diagnoseCloudBridge(cwd) {
184
- const cloud = resolveCloudConfig(cwd);
185
- const apiUrl = cloud?.apiUrl ?? process.env.BRAINCLAW_CLOUD_URL ?? DEFAULT_API_URL;
186
- const signingIdentity = resolveCloudSigningIdentity(cwd);
187
- const diag = {
188
- configured: Boolean(cloud),
189
- enabled: cloud?.enabled ?? false,
190
- apiUrl,
191
- projectId: cloud?.projectId,
192
- requireSigned: cloud?.requireSigned ?? false,
193
- signing: signingIdentity
194
- ? {
195
- available: true,
196
- cloudAgentId: signingIdentity.cloudAgentId,
197
- agentName: signingIdentity.agentName,
198
- fingerprint: signingIdentity.fingerprint,
199
- }
200
- : {
201
- available: false,
202
- reason: 'No approved agent id/name configured, or the agent has no local Ed25519 key. '
203
- + 'Register the agent and its key with the cloud first.',
204
- },
205
- };
206
- if (!cloud)
207
- return diag;
208
- // Remote health
209
- try {
210
- const res = await fetch(`${apiUrl}/api/v1/health`);
211
- const data = (await res.json());
212
- diag.health = { ok: res.ok, status: String(data.status ?? ''), version: String(data.version ?? '') };
213
- }
214
- catch (e) {
215
- diag.health = { ok: false, error: e.message };
216
- }
217
- // Approved-agent lookup + fingerprint match
218
- if (signingIdentity) {
219
- try {
220
- const res = await fetch(`${apiUrl}/api/v1/agents/${encodeURIComponent(signingIdentity.cloudAgentId)}`, {
221
- headers: { 'X-API-Key': cloud.apiKey },
222
- });
223
- if (res.ok) {
224
- const data = (await res.json());
225
- const agent = data.agent;
226
- diag.approvedAgent = {
227
- found: Boolean(agent),
228
- status: agent?.status,
229
- trustLevel: agent?.trust_level,
230
- fingerprintMatch: agent?.key_fingerprint
231
- ? agent.key_fingerprint === signingIdentity.fingerprint
232
- : false,
233
- };
234
- }
235
- else {
236
- diag.approvedAgent = { found: false, error: `HTTP ${res.status}` };
237
- }
238
- }
239
- catch (e) {
240
- diag.approvedAgent = { found: false, error: e.message };
241
- }
242
- }
243
- return diag;
244
- }
245
- //# sourceMappingURL=federation-cloud.js.map