agentbnb 3.1.6 → 4.0.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.
Files changed (37) hide show
  1. package/README.md +117 -86
  2. package/dist/{card-IE5UV5QX.js → card-4XH4AOTE.js} +11 -4
  3. package/dist/chunk-3MJT4PZG.js +50 -0
  4. package/dist/{conduct-IEQ567ET.js → chunk-3UKAVIMC.js} +70 -31
  5. package/dist/chunk-5AH3CMOX.js +62 -0
  6. package/dist/{chunk-IZZ4FP45.js → chunk-6K5WUVF3.js} +33 -166
  7. package/dist/chunk-75OC6E4F.js +33 -0
  8. package/dist/chunk-DVAS2443.js +63 -0
  9. package/dist/{chunk-XA63SD4T.js → chunk-FNKBHBYK.js} +3 -0
  10. package/dist/{websocket-client-5TIQDYQ4.js → chunk-JOY533UH.js} +38 -4
  11. package/dist/chunk-KJG2UJV5.js +83 -0
  12. package/dist/chunk-M3G5NR2Z.js +90 -0
  13. package/dist/{chunk-7OACGAFD.js → chunk-MQKYGY5I.js} +63 -24
  14. package/dist/chunk-ODBGCCEH.js +358 -0
  15. package/dist/{chunk-QSPWE5AE.js → chunk-Q7HRI666.js} +9 -6
  16. package/dist/chunk-QJEOCKVF.js +148 -0
  17. package/dist/{chunk-3Y36WQDV.js → chunk-QT7TEVNV.js} +14 -2
  18. package/dist/{chunk-UOGDK2S2.js → chunk-TLU7ALCZ.js} +1 -1
  19. package/dist/{chunk-QHQPXO67.js → chunk-XQHN6ITI.js} +1 -58
  20. package/dist/cli/index.js +2734 -850
  21. package/dist/client-BTPIFY7E.js +10 -0
  22. package/dist/conduct-CW62HBPT.js +52 -0
  23. package/dist/conduct-FXLVGKD5.js +19 -0
  24. package/dist/{conductor-mode-IO45PWMI.js → conductor-mode-3JS4VWCR.js} +16 -7
  25. package/dist/execute-EXOITLHN.js +10 -0
  26. package/dist/index.d.ts +1005 -916
  27. package/dist/index.js +516 -120
  28. package/dist/{peers-G36URZYB.js → peers-K7FSHPN3.js} +2 -1
  29. package/dist/request-CNZ3XIVX.js +196 -0
  30. package/dist/serve-skill-SUOGUM7N.js +104 -0
  31. package/dist/server-2LWHL24P.js +295 -0
  32. package/dist/types-FGBUZ3QV.js +18 -0
  33. package/dist/websocket-client-6IIDGXKB.js +7 -0
  34. package/package.json +4 -1
  35. package/dist/chunk-BEI5MTNZ.js +0 -91
  36. package/dist/cli/index.d.ts +0 -1
  37. package/dist/execute-SWWEHV2K.js +0 -9
@@ -1,9 +1,16 @@
1
1
  import {
2
- interpolateObject,
3
- requestCapability,
4
- scorePeers,
2
+ interpolateObject
3
+ } from "./chunk-3MJT4PZG.js";
4
+ import {
5
+ scorePeers
6
+ } from "./chunk-6K5WUVF3.js";
7
+ import {
8
+ fetchRemoteCards,
5
9
  searchCards
6
- } from "./chunk-IZZ4FP45.js";
10
+ } from "./chunk-QJEOCKVF.js";
11
+ import {
12
+ requestCapability
13
+ } from "./chunk-KJG2UJV5.js";
7
14
 
8
15
  // src/conductor/task-decomposer.ts
9
16
  import { randomUUID } from "crypto";
