agentbnb 4.0.0 → 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 (36) hide show
  1. package/dist/{card-IE5UV5QX.js → card-4XH4AOTE.js} +11 -4
  2. package/dist/chunk-3MJT4PZG.js +50 -0
  3. package/dist/{conduct-IQYAT6ZU.js → chunk-3UKAVIMC.js} +70 -33
  4. package/dist/chunk-5AH3CMOX.js +62 -0
  5. package/dist/{chunk-UJWYE7VL.js → chunk-6K5WUVF3.js} +28 -111
  6. package/dist/chunk-75OC6E4F.js +33 -0
  7. package/dist/{chunk-QO67IGCW.js → chunk-DVAS2443.js} +1 -1
  8. package/dist/{chunk-XA63SD4T.js → chunk-FNKBHBYK.js} +3 -0
  9. package/dist/{websocket-client-5TIQDYQ4.js → chunk-JOY533UH.js} +38 -4
  10. package/dist/{chunk-RSX4SCPN.js → chunk-KJG2UJV5.js} +3 -3
  11. package/dist/chunk-M3G5NR2Z.js +90 -0
  12. package/dist/{chunk-HEVXCYCY.js → chunk-MQKYGY5I.js} +61 -24
  13. package/dist/chunk-ODBGCCEH.js +358 -0
  14. package/dist/{chunk-CUVIWPQO.js → chunk-Q7HRI666.js} +7 -6
  15. package/dist/chunk-QJEOCKVF.js +148 -0
  16. package/dist/{chunk-3Y36WQDV.js → chunk-QT7TEVNV.js} +14 -2
  17. package/dist/{chunk-UOGDK2S2.js → chunk-TLU7ALCZ.js} +1 -1
  18. package/dist/{chunk-QVV2P3FN.js → chunk-XQHN6ITI.js} +1 -1
  19. package/dist/cli/index.js +2665 -845
  20. package/dist/{client-IOTK6GOS.js → client-BTPIFY7E.js} +3 -3
  21. package/dist/conduct-CW62HBPT.js +52 -0
  22. package/dist/conduct-FXLVGKD5.js +19 -0
  23. package/dist/{conductor-mode-XU7ONJWC.js → conductor-mode-3JS4VWCR.js} +16 -9
  24. package/dist/execute-EXOITLHN.js +10 -0
  25. package/dist/index.d.ts +1005 -916
  26. package/dist/index.js +516 -120
  27. package/dist/{peers-G36URZYB.js → peers-K7FSHPN3.js} +2 -1
  28. package/dist/request-CNZ3XIVX.js +196 -0
  29. package/dist/serve-skill-SUOGUM7N.js +104 -0
  30. package/dist/server-2LWHL24P.js +295 -0
  31. package/dist/types-FGBUZ3QV.js +18 -0
  32. package/dist/websocket-client-6IIDGXKB.js +7 -0
  33. package/package.json +1 -1
  34. package/dist/chunk-BEI5MTNZ.js +0 -91
  35. package/dist/cli/index.d.ts +0 -1
  36. package/dist/execute-GDGBU6DJ.js +0 -10
package/dist/index.js CHANGED
@@ -436,30 +436,6 @@ function getCard(db, id) {
436
436
  if (!row) return null;
437
437
  return JSON.parse(row.data);
438
438
  }
