agentbnb 4.0.0 → 4.0.2

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 (44) hide show
  1. package/README.md +2 -0
  2. package/dist/{card-IE5UV5QX.js → card-RSGDCHCV.js} +11 -4
  3. package/dist/chunk-3MJT4PZG.js +50 -0
  4. package/dist/{chunk-HEVXCYCY.js → chunk-4P3EMGL4.js} +61 -24
  5. package/dist/chunk-5AH3CMOX.js +62 -0
  6. package/dist/{chunk-QO67IGCW.js → chunk-5KFI5X7B.js} +1 -1
  7. package/dist/chunk-75OC6E4F.js +33 -0
  8. package/dist/{chunk-CUVIWPQO.js → chunk-7NA43XCG.js} +7 -6
  9. package/dist/{conduct-IQYAT6ZU.js → chunk-BH6WGYFB.js} +70 -33
  10. package/dist/{chunk-QVV2P3FN.js → chunk-DNWT5FZQ.js} +22 -2
  11. package/dist/chunk-FF226TIV.js +148 -0
  12. package/dist/{chunk-UJWYE7VL.js → chunk-GGYC5U2Z.js} +28 -111
  13. package/dist/chunk-HH24WMFN.js +373 -0
  14. package/dist/{websocket-client-5TIQDYQ4.js → chunk-JOY533UH.js} +38 -4
  15. package/dist/chunk-QITOPASZ.js +96 -0
  16. package/dist/{chunk-3Y36WQDV.js → chunk-QT7TEVNV.js} +14 -2
  17. package/dist/{chunk-UOGDK2S2.js → chunk-T7NS2J2B.js} +1 -1
  18. package/dist/{chunk-XA63SD4T.js → chunk-WGZ5AGOX.js} +37 -0
  19. package/dist/{chunk-RSX4SCPN.js → chunk-XND2DWTZ.js} +4 -3
  20. package/dist/cli/index.js +2924 -835
  21. package/dist/{client-IOTK6GOS.js → client-T5MTY3CS.js} +3 -3
  22. package/dist/conduct-GZQNFTRP.js +19 -0
  23. package/dist/conduct-N52JX7RT.js +52 -0
  24. package/dist/{conductor-mode-XU7ONJWC.js → conductor-mode-XUWGR4ZE.js} +16 -9
  25. package/dist/execute-PNGQOMYO.js +10 -0
  26. package/dist/index.d.ts +1148 -915
  27. package/dist/index.js +589 -127
  28. package/dist/{peers-G36URZYB.js → peers-K7FSHPN3.js} +2 -1
  29. package/dist/request-4GQSSM4B.js +196 -0
  30. package/dist/serve-skill-TPHZH6BS.js +104 -0
  31. package/dist/server-365V3GYD.js +295 -0
  32. package/dist/websocket-client-6IIDGXKB.js +7 -0
  33. package/package.json +3 -6
  34. package/skills/agentbnb/HEARTBEAT.rules.md +47 -0
  35. package/skills/agentbnb/SKILL.md +166 -0
  36. package/skills/agentbnb/auto-request.ts +14 -0
  37. package/skills/agentbnb/auto-share.ts +10 -0
  38. package/skills/agentbnb/bootstrap.test.ts +323 -0
  39. package/skills/agentbnb/bootstrap.ts +126 -0
  40. package/skills/agentbnb/credit-mgr.ts +11 -0
  41. package/skills/agentbnb/gateway.ts +12 -0
  42. package/skills/agentbnb/install.sh +210 -0
  43. package/dist/chunk-BEI5MTNZ.js +0 -91
  44. package/dist/execute-GDGBU6DJ.js +0 -10
package/dist/index.js CHANGED
@@ -83,12 +83,45 @@ var SkillSchema = z.object({
83
83
  */
84
84
  _internal: z.record(z.unknown()).optional()
85
85
  });