@@ -116,10 +123,17 @@ function decompose(task, _availableCapabilities) {
116
123
 
117
124
  // src/conductor/capability-matcher.ts
118
125
  var MAX_ALTERNATIVES = 2;
119
- function matchSubTasks(opts) {
120
- const { db, subtasks, conductorOwner } = opts;
121
- return subtasks.map((subtask) => {
122
- const cards = searchCards(db, subtask.required_capability, { online: true });
126
+ async function matchSubTasks(opts) {
127
+ const { db, subtasks, conductorOwner, registryUrl } = opts;
128
+ return Promise.all(subtasks.map(async (subtask) => {
129
+ let cards = searchCards(db, subtask.required_capability, { online: true });
130
+ if (cards.length === 0 && registryUrl) {
131
+ try {
132
+ cards = await fetchRemoteCards(registryUrl, { q: subtask.required_capability, online: true });
133
+ } catch {
134
+ cards = [];
135
+ }
136
+ }
123
137
  const candidates = [];
124
138
  for (const card of cards) {
125
139
  const cardAsV2 = card;
@@ -161,11 +175,12 @@ function matchSubTasks(opts) {
161
175
  subtask_id: subtask.id,
162
176
  selected_agent: top.card.owner,
163
177
  selected_skill: top.skillId ?? "",
178
+ selected_card_id: top.card.id,
164
179
  score: top.rawScore,
165
180
  credits: top.cost,
166
181
  alternatives
167
182
  };
168
- });
183
+ }));
169
184
  }
170
185
 
171
186
  // src/conductor/budget-controller.ts
@@ -260,7 +275,7 @@ function computeWaves(subtasks) {
260
275
  return waves;
261
276
  }
262
277
  async function orchestrate(opts) {
263
- const { subtasks, matches, gatewayToken, resolveAgentUrl, timeoutMs = 3e4, maxBudget } = opts;
278
+ const { subtasks, matches, gatewayToken, resolveAgentUrl, timeoutMs = 3e5, maxBudget, relayClient, requesterOwner } = opts;
264
279
  const startTime = Date.now();
265
280
  if (subtasks.length === 0) {
266
281
  return {
@@ -311,26 +326,50 @@ async function orchestrate(opts) {
311
326
  );
312
327
  const primary = resolveAgentUrl(m.selected_agent);
313
328
  try {
314
- const res = await requestCapability({
315
- gatewayUrl: primary.url,
316
- token: gatewayToken,
317
- cardId: primary.cardId,
318
- params: interpolatedParams,
319
- timeoutMs
320
- });
329
+ let res;
330
+ if (primary.url.startsWith("relay://") && relayClient) {
331
+ const targetOwner = primary.url.replace("relay://", "");
332
+ res = await relayClient.request({
333
+ targetOwner,
334
+ cardId: primary.cardId,
335
+ params: interpolatedParams,
336
+ requester: requesterOwner,
337
+ timeoutMs
338
+ });
339
+ } else {
340
+ res = await requestCapability({
341
+ gatewayUrl: primary.url,
342
+ token: gatewayToken,
343
+ cardId: primary.cardId,
344
+ params: interpolatedParams,
345
+ timeoutMs
346
+ });
347
+ }
321
348
  return { taskId, result: res, credits: m.credits };
322
349
  } catch (primaryErr) {
323
350
  if (m.alternatives.length > 0) {
324
351
  const alt = m.alternatives[0];
325
352
  const altAgent = resolveAgentUrl(alt.agent);
326
353
  try {
327
- const altRes = await requestCapability({
328
- gatewayUrl: altAgent.url,
329
- token: gatewayToken,
330
- cardId: altAgent.cardId,
331
- params: interpolatedParams,
332
- timeoutMs
333
- });
354
+ let altRes;
355
+ if (altAgent.url.startsWith("relay://") && relayClient) {
356
+ const targetOwner = altAgent.url.replace("relay://", "");
357
+ altRes = await relayClient.request({
358
+ targetOwner,
359
+ cardId: altAgent.cardId,
360
+ params: interpolatedParams,
361
+ requester: requesterOwner,
362
+ timeoutMs
363
+ });
364
+ } else {
365
+ altRes = await requestCapability({
366
+ gatewayUrl: altAgent.url,
367
+ token: gatewayToken,
368
+ cardId: altAgent.cardId,
369
+ params: interpolatedParams,
370
+ timeoutMs
371
+ });
372
+ }
334
373
  return { taskId, result: altRes, credits: alt.credits };
335
374
  } catch (altErr) {
336
375
  throw new Error(
@@ -0,0 +1,358 @@
1
+ import {
2
+ bootstrapAgent,
3
+ getBalance,
4
+ getTransactions,
5
+ holdEscrow,
6
+ openCreditDb,
7
+ releaseEscrow,
8
+ settleEscrow
9
+ } from "./chunk-XQHN6ITI.js";
10
+ import {
11
+ signEscrowReceipt,
12
+ verifyEscrowReceipt
13
+ } from "./chunk-DVAS2443.js";
14
+ import {
15
+ AgentBnBError
16
+ } from "./chunk-FNKBHBYK.js";
17
+
18
+ // src/credit/local-credit-ledger.ts
19
+ var LocalCreditLedger = class {
20
+ constructor(db) {
21
+ this.db = db;
22
+ }
23
+ /**
24
+ * Holds credits in escrow during capability execution.
25
+ *
26
+ * @param owner - Agent identifier (requester).
27
+ * @param amount - Number of credits to hold.
28
+ * @param cardId - Capability Card ID being requested.
29
+ * @returns EscrowResult with the new escrowId.
30
+ * @throws {AgentBnBError} with code 'INSUFFICIENT_CREDITS' if balance < amount.
31
+ */
32
+ async hold(owner, amount, cardId) {
33
+ const escrowId = holdEscrow(this.db, owner, amount, cardId);
34
+ return { escrowId };
35
+ }
36
+ /**
37
+ * Settles an escrow — transfers held credits to the capability provider.
38
+ *
39
+ * @param escrowId - The escrow ID to settle.
40
+ * @param recipientOwner - Agent identifier who will receive the credits.
41
+ * @throws {AgentBnBError} with code 'ESCROW_NOT_FOUND' if escrow does not exist.
42
+ * @throws {AgentBnBError} with code 'ESCROW_ALREADY_SETTLED' if escrow is not in 'held' status.
43
+ */
44
+ async settle(escrowId, recipientOwner) {
45
+ settleEscrow(this.db, escrowId, recipientOwner);
46
+ }
47
+ /**
48
+ * Releases an escrow — refunds credits back to the requester.
49
+ *
50
+ * @param escrowId - The escrow ID to release.
51
+ * @throws {AgentBnBError} with code 'ESCROW_NOT_FOUND' if escrow does not exist.
52
+ * @throws {AgentBnBError} with code 'ESCROW_ALREADY_SETTLED' if escrow is not in 'held' status.
53
+ */
54
+ async release(escrowId) {
55
+ releaseEscrow(this.db, escrowId);
56
+ }
57
+ /**
58
+ * Returns the current credit balance for an agent.
59
+ *
60
+ * @param owner - Agent identifier.
61
+ * @returns Current balance in credits (0 if agent is unknown).
62
+ */
63
+ async getBalance(owner) {
64
+ return getBalance(this.db, owner);
65
+ }
66
+ /**
67
+ * Returns the transaction history for an agent, newest first.
68
+ *
69
+ * @param owner - Agent identifier.
70
+ * @param limit - Maximum number of transactions to return. Defaults to 100.
71
+ * @returns Array of credit transactions ordered newest first.
72
+ */
73
+ async getHistory(owner, limit) {
74
+ return getTransactions(this.db, owner, limit);
75
+ }
76
+ /**
77
+ * Grants initial credits to an agent (bootstrap grant).
78
+ * Idempotent — calling multiple times has no additional effect on balance.
79
+ *
80
+ * @param owner - Agent identifier.
81
+ * @param amount - Number of credits to grant. Defaults to 100.
82
+ */
83
+ async grant(owner, amount) {
84
+ bootstrapAgent(this.db, owner, amount);
85
+ }
86
+ };
87
+
88
+ // src/registry/identity-auth.ts
89
+ var MAX_REQUEST_AGE_MS = 5 * 60 * 1e3;
90
+ async function verifyIdentity(request, reply) {
91
+ const publicKeyHex = request.headers["x-agent-publickey"];
92
+ const signature = request.headers["x-agent-signature"];
93
+ const timestamp = request.headers["x-agent-timestamp"];
94
+ if (!publicKeyHex || !signature || !timestamp) {
95
+ await reply.code(401).send({ error: "Missing identity headers" });
96
+ return false;
97
+ }
98
+ const requestTime = new Date(timestamp).getTime();
99
+ if (isNaN(requestTime) || Math.abs(Date.now() - requestTime) > MAX_REQUEST_AGE_MS) {
100
+ await reply.code(401).send({ error: "Request expired" });
101
+ return false;
102
+ }
103
+ const payload = {
104
+ method: request.method,
105
+ path: request.url,
106
+ timestamp,
107
+ publicKey: publicKeyHex
108
+ };
109
+ let publicKeyBuffer;
110
+ try {
111
+ publicKeyBuffer = Buffer.from(publicKeyHex, "hex");
112
+ } catch {
113
+ await reply.code(401).send({ error: "Invalid identity signature" });
114
+ return false;
115
+ }
116
+ const valid = verifyEscrowReceipt(payload, signature, publicKeyBuffer);
117
+ if (!valid) {
118
+ await reply.code(401).send({ error: "Invalid identity signature" });
119
+ return false;
120
+ }
121
+ request.agentPublicKey = publicKeyHex;
122
+ return true;
123
+ }
124
+ function identityAuthPlugin(fastify) {
125
+ fastify.addHook("onRequest", async (request, reply) => {
126
+ await verifyIdentity(request, reply);
127
+ });
128
+ }
129
+ function signRequest(method, path, body, privateKey, publicKeyHex) {
130
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
131
+ const payload = {
132
+ method,
133
+ path,
134
+ timestamp,
135
+ publicKey: publicKeyHex
136
+ };
137
+ void body;
138
+ const signature = signEscrowReceipt(payload, privateKey);
139
+ return {
140
+ "X-Agent-PublicKey": publicKeyHex,
141
+ "X-Agent-Signature": signature,
142
+ "X-Agent-Timestamp": timestamp
143
+ };
144
+ }
145
+
146
+ // src/credit/registry-credit-ledger.ts
147
+ var HTTP_TIMEOUT_MS = 1e4;
148
+ var RegistryCreditLedger = class {
149
+ config;
150
+ constructor(config) {
151
+ this.config = config;
152
+ }
153
+ /**
154
+ * Holds credits in escrow during capability execution.
155
+ *
156
+ * @param owner - Agent identifier (requester).
157
+ * @param amount - Number of credits to hold.
158
+ * @param cardId - Capability Card ID being requested.
159
+ * @returns EscrowResult with the new escrowId.
160
+ * @throws {AgentBnBError} with code 'INSUFFICIENT_CREDITS' if balance < amount.
161
+ */
162
+ async hold(owner, amount, cardId) {
163
+ if (this.config.mode === "direct") {
164
+ const escrowId = holdEscrow(this.config.db, owner, amount, cardId);
165
+ return { escrowId };
166
+ }
167
+ const data = await this.post("/api/credits/hold", owner, {
168
+ owner,
169
+ amount,
170
+ cardId
171
+ });
172
+ return { escrowId: data.escrowId };
173
+ }
174
+ /**
175
+ * Settles an escrow — transfers held credits to the capability provider.
176
+ *
177
+ * @param escrowId - The escrow ID to settle.
178
+ * @param recipientOwner - Agent identifier who will receive the credits.
179
+ * @throws {AgentBnBError} with code 'ESCROW_NOT_FOUND' if escrow does not exist.
180
+ * @throws {AgentBnBError} with code 'ESCROW_ALREADY_SETTLED' if escrow is not in 'held' status.
181
+ */
182
+ async settle(escrowId, recipientOwner) {
183
+ if (this.config.mode === "direct") {
184
+ settleEscrow(this.config.db, escrowId, recipientOwner);
185
+ return;
186
+ }
187
+ await this.post("/api/credits/settle", null, { escrowId, recipientOwner });
188
+ }
189
+ /**
190
+ * Releases an escrow — refunds credits back to the requester.
191
+ *
192
+ * @param escrowId - The escrow ID to release.
193
+ * @throws {AgentBnBError} with code 'ESCROW_NOT_FOUND' if escrow does not exist.
194
+ * @throws {AgentBnBError} with code 'ESCROW_ALREADY_SETTLED' if escrow is not in 'held' status.
195
+ */
196
+ async release(escrowId) {
197
+ if (this.config.mode === "direct") {
198
+ releaseEscrow(this.config.db, escrowId);
199
+ return;
200
+ }
201
+ await this.post("/api/credits/release", null, { escrowId });
202
+ }
203
+ /**
204
+ * Returns the current credit balance for an agent.
205
+ *
206
+ * @param owner - Agent identifier.
207
+ * @returns Current balance in credits (0 if agent is unknown).
208
+ */
209
+ async getBalance(owner) {
210
+ if (this.config.mode === "direct") {
211
+ return getBalance(this.config.db, owner);
212
+ }
213
+ const data = await this.get(`/api/credits/${owner}`, owner);
214
+ return data.balance;
215
+ }
216
+ /**
217
+ * Returns the transaction history for an agent, newest first.
218
+ *
219
+ * @param owner - Agent identifier.
220
+ * @param limit - Maximum number of transactions to return. Defaults to 100.
221
+ * @returns Array of credit transactions ordered newest first.
222
+ */
223
+ async getHistory(owner, limit = 100) {
224
+ if (this.config.mode === "direct") {
225
+ return getTransactions(this.config.db, owner, limit);
226
+ }
227
+ const data = await this.get(
228
+ `/api/credits/${owner}/history?limit=${limit}`,
229
+ owner
230
+ );
231
+ return data.transactions;
232
+ }
233
+ /**
234
+ * Grants initial credits to an agent (bootstrap grant).
235
+ * Idempotent — calling multiple times has no additional effect on balance.
236
+ *
237
+ * @param owner - Agent identifier.
238
+ * @param amount - Number of credits to grant. Defaults to 100.
239
+ */
240
+ async grant(owner, amount = 100) {
241
+ if (this.config.mode === "direct") {
242
+ bootstrapAgent(this.config.db, owner, amount);
243
+ return;
244
+ }
245
+ await this.post("/api/credits/grant", owner, { owner, amount });
246
+ }
247
+ // ─── Private HTTP helpers ─────────────────────────────────────────────────
248
+ /**
249
+ * Makes an authenticated POST request to the Registry HTTP API.
250
+ * Includes a 10s timeout via AbortController.
251
+ *
252
+ * @param path - API path (e.g., '/api/credits/hold').
253
+ * @param ownerForHeader - Agent owner identifier for X-Agent-Owner header, or null to omit.
254
+ * @param body - JSON body to send.
255
+ * @returns Parsed JSON response body.
256
+ * @throws {AgentBnBError} on non-2xx responses or network errors.
257
+ */
258
+ async post(path, ownerForHeader, body) {
259
+ const cfg = this.config;
260
+ const controller = new AbortController();
261
+ const timeoutId = setTimeout(() => controller.abort(), HTTP_TIMEOUT_MS);
262
+ try {
263
+ const authHeaders = signRequest("POST", path, body, cfg.privateKey, cfg.ownerPublicKey);
264
+ const headers = {
265
+ "Content-Type": "application/json",
266
+ ...authHeaders
267
+ };
268
+ void ownerForHeader;
269
+ const res = await fetch(`${cfg.registryUrl}${path}`, {
270
+ method: "POST",
271
+ headers,
272
+ body: JSON.stringify(body),
273
+ signal: controller.signal
274
+ });
275
+ return await this.handleResponse(res);
276
+ } catch (err) {
277
+ if (err instanceof AgentBnBError) throw err;
278
+ throw new AgentBnBError(
279
+ `Registry unreachable: ${err.message}`,
280
+ "REGISTRY_UNREACHABLE"
281
+ );
282
+ } finally {
283
+ clearTimeout(timeoutId);
284
+ }
285
+ }
286
+ /**
287
+ * Makes an authenticated GET request to the Registry HTTP API.
288
+ * Includes a 10s timeout via AbortController.
289
+ *
290
+ * @param path - API path (e.g., '/api/credits/owner-id').
291
+ * @param owner - Agent owner identifier for X-Agent-Owner header.
292
+ * @returns Parsed JSON response body.
293
+ * @throws {AgentBnBError} on non-2xx responses or network errors.
294
+ */
295
+ async get(path, owner) {
296
+ const cfg = this.config;
297
+ const controller = new AbortController();
298
+ const timeoutId = setTimeout(() => controller.abort(), HTTP_TIMEOUT_MS);
299
+ try {
300
+ const authHeaders = signRequest("GET", path, null, cfg.privateKey, cfg.ownerPublicKey);
301
+ void owner;
302
+ const res = await fetch(`${cfg.registryUrl}${path}`, {
303
+ method: "GET",
304
+ headers: {
305
+ "Content-Type": "application/json",
306
+ ...authHeaders
307
+ },
308
+ signal: controller.signal
309
+ });
310
+ return await this.handleResponse(res);
311
+ } catch (err) {
312
+ if (err instanceof AgentBnBError) throw err;
313
+ throw new AgentBnBError(
314
+ `Registry unreachable: ${err.message}`,
315
+ "REGISTRY_UNREACHABLE"
316
+ );
317
+ } finally {
318
+ clearTimeout(timeoutId);
319
+ }
320
+ }
321
+ /**
322
+ * Handles an HTTP response — returns parsed JSON on 2xx, throws AgentBnBError on error.
323
+ */
324
+ async handleResponse(res) {
325
+ const json = await res.json();
326
+ if (!res.ok) {
327
+ const code = typeof json["code"] === "string" ? json["code"] : "REGISTRY_ERROR";
328
+ const message = typeof json["error"] === "string" ? json["error"] : `HTTP ${res.status}`;
329
+ throw new AgentBnBError(message, code);
330
+ }
331
+ return json;
332
+ }
333
+ };
334
+
335
+ // src/credit/create-ledger.ts
336
+ function createLedger(opts) {
337
+ if ("registryUrl" in opts && opts.registryUrl !== void 0) {
338
+ return new RegistryCreditLedger({
339
+ mode: "http",
340
+ registryUrl: opts.registryUrl,
341
+ ownerPublicKey: opts.ownerPublicKey,
342
+ privateKey: opts.privateKey
343
+ });
344
+ }
345
+ if ("db" in opts && opts.db !== void 0) {
346
+ return new RegistryCreditLedger({
347
+ mode: "direct",
348
+ db: opts.db
349
+ });
350
+ }
351
+ const db = openCreditDb(opts.creditDbPath);
352
+ return new LocalCreditLedger(db);
353
+ }
354
+
355
+ export {
356
+ identityAuthPlugin,
357
+ createLedger
358
+ };
@@ -2,19 +2,21 @@ import {
2
2
  getCard,
3
3
  insertRequestLog,
4
4
  updateReputation
5
- } from "./chunk-UOGDK2S2.js";
5
+ } from "./chunk-TLU7ALCZ.js";
6
6
  import {
7
7
  confirmEscrowDebit,
8
8
  getBalance,
9
9
  holdEscrow,
10
10
  recordEarning,
11
11
  releaseEscrow,
12
- settleEscrow,
12
+ settleEscrow
13
+ } from "./chunk-XQHN6ITI.js";
14
+ import {
13
15
  verifyEscrowReceipt
14
- } from "./chunk-QHQPXO67.js";
16
+ } from "./chunk-DVAS2443.js";
15
17
  import {
16
18
  AgentBnBError
17
- } from "./chunk-XA63SD4T.js";
19
+ } from "./chunk-FNKBHBYK.js";
18
20
 
19
21
  // src/gateway/execute.ts
20
22
  import { randomUUID } from "crypto";
@@ -49,7 +51,8 @@ async function executeCapabilityRequest(opts) {
49
51
  escrowReceipt: receipt,
50
52
  skillExecutor,
51
53
  handlerUrl,
52
- timeoutMs = 3e4
54
+ timeoutMs = 3e5,
55
+ onProgress
53
56
  } = opts;
54
57
  const card = getCard(registryDb, cardId);
55
58
  if (!card) {
@@ -156,7 +159,7 @@ async function executeCapabilityRequest(opts) {
156
159
  if (skillExecutor) {
157
160
  const targetSkillId = resolvedSkillId ?? skillId ?? cardId;
158
161
  try {
159
- const execResult = await skillExecutor.execute(targetSkillId, params);
162
+ const execResult = await skillExecutor.execute(targetSkillId, params, onProgress);
160
163
  if (!execResult.success) {
161
164
  return handleFailure("failure", execResult.latency_ms, execResult.error ?? "Execution failed");
162
165
  }
@@ -0,0 +1,148 @@
1
+ import {
2
+ AgentBnBError
3
+ } from "./chunk-FNKBHBYK.js";
4
+
5
+ // src/registry/matcher.ts
6
+ function searchCards(db, query, filters = {}) {
7
+ const words = query.trim().split(/\s+/).map((w) => w.replace(/["*^{}():]/g, "")).filter((w) => w.length > 0);
8
+ if (words.length === 0) return [];
9
+ const ftsQuery = words.map((w) => `"${w}"`).join(" OR ");
10
+ const conditions = [];
11
+ const params = [ftsQuery];
12
+ if (filters.level !== void 0) {
13
+ conditions.push(`json_extract(cc.data, '$.level') = ?`);
14
+ params.push(filters.level);
15
+ }
16
+ if (filters.online !== void 0) {
17
+ conditions.push(`json_extract(cc.data, '$.availability.online') = ?`);
18
+ params.push(filters.online ? 1 : 0);
19
+ }
20
+ const whereClause = conditions.length > 0 ? `AND ${conditions.join(" AND ")}` : "";
21
+ const sql = `
22
+ SELECT cc.data
23
+ FROM capability_cards cc
24
+ JOIN cards_fts ON cc.rowid = cards_fts.rowid
25
+ WHERE cards_fts MATCH ?
26
+ ${whereClause}
27
+ ORDER BY bm25(cards_fts)
28
+ LIMIT 50
29
+ `;
30
+ const stmt = db.prepare(sql);
31
+ const rows = stmt.all(...params);
32
+ const results = rows.map((row) => JSON.parse(row.data));
33
+ if (filters.apis_used && filters.apis_used.length > 0) {
34
+ const requiredApis = filters.apis_used;
35
+ return results.filter((card) => {
36
+ const cardApis = card.metadata?.apis_used ?? [];
37
+ return requiredApis.every((api) => cardApis.includes(api));
38
+ });
39
+ }
40
+ return results;
41
+ }
42
+ function filterCards(db, filters) {
43
+ const conditions = [];
44
+ const params = [];
45
+ if (filters.level !== void 0) {
46
+ conditions.push(`json_extract(data, '$.level') = ?`);
47
+ params.push(filters.level);
48
+ }
49
+ if (filters.online !== void 0) {
50
+ conditions.push(`json_extract(data, '$.availability.online') = ?`);
51
+ params.push(filters.online ? 1 : 0);
52
+ }
53
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
54
+ const sql = `SELECT data FROM capability_cards ${whereClause}`;
55
+ const stmt = db.prepare(sql);
56
+ const rows = stmt.all(...params);
57
+ return rows.map((row) => JSON.parse(row.data));
58
+ }
59
+
60
+ // src/cli/remote-registry.ts
61
+ var RegistryTimeoutError = class extends AgentBnBError {
62
+ constructor(url) {
63
+ super(
64
+ `Registry at ${url} did not respond within 5s. Showing local results only.`,
65
+ "REGISTRY_TIMEOUT"
66
+ );
67
+ this.name = "RegistryTimeoutError";
68
+ }
69
+ };
70
+ var RegistryConnectionError = class extends AgentBnBError {
71
+ constructor(url) {
72
+ super(
73
+ `Cannot reach ${url}. Is the registry running? Showing local results only.`,
74
+ "REGISTRY_CONNECTION"
75
+ );
76
+ this.name = "RegistryConnectionError";
77
+ }
78
+ };
79
+ var RegistryAuthError = class extends AgentBnBError {
80
+ constructor(url) {
81
+ super(
82
+ `Authentication failed for ${url}. Run \`agentbnb config set token <your-token>\`.`,
83
+ "REGISTRY_AUTH"
84
+ );
85
+ this.name = "RegistryAuthError";
86
+ }
87
+ };
88
+ async function fetchRemoteCards(registryUrl, params, timeoutMs = 5e3) {
89
+ let cardsUrl;
90
+ try {
91
+ cardsUrl = new URL("/cards", registryUrl);
92
+ } catch {
93
+ throw new AgentBnBError(`Invalid registry URL: ${registryUrl}`, "INVALID_REGISTRY_URL");
94
+ }
95
+ const searchParams = new URLSearchParams();
96
+ if (params.q !== void 0) searchParams.set("q", params.q);
97
+ if (params.level !== void 0) searchParams.set("level", String(params.level));
98
+ if (params.online !== void 0) searchParams.set("online", String(params.online));
99
+ if (params.tag !== void 0) searchParams.set("tag", params.tag);
100
+ searchParams.set("limit", "100");
101
+ cardsUrl.search = searchParams.toString();
102
+ const controller = new AbortController();
103
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
104
+ let response;
105
+ try {
106
+ response = await fetch(cardsUrl.toString(), { signal: controller.signal });
107
+ } catch (err) {
108
+ clearTimeout(timer);
109
+ const isTimeout = err instanceof Error && err.name === "AbortError";
110
+ if (isTimeout) {
111
+ throw new RegistryTimeoutError(registryUrl);
112
+ }
113
+ throw new RegistryConnectionError(registryUrl);
114
+ } finally {
115
+ clearTimeout(timer);
116
+ }
117
+ if (response.status === 401 || response.status === 403) {
118
+ throw new RegistryAuthError(registryUrl);
119
+ }
120
+ if (!response.ok) {
121
+ throw new RegistryConnectionError(registryUrl);
122
+ }
123
+ const body = await response.json();
124
+ return body.items;
125
+ }
126
+ function mergeResults(localCards, remoteCards, hasQuery) {
127
+ const taggedLocal = localCards.map((c) => ({ ...c, source: "local" }));
128
+ const taggedRemote = remoteCards.map((c) => ({ ...c, source: "remote" }));
129
+ const localIds = new Set(localCards.map((c) => c.id));
130
+ const dedupedRemote = taggedRemote.filter((c) => !localIds.has(c.id));
131
+ if (!hasQuery) {
132
+ return [...taggedLocal, ...dedupedRemote];
133
+ }
134
+ const result = [];
135
+ const maxLen = Math.max(taggedLocal.length, dedupedRemote.length);
136
+ for (let i = 0; i < maxLen; i++) {
137
+ if (i < taggedLocal.length) result.push(taggedLocal[i]);
138
+ if (i < dedupedRemote.length) result.push(dedupedRemote[i]);
139
+ }
140
+ return result;
141
+ }
142
+
143
+ export {
144
+ searchCards,
145
+ filterCards,
146
+ fetchRemoteCards,
147
+ mergeResults
148
+ };