439
- function updateCard(db, id, owner, updates) {
440
- const existing = getCard(db, id);
441
- if (!existing) {
442
- throw new AgentBnBError(`Card not found: ${id}`, "NOT_FOUND");
443
- }
444
- if (existing.owner !== owner) {
445
- throw new AgentBnBError("Forbidden: you do not own this card", "FORBIDDEN");
446
- }
447
- const now = (/* @__PURE__ */ new Date()).toISOString();
448
- const merged = { ...existing, ...updates, updated_at: now };
449
- const parsed = CapabilityCardSchema.safeParse(merged);
450
- if (!parsed.success) {
451
- throw new AgentBnBError(
452
- `Card validation failed: ${parsed.error.message}`,
453
- "VALIDATION_ERROR"
454
- );
455
- }
456
- const stmt = db.prepare(`
457
- UPDATE capability_cards
458
- SET data = ?, updated_at = ?
459
- WHERE id = ?
460
- `);
461
- stmt.run(JSON.stringify(parsed.data), now, id);
462
- }
463
439
  function updateReputation(db, cardId, success, latencyMs) {
464
440
  const existing = getCard(db, cardId);
465
441
  if (!existing) return;
@@ -783,7 +759,8 @@ async function executeCapabilityRequest(opts) {
783
759
  escrowReceipt: receipt,
784
760
  skillExecutor,
785
761
  handlerUrl,
786
- timeoutMs = 3e4
762
+ timeoutMs = 3e5,
763
+ onProgress
787
764
  } = opts;
788
765
  const card = getCard(registryDb, cardId);
789
766
  if (!card) {
@@ -890,7 +867,7 @@ async function executeCapabilityRequest(opts) {
890
867
  if (skillExecutor) {
891
868
  const targetSkillId = resolvedSkillId ?? skillId ?? cardId;
892
869
  try {
893
- const execResult = await skillExecutor.execute(targetSkillId, params);
870
+ const execResult = await skillExecutor.execute(targetSkillId, params, onProgress);
894
871
  if (!execResult.success) {
895
872
  return handleFailure("failure", execResult.latency_ms, execResult.error ?? "Execution failed");
896
873
  }
@@ -937,7 +914,7 @@ function createGatewayServer(opts) {
937
914
  creditDb,
938
915
  tokens,
939
916
  handlerUrl,
940
- timeoutMs = 3e4,
917
+ timeoutMs = 3e5,
941
918
  silent = false,
942
919
  skillExecutor
943
920
  } = opts;
@@ -1054,7 +1031,7 @@ var SkillExecutor = class {
1054
1031
  * @param params - Input parameters for the skill.
1055
1032
  * @returns ExecutionResult including success, result/error, and latency_ms.
1056
1033
  */
1057
- async execute(skillId, params) {
1034
+ async execute(skillId, params, onProgress) {
1058
1035
  const startTime = Date.now();
1059
1036
  const config = this.skillMap.get(skillId);
1060
1037
  if (!config) {
@@ -1073,7 +1050,7 @@ var SkillExecutor = class {
1073
1050
  };
1074
1051
  }
1075
1052
  try {
1076
- const modeResult = await mode.execute(config, params);
1053
+ const modeResult = await mode.execute(config, params, onProgress);
1077
1054
  return {
1078
1055
  ...modeResult,
1079
1056
  latency_ms: Date.now() - startTime
@@ -1493,7 +1470,7 @@ var PipelineExecutor = class {
1493
1470
  * @param params - Input parameters from the caller.
1494
1471
  * @returns Partial ExecutionResult (without latency_ms — added by SkillExecutor wrapper).
1495
1472
  */
1496
- async execute(config, params) {
1473
+ async execute(config, params, onProgress) {
1497
1474
  const pipelineConfig = config;
1498
1475
  const steps = pipelineConfig.steps ?? [];
1499
1476
  if (steps.length === 0) {
@@ -1552,6 +1529,13 @@ var PipelineExecutor = class {
1552
1529
  }
1553
1530
  context.steps.push({ result: stepResult });
1554
1531
  context.prev = { result: stepResult };
1532
+ if (onProgress && i < steps.length - 1) {
1533
+ onProgress({
1534
+ step: i + 1,
1535
+ total: steps.length,
1536
+ message: `Completed step ${i + 1}/${steps.length}`
1537
+ });
1538
+ }
1555
1539
  }
1556
1540
  const lastStep = context.steps[context.steps.length - 1];
1557
1541
  return {
@@ -1930,7 +1914,7 @@ function decompose(task, _availableCapabilities) {
1930
1914
  // src/gateway/client.ts
1931
1915
  import { randomUUID as randomUUID5 } from "crypto";
1932
1916
  async function requestCapability(opts) {
1933
- const { gatewayUrl, token, cardId, params = {}, timeoutMs = 3e4, escrowReceipt, identity } = opts;
1917
+ const { gatewayUrl, token, cardId, params = {}, timeoutMs = 3e5, escrowReceipt, identity } = opts;
1934
1918
  const id = randomUUID5();
1935
1919
  const payload = {
1936
1920
  jsonrpc: "2.0",
@@ -2030,6 +2014,73 @@ function loadConfig() {
2030
2014
  }
2031
2015
  }
2032
2016
 
2017
+ // src/cli/remote-registry.ts
2018
+ var RegistryTimeoutError = class extends AgentBnBError {
2019
+ constructor(url) {
2020
+ super(
2021
+ `Registry at ${url} did not respond within 5s. Showing local results only.`,
2022
+ "REGISTRY_TIMEOUT"
2023
+ );
2024
+ this.name = "RegistryTimeoutError";
2025
+ }
2026
+ };
2027
+ var RegistryConnectionError = class extends AgentBnBError {
2028
+ constructor(url) {
2029
+ super(
2030
+ `Cannot reach ${url}. Is the registry running? Showing local results only.`,
2031
+ "REGISTRY_CONNECTION"
2032
+ );
2033
+ this.name = "RegistryConnectionError";
2034
+ }
2035
+ };
2036
+ var RegistryAuthError = class extends AgentBnBError {
2037
+ constructor(url) {
2038
+ super(
2039
+ `Authentication failed for ${url}. Run \`agentbnb config set token <your-token>\`.`,
2040
+ "REGISTRY_AUTH"
2041
+ );
2042
+ this.name = "RegistryAuthError";
2043
+ }
2044
+ };
2045
+ async function fetchRemoteCards(registryUrl, params, timeoutMs = 5e3) {
2046
+ let cardsUrl;
2047
+ try {
2048
+ cardsUrl = new URL("/cards", registryUrl);
2049
+ } catch {
2050
+ throw new AgentBnBError(`Invalid registry URL: ${registryUrl}`, "INVALID_REGISTRY_URL");
2051
+ }
2052
+ const searchParams = new URLSearchParams();
2053
+ if (params.q !== void 0) searchParams.set("q", params.q);
2054
+ if (params.level !== void 0) searchParams.set("level", String(params.level));
2055
+ if (params.online !== void 0) searchParams.set("online", String(params.online));
2056
+ if (params.tag !== void 0) searchParams.set("tag", params.tag);
2057
+ searchParams.set("limit", "100");
2058
+ cardsUrl.search = searchParams.toString();
2059
+ const controller = new AbortController();
2060
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
2061
+ let response;
2062
+ try {
2063
+ response = await fetch(cardsUrl.toString(), { signal: controller.signal });
2064
+ } catch (err) {
2065
+ clearTimeout(timer);
2066
+ const isTimeout = err instanceof Error && err.name === "AbortError";
2067
+ if (isTimeout) {
2068
+ throw new RegistryTimeoutError(registryUrl);
2069
+ }
2070
+ throw new RegistryConnectionError(registryUrl);
2071
+ } finally {
2072
+ clearTimeout(timer);
2073
+ }
2074
+ if (response.status === 401 || response.status === 403) {
2075
+ throw new RegistryAuthError(registryUrl);
2076
+ }
2077
+ if (!response.ok) {
2078
+ throw new RegistryConnectionError(registryUrl);
2079
+ }
2080
+ const body = await response.json();
2081
+ return body.items;
2082
+ }
2083
+
2033
2084
  // src/autonomy/auto-request.ts
2034
2085
  function minMaxNormalize(values) {
2035
2086
  if (values.length === 0) return [];
@@ -2064,10 +2115,17 @@ function scorePeers(candidates, selfOwner) {
2064
2115
 
2065
2116
  // src/conductor/capability-matcher.ts
2066
2117
  var MAX_ALTERNATIVES = 2;
2067
- function matchSubTasks(opts) {
2068
- const { db, subtasks, conductorOwner } = opts;
2069
- return subtasks.map((subtask) => {
2070
- const cards = searchCards(db, subtask.required_capability, { online: true });
2118
+ async function matchSubTasks(opts) {
2119
+ const { db, subtasks, conductorOwner, registryUrl } = opts;
2120
+ return Promise.all(subtasks.map(async (subtask) => {
2121
+ let cards = searchCards(db, subtask.required_capability, { online: true });
2122
+ if (cards.length === 0 && registryUrl) {
2123
+ try {
2124
+ cards = await fetchRemoteCards(registryUrl, { q: subtask.required_capability, online: true });
2125
+ } catch {
2126
+ cards = [];
2127
+ }
2128
+ }
2071
2129
  const candidates = [];
2072
2130
  for (const card of cards) {
2073
2131
  const cardAsV2 = card;
@@ -2109,11 +2167,12 @@ function matchSubTasks(opts) {
2109
2167
  subtask_id: subtask.id,
2110
2168
  selected_agent: top.card.owner,
2111
2169
  selected_skill: top.skillId ?? "",
2170
+ selected_card_id: top.card.id,
2112
2171
  score: top.rawScore,
2113
2172
  credits: top.cost,
2114
2173
  alternatives
2115
2174
  };
2116
- });
2175
+ }));
2117
2176
  }
2118
2177
 
2119
2178
  // src/conductor/budget-controller.ts
@@ -2184,13 +2243,20 @@ var BudgetController = class {
2184
2243
  };
2185
2244
 
2186
2245
  // src/conductor/card.ts
2246
+ import { createHash } from "crypto";
2187
2247
  var CONDUCTOR_OWNER = "agentbnb-conductor";
2188
2248
  var CONDUCTOR_CARD_ID = "00000000-0000-4000-8000-000000000001";
2189
- function buildConductorCard() {
2249
+ function ownerToCardId(owner) {
2250
+ const hash = createHash("sha256").update(owner).digest("hex").slice(0, 32);
2251
+ return `${hash.slice(0, 8)}-${hash.slice(8, 12)}-4${hash.slice(13, 16)}-8${hash.slice(17, 20)}-${hash.slice(20, 32)}`;
2252
+ }
2253
+ function buildConductorCard(owner) {
2254
+ const cardOwner = owner ?? CONDUCTOR_OWNER;
2255
+ const cardId = owner ? ownerToCardId(owner) : CONDUCTOR_CARD_ID;
2190
2256
  const card = {
2191
2257
  spec_version: "2.0",
2192
- id: CONDUCTOR_CARD_ID,
2193
- owner: CONDUCTOR_OWNER,
2258
+ id: cardId,
2259
+ owner: cardOwner,
2194
2260
  agent_name: "AgentBnB Conductor",
2195
2261
  skills: [
2196
2262
  {
@@ -2281,7 +2347,7 @@ function computeWaves(subtasks) {
2281
2347
  return waves;
2282
2348
  }
2283
2349
  async function orchestrate(opts) {
2284
- const { subtasks, matches, gatewayToken, resolveAgentUrl, timeoutMs = 3e4, maxBudget } = opts;
2350
+ const { subtasks, matches, gatewayToken, resolveAgentUrl, timeoutMs = 3e5, maxBudget, relayClient, requesterOwner } = opts;
2285
2351
  const startTime = Date.now();
2286
2352
  if (subtasks.length === 0) {
2287
2353
  return {
@@ -2332,26 +2398,50 @@ async function orchestrate(opts) {
2332
2398
  );
2333
2399
  const primary = resolveAgentUrl(m.selected_agent);
2334
2400
  try {
2335
- const res = await requestCapability({
2336
- gatewayUrl: primary.url,
2337
- token: gatewayToken,
2338
- cardId: primary.cardId,
2339
- params: interpolatedParams,
2340
- timeoutMs
2341
- });
2401
+ let res;
2402
+ if (primary.url.startsWith("relay://") && relayClient) {
2403
+ const targetOwner = primary.url.replace("relay://", "");
2404
+ res = await relayClient.request({
2405
+ targetOwner,
2406
+ cardId: primary.cardId,
2407
+ params: interpolatedParams,
2408
+ requester: requesterOwner,
2409
+ timeoutMs
2410
+ });
2411
+ } else {
2412
+ res = await requestCapability({
2413
+ gatewayUrl: primary.url,
2414
+ token: gatewayToken,
2415
+ cardId: primary.cardId,
2416
+ params: interpolatedParams,
2417
+ timeoutMs
2418
+ });
2419
+ }
2342
2420
  return { taskId, result: res, credits: m.credits };
2343
2421
  } catch (primaryErr) {
2344
2422
  if (m.alternatives.length > 0) {
2345
2423
  const alt = m.alternatives[0];
2346
2424
  const altAgent = resolveAgentUrl(alt.agent);
2347
2425
  try {
2348
- const altRes = await requestCapability({
2349
- gatewayUrl: altAgent.url,
2350
- token: gatewayToken,
2351
- cardId: altAgent.cardId,
2352
- params: interpolatedParams,
2353
- timeoutMs
2354
- });
2426
+ let altRes;
2427
+ if (altAgent.url.startsWith("relay://") && relayClient) {
2428
+ const targetOwner = altAgent.url.replace("relay://", "");
2429
+ altRes = await relayClient.request({
2430
+ targetOwner,
2431
+ cardId: altAgent.cardId,
2432
+ params: interpolatedParams,
2433
+ requester: requesterOwner,
2434
+ timeoutMs
2435
+ });
2436
+ } else {
2437
+ altRes = await requestCapability({
2438
+ gatewayUrl: altAgent.url,
2439
+ token: gatewayToken,
2440
+ cardId: altAgent.cardId,
2441
+ params: interpolatedParams,
2442
+ timeoutMs
2443
+ });
2444
+ }
2355
2445
  return { taskId, result: altRes, credits: alt.credits };
2356
2446
  } catch (altErr) {
2357
2447
  throw new Error(
@@ -2452,7 +2542,7 @@ var ConductorMode = class {
2452
2542
  * @param params - Must include `task` string.
2453
2543
  * @returns Execution result without latency_ms (added by SkillExecutor).
2454
2544
  */
2455
- async execute(config, params) {
2545
+ async execute(config, params, onProgress) {
2456
2546
  const conductorSkill = config.conductor_skill;
2457
2547
  if (conductorSkill !== "orchestrate" && conductorSkill !== "plan") {
2458
2548
  return {
@@ -2474,11 +2564,13 @@ var ConductorMode = class {
2474
2564
  error: "No template matches task"
2475
2565
  };
2476
2566
  }
2477
- const matchResults = matchSubTasks({
2567
+ onProgress?.({ step: 1, total: 5, message: `Decomposed into ${subtasks.length} sub-tasks` });
2568
+ const matchResults = await matchSubTasks({
2478
2569
  db: this.db,
2479
2570
  subtasks,
2480
2571
  conductorOwner: this.conductorOwner
2481
2572
  });
2573
+ onProgress?.({ step: 2, total: 5, message: `Matched ${matchResults.length} sub-tasks to agents` });
2482
2574
  const budgetManager = new BudgetManager(this.creditDb, this.conductorOwner);
2483
2575
  const budgetController = new BudgetController(budgetManager, this.maxBudget);
2484
2576
  const executionBudget = budgetController.calculateBudget(matchResults);
@@ -2488,6 +2580,7 @@ var ConductorMode = class {
2488
2580
  error: `Budget exceeded: estimated ${executionBudget.estimated_total} cr, max ${this.maxBudget} cr`
2489
2581
  };
2490
2582
  }
2583
+ onProgress?.({ step: 3, total: 5, message: `Budget approved: ${executionBudget.estimated_total} cr` });
2491
2584
  if (conductorSkill === "plan") {
2492
2585
  return {
2493
2586
  success: true,
@@ -2508,6 +2601,7 @@ var ConductorMode = class {
2508
2601
  resolveAgentUrl: this.resolveAgentUrl,
2509
2602
  maxBudget: this.maxBudget
2510
2603
  });
2604
+ onProgress?.({ step: 4, total: 5, message: "Pipeline execution complete" });
2511
2605
  const resultObj = {};
2512
2606
  for (const [key, value] of orchResult.results) {
2513
2607
  resultObj[key] = value;
@@ -2560,7 +2654,7 @@ function createSignedEscrowReceipt(db, privateKey, publicKey, opts) {
2560
2654
 
2561
2655
  // src/identity/identity.ts
2562
2656
  import { z as z4 } from "zod";
2563
- import { createHash } from "crypto";
2657
+ import { createHash as createHash2 } from "crypto";
2564
2658
  import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, existsSync as existsSync4, mkdirSync as mkdirSync3 } from "fs";
2565
2659
  import { join as join4 } from "path";
2566
2660
  var AgentIdentitySchema = z4.object({
@@ -2591,7 +2685,7 @@ var AgentCertificateSchema = z4.object({
2591
2685
  });
2592
2686
  var IDENTITY_FILENAME = "identity.json";
2593
2687
  function deriveAgentId(publicKeyHex) {
2594
- return createHash("sha256").update(publicKeyHex, "hex").digest("hex").slice(0, 16);
2688
+ return createHash2("sha256").update(publicKeyHex, "hex").digest("hex").slice(0, 16);
2595
2689
  }
2596
2690
  function createIdentity(configDir, owner) {
2597
2691
  if (!existsSync4(configDir)) {
@@ -3036,8 +3130,10 @@ var RegisterMessageSchema = z6.object({
3036
3130
  type: z6.literal("register"),
3037
3131
  owner: z6.string().min(1),
3038
3132
  token: z6.string().min(1),
3039
- card: z6.record(z6.unknown())
3133
+ card: z6.record(z6.unknown()),
3040
3134
  // CapabilityCard (validated separately)
3135
+ cards: z6.array(z6.record(z6.unknown())).optional()
3136
+ // Additional cards (e.g., conductor card)
3041
3137
  });
3042
3138
  var RegisteredMessageSchema = z6.object({
3043
3139
  type: z6.literal("registered"),
@@ -3087,6 +3183,15 @@ var ErrorMessageSchema = z6.object({
3087
3183
  message: z6.string(),
3088
3184
  request_id: z6.string().optional()
3089
3185
  });
3186
+ var RelayProgressMessageSchema = z6.object({
3187
+ type: z6.literal("relay_progress"),
3188
+ id: z6.string().uuid(),
3189
+ // request ID this progress relates to
3190
+ progress: z6.number().min(0).max(100).optional(),
3191
+ // optional percentage
3192
+ message: z6.string().optional()
3193
+ // optional status message
3194
+ });
3090
3195
  var RelayMessageSchema = z6.discriminatedUnion("type", [
3091
3196
  RegisterMessageSchema,
3092
3197
  RegisteredMessageSchema,
@@ -3094,18 +3199,105 @@ var RelayMessageSchema = z6.discriminatedUnion("type", [
3094
3199
  IncomingRequestMessageSchema,
3095
3200
  RelayResponseMessageSchema,
3096
3201
  ResponseMessageSchema,
3097
- ErrorMessageSchema
3202
+ ErrorMessageSchema,
3203
+ RelayProgressMessageSchema
3098
3204
  ]);
3099
3205
 
3100
3206
  // src/relay/websocket-relay.ts
3207
+ import { randomUUID as randomUUID12 } from "crypto";
3208
+
3209
+ // src/relay/relay-credit.ts
3210
+ function lookupCardPrice(registryDb, cardId, skillId) {
3211
+ const row = registryDb.prepare("SELECT data FROM capability_cards WHERE id = ?").get(cardId);
3212
+ if (!row) return null;
3213
+ let card;
3214
+ try {
3215
+ card = JSON.parse(row.data);
3216
+ } catch {
3217
+ return null;
3218
+ }
3219
+ if (skillId && Array.isArray(card.skills)) {
3220
+ const skills = card.skills;
3221
+ const skill = skills.find((s) => s.id === skillId);
3222
+ if (skill) {
3223
+ const skillPricing = skill.pricing;
3224
+ if (skillPricing && typeof skillPricing.credits_per_call === "number") {
3225
+ return skillPricing.credits_per_call;
3226
+ }
3227
+ }
3228
+ }
3229
+ const pricing = card.pricing;
3230
+ if (!pricing || typeof pricing.credits_per_call !== "number") {
3231
+ return null;
3232
+ }
3233
+ return pricing.credits_per_call;
3234
+ }
3235
+ function holdForRelay(creditDb, owner, amount, cardId) {
3236
+ return holdEscrow(creditDb, owner, amount, cardId);
3237
+ }
3238
+ function settleForRelay(creditDb, escrowId, recipientOwner) {
3239
+ settleEscrow(creditDb, escrowId, recipientOwner);
3240
+ }
3241
+ function calculateConductorFee(totalSubTaskCost) {
3242
+ if (totalSubTaskCost <= 0) return 0;
3243
+ const fee = Math.ceil(totalSubTaskCost * 0.1);
3244
+ return Math.max(1, Math.min(20, fee));
3245
+ }
3246
+ function releaseForRelay(creditDb, escrowId) {
3247
+ if (escrowId === void 0) return;
3248
+ releaseEscrow(creditDb, escrowId);
3249
+ }
3250
+
3251
+ // src/hub-agent/relay-bridge.ts
3252
+ import { randomUUID as randomUUID11 } from "crypto";
3253
+
3254
+ // src/hub-agent/job-queue.ts
3101
3255
  import { randomUUID as randomUUID10 } from "crypto";
3256
+ function updateJobStatus(db, jobId, status, result) {
3257
+ const now = (/* @__PURE__ */ new Date()).toISOString();
3258
+ if (result !== void 0) {
3259
+ db.prepare("UPDATE hub_agent_jobs SET status = ?, result = ?, updated_at = ? WHERE id = ?").run(status, result, now, jobId);
3260
+ } else {
3261
+ db.prepare("UPDATE hub_agent_jobs SET status = ?, updated_at = ? WHERE id = ?").run(status, now, jobId);
3262
+ }
3263
+ }
3264
+
3265
+ // src/hub-agent/crypto.ts
3266
+ import { createCipheriv, createDecipheriv, randomBytes } from "crypto";
3267
+
3268
+ // src/hub-agent/relay-bridge.ts
3269
+ function handleJobRelayResponse(opts) {
3270
+ const { registryDb, creditDb, jobId, escrowId, relayOwner, result, error } = opts;
3271
+ if (error) {
3272
+ updateJobStatus(registryDb, jobId, "failed", JSON.stringify(error));
3273
+ if (escrowId) {
3274
+ try {
3275
+ releaseForRelay(creditDb, escrowId);
3276
+ } catch (e) {
3277
+ console.error("[relay-bridge] escrow release on error failed:", e);
3278
+ }
3279
+ }
3280
+ } else {
3281
+ updateJobStatus(registryDb, jobId, "completed", JSON.stringify(result));
3282
+ if (escrowId) {
3283
+ try {
3284
+ settleForRelay(creditDb, escrowId, relayOwner);
3285
+ } catch (e) {
3286
+ console.error("[relay-bridge] escrow settle failed:", e);
3287
+ }
3288
+ }
3289
+ }
3290
+ }
3291
+
3292
+ // src/relay/websocket-relay.ts
3102
3293
  var RATE_LIMIT_MAX = 60;
3103
3294
  var RATE_LIMIT_WINDOW_MS = 6e4;
3104
- var RELAY_TIMEOUT_MS = 3e4;
3105
- function registerWebSocketRelay(server, db) {
3295
+ var RELAY_TIMEOUT_MS = 3e5;
3296
+ function registerWebSocketRelay(server, db, creditDb) {
3106
3297
  const connections = /* @__PURE__ */ new Map();
3107
3298
  const pendingRequests = /* @__PURE__ */ new Map();
3108
3299
  const rateLimits = /* @__PURE__ */ new Map();
3300
+ let onAgentOnlineCallback;
3109
3301
  function checkRateLimit(owner) {
3110
3302
  const now = Date.now();
3111
3303
  const entry = rateLimits.get(owner);
@@ -3156,21 +3348,32 @@ function registerWebSocketRelay(server, db) {
3156
3348
  }
3157
3349
  }
3158
3350
  function upsertCard(cardData, owner) {
3159
- const cardId = cardData.id;
3160
- const existing = getCard(db, cardId);
3351
+ const parsed = AnyCardSchema.safeParse(cardData);
3352
+ if (!parsed.success) {
3353
+ throw new AgentBnBError(
3354
+ `Card validation failed: ${parsed.error.message}`,
3355
+ "VALIDATION_ERROR"
3356
+ );
3357
+ }
3358
+ const card = { ...parsed.data, availability: { ...parsed.data.availability, online: true } };
3359
+ const cardId = card.id;
3360
+ const now = (/* @__PURE__ */ new Date()).toISOString();
3361
+ const existing = db.prepare("SELECT id FROM capability_cards WHERE id = ?").get(cardId);
3161
3362
  if (existing) {
3162
- const updates = { ...cardData, availability: { ...cardData.availability ?? {}, online: true } };
3163
- updateCard(db, cardId, owner, updates);
3363
+ db.prepare(
3364
+ "UPDATE capability_cards SET data = ?, updated_at = ? WHERE id = ?"
3365
+ ).run(JSON.stringify(card), now, cardId);
3164
3366
  } else {
3165
- const card = { ...cardData, availability: { ...cardData.availability ?? {}, online: true } };
3166
- insertCard(db, card);
3367
+ db.prepare(
3368
+ "INSERT INTO capability_cards (id, owner, data, created_at, updated_at) VALUES (?, ?, ?, ?, ?)"
3369
+ ).run(cardId, owner, JSON.stringify(card), now, now);
3167
3370
  }
3168
3371
  return cardId;
3169
3372
  }
3170
3373
  function logAgentJoined(owner, cardName, cardId) {
3171
3374
  try {
3172
3375
  insertRequestLog(db, {
3173
- id: randomUUID10(),
3376
+ id: randomUUID12(),
3174
3377
  card_id: cardId,
3175
3378
  card_name: cardName,
3176
3379
  requester: owner,
@@ -3201,10 +3404,25 @@ function registerWebSocketRelay(server, db) {
3201
3404
  const cardId = upsertCard(card, owner);
3202
3405
  const cardName = card.name ?? card.agent_name ?? owner;
3203
3406
  logAgentJoined(owner, cardName, cardId);
3407
+ if (msg.cards && msg.cards.length > 0) {
3408
+ for (const extraCard of msg.cards) {
3409
+ try {
3410
+ upsertCard(extraCard, owner);
3411
+ } catch {
3412
+ }
3413
+ }
3414
+ }
3204
3415
  markOwnerOnline(owner);
3416
+ if (onAgentOnlineCallback) {
3417
+ try {
3418
+ onAgentOnlineCallback(owner);
3419
+ } catch (e) {
3420
+ console.error("[relay] onAgentOnline callback error:", e);
3421
+ }
3422
+ }
3205
3423
  sendMessage(ws, { type: "registered", agent_id: cardId });
3206
3424
  }
3207
- function handleRelayRequest(ws, msg, fromOwner) {
3425
+ async function handleRelayRequest(ws, msg, fromOwner) {
3208
3426
  if (!checkRateLimit(fromOwner)) {
3209
3427
  sendMessage(ws, {
3210
3428
  type: "error",
@@ -3223,15 +3441,42 @@ function registerWebSocketRelay(server, db) {
3223
3441
  });
3224
3442
  return;
3225
3443
  }
3444
+ let escrowId;
3445
+ if (creditDb) {
3446
+ try {
3447
+ const price = lookupCardPrice(db, msg.card_id, msg.skill_id);
3448
+ if (price !== null && price > 0) {
3449
+ escrowId = holdForRelay(creditDb, fromOwner, price, msg.card_id);
3450
+ }
3451
+ } catch (err) {
3452
+ if (err instanceof AgentBnBError && err.code === "INSUFFICIENT_CREDITS") {
3453
+ sendMessage(ws, {
3454
+ type: "response",
3455
+ id: msg.id,
3456
+ error: { code: -32603, message: "Insufficient credits" }
3457
+ });
3458
+ return;
3459
+ }
3460
+ console.error("[relay] credit hold error (non-fatal):", err);
3461
+ }
3462
+ }
3226
3463
  const timeout = setTimeout(() => {
3464
+ const pending = pendingRequests.get(msg.id);
3227
3465
  pendingRequests.delete(msg.id);
3466
+ if (pending?.escrowId && creditDb) {
3467
+ try {
3468
+ releaseForRelay(creditDb, pending.escrowId);
3469
+ } catch (e) {
3470
+ console.error("[relay] escrow release on timeout failed:", e);
3471
+ }
3472
+ }
3228
3473
  sendMessage(ws, {
3229
3474
  type: "response",
3230
3475
  id: msg.id,
3231
3476
  error: { code: -32603, message: "Relay request timeout" }
3232
3477
  });
3233
3478
  }, RELAY_TIMEOUT_MS);
3234
- pendingRequests.set(msg.id, { originOwner: fromOwner, timeout });
3479
+ pendingRequests.set(msg.id, { originOwner: fromOwner, timeout, escrowId, targetOwner: msg.target_owner });
3235
3480
  sendMessage(targetWs, {
3236
3481
  type: "incoming_request",
3237
3482
  id: msg.id,
@@ -3243,18 +3488,98 @@ function registerWebSocketRelay(server, db) {
3243
3488
  escrow_receipt: msg.escrow_receipt
3244
3489
  });
3245
3490
  }
3491
+ function handleRelayProgress(msg) {
3492
+ const pending = pendingRequests.get(msg.id);
3493
+ if (!pending) return;
3494
+ clearTimeout(pending.timeout);
3495
+ const newTimeout = setTimeout(() => {
3496
+ const p = pendingRequests.get(msg.id);
3497
+ pendingRequests.delete(msg.id);
3498
+ if (p?.escrowId && creditDb) {
3499
+ try {
3500
+ releaseForRelay(creditDb, p.escrowId);
3501
+ } catch (e) {
3502
+ console.error("[relay] escrow release on progress timeout failed:", e);
3503
+ }
3504
+ }
3505
+ const originWs2 = connections.get(pending.originOwner);
3506
+ if (originWs2 && originWs2.readyState === 1) {
3507
+ sendMessage(originWs2, {
3508
+ type: "response",
3509
+ id: msg.id,
3510
+ error: { code: -32603, message: "Relay request timeout" }
3511
+ });
3512
+ }
3513
+ }, RELAY_TIMEOUT_MS);
3514
+ pending.timeout = newTimeout;
3515
+ const originWs = connections.get(pending.originOwner);
3516
+ if (originWs && originWs.readyState === 1) {
3517
+ sendMessage(originWs, {
3518
+ type: "relay_progress",
3519
+ id: msg.id,
3520
+ progress: msg.progress,
3521
+ message: msg.message
3522
+ });
3523
+ }
3524
+ }
3246
3525
  function handleRelayResponse(msg) {
3247
3526
  const pending = pendingRequests.get(msg.id);
3248
3527
  if (!pending) return;
3249
3528
  clearTimeout(pending.timeout);
3250
3529
  pendingRequests.delete(msg.id);
3530
+ if (pending.jobId && creditDb) {
3531
+ try {
3532
+ handleJobRelayResponse({
3533
+ registryDb: db,
3534
+ creditDb,
3535
+ jobId: pending.jobId,
3536
+ escrowId: pending.escrowId,
3537
+ relayOwner: pending.targetOwner ?? "",
3538
+ result: msg.error === void 0 ? msg.result : void 0,
3539
+ error: msg.error
3540
+ });
3541
+ } catch (e) {
3542
+ console.error("[relay] job relay response handling failed:", e);
3543
+ }
3544
+ const originWs2 = connections.get(pending.originOwner);
3545
+ if (originWs2 && originWs2.readyState === 1) {
3546
+ sendMessage(originWs2, { type: "response", id: msg.id, result: msg.result, error: msg.error });
3547
+ }
3548
+ return;
3549
+ }
3550
+ if (pending.escrowId && creditDb) {
3551
+ try {
3552
+ if (msg.error === void 0) {
3553
+ settleForRelay(creditDb, pending.escrowId, pending.targetOwner);
3554
+ } else {
3555
+ releaseForRelay(creditDb, pending.escrowId);
3556
+ }
3557
+ } catch (e) {
3558
+ console.error("[relay] escrow settle/release on response failed:", e);
3559
+ }
3560
+ }
3561
+ let conductorFee = 0;
3562
+ if (creditDb && msg.error === void 0 && typeof msg.result === "object" && msg.result !== null && "total_credits" in msg.result && typeof msg.result.total_credits === "number") {
3563
+ const totalCredits = msg.result.total_credits;
3564
+ conductorFee = calculateConductorFee(totalCredits);
3565
+ if (conductorFee > 0) {
3566
+ try {
3567
+ const feeEscrowId = holdForRelay(creditDb, pending.originOwner, conductorFee, msg.id);
3568
+ settleForRelay(creditDb, feeEscrowId, pending.targetOwner);
3569
+ } catch (e) {
3570
+ console.error("[relay] conductor fee settlement failed (non-fatal):", e);
3571
+ conductorFee = 0;
3572
+ }
3573
+ }
3574
+ }
3251
3575
  const originWs = connections.get(pending.originOwner);
3252
3576
  if (originWs && originWs.readyState === 1) {
3253
3577
  sendMessage(originWs, {
3254
3578
  type: "response",
3255
3579
  id: msg.id,
3256
3580
  result: msg.result,
3257
- error: msg.error
3581
+ error: msg.error,
3582
+ ...conductorFee > 0 ? { conductor_fee: conductorFee } : {}
3258
3583
  });
3259
3584
  }
3260
3585
  }
@@ -3264,9 +3589,34 @@ function registerWebSocketRelay(server, db) {
3264
3589
  rateLimits.delete(owner);
3265
3590
  markOwnerOffline(owner);
3266
3591
  for (const [reqId, pending] of pendingRequests) {
3267
- if (pending.originOwner === owner) {
3592
+ if (pending.targetOwner === owner) {
3593
+ clearTimeout(pending.timeout);
3594
+ pendingRequests.delete(reqId);
3595
+ if (pending.escrowId && creditDb) {
3596
+ try {
3597
+ releaseForRelay(creditDb, pending.escrowId);
3598
+ } catch (e) {
3599
+ console.error("[relay] escrow release on disconnect failed:", e);
3600
+ }
3601
+ }
3602
+ const originWs = connections.get(pending.originOwner);
3603
+ if (originWs && originWs.readyState === 1) {
3604
+ sendMessage(originWs, {
3605
+ type: "response",
3606
+ id: reqId,
3607
+ error: { code: -32603, message: "Provider disconnected" }
3608
+ });
3609
+ }
3610
+ } else if (pending.originOwner === owner) {
3268
3611
  clearTimeout(pending.timeout);
3269
3612
  pendingRequests.delete(reqId);
3613
+ if (pending.escrowId && creditDb) {
3614
+ try {
3615
+ releaseForRelay(creditDb, pending.escrowId);
3616
+ } catch (e) {
3617
+ console.error("[relay] escrow release on requester disconnect failed:", e);
3618
+ }
3619
+ }
3270
3620
  }
3271
3621
  }
3272
3622
  }
@@ -3274,45 +3624,50 @@ function registerWebSocketRelay(server, db) {
3274
3624
  const socket = rawSocket;
3275
3625
  let registeredOwner;
3276
3626
  socket.on("message", (raw) => {
3277
- let data;
3278
- try {
3279
- data = JSON.parse(typeof raw === "string" ? raw : raw.toString("utf-8"));
3280
- } catch {
3281
- sendMessage(socket, { type: "error", code: "invalid_json", message: "Invalid JSON" });
3282
- return;
3283
- }
3284
- const parsed = RelayMessageSchema.safeParse(data);
3285
- if (!parsed.success) {
3286
- sendMessage(socket, {
3287
- type: "error",
3288
- code: "invalid_message",
3289
- message: `Invalid message: ${parsed.error.issues[0]?.message ?? "unknown error"}`
3290
- });
3291
- return;
3292
- }
3293
- const msg = parsed.data;
3294
- switch (msg.type) {
3295
- case "register":
3296
- registeredOwner = msg.owner;
3297
- handleRegister(socket, msg);
3298
- break;
3299
- case "relay_request":
3300
- if (!registeredOwner) {
3301
- sendMessage(socket, {
3302
- type: "error",
3303
- code: "not_registered",
3304
- message: "Must send register message before relay requests"
3305
- });
3306
- return;
3307
- }
3308
- handleRelayRequest(socket, msg, registeredOwner);
3309
- break;
3310
- case "relay_response":
3311
- handleRelayResponse(msg);
3312
- break;
3313
- default:
3314
- break;
3315
- }
3627
+ void (async () => {
3628
+ let data;
3629
+ try {
3630
+ data = JSON.parse(typeof raw === "string" ? raw : raw.toString("utf-8"));
3631
+ } catch {
3632
+ sendMessage(socket, { type: "error", code: "invalid_json", message: "Invalid JSON" });
3633
+ return;
3634
+ }
3635
+ const parsed = RelayMessageSchema.safeParse(data);
3636
+ if (!parsed.success) {
3637
+ sendMessage(socket, {
3638
+ type: "error",
3639
+ code: "invalid_message",
3640
+ message: `Invalid message: ${parsed.error.issues[0]?.message ?? "unknown error"}`
3641
+ });
3642
+ return;
3643
+ }
3644
+ const msg = parsed.data;
3645
+ switch (msg.type) {
3646
+ case "register":
3647
+ registeredOwner = msg.owner;
3648
+ handleRegister(socket, msg);
3649
+ break;
3650
+ case "relay_request":
3651
+ if (!registeredOwner) {
3652
+ sendMessage(socket, {
3653
+ type: "error",
3654
+ code: "not_registered",
3655
+ message: "Must send register message before relay requests"
3656
+ });
3657
+ return;
3658
+ }
3659
+ await handleRelayRequest(socket, msg, registeredOwner);
3660
+ break;
3661
+ case "relay_response":
3662
+ handleRelayResponse(msg);
3663
+ break;
3664
+ case "relay_progress":
3665
+ handleRelayProgress(msg);
3666
+ break;
3667
+ default:
3668
+ break;
3669
+ }
3670
+ })();
3316
3671
  });
3317
3672
  socket.on("close", () => {
3318
3673
  handleDisconnect(registeredOwner);
@@ -3337,13 +3692,21 @@ function registerWebSocketRelay(server, db) {
3337
3692
  }
3338
3693
  pendingRequests.clear();
3339
3694
  rateLimits.clear();
3695
+ },
3696
+ setOnAgentOnline: (cb) => {
3697
+ onAgentOnlineCallback = cb;
3698
+ },
3699
+ getConnections: () => connections,
3700
+ getPendingRequests: () => pendingRequests,
3701
+ sendMessage: (ws, msg) => {
3702
+ sendMessage(ws, msg);
3340
3703
  }
3341
3704
  };
3342
3705
  }
3343
3706
 
3344
3707
  // src/relay/websocket-client.ts
3345
3708
  import WebSocket from "ws";
3346
- import { randomUUID as randomUUID11 } from "crypto";
3709
+ import { randomUUID as randomUUID13 } from "crypto";
3347
3710
  var RelayClient = class {
3348
3711
  ws = null;
3349
3712
  opts;
@@ -3375,7 +3738,8 @@ var RelayClient = class {
3375
3738
  type: "register",
3376
3739
  owner: this.opts.owner,
3377
3740
  token: this.opts.token,
3378
- card: this.opts.card
3741
+ card: this.opts.card,
3742
+ ...this.opts.cards && this.opts.cards.length > 0 ? { cards: this.opts.cards } : {}
3379
3743
  });
3380
3744
  });
3381
3745
  this.ws.on("message", (raw) => {
@@ -3439,14 +3803,14 @@ var RelayClient = class {
3439
3803
  if (!this.ws || this.ws.readyState !== WebSocket.OPEN || !this.registered) {
3440
3804
  throw new Error("Not connected to registry relay");
3441
3805
  }
3442
- const id = randomUUID11();
3443
- const timeoutMs = opts.timeoutMs ?? 3e4;
3806
+ const id = randomUUID13();
3807
+ const timeoutMs = opts.timeoutMs ?? 3e5;
3444
3808
  return new Promise((resolve, reject) => {
3445
3809
  const timeout = setTimeout(() => {
3446
3810
  this.pendingRequests.delete(id);
3447
3811
  reject(new Error("Relay request timeout"));
3448
3812
  }, timeoutMs);
3449
- this.pendingRequests.set(id, { resolve, reject, timeout });
3813
+ this.pendingRequests.set(id, { resolve, reject, timeout, timeoutMs, onProgress: opts.onProgress });
3450
3814
  this.send({
3451
3815
  type: "relay_request",
3452
3816
  id,
@@ -3459,6 +3823,22 @@ var RelayClient = class {
3459
3823
  });
3460
3824
  });
3461
3825
  }
3826
+ /**
3827
+ * Send a relay_progress message to the relay server for a given request.
3828
+ * Used by the onRequest handler to forward SkillExecutor progress updates
3829
+ * to the requesting agent so it can reset its timeout window.
3830
+ *
3831
+ * @param requestId - The relay request ID to associate progress with.
3832
+ * @param info - Progress details (step, total, message).
3833
+ */
3834
+ sendProgress(requestId, info) {
3835
+ this.send({
3836
+ type: "relay_progress",
3837
+ id: requestId,
3838
+ progress: Math.round(info.step / info.total * 100),
3839
+ message: info.message
3840
+ });
3841
+ }
3462
3842
  /** Whether the client is connected and registered */
3463
3843
  get isConnected() {
3464
3844
  return this.ws !== null && this.ws.readyState === WebSocket.OPEN && this.registered;
@@ -3505,6 +3885,9 @@ var RelayClient = class {
3505
3885
  case "error":
3506
3886
  this.handleError(msg);
3507
3887
  break;
3888
+ case "relay_progress":
3889
+ this.handleProgress(msg);
3890
+ break;
3508
3891
  default:
3509
3892
  break;
3510
3893
  }
@@ -3550,6 +3933,19 @@ var RelayClient = class {
3550
3933
  }
3551
3934
  }
3552
3935
  }
3936
+ handleProgress(msg) {
3937
+ const pending = this.pendingRequests.get(msg.id);
3938
+ if (!pending) return;
3939
+ clearTimeout(pending.timeout);
3940
+ const newTimeout = setTimeout(() => {
3941
+ this.pendingRequests.delete(msg.id);
3942
+ pending.reject(new Error("Relay request timeout"));
3943
+ }, pending.timeoutMs);
3944
+ pending.timeout = newTimeout;
3945
+ if (pending.onProgress) {
3946
+ pending.onProgress({ id: msg.id, progress: msg.progress, message: msg.message });
3947
+ }
3948
+ }
3553
3949
  send(msg) {
3554
3950
  if (this.ws && this.ws.readyState === WebSocket.OPEN) {
3555
3951
  this.ws.send(JSON.stringify(msg));
@@ -3611,12 +4007,12 @@ var RelayClient = class {
3611
4007
  };
3612
4008
 
3613
4009
  // src/onboarding/index.ts
3614
- import { randomUUID as randomUUID13 } from "crypto";
4010
+ import { randomUUID as randomUUID15 } from "crypto";
3615
4011
  import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
3616
4012
  import { join as join5 } from "path";
3617
4013
 
3618
4014
  // src/cli/onboarding.ts
3619
- import { randomUUID as randomUUID12 } from "crypto";
4015
+ import { randomUUID as randomUUID14 } from "crypto";
3620
4016
  import { createConnection } from "net";
3621
4017
  var KNOWN_API_KEYS = [
3622
4018
  "OPENAI_API_KEY",
@@ -3758,7 +4154,7 @@ function capabilitiesToV2Card(capabilities, owner, agentName) {
3758
4154
  }));
3759
4155
  const card = {
3760
4156
  spec_version: "2.0",
3761
- id: randomUUID13(),
4157
+ id: randomUUID15(),
3762
4158
  owner,
3763
4159
  agent_name: agentName ?? owner,
3764
4160
  skills,