86
+ var SuitabilitySchema = z.object({
87
+ /** Use cases this agent/skill is optimised for. */
88
+ ideal_for: z.array(z.string()).optional(),
89
+ /** Scenarios this agent/skill cannot reliably handle. */
90
+ not_suitable_for: z.array(z.string()).optional(),
91
+ /** Domains explicitly excluded (used for routing exclusions in later phases). */
92
+ excluded_domains: z.array(z.string()).optional(),
93
+ /** Conditions that increase failure risk, shown as warnings in the Hub. */
94
+ risk_conditions: z.array(z.string()).optional(),
95
+ /** Recommended alternative when this agent is unsuitable. */
96
+ fallback_recommendation: z.string().optional()
97
+ });
98
+ var LearningSchema = z.object({
99
+ /** Known limitations that may affect reliability (self-declared). */
100
+ known_limitations: z.array(z.string()).optional(),
101
+ /** Common failure patterns observed by the provider. */
102
+ common_failure_patterns: z.array(z.string()).optional(),
103
+ /** Version-tagged improvements the provider has shipped. */
104
+ recent_improvements: z.array(z.object({
105
+ version: z.string(),
106
+ summary: z.string(),
107
+ timestamp: z.string()
108
+ })).optional(),
109
+ /** Structured critiques from external sources (phase 2+). */
110
+ critiques: z.array(z.object({
111
+ type: z.literal("structured"),
112
+ summary: z.string(),
113
+ source_tier: z.string(),
114
+ timestamp: z.string()
115
+ })).optional()
116
+ });
86
117
  var CapabilityCardV2Schema = z.object({
87
118
  spec_version: z.literal("2.0"),
88
119
  id: z.string().uuid(),
89
120
  owner: z.string().min(1),
90
121
  /** Agent display name — was 'name' in v1.0. */
91
122
  agent_name: z.string().min(1).max(100),
123
+ /** Short one-liner shown in Hub v2 Identity Header. */
124
+ short_description: z.string().max(200).optional(),
92
125
  /** At least one skill is required. */
93
126
  skills: z.array(SkillSchema).min(1),
94
127
  availability: z.object({
@@ -100,6 +133,10 @@ var CapabilityCardV2Schema = z.object({
100
133
  runtime: z.string(),
101
134
  region: z.string().optional()
102
135
  }).optional(),
136
+ /** Suitability metadata for Hub v2 profile and future routing warnings. */
137
+ suitability: SuitabilitySchema.optional(),
138
+ /** Learning signals — self-declared limitations, improvements, critiques. */
139
+ learning: LearningSchema.optional(),
103
140
  /**
104
141
  * Private per-card metadata. Stripped from all API and CLI responses —
105
142
  * never transmitted beyond the local store.
@@ -436,30 +473,6 @@ function getCard(db, id) {
436
473
  if (!row) return null;
437
474
  return JSON.parse(row.data);
438
475
  }
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
476
  function updateReputation(db, cardId, success, latencyMs) {
464
477
  const existing = getCard(db, cardId);
465
478
  if (!existing) return;
@@ -783,7 +796,8 @@ async function executeCapabilityRequest(opts) {
783
796
  escrowReceipt: receipt,
784
797
  skillExecutor,
785
798
  handlerUrl,
786
- timeoutMs = 3e4
799
+ timeoutMs = 3e5,
800
+ onProgress
787
801
  } = opts;
788
802
  const card = getCard(registryDb, cardId);
789
803
  if (!card) {
@@ -890,7 +904,7 @@ async function executeCapabilityRequest(opts) {
890
904
  if (skillExecutor) {
891
905
  const targetSkillId = resolvedSkillId ?? skillId ?? cardId;
892
906
  try {
893
- const execResult = await skillExecutor.execute(targetSkillId, params);
907
+ const execResult = await skillExecutor.execute(targetSkillId, params, onProgress);
894
908
  if (!execResult.success) {
895
909
  return handleFailure("failure", execResult.latency_ms, execResult.error ?? "Execution failed");
896
910
  }
@@ -937,7 +951,7 @@ function createGatewayServer(opts) {
937
951
  creditDb,
938
952
  tokens,
939
953
  handlerUrl,
940
- timeoutMs = 3e4,
954
+ timeoutMs = 3e5,
941
955
  silent = false,
942
956
  skillExecutor
943
957
  } = opts;
@@ -1054,7 +1068,7 @@ var SkillExecutor = class {
1054
1068
  * @param params - Input parameters for the skill.
1055
1069
  * @returns ExecutionResult including success, result/error, and latency_ms.
1056
1070
  */
1057
- async execute(skillId, params) {
1071
+ async execute(skillId, params, onProgress) {
1058
1072
  const startTime = Date.now();
1059
1073
  const config = this.skillMap.get(skillId);
1060
1074
  if (!config) {
@@ -1073,7 +1087,7 @@ var SkillExecutor = class {
1073
1087
  };
1074
1088
  }
1075
1089
  try {
1076
- const modeResult = await mode.execute(config, params);
1090
+ const modeResult = await mode.execute(config, params, onProgress);
1077
1091
  return {
1078
1092
  ...modeResult,
1079
1093
  latency_ms: Date.now() - startTime
@@ -1493,7 +1507,7 @@ var PipelineExecutor = class {
1493
1507
  * @param params - Input parameters from the caller.
1494
1508
  * @returns Partial ExecutionResult (without latency_ms — added by SkillExecutor wrapper).
1495
1509
  */
1496
- async execute(config, params) {
1510
+ async execute(config, params, onProgress) {
1497
1511
  const pipelineConfig = config;
1498
1512
  const steps = pipelineConfig.steps ?? [];
1499
1513
  if (steps.length === 0) {
@@ -1552,6 +1566,13 @@ var PipelineExecutor = class {
1552
1566
  }
1553
1567
  context.steps.push({ result: stepResult });
1554
1568
  context.prev = { result: stepResult };
1569
+ if (onProgress && i < steps.length - 1) {
1570
+ onProgress({
1571
+ step: i + 1,
1572
+ total: steps.length,
1573
+ message: `Completed step ${i + 1}/${steps.length}`
1574
+ });
1575
+ }
1555
1576
  }
1556
1577
  const lastStep = context.steps[context.steps.length - 1];
1557
1578
  return {
@@ -1930,7 +1951,7 @@ function decompose(task, _availableCapabilities) {
1930
1951
  // src/gateway/client.ts
1931
1952
  import { randomUUID as randomUUID5 } from "crypto";
1932
1953
  async function requestCapability(opts) {
1933
- const { gatewayUrl, token, cardId, params = {}, timeoutMs = 3e4, escrowReceipt, identity } = opts;
1954
+ const { gatewayUrl, token, cardId, params = {}, timeoutMs = 3e5, escrowReceipt, identity } = opts;
1934
1955
  const id = randomUUID5();
1935
1956
  const payload = {
1936
1957
  jsonrpc: "2.0",
@@ -1984,6 +2005,7 @@ async function requestViaRelay(relay, opts) {
1984
2005
  cardId: opts.cardId,
1985
2006
  skillId: opts.skillId,
1986
2007
  params: opts.params ?? {},
2008
+ requester: opts.requester,
1987
2009
  escrowReceipt: opts.escrowReceipt,
1988
2010
  timeoutMs: opts.timeoutMs
1989
2011
  });
@@ -2030,6 +2052,73 @@ function loadConfig() {
2030
2052
  }
2031
2053
  }
2032
2054
 
2055
+ // src/cli/remote-registry.ts
2056
+ var RegistryTimeoutError = class extends AgentBnBError {
2057
+ constructor(url) {
2058
+ super(
2059
+ `Registry at ${url} did not respond within 5s. Showing local results only.`,
2060
+ "REGISTRY_TIMEOUT"
2061
+ );
2062
+ this.name = "RegistryTimeoutError";
2063
+ }
2064
+ };
2065
+ var RegistryConnectionError = class extends AgentBnBError {
2066
+ constructor(url) {
2067
+ super(
2068
+ `Cannot reach ${url}. Is the registry running? Showing local results only.`,
2069
+ "REGISTRY_CONNECTION"
2070
+ );
2071
+ this.name = "RegistryConnectionError";
2072
+ }
2073
+ };
2074
+ var RegistryAuthError = class extends AgentBnBError {
2075
+ constructor(url) {
2076
+ super(
2077
+ `Authentication failed for ${url}. Run \`agentbnb config set token <your-token>\`.`,
2078
+ "REGISTRY_AUTH"
2079
+ );
2080
+ this.name = "RegistryAuthError";
2081
+ }
2082
+ };
2083
+ async function fetchRemoteCards(registryUrl, params, timeoutMs = 5e3) {
2084
+ let cardsUrl;
2085
+ try {
2086
+ cardsUrl = new URL("/cards", registryUrl);
2087
+ } catch {
2088
+ throw new AgentBnBError(`Invalid registry URL: ${registryUrl}`, "INVALID_REGISTRY_URL");
2089
+ }
2090
+ const searchParams = new URLSearchParams();
2091
+ if (params.q !== void 0) searchParams.set("q", params.q);
2092
+ if (params.level !== void 0) searchParams.set("level", String(params.level));
2093
+ if (params.online !== void 0) searchParams.set("online", String(params.online));
2094
+ if (params.tag !== void 0) searchParams.set("tag", params.tag);
2095
+ searchParams.set("limit", "100");
2096
+ cardsUrl.search = searchParams.toString();
2097
+ const controller = new AbortController();
2098
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
2099
+ let response;
2100
+ try {
2101
+ response = await fetch(cardsUrl.toString(), { signal: controller.signal });
2102
+ } catch (err) {
2103
+ clearTimeout(timer);
2104
+ const isTimeout = err instanceof Error && err.name === "AbortError";
2105
+ if (isTimeout) {
2106
+ throw new RegistryTimeoutError(registryUrl);
2107
+ }
2108
+ throw new RegistryConnectionError(registryUrl);
2109
+ } finally {
2110
+ clearTimeout(timer);
2111
+ }
2112
+ if (response.status === 401 || response.status === 403) {
2113
+ throw new RegistryAuthError(registryUrl);
2114
+ }
2115
+ if (!response.ok) {
2116
+ throw new RegistryConnectionError(registryUrl);
2117
+ }
2118
+ const body = await response.json();
2119
+ return body.items;
2120
+ }
2121
+
2033
2122
  // src/autonomy/auto-request.ts
2034
2123
  function minMaxNormalize(values) {
2035
2124
  if (values.length === 0) return [];
@@ -2064,10 +2153,17 @@ function scorePeers(candidates, selfOwner) {
2064
2153
 
2065
2154
  // src/conductor/capability-matcher.ts
2066
2155
  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 });
2156
+ async function matchSubTasks(opts) {
2157
+ const { db, subtasks, conductorOwner, registryUrl } = opts;
2158
+ return Promise.all(subtasks.map(async (subtask) => {
2159
+ let cards = searchCards(db, subtask.required_capability, { online: true });
2160
+ if (cards.length === 0 && registryUrl) {
2161
+ try {
2162
+ cards = await fetchRemoteCards(registryUrl, { q: subtask.required_capability, online: true });
2163
+ } catch {
2164
+ cards = [];
2165
+ }
2166
+ }
2071
2167
  const candidates = [];
2072
2168
  for (const card of cards) {
2073
2169
  const cardAsV2 = card;
@@ -2109,11 +2205,12 @@ function matchSubTasks(opts) {
2109
2205
  subtask_id: subtask.id,
2110
2206
  selected_agent: top.card.owner,
2111
2207
  selected_skill: top.skillId ?? "",
2208
+ selected_card_id: top.card.id,
2112
2209
  score: top.rawScore,
2113
2210
  credits: top.cost,
2114
2211
  alternatives
2115
2212
  };
2116
- });
2213
+ }));
2117
2214
  }
2118
2215
 
2119
2216
  // src/conductor/budget-controller.ts
@@ -2184,13 +2281,20 @@ var BudgetController = class {
2184
2281
  };
2185
2282
 
2186
2283
  // src/conductor/card.ts
2284
+ import { createHash } from "crypto";
2187
2285
  var CONDUCTOR_OWNER = "agentbnb-conductor";
2188
2286
  var CONDUCTOR_CARD_ID = "00000000-0000-4000-8000-000000000001";
2189
- function buildConductorCard() {
2287
+ function ownerToCardId(owner) {
2288
+ const hash = createHash("sha256").update(owner).digest("hex").slice(0, 32);
2289
+ return `${hash.slice(0, 8)}-${hash.slice(8, 12)}-4${hash.slice(13, 16)}-8${hash.slice(17, 20)}-${hash.slice(20, 32)}`;
2290
+ }
2291
+ function buildConductorCard(owner) {
2292
+ const cardOwner = owner ?? CONDUCTOR_OWNER;
2293
+ const cardId = owner ? ownerToCardId(owner) : CONDUCTOR_CARD_ID;
2190
2294
  const card = {
2191
2295
  spec_version: "2.0",
2192
- id: CONDUCTOR_CARD_ID,
2193
- owner: CONDUCTOR_OWNER,
2296
+ id: cardId,
2297
+ owner: cardOwner,
2194
2298
  agent_name: "AgentBnB Conductor",
2195
2299
  skills: [
2196
2300
  {
@@ -2281,7 +2385,7 @@ function computeWaves(subtasks) {
2281
2385
  return waves;
2282
2386
  }
2283
2387
  async function orchestrate(opts) {
2284
- const { subtasks, matches, gatewayToken, resolveAgentUrl, timeoutMs = 3e4, maxBudget } = opts;
2388
+ const { subtasks, matches, gatewayToken, resolveAgentUrl, timeoutMs = 3e5, maxBudget, relayClient, requesterOwner } = opts;
2285
2389
  const startTime = Date.now();
2286
2390
  if (subtasks.length === 0) {
2287
2391
  return {
@@ -2332,26 +2436,50 @@ async function orchestrate(opts) {
2332
2436
  );
2333
2437
  const primary = resolveAgentUrl(m.selected_agent);
2334
2438
  try {
2335
- const res = await requestCapability({
2336
- gatewayUrl: primary.url,
2337
- token: gatewayToken,
2338
- cardId: primary.cardId,
2339
- params: interpolatedParams,
2340
- timeoutMs
2341
- });
2439
+ let res;
2440
+ if (primary.url.startsWith("relay://") && relayClient) {
2441
+ const targetOwner = primary.url.replace("relay://", "");
2442
+ res = await relayClient.request({
2443
+ targetOwner,
2444
+ cardId: primary.cardId,
2445
+ params: interpolatedParams,
2446
+ requester: requesterOwner,
2447
+ timeoutMs
2448
+ });
2449
+ } else {
2450
+ res = await requestCapability({
2451
+ gatewayUrl: primary.url,
2452
+ token: gatewayToken,
2453
+ cardId: primary.cardId,
2454
+ params: interpolatedParams,
2455
+ timeoutMs
2456
+ });
2457
+ }
2342
2458
  return { taskId, result: res, credits: m.credits };
2343
2459
  } catch (primaryErr) {
2344
2460
  if (m.alternatives.length > 0) {
2345
2461
  const alt = m.alternatives[0];
2346
2462
  const altAgent = resolveAgentUrl(alt.agent);
2347
2463
  try {
2348
- const altRes = await requestCapability({
2349
- gatewayUrl: altAgent.url,
2350
- token: gatewayToken,
2351
- cardId: altAgent.cardId,
2352
- params: interpolatedParams,
2353
- timeoutMs
2354
- });
2464
+ let altRes;
2465
+ if (altAgent.url.startsWith("relay://") && relayClient) {
2466
+ const targetOwner = altAgent.url.replace("relay://", "");
2467
+ altRes = await relayClient.request({
2468
+ targetOwner,
2469
+ cardId: altAgent.cardId,
2470
+ params: interpolatedParams,
2471
+ requester: requesterOwner,
2472
+ timeoutMs
2473
+ });
2474
+ } else {
2475
+ altRes = await requestCapability({
2476
+ gatewayUrl: altAgent.url,
2477
+ token: gatewayToken,
2478
+ cardId: altAgent.cardId,
2479
+ params: interpolatedParams,
2480
+ timeoutMs
2481
+ });
2482
+ }
2355
2483
  return { taskId, result: altRes, credits: alt.credits };
2356
2484
  } catch (altErr) {
2357
2485
  throw new Error(
@@ -2452,7 +2580,7 @@ var ConductorMode = class {
2452
2580
  * @param params - Must include `task` string.
2453
2581
  * @returns Execution result without latency_ms (added by SkillExecutor).
2454
2582
  */
2455
- async execute(config, params) {
2583
+ async execute(config, params, onProgress) {
2456
2584
  const conductorSkill = config.conductor_skill;
2457
2585
  if (conductorSkill !== "orchestrate" && conductorSkill !== "plan") {
2458
2586
  return {
@@ -2474,11 +2602,13 @@ var ConductorMode = class {
2474
2602
  error: "No template matches task"
2475
2603
  };
2476
2604
  }
2477
- const matchResults = matchSubTasks({
2605
+ onProgress?.({ step: 1, total: 5, message: `Decomposed into ${subtasks.length} sub-tasks` });
2606
+ const matchResults = await matchSubTasks({
2478
2607
  db: this.db,
2479
2608
  subtasks,
2480
2609
  conductorOwner: this.conductorOwner
2481
2610
  });
2611
+ onProgress?.({ step: 2, total: 5, message: `Matched ${matchResults.length} sub-tasks to agents` });
2482
2612
  const budgetManager = new BudgetManager(this.creditDb, this.conductorOwner);
2483
2613
  const budgetController = new BudgetController(budgetManager, this.maxBudget);
2484
2614
  const executionBudget = budgetController.calculateBudget(matchResults);
@@ -2488,6 +2618,7 @@ var ConductorMode = class {
2488
2618
  error: `Budget exceeded: estimated ${executionBudget.estimated_total} cr, max ${this.maxBudget} cr`
2489
2619
  };
2490
2620
  }
2621
+ onProgress?.({ step: 3, total: 5, message: `Budget approved: ${executionBudget.estimated_total} cr` });
2491
2622
  if (conductorSkill === "plan") {
2492
2623
  return {
2493
2624
  success: true,
@@ -2508,6 +2639,7 @@ var ConductorMode = class {
2508
2639
  resolveAgentUrl: this.resolveAgentUrl,
2509
2640
  maxBudget: this.maxBudget
2510
2641
  });
2642
+ onProgress?.({ step: 4, total: 5, message: "Pipeline execution complete" });
2511
2643
  const resultObj = {};
2512
2644
  for (const [key, value] of orchResult.results) {
2513
2645
  resultObj[key] = value;
@@ -2560,7 +2692,7 @@ function createSignedEscrowReceipt(db, privateKey, publicKey, opts) {
2560
2692
 
2561
2693
  // src/identity/identity.ts
2562
2694
  import { z as z4 } from "zod";
2563
- import { createHash } from "crypto";
2695
+ import { createHash as createHash2 } from "crypto";
2564
2696
  import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, existsSync as existsSync4, mkdirSync as mkdirSync3 } from "fs";
2565
2697
  import { join as join4 } from "path";
2566
2698
  var AgentIdentitySchema = z4.object({
@@ -2591,7 +2723,7 @@ var AgentCertificateSchema = z4.object({
2591
2723
  });
2592
2724
  var IDENTITY_FILENAME = "identity.json";
2593
2725
  function deriveAgentId(publicKeyHex) {
2594
- return createHash("sha256").update(publicKeyHex, "hex").digest("hex").slice(0, 16);
2726
+ return createHash2("sha256").update(publicKeyHex, "hex").digest("hex").slice(0, 16);
2595
2727
  }
2596
2728
  function createIdentity(configDir, owner) {
2597
2729
  if (!existsSync4(configDir)) {
@@ -2666,7 +2798,13 @@ function verifyAgentCertificate(cert) {
2666
2798
  }
2667
2799
  function ensureIdentity(configDir, owner) {
2668
2800
  const existing = loadIdentity(configDir);
2669
- if (existing) return existing;
2801
+ if (existing) {
2802
+ if (existing.owner !== owner) {
2803
+ existing.owner = owner;
2804
+ saveIdentity(configDir, existing);
2805
+ }
2806
+ return existing;
2807
+ }
2670
2808
  return createIdentity(configDir, owner);
2671
2809
  }
2672
2810
 
@@ -3036,8 +3174,10 @@ var RegisterMessageSchema = z6.object({
3036
3174
  type: z6.literal("register"),
3037
3175
  owner: z6.string().min(1),
3038
3176
  token: z6.string().min(1),
3039
- card: z6.record(z6.unknown())
3177
+ card: z6.record(z6.unknown()),
3040
3178
  // CapabilityCard (validated separately)
3179
+ cards: z6.array(z6.record(z6.unknown())).optional()
3180
+ // Additional cards (e.g., conductor card)
3041
3181
  });
3042
3182
  var RegisteredMessageSchema = z6.object({
3043
3183
  type: z6.literal("registered"),
@@ -3087,6 +3227,15 @@ var ErrorMessageSchema = z6.object({
3087
3227
  message: z6.string(),
3088
3228
  request_id: z6.string().optional()
3089
3229
  });
3230
+ var RelayProgressMessageSchema = z6.object({
3231
+ type: z6.literal("relay_progress"),
3232
+ id: z6.string().uuid(),
3233
+ // request ID this progress relates to
3234
+ progress: z6.number().min(0).max(100).optional(),
3235
+ // optional percentage
3236
+ message: z6.string().optional()
3237
+ // optional status message
3238
+ });
3090
3239
  var RelayMessageSchema = z6.discriminatedUnion("type", [
3091
3240
  RegisterMessageSchema,
3092
3241
  RegisteredMessageSchema,
@@ -3094,18 +3243,118 @@ var RelayMessageSchema = z6.discriminatedUnion("type", [
3094
3243
  IncomingRequestMessageSchema,
3095
3244
  RelayResponseMessageSchema,
3096
3245
  ResponseMessageSchema,
3097
- ErrorMessageSchema
3246
+ ErrorMessageSchema,
3247
+ RelayProgressMessageSchema
3098
3248
  ]);
3099
3249
 
3100
3250
  // src/relay/websocket-relay.ts
3251
+ import { randomUUID as randomUUID12 } from "crypto";
3252
+
3253
+ // src/relay/relay-credit.ts
3254
+ function lookupCardPrice(registryDb, cardId, skillId) {
3255
+ const row = registryDb.prepare("SELECT data FROM capability_cards WHERE id = ?").get(cardId);
3256
+ if (!row) return null;
3257
+ let card;
3258
+ try {
3259
+ card = JSON.parse(row.data);
3260
+ } catch {
3261
+ return null;
3262
+ }
3263
+ if (Array.isArray(card.skills) && card.skills.length > 0) {
3264
+ const skills = card.skills;
3265
+ if (skillId) {
3266
+ const skill = skills.find((s) => s.id === skillId);
3267
+ if (skill) {
3268
+ const skillPricing = skill.pricing;
3269
+ if (skillPricing && typeof skillPricing.credits_per_call === "number") {
3270
+ return skillPricing.credits_per_call;
3271
+ }
3272
+ }
3273
+ } else {
3274
+ let minPrice = null;
3275
+ for (const s of skills) {
3276
+ const sp = s.pricing;
3277
+ if (sp && typeof sp.credits_per_call === "number" && sp.credits_per_call > 0) {
3278
+ if (minPrice === null || sp.credits_per_call < minPrice) {
3279
+ minPrice = sp.credits_per_call;
3280
+ }
3281
+ }
3282
+ }
3283
+ if (minPrice !== null) return minPrice;
3284
+ }
3285
+ }
3286
+ const pricing = card.pricing;
3287
+ if (!pricing || typeof pricing.credits_per_call !== "number") {
3288
+ return null;
3289
+ }
3290
+ return pricing.credits_per_call;
3291
+ }
3292
+ function holdForRelay(creditDb, owner, amount, cardId) {
3293
+ return holdEscrow(creditDb, owner, amount, cardId);
3294
+ }
3295
+ function settleForRelay(creditDb, escrowId, recipientOwner) {
3296
+ settleEscrow(creditDb, escrowId, recipientOwner);
3297
+ }
3298
+ function calculateConductorFee(totalSubTaskCost) {
3299
+ if (totalSubTaskCost <= 0) return 0;
3300
+ const fee = Math.ceil(totalSubTaskCost * 0.1);
3301
+ return Math.max(1, Math.min(20, fee));
3302
+ }
3303
+ function releaseForRelay(creditDb, escrowId) {
3304
+ if (escrowId === void 0) return;
3305
+ releaseEscrow(creditDb, escrowId);
3306
+ }
3307
+
3308
+ // src/hub-agent/relay-bridge.ts
3309
+ import { randomUUID as randomUUID11 } from "crypto";
3310
+
3311
+ // src/hub-agent/job-queue.ts
3101
3312
  import { randomUUID as randomUUID10 } from "crypto";
3313
+ function updateJobStatus(db, jobId, status, result) {
3314
+ const now = (/* @__PURE__ */ new Date()).toISOString();
3315
+ if (result !== void 0) {
3316
+ db.prepare("UPDATE hub_agent_jobs SET status = ?, result = ?, updated_at = ? WHERE id = ?").run(status, result, now, jobId);
3317
+ } else {
3318
+ db.prepare("UPDATE hub_agent_jobs SET status = ?, updated_at = ? WHERE id = ?").run(status, now, jobId);
3319
+ }
3320
+ }
3321
+
3322
+ // src/hub-agent/crypto.ts
3323
+ import { createCipheriv, createDecipheriv, randomBytes } from "crypto";
3324
+
3325
+ // src/hub-agent/relay-bridge.ts
3326
+ function handleJobRelayResponse(opts) {
3327
+ const { registryDb, creditDb, jobId, escrowId, relayOwner, result, error } = opts;
3328
+ if (error) {
3329
+ updateJobStatus(registryDb, jobId, "failed", JSON.stringify(error));
3330
+ if (escrowId) {
3331
+ try {
3332
+ releaseForRelay(creditDb, escrowId);
3333
+ } catch (e) {
3334
+ console.error("[relay-bridge] escrow release on error failed:", e);
3335
+ }
3336
+ }
3337
+ } else {
3338
+ updateJobStatus(registryDb, jobId, "completed", JSON.stringify(result));
3339
+ if (escrowId) {
3340
+ try {
3341
+ settleForRelay(creditDb, escrowId, relayOwner);
3342
+ } catch (e) {
3343
+ console.error("[relay-bridge] escrow settle failed:", e);
3344
+ }
3345
+ }
3346
+ }
3347
+ }
3348
+
3349
+ // src/relay/websocket-relay.ts
3102
3350
  var RATE_LIMIT_MAX = 60;
3103
3351
  var RATE_LIMIT_WINDOW_MS = 6e4;
3104
- var RELAY_TIMEOUT_MS = 3e4;
3105
- function registerWebSocketRelay(server, db) {
3352
+ var RELAY_TIMEOUT_MS = 3e5;
3353
+ function registerWebSocketRelay(server, db, creditDb) {
3106
3354
  const connections = /* @__PURE__ */ new Map();
3107
3355
  const pendingRequests = /* @__PURE__ */ new Map();
3108
3356
  const rateLimits = /* @__PURE__ */ new Map();
3357
+ let onAgentOnlineCallback;
3109
3358
  function checkRateLimit(owner) {
3110
3359
  const now = Date.now();
3111
3360
  const entry = rateLimits.get(owner);
@@ -3156,21 +3405,32 @@ function registerWebSocketRelay(server, db) {
3156
3405
  }
3157
3406
  }
3158
3407
  function upsertCard(cardData, owner) {
3159
- const cardId = cardData.id;
3160
- const existing = getCard(db, cardId);
3408
+ const parsed = AnyCardSchema.safeParse(cardData);
3409
+ if (!parsed.success) {
3410
+ throw new AgentBnBError(
3411
+ `Card validation failed: ${parsed.error.message}`,
3412
+ "VALIDATION_ERROR"
3413
+ );
3414
+ }
3415
+ const card = { ...parsed.data, availability: { ...parsed.data.availability, online: true } };
3416
+ const cardId = card.id;
3417
+ const now = (/* @__PURE__ */ new Date()).toISOString();
3418
+ const existing = db.prepare("SELECT id FROM capability_cards WHERE id = ?").get(cardId);
3161
3419
  if (existing) {
3162
- const updates = { ...cardData, availability: { ...cardData.availability ?? {}, online: true } };
3163
- updateCard(db, cardId, owner, updates);
3420
+ db.prepare(
3421
+ "UPDATE capability_cards SET data = ?, updated_at = ? WHERE id = ?"
3422
+ ).run(JSON.stringify(card), now, cardId);
3164
3423
  } else {
3165
- const card = { ...cardData, availability: { ...cardData.availability ?? {}, online: true } };
3166
- insertCard(db, card);
3424
+ db.prepare(
3425
+ "INSERT INTO capability_cards (id, owner, data, created_at, updated_at) VALUES (?, ?, ?, ?, ?)"
3426
+ ).run(cardId, owner, JSON.stringify(card), now, now);
3167
3427
  }
3168
3428
  return cardId;
3169
3429
  }
3170
3430
  function logAgentJoined(owner, cardName, cardId) {
3171
3431
  try {
3172
3432
  insertRequestLog(db, {
3173
- id: randomUUID10(),
3433
+ id: randomUUID12(),
3174
3434
  card_id: cardId,
3175
3435
  card_name: cardName,
3176
3436
  requester: owner,
@@ -3198,13 +3458,34 @@ function registerWebSocketRelay(server, db) {
3198
3458
  }
3199
3459
  }
3200
3460
  connections.set(owner, ws);
3201
- const cardId = upsertCard(card, owner);
3461
+ let cardId;
3462
+ try {
3463
+ cardId = upsertCard(card, owner);
3464
+ } catch (err) {
3465
+ console.error(`[relay] card validation failed for ${owner}:`, err instanceof Error ? err.message : err);
3466
+ cardId = card.id ?? owner;
3467
+ }
3202
3468
  const cardName = card.name ?? card.agent_name ?? owner;
3203
3469
  logAgentJoined(owner, cardName, cardId);
3470
+ if (msg.cards && msg.cards.length > 0) {
3471
+ for (const extraCard of msg.cards) {
3472
+ try {
3473
+ upsertCard(extraCard, owner);
3474
+ } catch {
3475
+ }
3476
+ }
3477
+ }
3204
3478
  markOwnerOnline(owner);
3479
+ if (onAgentOnlineCallback) {
3480
+ try {
3481
+ onAgentOnlineCallback(owner);
3482
+ } catch (e) {
3483
+ console.error("[relay] onAgentOnline callback error:", e);
3484
+ }
3485
+ }
3205
3486
  sendMessage(ws, { type: "registered", agent_id: cardId });
3206
3487
  }
3207
- function handleRelayRequest(ws, msg, fromOwner) {
3488
+ async function handleRelayRequest(ws, msg, fromOwner) {
3208
3489
  if (!checkRateLimit(fromOwner)) {
3209
3490
  sendMessage(ws, {
3210
3491
  type: "error",
@@ -3223,15 +3504,43 @@ function registerWebSocketRelay(server, db) {
3223
3504
  });
3224
3505
  return;
3225
3506
  }
3507
+ const creditOwner = msg.requester ?? fromOwner;
3508
+ let escrowId;
3509
+ if (creditDb) {
3510
+ try {
3511
+ const price = lookupCardPrice(db, msg.card_id, msg.skill_id);
3512
+ if (price !== null && price > 0) {
3513
+ escrowId = holdForRelay(creditDb, creditOwner, price, msg.card_id);
3514
+ }
3515
+ } catch (err) {
3516
+ if (err instanceof AgentBnBError && err.code === "INSUFFICIENT_CREDITS") {
3517
+ sendMessage(ws, {
3518
+ type: "response",
3519
+ id: msg.id,
3520
+ error: { code: -32603, message: "Insufficient credits" }
3521
+ });
3522
+ return;
3523
+ }
3524
+ console.error("[relay] credit hold error (non-fatal):", err);
3525
+ }
3526
+ }
3226
3527
  const timeout = setTimeout(() => {
3528
+ const pending = pendingRequests.get(msg.id);
3227
3529
  pendingRequests.delete(msg.id);
3530
+ if (pending?.escrowId && creditDb) {
3531
+ try {
3532
+ releaseForRelay(creditDb, pending.escrowId);
3533
+ } catch (e) {
3534
+ console.error("[relay] escrow release on timeout failed:", e);
3535
+ }
3536
+ }
3228
3537
  sendMessage(ws, {
3229
3538
  type: "response",
3230
3539
  id: msg.id,
3231
3540
  error: { code: -32603, message: "Relay request timeout" }
3232
3541
  });
3233
3542
  }, RELAY_TIMEOUT_MS);
3234
- pendingRequests.set(msg.id, { originOwner: fromOwner, timeout });
3543
+ pendingRequests.set(msg.id, { originOwner: fromOwner, creditOwner, timeout, escrowId, targetOwner: msg.target_owner });
3235
3544
  sendMessage(targetWs, {
3236
3545
  type: "incoming_request",
3237
3546
  id: msg.id,
@@ -3243,18 +3552,98 @@ function registerWebSocketRelay(server, db) {
3243
3552
  escrow_receipt: msg.escrow_receipt
3244
3553
  });
3245
3554
  }
3555
+ function handleRelayProgress(msg) {
3556
+ const pending = pendingRequests.get(msg.id);
3557
+ if (!pending) return;
3558
+ clearTimeout(pending.timeout);
3559
+ const newTimeout = setTimeout(() => {
3560
+ const p = pendingRequests.get(msg.id);
3561
+ pendingRequests.delete(msg.id);
3562
+ if (p?.escrowId && creditDb) {
3563
+ try {
3564
+ releaseForRelay(creditDb, p.escrowId);
3565
+ } catch (e) {
3566
+ console.error("[relay] escrow release on progress timeout failed:", e);
3567
+ }
3568
+ }
3569
+ const originWs2 = connections.get(pending.originOwner);
3570
+ if (originWs2 && originWs2.readyState === 1) {
3571
+ sendMessage(originWs2, {
3572
+ type: "response",
3573
+ id: msg.id,
3574
+ error: { code: -32603, message: "Relay request timeout" }
3575
+ });
3576
+ }
3577
+ }, RELAY_TIMEOUT_MS);
3578
+ pending.timeout = newTimeout;
3579
+ const originWs = connections.get(pending.originOwner);
3580
+ if (originWs && originWs.readyState === 1) {
3581
+ sendMessage(originWs, {
3582
+ type: "relay_progress",
3583
+ id: msg.id,
3584
+ progress: msg.progress,
3585
+ message: msg.message
3586
+ });
3587
+ }
3588
+ }
3246
3589
  function handleRelayResponse(msg) {
3247
3590
  const pending = pendingRequests.get(msg.id);
3248
3591
  if (!pending) return;
3249
3592
  clearTimeout(pending.timeout);
3250
3593
  pendingRequests.delete(msg.id);
3594
+ if (pending.jobId && creditDb) {
3595
+ try {
3596
+ handleJobRelayResponse({
3597
+ registryDb: db,
3598
+ creditDb,
3599
+ jobId: pending.jobId,
3600
+ escrowId: pending.escrowId,
3601
+ relayOwner: pending.targetOwner ?? "",
3602
+ result: msg.error === void 0 ? msg.result : void 0,
3603
+ error: msg.error
3604
+ });
3605
+ } catch (e) {
3606
+ console.error("[relay] job relay response handling failed:", e);
3607
+ }
3608
+ const originWs2 = connections.get(pending.originOwner);
3609
+ if (originWs2 && originWs2.readyState === 1) {
3610
+ sendMessage(originWs2, { type: "response", id: msg.id, result: msg.result, error: msg.error });
3611
+ }
3612
+ return;
3613
+ }
3614
+ if (pending.escrowId && creditDb) {
3615
+ try {
3616
+ if (msg.error === void 0) {
3617
+ settleForRelay(creditDb, pending.escrowId, pending.targetOwner);
3618
+ } else {
3619
+ releaseForRelay(creditDb, pending.escrowId);
3620
+ }
3621
+ } catch (e) {
3622
+ console.error("[relay] escrow settle/release on response failed:", e);
3623
+ }
3624
+ }
3625
+ let conductorFee = 0;
3626
+ if (creditDb && msg.error === void 0 && typeof msg.result === "object" && msg.result !== null && "total_credits" in msg.result && typeof msg.result.total_credits === "number") {
3627
+ const totalCredits = msg.result.total_credits;
3628
+ conductorFee = calculateConductorFee(totalCredits);
3629
+ if (conductorFee > 0) {
3630
+ try {
3631
+ const feeEscrowId = holdForRelay(creditDb, pending.creditOwner ?? pending.originOwner, conductorFee, msg.id);
3632
+ settleForRelay(creditDb, feeEscrowId, pending.targetOwner);
3633
+ } catch (e) {
3634
+ console.error("[relay] conductor fee settlement failed (non-fatal):", e);
3635
+ conductorFee = 0;
3636
+ }
3637
+ }
3638
+ }
3251
3639
  const originWs = connections.get(pending.originOwner);
3252
3640
  if (originWs && originWs.readyState === 1) {
3253
3641
  sendMessage(originWs, {
3254
3642
  type: "response",
3255
3643
  id: msg.id,
3256
3644
  result: msg.result,
3257
- error: msg.error
3645
+ error: msg.error,
3646
+ ...conductorFee > 0 ? { conductor_fee: conductorFee } : {}
3258
3647
  });
3259
3648
  }
3260
3649
  }
@@ -3264,61 +3653,93 @@ function registerWebSocketRelay(server, db) {
3264
3653
  rateLimits.delete(owner);
3265
3654
  markOwnerOffline(owner);
3266
3655
  for (const [reqId, pending] of pendingRequests) {
3267
- if (pending.originOwner === owner) {
3656
+ if (pending.targetOwner === owner) {
3268
3657
  clearTimeout(pending.timeout);
3269
3658
  pendingRequests.delete(reqId);
3659
+ if (pending.escrowId && creditDb) {
3660
+ try {
3661
+ releaseForRelay(creditDb, pending.escrowId);
3662
+ } catch (e) {
3663
+ console.error("[relay] escrow release on disconnect failed:", e);
3664
+ }
3665
+ }
3666
+ const originWs = connections.get(pending.originOwner);
3667
+ if (originWs && originWs.readyState === 1) {
3668
+ sendMessage(originWs, {
3669
+ type: "response",
3670
+ id: reqId,
3671
+ error: { code: -32603, message: "Provider disconnected" }
3672
+ });
3673
+ }
3674
+ } else if (pending.originOwner === owner) {
3675
+ clearTimeout(pending.timeout);
3676
+ pendingRequests.delete(reqId);
3677
+ if (pending.escrowId && creditDb) {
3678
+ try {
3679
+ releaseForRelay(creditDb, pending.escrowId);
3680
+ } catch (e) {
3681
+ console.error("[relay] escrow release on requester disconnect failed:", e);
3682
+ }
3683
+ }
3270
3684
  }
3271
3685
  }
3272
3686
  }
3273
- server.get("/ws", { websocket: true }, (rawSocket, _request) => {
3274
- const socket = rawSocket;
3275
- let registeredOwner;
3276
- 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) {
3687
+ void server.register(async (app) => {
3688
+ app.get("/ws", { websocket: true }, (rawSocket, _request) => {
3689
+ const socket = rawSocket;
3690
+ let registeredOwner;
3691
+ socket.on("message", (raw) => {
3692
+ void (async () => {
3693
+ let data;
3694
+ try {
3695
+ data = JSON.parse(typeof raw === "string" ? raw : raw.toString("utf-8"));
3696
+ } catch {
3697
+ sendMessage(socket, { type: "error", code: "invalid_json", message: "Invalid JSON" });
3698
+ return;
3699
+ }
3700
+ const parsed = RelayMessageSchema.safeParse(data);
3701
+ if (!parsed.success) {
3301
3702
  sendMessage(socket, {
3302
3703
  type: "error",
3303
- code: "not_registered",
3304
- message: "Must send register message before relay requests"
3704
+ code: "invalid_message",
3705
+ message: `Invalid message: ${parsed.error.issues[0]?.message ?? "unknown error"}`
3305
3706
  });
3306
3707
  return;
3307
3708
  }
3308
- handleRelayRequest(socket, msg, registeredOwner);
3309
- break;
3310
- case "relay_response":
3311
- handleRelayResponse(msg);
3312
- break;
3313
- default:
3314
- break;
3315
- }
3316
- });
3317
- socket.on("close", () => {
3318
- handleDisconnect(registeredOwner);
3319
- });
3320
- socket.on("error", () => {
3321
- handleDisconnect(registeredOwner);
3709
+ const msg = parsed.data;
3710
+ switch (msg.type) {
3711
+ case "register":
3712
+ registeredOwner = msg.owner;
3713
+ handleRegister(socket, msg);
3714
+ break;
3715
+ case "relay_request":
3716
+ if (!registeredOwner) {
3717
+ sendMessage(socket, {
3718
+ type: "error",
3719
+ code: "not_registered",
3720
+ message: "Must send register message before relay requests"
3721
+ });
3722
+ return;
3723
+ }
3724
+ await handleRelayRequest(socket, msg, registeredOwner);
3725
+ break;
3726
+ case "relay_response":
3727
+ handleRelayResponse(msg);
3728
+ break;
3729
+ case "relay_progress":
3730
+ handleRelayProgress(msg);
3731
+ break;
3732
+ default:
3733
+ break;
3734
+ }
3735
+ })();
3736
+ });
3737
+ socket.on("close", () => {
3738
+ handleDisconnect(registeredOwner);
3739
+ });
3740
+ socket.on("error", () => {
3741
+ handleDisconnect(registeredOwner);
3742
+ });
3322
3743
  });
3323
3744
  });
3324
3745
  return {
@@ -3337,13 +3758,21 @@ function registerWebSocketRelay(server, db) {
3337
3758
  }
3338
3759
  pendingRequests.clear();
3339
3760
  rateLimits.clear();
3761
+ },
3762
+ setOnAgentOnline: (cb) => {
3763
+ onAgentOnlineCallback = cb;
3764
+ },
3765
+ getConnections: () => connections,
3766
+ getPendingRequests: () => pendingRequests,
3767
+ sendMessage: (ws, msg) => {
3768
+ sendMessage(ws, msg);
3340
3769
  }
3341
3770
  };
3342
3771
  }
3343
3772
 
3344
3773
  // src/relay/websocket-client.ts
3345
3774
  import WebSocket from "ws";
3346
- import { randomUUID as randomUUID11 } from "crypto";
3775
+ import { randomUUID as randomUUID13 } from "crypto";
3347
3776
  var RelayClient = class {
3348
3777
  ws = null;
3349
3778
  opts;
@@ -3375,7 +3804,8 @@ var RelayClient = class {
3375
3804
  type: "register",
3376
3805
  owner: this.opts.owner,
3377
3806
  token: this.opts.token,
3378
- card: this.opts.card
3807
+ card: this.opts.card,
3808
+ ...this.opts.cards && this.opts.cards.length > 0 ? { cards: this.opts.cards } : {}
3379
3809
  });
3380
3810
  });
3381
3811
  this.ws.on("message", (raw) => {
@@ -3439,14 +3869,14 @@ var RelayClient = class {
3439
3869
  if (!this.ws || this.ws.readyState !== WebSocket.OPEN || !this.registered) {
3440
3870
  throw new Error("Not connected to registry relay");
3441
3871
  }
3442
- const id = randomUUID11();
3443
- const timeoutMs = opts.timeoutMs ?? 3e4;
3872
+ const id = randomUUID13();
3873
+ const timeoutMs = opts.timeoutMs ?? 3e5;
3444
3874
  return new Promise((resolve, reject) => {
3445
3875
  const timeout = setTimeout(() => {
3446
3876
  this.pendingRequests.delete(id);
3447
3877
  reject(new Error("Relay request timeout"));
3448
3878
  }, timeoutMs);
3449
- this.pendingRequests.set(id, { resolve, reject, timeout });
3879
+ this.pendingRequests.set(id, { resolve, reject, timeout, timeoutMs, onProgress: opts.onProgress });
3450
3880
  this.send({
3451
3881
  type: "relay_request",
3452
3882
  id,
@@ -3459,6 +3889,22 @@ var RelayClient = class {
3459
3889
  });
3460
3890
  });
3461
3891
  }
3892
+ /**
3893
+ * Send a relay_progress message to the relay server for a given request.
3894
+ * Used by the onRequest handler to forward SkillExecutor progress updates
3895
+ * to the requesting agent so it can reset its timeout window.
3896
+ *
3897
+ * @param requestId - The relay request ID to associate progress with.
3898
+ * @param info - Progress details (step, total, message).
3899
+ */
3900
+ sendProgress(requestId, info) {
3901
+ this.send({
3902
+ type: "relay_progress",
3903
+ id: requestId,
3904
+ progress: Math.round(info.step / info.total * 100),
3905
+ message: info.message
3906
+ });
3907
+ }
3462
3908
  /** Whether the client is connected and registered */
3463
3909
  get isConnected() {
3464
3910
  return this.ws !== null && this.ws.readyState === WebSocket.OPEN && this.registered;
@@ -3505,6 +3951,9 @@ var RelayClient = class {
3505
3951
  case "error":
3506
3952
  this.handleError(msg);
3507
3953
  break;
3954
+ case "relay_progress":
3955
+ this.handleProgress(msg);
3956
+ break;
3508
3957
  default:
3509
3958
  break;
3510
3959
  }
@@ -3550,6 +3999,19 @@ var RelayClient = class {
3550
3999
  }
3551
4000
  }
3552
4001
  }
4002
+ handleProgress(msg) {
4003
+ const pending = this.pendingRequests.get(msg.id);
4004
+ if (!pending) return;
4005
+ clearTimeout(pending.timeout);
4006
+ const newTimeout = setTimeout(() => {
4007
+ this.pendingRequests.delete(msg.id);
4008
+ pending.reject(new Error("Relay request timeout"));
4009
+ }, pending.timeoutMs);
4010
+ pending.timeout = newTimeout;
4011
+ if (pending.onProgress) {
4012
+ pending.onProgress({ id: msg.id, progress: msg.progress, message: msg.message });
4013
+ }
4014
+ }
3553
4015
  send(msg) {
3554
4016
  if (this.ws && this.ws.readyState === WebSocket.OPEN) {
3555
4017
  this.ws.send(JSON.stringify(msg));
@@ -3611,12 +4073,12 @@ var RelayClient = class {
3611
4073
  };
3612
4074
 
3613
4075
  // src/onboarding/index.ts
3614
- import { randomUUID as randomUUID13 } from "crypto";
4076
+ import { randomUUID as randomUUID15 } from "crypto";
3615
4077
  import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
3616
4078
  import { join as join5 } from "path";
3617
4079
 
3618
4080
  // src/cli/onboarding.ts
3619
- import { randomUUID as randomUUID12 } from "crypto";
4081
+ import { randomUUID as randomUUID14 } from "crypto";
3620
4082
  import { createConnection } from "net";
3621
4083
  var KNOWN_API_KEYS = [
3622
4084
  "OPENAI_API_KEY",
@@ -3758,7 +4220,7 @@ function capabilitiesToV2Card(capabilities, owner, agentName) {
3758
4220
  }));
3759
4221
  const card = {
3760
4222
  spec_version: "2.0",
3761
- id: randomUUID13(),
4223
+ id: randomUUID15(),
3762
4224
  owner,
3763
4225
  agent_name: agentName ?? owner,
3764
4226
  skills,