agentbnb 8.2.3 → 8.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/dist/{card-EX2EYGCZ.js → card-BN643ZOY.js} +6 -2
  2. package/dist/card-T2XJZA5A.js +92 -0
  3. package/dist/{chunk-3LWBH7P3.js → chunk-4NFJ3VYZ.js} +20 -1
  4. package/dist/chunk-5AIYALBX.js +857 -0
  5. package/dist/chunk-6QMDJVMS.js +238 -0
  6. package/dist/{chunk-LKLKYXLV.js → chunk-74LZDEDT.js} +6 -4
  7. package/dist/{chunk-GKVTD4EZ.js → chunk-77KGEDH4.js} +1 -1
  8. package/dist/{chunk-QCGIG7WW.js → chunk-7IQE34QK.js} +14 -7
  9. package/dist/{chunk-QHZGOG3O.js → chunk-D242QZCR.js} +168 -41
  10. package/dist/chunk-EE3V3DXK.js +60 -0
  11. package/dist/{chunk-RYISHSHB.js → chunk-F3KIEVJ2.js} +207 -265
  12. package/dist/{chunk-XBGVQMQJ.js → chunk-FELGHDCA.js} +16 -39
  13. package/dist/{chunk-EJKW57ZV.js → chunk-GIEJVKZZ.js} +1 -1
  14. package/dist/{chunk-WVY2W7AA.js → chunk-I7KWA7OB.js} +20 -0
  15. package/dist/{chunk-4IPJJRTP.js → chunk-IGQNP3ZO.js} +5 -2
  16. package/dist/chunk-NQANA6WH.js +797 -0
  17. package/dist/{chunk-Z4MCGKTL.js → chunk-NX27AFPA.js} +15 -2
  18. package/dist/{chunk-Z2GEFFDO.js → chunk-O4Q7BRG6.js} +2 -2
  19. package/dist/{chunk-SSK653A6.js → chunk-PQIP7EXY.js} +6 -0
  20. package/dist/{chunk-EG6RS4JC.js → chunk-QFPXZITP.js} +20 -65
  21. package/dist/chunk-R4F4XII4.js +264 -0
  22. package/dist/{chunk-DYQOFGGI.js → chunk-RVBW2QXU.js} +178 -49
  23. package/dist/{chunk-CQFBNTGT.js → chunk-S7DZHKCG.js} +25 -12
  24. package/dist/chunk-U6LP4KWN.js +238 -0
  25. package/dist/{chunk-MWOXW7JQ.js → chunk-VJ7XBEY6.js} +24 -16
  26. package/dist/chunk-WTHMHNKC.js +129 -0
  27. package/dist/{chunk-OCSU2S6W.js → chunk-WX3GZVFG.js} +2 -1
  28. package/dist/{chunk-CKOOVZOI.js → chunk-YKMBFQC2.js} +37 -5
  29. package/dist/{chunk-S3V6R3EN.js → chunk-ZU2TP7CN.js} +70 -27
  30. package/dist/cli/index.js +203 -237
  31. package/dist/client-OKJJ3UP2.js +19 -0
  32. package/dist/client-UQBGCIPA.js +20 -0
  33. package/dist/conduct-4JDMWBQD.js +22 -0
  34. package/dist/{conduct-AZFLNUX3.js → conduct-VYYBCPHA.js} +14 -13
  35. package/dist/{conductor-mode-WKB42PYM.js → conductor-mode-OPGQJFLA.js} +12 -8
  36. package/dist/{conductor-mode-PLTB6MS3.js → conductor-mode-SBDCRIX6.js} +16 -11
  37. package/dist/execute-FZLQGIXB.js +14 -0
  38. package/dist/execute-TEZPQ5WP.js +15 -0
  39. package/dist/index.d.ts +172 -11
  40. package/dist/index.js +1529 -433
  41. package/dist/{process-guard-GH5LRNWO.js → process-guard-TNSUNHSR.js} +1 -1
  42. package/dist/{publish-capability-QDR2QIZ2.js → publish-capability-HVYILTPR.js} +4 -3
  43. package/dist/{reliability-metrics-QG7WC5QK.js → reliability-metrics-G7LPUYJD.js} +3 -1
  44. package/dist/reliability-metrics-RRUKJ4ME.js +20 -0
  45. package/dist/{request-OERS5BE7.js → request-KJNKR27T.js} +76 -71
  46. package/dist/{serve-skill-E6EJQYAK.js → serve-skill-GC6NIQ5T.js} +10 -11
  47. package/dist/{server-46VEG2W7.js → server-YV3XPTX5.js} +11 -10
  48. package/dist/{service-coordinator-KMSA6BST.js → service-coordinator-RY5AKUZS.js} +420 -171
  49. package/dist/{skill-config-FETXPNVP.js → skill-config-5O2VR546.js} +1 -1
  50. package/dist/skills/agentbnb/bootstrap.js +528 -253
  51. package/dist/websocket-client-3U27WJUU.js +7 -0
  52. package/dist/{websocket-client-4Z5P54RU.js → websocket-client-SNDF3B6N.js} +1 -1
  53. package/package.json +1 -1
  54. package/dist/chunk-MCED4GDW.js +0 -1572
  55. package/dist/chunk-NWIQJ2CL.js +0 -108
  56. package/dist/chunk-TUCEDQGM.js +0 -44
  57. package/dist/chunk-WNXXLCV5.js +0 -32
  58. package/dist/client-XOLP5IUZ.js +0 -12
  59. package/dist/conduct-VPUYTNEA.js +0 -21
  60. package/dist/execute-NNDCXTN4.js +0 -13
  61. package/dist/execute-RIRHTIBU.js +0 -16
  62. package/dist/websocket-client-QOVARTRN.js +0 -7
@@ -1,7 +1,3 @@
1
- import {
2
- RelayClient,
3
- RelayMessageSchema
4
- } from "../../chunk-3LWBH7P3.js";
5
1
  import {
6
2
  AutoRequestor,
7
3
  BudgetController,
@@ -16,21 +12,29 @@ import {
16
12
  listPendingRequests,
17
13
  matchSubTasks,
18
14
  orchestrate,
15
+ requestViaTemporaryRelay,
19
16
  resolvePendingRequest
20
- } from "../../chunk-DYQOFGGI.js";
17
+ } from "../../chunk-RVBW2QXU.js";
21
18
  import {
19
+ generateKeyPair,
20
+ loadKeyPair,
22
21
  requestCapability,
23
- requestViaRelay
24
- } from "../../chunk-CKOOVZOI.js";
22
+ requestViaRelay,
23
+ saveKeyPair,
24
+ signEscrowReceipt,
25
+ verifyEscrowReceipt
26
+ } from "../../chunk-R4F4XII4.js";
27
+ import {
28
+ RelayClient,
29
+ RelayMessageSchema
30
+ } from "../../chunk-4NFJ3VYZ.js";
25
31
  import {
26
32
  loadPeers
27
33
  } from "../../chunk-HLUEOLSZ.js";
28
34
  import {
29
35
  executeCapabilityBatch,
30
- executeCapabilityRequest,
31
- releaseRequesterEscrow,
32
- settleRequesterEscrow
33
- } from "../../chunk-EG6RS4JC.js";
36
+ executeCapabilityRequest
37
+ } from "../../chunk-QFPXZITP.js";
34
38
  import {
35
39
  bootstrapAgent,
36
40
  buildReputationMap,
@@ -38,8 +42,27 @@ import {
38
42
  confirmEscrowDebit,
39
43
  fetchRemoteCards,
40
44
  filterCards,
41
- getActivityFeed,
42
45
  getBalance,
46
+ getTransactions,
47
+ holdEscrow,
48
+ markEscrowAbandoned,
49
+ markEscrowProgressing,
50
+ markEscrowStarted,
51
+ mergeResults,
52
+ migrateOwner,
53
+ openCreditDb,
54
+ releaseEscrow,
55
+ searchCards,
56
+ settleEscrow
57
+ } from "../../chunk-NQANA6WH.js";
58
+ import "../../chunk-6QMDJVMS.js";
59
+ import {
60
+ getConfigDir,
61
+ loadConfig
62
+ } from "../../chunk-IVOYM3WG.js";
63
+ import {
64
+ attachCanonicalAgentId,
65
+ getActivityFeed,
43
66
  getCard,
44
67
  getCardsBySkillCapability,
45
68
  getEvolutionHistory,
@@ -48,41 +71,23 @@ import {
48
71
  getLatestEvolution,
49
72
  getRequestLog,
50
73
  getSkillRequestCount,
51
- getTransactions,
52
- holdEscrow,
53
74
  insertCard,
54
75
  insertEvolution,
55
76
  insertFeedback,
56
77
  insertRequestLog,
57
78
  listCards,
58
- lookupAgent,
59
- mergeResults,
60
- migrateOwner,
61
- openCreditDb,
62
79
  openDatabase,
63
- releaseEscrow,
64
- searchCards,
65
- settleEscrow,
66
80
  updateCard,
67
81
  updateSkillAvailability,
68
82
  updateSkillIdleRate
69
- } from "../../chunk-MCED4GDW.js";
70
- import "../../chunk-NWIQJ2CL.js";
83
+ } from "../../chunk-ZU2TP7CN.js";
71
84
  import {
72
- generateKeyPair,
73
- loadKeyPair,
74
- saveKeyPair,
75
- signEscrowReceipt,
76
- verifyEscrowReceipt
77
- } from "../../chunk-EJKW57ZV.js";
78
- import {
79
- getConfigDir,
80
- loadConfig
81
- } from "../../chunk-IVOYM3WG.js";
85
+ lookupAgent
86
+ } from "../../chunk-EE3V3DXK.js";
82
87
  import {
83
88
  AgentBnBError,
84
89
  AnyCardSchema
85
- } from "../../chunk-WVY2W7AA.js";
90
+ } from "../../chunk-I7KWA7OB.js";
86
91
 
87
92
  // skills/agentbnb/bootstrap.ts
88
93
  import { join as join7, basename as basename2, dirname as dirname3 } from "path";
@@ -265,6 +270,9 @@ function isPidFileContent(value) {
265
270
  import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
266
271
 
267
272
  // src/skills/executor.ts
273
+ function buildTimeoutError(skillId, timeoutMs) {
274
+ return `Skill "${skillId}" timed out after ${timeoutMs}ms`;
275
+ }
268
276
  var SkillExecutor = class {
269
277
  skillMap;
270
278
  modeMap;
@@ -323,7 +331,20 @@ var SkillExecutor = class {
323
331
  this.concurrencyGuard.acquire(skillId);
324
332
  }
325
333
  try {
326
- const modeResult = await mode.execute(config, params, onProgress);
334
+ const configuredTimeoutMs = typeof config.timeout_ms === "number" ? config.timeout_ms : void 0;
335
+ const modeExecution = mode.execute(config, params, onProgress);
336
+ const modeResult = configuredTimeoutMs === void 0 ? await modeExecution : await new Promise((resolve2, reject) => {
337
+ const timeout = setTimeout(() => {
338
+ reject(new Error(buildTimeoutError(skillId, configuredTimeoutMs)));
339
+ }, configuredTimeoutMs);
340
+ modeExecution.then((value) => {
341
+ clearTimeout(timeout);
342
+ resolve2(value);
343
+ }).catch((err) => {
344
+ clearTimeout(timeout);
345
+ reject(err);
346
+ });
347
+ });
327
348
  return {
328
349
  ...modeResult,
329
350
  latency_ms: Date.now() - startTime
@@ -394,7 +415,8 @@ var CapabilityDeclarationSchema = {
394
415
  description: z.string().optional(),
395
416
  capability_types: z.array(z.string()).optional(),
396
417
  requires_capabilities: z.array(z.string()).optional(),
397
- visibility: z.enum(["public", "private"]).optional()
418
+ visibility: z.enum(["public", "private"]).optional(),
419
+ expected_duration_ms: z.number().positive().optional()
398
420
  };
399
421
  var ApiSkillConfigSchema = z.object({
400
422
  id: z.string().min(1),
@@ -678,6 +700,15 @@ var ApiExecutor = class {
678
700
  import { execFile } from "child_process";
679
701
  import { promisify } from "util";
680
702
  var execFileAsync = promisify(execFile);
703
+ function isTimedOutCommandError(err) {
704
+ if (!(err instanceof Error)) {
705
+ return false;
706
+ }
707
+ const timeoutCode = err.code;
708
+ const signal = err.signal;
709
+ const killed = err.killed;
710
+ return timeoutCode === "ETIMEDOUT" || err.message.includes("timed out") || killed === true && typeof signal === "string";
711
+ }
681
712
  function shellEscape(value) {
682
713
  return "'" + value.replace(/'/g, "'\\''") + "'";
683
714
  }
@@ -729,6 +760,8 @@ var PipelineExecutor = class {
729
760
  async execute(config, params, onProgress) {
730
761
  const pipelineConfig = config;
731
762
  const steps = pipelineConfig.steps ?? [];
763
+ const pipelineTimeoutMs = pipelineConfig.timeout_ms;
764
+ const deadline = typeof pipelineTimeoutMs === "number" ? Date.now() + pipelineTimeoutMs : void 0;
732
765
  if (steps.length === 0) {
733
766
  return { success: true, result: null };
734
767
  }
@@ -750,11 +783,41 @@ var PipelineExecutor = class {
750
783
  context
751
784
  );
752
785
  let stepResult;
786
+ const runWithRemainingDeadline = async (operation) => {
787
+ if (deadline === void 0) {
788
+ return operation();
789
+ }
790
+ const remainingMs = deadline - Date.now();
791
+ if (remainingMs <= 0) {
792
+ throw new Error(`pipeline timed out after ${pipelineTimeoutMs}ms`);
793
+ }
794
+ return new Promise((resolve2, reject) => {
795
+ const timeout = setTimeout(() => {
796
+ reject(new Error(`pipeline timed out after ${pipelineTimeoutMs}ms`));
797
+ }, remainingMs);
798
+ operation().then((value) => {
799
+ clearTimeout(timeout);
800
+ resolve2(value);
801
+ }).catch((err) => {
802
+ clearTimeout(timeout);
803
+ reject(err);
804
+ });
805
+ });
806
+ };
753
807
  if ("skill_id" in step && step.skill_id) {
754
- const subResult = await this.skillExecutor.execute(
755
- step.skill_id,
756
- resolvedInputs
757
- );
808
+ let subResult;
809
+ try {
810
+ subResult = await runWithRemainingDeadline(() => this.skillExecutor.execute(
811
+ step.skill_id,
812
+ resolvedInputs
813
+ ));
814
+ } catch (err) {
815
+ const message = err instanceof Error ? err.message : String(err);
816
+ return {
817
+ success: false,
818
+ error: `Step ${i} failed: ${message}`
819
+ };
820
+ }
758
821
  if (!subResult.success) {
759
822
  return {
760
823
  success: false,
@@ -768,10 +831,16 @@ var PipelineExecutor = class {
768
831
  context
769
832
  );
770
833
  try {
771
- const { stdout } = await execFileAsync("/bin/sh", ["-c", interpolatedCommand], { timeout: 3e4 });
834
+ const remainingMs = deadline === void 0 ? void 0 : deadline - Date.now();
835
+ if (remainingMs !== void 0 && remainingMs <= 0) {
836
+ throw new Error(`pipeline timed out after ${pipelineTimeoutMs}ms`);
837
+ }
838
+ const { stdout } = await execFileAsync("/bin/sh", ["-c", interpolatedCommand], {
839
+ timeout: remainingMs !== void 0 ? remainingMs : 3e4
840
+ });
772
841
  stepResult = stdout.trim();
773
842
  } catch (err) {
774
- const message = err instanceof Error ? err.message : String(err);
843
+ const message = pipelineTimeoutMs !== void 0 && isTimedOutCommandError(err) ? `pipeline timed out after ${pipelineTimeoutMs}ms` : err instanceof Error ? err.message : String(err);
775
844
  return {
776
845
  success: false,
777
846
  error: `Step ${i} failed: ${message}`
@@ -858,17 +927,43 @@ function executeProcess(config, payload) {
858
927
  error: `Invalid agent name: "${config.agent_name}" (only alphanumeric, hyphens, underscores, dots allowed)`
859
928
  };
860
929
  }
861
- const inputJson = JSON.stringify(payload);
930
+ const skillId = config.id;
931
+ const message = `[AgentBnB Rental Request]
932
+ You are executing the "${skillId}" skill for an AgentBnB network rental.
933
+ Read your skills/${skillId}/SKILL.md for detailed instructions.
934
+
935
+ Input parameters:
936
+ ${JSON.stringify(payload.params ?? {}, null, 2)}
937
+
938
+ IMPORTANT: Return ONLY a JSON object as your response.
939
+ Do NOT include explanations, markdown formatting, or code blocks.
940
+ The JSON should contain the output fields specified in your SKILL.md.
941
+ If you cannot complete the task, return: {"error": "reason"}`;
862
942
  try {
863
- const stdout = execFileSync("openclaw", ["run", config.agent_name, "--input", inputJson], {
943
+ const stdout = execFileSync("openclaw", [
944
+ "agent",
945
+ "--agent",
946
+ config.agent_name,
947
+ "--message",
948
+ message,
949
+ "--json",
950
+ "--local"
951
+ ], {
864
952
  timeout: timeoutMs
865
953
  });
866
954
  const text = stdout.toString().trim();
867
- const result = JSON.parse(text);
868
- return { success: true, result };
955
+ try {
956
+ const parsed = JSON.parse(text);
957
+ return { success: true, result: parsed };
958
+ } catch {
959
+ return {
960
+ success: false,
961
+ error: `OpenClaw process channel returned invalid JSON: ${text}`
962
+ };
963
+ }
869
964
  } catch (err) {
870
- const message = err instanceof Error ? err.message : String(err);
871
- return { success: false, error: message };
965
+ const message2 = err instanceof Error ? err.message : String(err);
966
+ return { success: false, error: message2 };
872
967
  }
873
968
  }
874
969
  async function executeTelegram(config, payload) {
@@ -1277,23 +1372,27 @@ var AgentRuntime = class {
1277
1372
  }
1278
1373
  const modes = /* @__PURE__ */ new Map();
1279
1374
  if (this.conductorEnabled) {
1280
- const { ConductorMode } = await import("../../conductor-mode-WKB42PYM.js");
1281
- const { registerConductorCard, CONDUCTOR_OWNER } = await import("../../card-EX2EYGCZ.js");
1375
+ const { ConductorMode } = await import("../../conductor-mode-OPGQJFLA.js");
1376
+ const { registerConductorCard, CONDUCTOR_OWNER } = await import("../../card-BN643ZOY.js");
1282
1377
  const { loadPeers: loadPeers2 } = await import("../../peers-CJ7T4RJO.js");
1283
1378
  registerConductorCard(this.registryDb);
1284
1379
  const resolveAgentUrl = (owner) => {
1285
1380
  const peers = loadPeers2();
1286
- const peer = peers.find((p) => p.name.toLowerCase() === owner.toLowerCase());
1381
+ const matchingCards = listCards(this.registryDb, owner);
1382
+ const candidateNames = /* @__PURE__ */ new Set([owner.toLowerCase()]);
1383
+ for (const card of matchingCards) {
1384
+ candidateNames.add(card.owner.toLowerCase());
1385
+ if (typeof card.agent_id === "string" && card.agent_id.length > 0) {
1386
+ candidateNames.add(card.agent_id.toLowerCase());
1387
+ }
1388
+ }
1389
+ const peer = peers.find((p) => candidateNames.has(p.name.toLowerCase()));
1287
1390
  if (!peer) {
1288
1391
  throw new Error(
1289
1392
  `No peer found for agent owner "${owner}". Add with: agentbnb connect ${owner} <url> <token>`
1290
1393
  );
1291
1394
  }
1292
- const stmt = this.registryDb.prepare(
1293
- "SELECT id FROM capability_cards WHERE owner = ? LIMIT 1"
1294
- );
1295
- const row = stmt.get(owner);
1296
- const cardId = row?.id ?? owner;
1395
+ const cardId = matchingCards[0]?.id ?? owner;
1297
1396
  return { url: peer.url, cardId };
1298
1397
  };
1299
1398
  const conductorMode = new ConductorMode({
@@ -1335,7 +1434,8 @@ var AgentRuntime = class {
1335
1434
  }
1336
1435
  /**
1337
1436
  * Recovers orphaned escrows by releasing them.
1338
- * Orphaned escrows are 'held' escrows older than the configured age threshold.
1437
+ * Orphaned escrows are stale 'held' or 'abandoned' escrows older than the
1438
+ * configured age threshold. In-flight started/progressing escrows are not touched.
1339
1439
  * Errors during individual release are swallowed (escrow may have settled between query and release).
1340
1440
  */
1341
1441
  async recoverOrphanedEscrows() {
@@ -1343,7 +1443,7 @@ var AgentRuntime = class {
1343
1443
  Date.now() - this.orphanedEscrowAgeMinutes * 60 * 1e3
1344
1444
  ).toISOString();
1345
1445
  const orphaned = this.creditDb.prepare(
1346
- "SELECT id FROM credit_escrow WHERE status = 'held' AND created_at < ?"
1446
+ "SELECT id FROM credit_escrow WHERE status IN ('held', 'abandoned') AND created_at < ?"
1347
1447
  ).all(cutoff);
1348
1448
  for (const row of orphaned) {
1349
1449
  try {
@@ -2439,14 +2539,16 @@ function processEscrowSettle(creditDb, escrowId, success, providerAgentId, signa
2439
2539
  throw new Error("Invalid consumer signature on escrow settle");
2440
2540
  }
2441
2541
  }
2442
- const escrowRow = creditDb.prepare("SELECT amount, owner FROM credit_escrow WHERE id = ? AND status = ?").get(escrowId, "held");
2542
+ const escrowRow = creditDb.prepare(
2543
+ "SELECT amount, owner FROM credit_escrow WHERE id = ? AND status IN ('held', 'started', 'progressing', 'abandoned')"
2544
+ ).get(escrowId);
2443
2545
  if (!escrowRow) {
2444
2546
  throw new Error(`Escrow not found or already settled: ${escrowId}`);
2445
2547
  }
2446
- const NETWORK_FEE_RATE = 0.05;
2548
+ const NETWORK_FEE_RATE2 = 0.05;
2447
2549
  if (success) {
2448
2550
  settleEscrow(creditDb, escrowId, providerAgentId);
2449
- const networkFee = Math.floor(escrowRow.amount * NETWORK_FEE_RATE);
2551
+ const networkFee = Math.floor(escrowRow.amount * NETWORK_FEE_RATE2);
2450
2552
  const providerAmount = escrowRow.amount - networkFee;
2451
2553
  return {
2452
2554
  escrow_id: escrowId,
@@ -2770,7 +2872,16 @@ function handleJobRelayResponse(opts) {
2770
2872
  // src/relay/websocket-relay.ts
2771
2873
  var RATE_LIMIT_MAX = 60;
2772
2874
  var RATE_LIMIT_WINDOW_MS = 6e4;
2773
- var RELAY_TIMEOUT_MS = 3e5;
2875
+ var RELAY_IDLE_TIMEOUT_MS = 3e4;
2876
+ var RELAY_HARD_TIMEOUT_MS = 3e5;
2877
+ var RELAY_DISCONNECT_GRACE_MS = RELAY_HARD_TIMEOUT_MS + 3e4;
2878
+ function readTimeoutOverride(envKey, fallbackMs) {
2879
+ const raw = process.env[envKey];
2880
+ if (!raw) return fallbackMs;
2881
+ const parsed = Number(raw);
2882
+ if (!Number.isFinite(parsed) || parsed <= 0) return fallbackMs;
2883
+ return Math.floor(parsed);
2884
+ }
2774
2885
  function registerWebSocketRelay(server, db, creditDb) {
2775
2886
  const connections = /* @__PURE__ */ new Map();
2776
2887
  const agentIdToOwner = /* @__PURE__ */ new Map();
@@ -2784,6 +2895,136 @@ function registerWebSocketRelay(server, db, creditDb) {
2784
2895
  if (connections.has(target)) return target;
2785
2896
  return void 0;
2786
2897
  }
2898
+ function clearPendingTimers(pending) {
2899
+ clearTimeout(pending.timeout);
2900
+ if (pending.idleTimeout) clearTimeout(pending.idleTimeout);
2901
+ if (pending.hardTimeout) clearTimeout(pending.hardTimeout);
2902
+ if (pending.graceTimeout) clearTimeout(pending.graceTimeout);
2903
+ }
2904
+ function setPendingTimer(pending, timerType, timer) {
2905
+ const existing = pending[timerType];
2906
+ if (existing) clearTimeout(existing);
2907
+ pending[timerType] = timer;
2908
+ pending.timeout = timer;
2909
+ }
2910
+ function scheduleIdleTimeout(requestId, pending) {
2911
+ const timeoutMs = readTimeoutOverride("AGENTBNB_RELAY_IDLE_TIMEOUT_MS", RELAY_IDLE_TIMEOUT_MS);
2912
+ const timer = setTimeout(() => {
2913
+ const current = pendingRequests.get(requestId);
2914
+ if (!current || (current.lifecycle ?? "held") !== "held") return;
2915
+ pendingRequests.delete(requestId);
2916
+ clearPendingTimers(current);
2917
+ if (current.escrowId && creditDb) {
2918
+ try {
2919
+ releaseForRelay(creditDb, current.escrowId);
2920
+ } catch (e) {
2921
+ console.error("[relay] escrow release on idle timeout failed:", e);
2922
+ }
2923
+ }
2924
+ const originWs = connections.get(current.originOwner);
2925
+ if (originWs && originWs.readyState === 1) {
2926
+ sendMessage(originWs, {
2927
+ type: "response",
2928
+ id: requestId,
2929
+ error: { code: -32603, message: "Relay request timeout" }
2930
+ });
2931
+ }
2932
+ }, timeoutMs);
2933
+ setPendingTimer(pending, "idleTimeout", timer);
2934
+ }
2935
+ function scheduleHardTimeout(requestId, pending) {
2936
+ const timeoutMs = readTimeoutOverride("AGENTBNB_RELAY_HARD_TIMEOUT_MS", RELAY_HARD_TIMEOUT_MS);
2937
+ const timer = setTimeout(() => {
2938
+ const current = pendingRequests.get(requestId);
2939
+ if (!current || current.lifecycle === "abandoned") return;
2940
+ pendingRequests.delete(requestId);
2941
+ clearPendingTimers(current);
2942
+ if (current.escrowId && creditDb) {
2943
+ try {
2944
+ releaseForRelay(creditDb, current.escrowId);
2945
+ } catch (e) {
2946
+ console.error("[relay] escrow release on hard timeout failed:", e);
2947
+ }
2948
+ }
2949
+ const originWs = connections.get(current.originOwner);
2950
+ if (originWs && originWs.readyState === 1) {
2951
+ sendMessage(originWs, {
2952
+ type: "response",
2953
+ id: requestId,
2954
+ error: { code: -32603, message: "Relay request timeout" }
2955
+ });
2956
+ }
2957
+ }, timeoutMs);
2958
+ setPendingTimer(pending, "hardTimeout", timer);
2959
+ }
2960
+ function scheduleGraceTimeout(requestId, pending) {
2961
+ const timeoutMs = readTimeoutOverride(
2962
+ "AGENTBNB_RELAY_DISCONNECT_GRACE_MS",
2963
+ RELAY_DISCONNECT_GRACE_MS
2964
+ );
2965
+ const timer = setTimeout(() => {
2966
+ const current = pendingRequests.get(requestId);
2967
+ if (!current || current.lifecycle !== "abandoned") return;
2968
+ pendingRequests.delete(requestId);
2969
+ clearPendingTimers(current);
2970
+ if (current.escrowId && creditDb) {
2971
+ try {
2972
+ releaseForRelay(creditDb, current.escrowId);
2973
+ } catch (e) {
2974
+ console.error("[relay] escrow release after grace timeout failed:", e);
2975
+ }
2976
+ }
2977
+ }, timeoutMs);
2978
+ setPendingTimer(pending, "graceTimeout", timer);
2979
+ }
2980
+ function transitionPendingToStarted(requestId, pending) {
2981
+ if (pending.lifecycle === "started" || pending.lifecycle === "progressing" || pending.lifecycle === "abandoned") {
2982
+ return;
2983
+ }
2984
+ pending.lifecycle = "started";
2985
+ pending.startedAt = Date.now();
2986
+ if (pending.idleTimeout) clearTimeout(pending.idleTimeout);
2987
+ if (pending.graceTimeout) clearTimeout(pending.graceTimeout);
2988
+ if (pending.escrowId && creditDb) {
2989
+ try {
2990
+ markEscrowStarted(creditDb, pending.escrowId);
2991
+ } catch (e) {
2992
+ console.error("[relay] escrow transition to started failed:", e);
2993
+ }
2994
+ }
2995
+ scheduleHardTimeout(requestId, pending);
2996
+ }
2997
+ function transitionPendingToProgressing(requestId, pending) {
2998
+ if (pending.lifecycle === "abandoned") {
2999
+ return;
3000
+ }
3001
+ if ((pending.lifecycle ?? "held") === "held") {
3002
+ transitionPendingToStarted(requestId, pending);
3003
+ }
3004
+ pending.lifecycle = "progressing";
3005
+ if (pending.escrowId && creditDb) {
3006
+ try {
3007
+ markEscrowProgressing(creditDb, pending.escrowId);
3008
+ } catch (e) {
3009
+ console.error("[relay] escrow transition to progressing failed:", e);
3010
+ }
3011
+ }
3012
+ }
3013
+ function transitionPendingToAbandoned(requestId, pending) {
3014
+ if (pending.lifecycle === "abandoned") return;
3015
+ pending.lifecycle = "abandoned";
3016
+ pending.abandonedAt = Date.now();
3017
+ if (pending.idleTimeout) clearTimeout(pending.idleTimeout);
3018
+ if (pending.hardTimeout) clearTimeout(pending.hardTimeout);
3019
+ if (pending.escrowId && creditDb) {
3020
+ try {
3021
+ markEscrowAbandoned(creditDb, pending.escrowId);
3022
+ } catch (e) {
3023
+ console.error("[relay] escrow transition to abandoned failed:", e);
3024
+ }
3025
+ }
3026
+ scheduleGraceTimeout(requestId, pending);
3027
+ }
2787
3028
  function checkRateLimit(owner) {
2788
3029
  const now = Date.now();
2789
3030
  const entry = rateLimits.get(owner);
@@ -2833,15 +3074,20 @@ function registerWebSocketRelay(server, db, creditDb) {
2833
3074
  } catch {
2834
3075
  }
2835
3076
  }
2836
- function upsertCard(cardData, owner) {
2837
- const parsed = AnyCardSchema.safeParse(cardData);
3077
+ function upsertCard(cardData, owner, agentId) {
3078
+ const parsed = AnyCardSchema.safeParse(
3079
+ agentId ? { ...cardData, agent_id: agentId } : cardData
3080
+ );
2838
3081
  if (!parsed.success) {
2839
3082
  throw new AgentBnBError(
2840
3083
  `Card validation failed: ${parsed.error.message}`,
2841
3084
  "VALIDATION_ERROR"
2842
3085
  );
2843
3086
  }
2844
- const card = { ...parsed.data, availability: { ...parsed.data.availability, online: true } };
3087
+ const card = attachCanonicalAgentId(db, {
3088
+ ...parsed.data,
3089
+ availability: { ...parsed.data.availability, online: true }
3090
+ });
2845
3091
  const cardId = card.id;
2846
3092
  const now = (/* @__PURE__ */ new Date()).toISOString();
2847
3093
  const existing = db.prepare("SELECT id FROM capability_cards WHERE id = ?").get(cardId);
@@ -2895,7 +3141,7 @@ function registerWebSocketRelay(server, db, creditDb) {
2895
3141
  agentIdToOwner.set(agentEntry.agent_id, owner);
2896
3142
  for (const agentCard of agentEntry.cards) {
2897
3143
  try {
2898
- upsertCard(agentCard, owner);
3144
+ upsertCard(agentCard, owner, agentEntry.agent_id);
2899
3145
  } catch {
2900
3146
  }
2901
3147
  }
@@ -2909,7 +3155,7 @@ function registerWebSocketRelay(server, db, creditDb) {
2909
3155
  }
2910
3156
  let cardId;
2911
3157
  try {
2912
- cardId = upsertCard(card, owner);
3158
+ cardId = upsertCard(card, owner, msg.agent_id);
2913
3159
  } catch (err) {
2914
3160
  console.error(`[relay] card validation failed for ${owner}:`, err instanceof Error ? err.message : err);
2915
3161
  cardId = card.id ?? owner;
@@ -2919,7 +3165,7 @@ function registerWebSocketRelay(server, db, creditDb) {
2919
3165
  if (msg.cards && msg.cards.length > 0) {
2920
3166
  for (const extraCard of msg.cards) {
2921
3167
  try {
2922
- upsertCard(extraCard, owner);
3168
+ upsertCard(extraCard, owner, msg.agent_id);
2923
3169
  } catch {
2924
3170
  }
2925
3171
  }
@@ -2974,23 +3220,17 @@ function registerWebSocketRelay(server, db, creditDb) {
2974
3220
  console.error("[relay] credit hold error (non-fatal):", err);
2975
3221
  }
2976
3222
  }
2977
- const timeout = setTimeout(() => {
2978
- const pending = pendingRequests.get(msg.id);
2979
- pendingRequests.delete(msg.id);
2980
- if (pending?.escrowId && creditDb) {
2981
- try {
2982
- releaseForRelay(creditDb, pending.escrowId);
2983
- } catch (e) {
2984
- console.error("[relay] escrow release on timeout failed:", e);
2985
- }
2986
- }
2987
- sendMessage(ws, {
2988
- type: "response",
2989
- id: msg.id,
2990
- error: { code: -32603, message: "Relay request timeout" }
2991
- });
2992
- }, RELAY_TIMEOUT_MS);
2993
- pendingRequests.set(msg.id, { originOwner: fromOwner, creditOwner, timeout, escrowId, targetOwner: msg.target_owner });
3223
+ const pending = {
3224
+ originOwner: fromOwner,
3225
+ creditOwner,
3226
+ timeout: setTimeout(() => void 0, 1),
3227
+ escrowId,
3228
+ targetOwner: targetKey ?? msg.target_owner,
3229
+ lifecycle: "held",
3230
+ createdAt: Date.now()
3231
+ };
3232
+ pendingRequests.set(msg.id, pending);
3233
+ scheduleIdleTimeout(msg.id, pending);
2994
3234
  sendMessage(targetWs, {
2995
3235
  type: "incoming_request",
2996
3236
  id: msg.id,
@@ -3002,30 +3242,23 @@ function registerWebSocketRelay(server, db, creditDb) {
3002
3242
  escrow_receipt: msg.escrow_receipt
3003
3243
  });
3004
3244
  }
3245
+ function handleRelayStarted(msg) {
3246
+ const pending = pendingRequests.get(msg.id);
3247
+ if (!pending) return;
3248
+ transitionPendingToStarted(msg.id, pending);
3249
+ const originWs = connections.get(pending.originOwner);
3250
+ if (originWs && originWs.readyState === 1) {
3251
+ sendMessage(originWs, {
3252
+ type: "relay_started",
3253
+ id: msg.id,
3254
+ message: msg.message
3255
+ });
3256
+ }
3257
+ }
3005
3258
  function handleRelayProgress(msg) {
3006
3259
  const pending = pendingRequests.get(msg.id);
3007
3260
  if (!pending) return;
3008
- clearTimeout(pending.timeout);
3009
- const newTimeout = setTimeout(() => {
3010
- const p = pendingRequests.get(msg.id);
3011
- pendingRequests.delete(msg.id);
3012
- if (p?.escrowId && creditDb) {
3013
- try {
3014
- releaseForRelay(creditDb, p.escrowId);
3015
- } catch (e) {
3016
- console.error("[relay] escrow release on progress timeout failed:", e);
3017
- }
3018
- }
3019
- const originWs2 = connections.get(pending.originOwner);
3020
- if (originWs2 && originWs2.readyState === 1) {
3021
- sendMessage(originWs2, {
3022
- type: "response",
3023
- id: msg.id,
3024
- error: { code: -32603, message: "Relay request timeout" }
3025
- });
3026
- }
3027
- }, RELAY_TIMEOUT_MS);
3028
- pending.timeout = newTimeout;
3261
+ transitionPendingToProgressing(msg.id, pending);
3029
3262
  const originWs = connections.get(pending.originOwner);
3030
3263
  if (originWs && originWs.readyState === 1) {
3031
3264
  sendMessage(originWs, {
@@ -3039,7 +3272,7 @@ function registerWebSocketRelay(server, db, creditDb) {
3039
3272
  function handleRelayResponse(msg) {
3040
3273
  const pending = pendingRequests.get(msg.id);
3041
3274
  if (!pending) return;
3042
- clearTimeout(pending.timeout);
3275
+ clearPendingTimers(pending);
3043
3276
  pendingRequests.delete(msg.id);
3044
3277
  if (pending.jobId && creditDb) {
3045
3278
  try {
@@ -3102,16 +3335,15 @@ function registerWebSocketRelay(server, db, creditDb) {
3102
3335
  connections.delete(owner);
3103
3336
  rateLimits.delete(owner);
3104
3337
  agentCapacities.delete(owner);
3105
- for (const [agentId, o] of agentIdToOwner) {
3106
- if (o === owner) {
3338
+ for (const [agentId, mappedOwner] of Array.from(agentIdToOwner.entries())) {
3339
+ if (mappedOwner === owner) {
3107
3340
  agentIdToOwner.delete(agentId);
3108
- break;
3109
3341
  }
3110
3342
  }
3111
3343
  markOwnerOffline(owner);
3112
3344
  for (const [reqId, pending] of pendingRequests) {
3113
3345
  if (pending.targetOwner === owner) {
3114
- clearTimeout(pending.timeout);
3346
+ clearPendingTimers(pending);
3115
3347
  pendingRequests.delete(reqId);
3116
3348
  if (pending.escrowId && creditDb) {
3117
3349
  try {
@@ -3129,14 +3361,19 @@ function registerWebSocketRelay(server, db, creditDb) {
3129
3361
  });
3130
3362
  }
3131
3363
  } else if (pending.originOwner === owner) {
3132
- clearTimeout(pending.timeout);
3133
- pendingRequests.delete(reqId);
3134
- if (pending.escrowId && creditDb) {
3135
- try {
3136
- releaseForRelay(creditDb, pending.escrowId);
3137
- } catch (e) {
3138
- console.error("[relay] escrow release on requester disconnect failed:", e);
3364
+ const lifecycle = pending.lifecycle ?? "held";
3365
+ if (lifecycle === "held") {
3366
+ clearPendingTimers(pending);
3367
+ pendingRequests.delete(reqId);
3368
+ if (pending.escrowId && creditDb) {
3369
+ try {
3370
+ releaseForRelay(creditDb, pending.escrowId);
3371
+ } catch (e) {
3372
+ console.error("[relay] escrow release on requester disconnect failed:", e);
3373
+ }
3139
3374
  }
3375
+ } else {
3376
+ transitionPendingToAbandoned(reqId, pending);
3140
3377
  }
3141
3378
  }
3142
3379
  }
@@ -3179,7 +3416,9 @@ function registerWebSocketRelay(server, db, creditDb) {
3179
3416
  return;
3180
3417
  }
3181
3418
  try {
3182
- const escrow = creditDb.prepare("SELECT card_id FROM credit_escrow WHERE id = ? AND status = ?").get(msg.escrow_id, "held");
3419
+ const escrow = creditDb.prepare(
3420
+ "SELECT card_id FROM credit_escrow WHERE id = ? AND status IN ('held', 'started', 'progressing', 'abandoned')"
3421
+ ).get(msg.escrow_id);
3183
3422
  if (!escrow) {
3184
3423
  sendMessage(ws, { type: "error", code: "escrow_not_found", message: `Escrow not found: ${msg.escrow_id}`, request_id: msg.request_id });
3185
3424
  return;
@@ -3276,6 +3515,9 @@ function registerWebSocketRelay(server, db, creditDb) {
3276
3515
  case "relay_response":
3277
3516
  handleRelayResponse(msg);
3278
3517
  break;
3518
+ case "relay_started":
3519
+ handleRelayStarted(msg);
3520
+ break;
3279
3521
  case "relay_progress":
3280
3522
  handleRelayProgress(msg);
3281
3523
  break;
@@ -3322,7 +3564,7 @@ function registerWebSocketRelay(server, db, creditDb) {
3322
3564
  }
3323
3565
  connections.clear();
3324
3566
  for (const [, pending] of pendingRequests) {
3325
- clearTimeout(pending.timeout);
3567
+ clearPendingTimers(pending);
3326
3568
  }
3327
3569
  pendingRequests.clear();
3328
3570
  rateLimits.clear();
@@ -4621,6 +4863,9 @@ function stripInternal(card) {
4621
4863
  const { _internal: _, ...publicCard } = card;
4622
4864
  return publicCard;
4623
4865
  }
4866
+ function buildSqlPlaceholders(count) {
4867
+ return Array.from({ length: count }, () => "?").join(", ");
4868
+ }
4624
4869
  function createRegistryServer(opts) {
4625
4870
  const { registryDb: db, silent = false } = opts;
4626
4871
  const server = Fastify2({ logger: !silent });
@@ -5030,11 +5275,11 @@ function createRegistryServer(opts) {
5030
5275
  const card = result.data;
5031
5276
  const now = (/* @__PURE__ */ new Date()).toISOString();
5032
5277
  if (card.spec_version === "2.0") {
5033
- const cardWithTimestamps = {
5278
+ const cardWithTimestamps = attachCanonicalAgentId(db, {
5034
5279
  ...card,
5035
5280
  created_at: card.created_at ?? now,
5036
5281
  updated_at: now
5037
- };
5282
+ });
5038
5283
  db.prepare(
5039
5284
  `INSERT OR REPLACE INTO capability_cards (id, owner, data, created_at, updated_at)
5040
5285
  VALUES (?, ?, ?, ?, ?)`
@@ -5155,19 +5400,16 @@ function createRegistryServer(opts) {
5155
5400
  if (ownerCards.length === 0) {
5156
5401
  return reply.status(404).send({ error: "Agent not found" });
5157
5402
  }
5158
- const memberStmt = db.prepare(
5159
- "SELECT MIN(created_at) as earliest, MAX(created_at) as latest FROM capability_cards WHERE owner = ?"
5403
+ const resolvedOwner = ownerCards[0]?.owner ?? owner;
5404
+ const ownerCardIds = ownerCards.map((card) => card.id);
5405
+ const cardIdPlaceholders = buildSqlPlaceholders(ownerCardIds.length);
5406
+ const joinedAt = ownerCards.map((card) => card.created_at).filter((value) => typeof value === "string" && value.length > 0).sort((left, right) => left.localeCompare(right))[0] ?? (/* @__PURE__ */ new Date()).toISOString();
5407
+ const latestCardUpdate = ownerCards.map((card) => card.updated_at ?? card.created_at).filter((value) => typeof value === "string" && value.length > 0).sort((left, right) => right.localeCompare(left))[0] ?? joinedAt;
5408
+ const lastActiveStmt = db.prepare(
5409
+ `SELECT MAX(created_at) as last_req FROM request_log WHERE card_id IN (${cardIdPlaceholders})`
5160
5410
  );
5161
- const memberRow = memberStmt.get(owner);
5162
- const joinedAt = memberRow?.earliest ?? (/* @__PURE__ */ new Date()).toISOString();
5163
- const lastActiveStmt = db.prepare(`
5164
- SELECT MAX(rl.created_at) as last_req
5165
- FROM request_log rl
5166
- INNER JOIN capability_cards cc ON rl.card_id = cc.id
5167
- WHERE cc.owner = ?
5168
- `);
5169
- const lastActiveRow = lastActiveStmt.get(owner);
5170
- const lastActive = lastActiveRow?.last_req ?? memberRow?.latest ?? joinedAt;
5411
+ const lastActiveRow = lastActiveStmt.get(...ownerCardIds);
5412
+ const lastActive = lastActiveRow?.last_req ?? latestCardUpdate ?? joinedAt;
5171
5413
  const metricsStmt = db.prepare(`
5172
5414
  SELECT
5173
5415
  SUM(CASE WHEN rl.failure_reason IS NULL OR rl.failure_reason IN ('bad_execution','auth_error')
@@ -5177,10 +5419,9 @@ function createRegistryServer(opts) {
5177
5419
  COUNT(DISTINCT rl.requester) as unique_requesters,
5178
5420
  COUNT(DISTINCT CASE WHEN rl.status = 'success' THEN rl.requester END) as repeat_success_requesters
5179
5421
  FROM request_log rl
5180
- INNER JOIN capability_cards cc ON rl.card_id = cc.id
5181
- WHERE cc.owner = ? AND rl.action_type IS NULL
5422
+ WHERE rl.card_id IN (${cardIdPlaceholders}) AND rl.action_type IS NULL
5182
5423
  `);
5183
- const metricsRow = metricsStmt.get(owner);
5424
+ const metricsRow = metricsStmt.get(...ownerCardIds);
5184
5425
  const totalExec = metricsRow?.total ?? 0;
5185
5426
  const successExec = metricsRow?.successes ?? 0;
5186
5427
  const successRate = totalExec > 0 ? successExec / totalExec : 0;
@@ -5194,25 +5435,23 @@ function createRegistryServer(opts) {
5194
5435
  COUNT(*) as count,
5195
5436
  SUM(CASE WHEN rl.status = 'success' THEN 1 ELSE 0 END) as success
5196
5437
  FROM request_log rl
5197
- INNER JOIN capability_cards cc ON rl.card_id = cc.id
5198
- WHERE cc.owner = ? AND rl.action_type IS NULL
5438
+ WHERE rl.card_id IN (${cardIdPlaceholders}) AND rl.action_type IS NULL
5199
5439
  AND rl.created_at >= DATE('now', '-7 days')
5200
5440
  GROUP BY DATE(rl.created_at)
5201
5441
  ORDER BY day ASC
5202
5442
  `);
5203
- const trend_7d = trendStmt.all(owner).map((r) => ({ date: r.day, count: r.count, success: r.success }));
5443
+ const trend_7d = trendStmt.all(...ownerCardIds).map((r) => ({ date: r.day, count: r.count, success: r.success }));
5204
5444
  let performanceTier = 0;
5205
5445
  if (totalExec > 10) performanceTier = 1;
5206
5446
  if (totalExec > 50 && successRate >= 0.85) performanceTier = 2;
5207
5447
  const proofsStmt = db.prepare(`
5208
5448
  SELECT rl.card_name, rl.status, rl.latency_ms, rl.id, rl.created_at
5209
5449
  FROM request_log rl
5210
- INNER JOIN capability_cards cc ON rl.card_id = cc.id
5211
- WHERE cc.owner = ? AND rl.action_type IS NULL
5450
+ WHERE rl.card_id IN (${cardIdPlaceholders}) AND rl.action_type IS NULL
5212
5451
  ORDER BY rl.created_at DESC
5213
5452
  LIMIT 10
5214
5453
  `);
5215
- const proofRows = proofsStmt.all(owner);
5454
+ const proofRows = proofsStmt.all(...ownerCardIds);
5216
5455
  const statusToOutcomeClass = (s) => {
5217
5456
  if (s === "success") return "completed";
5218
5457
  if (s === "timeout") return "cancelled";
@@ -5238,22 +5477,20 @@ function createRegistryServer(opts) {
5238
5477
  const activityStmt = db.prepare(`
5239
5478
  SELECT rl.id, rl.card_name, rl.requester, rl.status, rl.credits_charged, rl.created_at
5240
5479
  FROM request_log rl
5241
- INNER JOIN capability_cards cc ON rl.card_id = cc.id
5242
- WHERE cc.owner = ?
5480
+ WHERE rl.card_id IN (${cardIdPlaceholders})
5243
5481
  ORDER BY rl.created_at DESC
5244
5482
  LIMIT 10
5245
5483
  `);
5246
- const recentActivity = activityStmt.all(owner);
5484
+ const recentActivity = activityStmt.all(...ownerCardIds);
5247
5485
  const skillCount = ownerCards.reduce((sum, card) => sum + (card.skills?.length ?? 1), 0);
5248
5486
  const creditsStmt = db.prepare(`
5249
- SELECT SUM(CASE WHEN rl.status = 'success' THEN rl.credits_charged ELSE 0 END) as credits_earned
5250
- FROM capability_cards cc
5251
- LEFT JOIN request_log rl ON rl.card_id = cc.id
5252
- WHERE cc.owner = ?
5487
+ SELECT COALESCE(SUM(CASE WHEN rl.status = 'success' THEN rl.credits_charged ELSE 0 END), 0) as credits_earned
5488
+ FROM request_log rl
5489
+ WHERE rl.card_id IN (${cardIdPlaceholders})
5253
5490
  `);
5254
- const creditsRow = creditsStmt.get(owner);
5491
+ const creditsRow = creditsStmt.get(...ownerCardIds);
5255
5492
  const response = {
5256
- owner,
5493
+ owner: resolvedOwner,
5257
5494
  agent_name: v2Card?.agent_name,
5258
5495
  short_description: v2Card?.short_description,
5259
5496
  joined_at: joinedAt,
@@ -5285,7 +5522,7 @@ function createRegistryServer(opts) {
5285
5522
  return reply.send({
5286
5523
  ...response,
5287
5524
  profile: {
5288
- owner,
5525
+ owner: resolvedOwner,
5289
5526
  skill_count: skillCount,
5290
5527
  success_rate: successRate > 0 ? successRate : null,
5291
5528
  total_earned: creditsRow?.credits_earned ?? 0,
@@ -5557,7 +5794,7 @@ function createRegistryServer(opts) {
5557
5794
  return { card_id: target.cardId, skill_id: target.skillId };
5558
5795
  }
5559
5796
  if (!relayClient) {
5560
- const { RelayClient: RelayClient2 } = await import("../../websocket-client-4Z5P54RU.js");
5797
+ const { RelayClient: RelayClient2 } = await import("../../websocket-client-SNDF3B6N.js");
5561
5798
  relayClient = new RelayClient2({
5562
5799
  registryUrl: relayRegistryUrl,
5563
5800
  owner: relayRequesterOwner,
@@ -5579,7 +5816,7 @@ function createRegistryServer(opts) {
5579
5816
  });
5580
5817
  await relayClient.connect();
5581
5818
  }
5582
- const { requestViaRelay: requestViaRelay2 } = await import("../../client-XOLP5IUZ.js");
5819
+ const { requestViaRelay: requestViaRelay2 } = await import("../../client-OKJJ3UP2.js");
5583
5820
  return requestViaRelay2(relayClient, {
5584
5821
  targetOwner: target.owner,
5585
5822
  cardId: target.cardId,
@@ -5835,7 +6072,7 @@ function createRegistryServer(opts) {
5835
6072
  if (!opts.creditDb) {
5836
6073
  return reply.code(404).send({ error: "Credit system not enabled" });
5837
6074
  }
5838
- const { getReliabilityMetrics } = await import("../../reliability-metrics-QG7WC5QK.js");
6075
+ const { getReliabilityMetrics } = await import("../../reliability-metrics-G7LPUYJD.js");
5839
6076
  const metrics = getReliabilityMetrics(opts.creditDb, owner);
5840
6077
  if (!metrics) {
5841
6078
  return reply.code(404).send({ error: "No reliability data for this provider" });
@@ -5853,38 +6090,37 @@ function createRegistryServer(opts) {
5853
6090
  }
5854
6091
  }, async (request, reply) => {
5855
6092
  const { owner } = request.params;
5856
- const rows = db.prepare(
5857
- "SELECT id, data FROM capability_cards WHERE owner = ?"
5858
- ).all(owner);
6093
+ const cards = listCards(db, owner);
5859
6094
  const agents = [];
5860
- for (const row of rows) {
6095
+ for (const card of cards) {
5861
6096
  try {
5862
- const card = JSON.parse(row.data);
6097
+ const rawCard = card;
6098
+ const providerIdentity = typeof card.agent_id === "string" && card.agent_id.length > 0 ? card.agent_id : card.owner;
5863
6099
  let earnings = 0;
5864
6100
  let spend = 0;
5865
6101
  if (opts.creditDb) {
5866
6102
  const earningRow = opts.creditDb.prepare(
5867
6103
  "SELECT COALESCE(SUM(amount), 0) as total FROM credit_transactions WHERE owner = ? AND reason = 'settlement' AND amount > 0"
5868
- ).get(owner);
6104
+ ).get(providerIdentity);
5869
6105
  earnings = earningRow.total;
5870
6106
  const spendRow = opts.creditDb.prepare(
5871
6107
  "SELECT COALESCE(SUM(ABS(amount)), 0) as total FROM credit_transactions WHERE owner = ? AND reason = 'escrow_hold'"
5872
- ).get(owner);
6108
+ ).get(providerIdentity);
5873
6109
  spend = spendRow.total;
5874
6110
  }
5875
6111
  const successCount = db.prepare(
5876
6112
  "SELECT COUNT(*) as cnt FROM request_log WHERE card_id = ? AND status = 'success' AND (action_type IS NULL OR action_type = 'auto_share')"
5877
- ).get(row.id).cnt;
6113
+ ).get(card.id).cnt;
5878
6114
  const failureCount = db.prepare(
5879
6115
  "SELECT COUNT(*) as cnt FROM request_log WHERE card_id = ? AND status IN ('failure', 'timeout', 'refunded') AND (action_type IS NULL OR action_type = 'auto_share')"
5880
- ).get(row.id).cnt;
6116
+ ).get(card.id).cnt;
5881
6117
  const totalExec = successCount + failureCount;
5882
6118
  const successRate = totalExec > 0 ? successCount / totalExec : 0;
5883
6119
  let failureBreakdown = {};
5884
6120
  try {
5885
6121
  const failureRows = db.prepare(
5886
6122
  "SELECT failure_reason, COUNT(*) as cnt FROM request_log WHERE card_id = ? AND status IN ('failure', 'timeout', 'refunded') AND failure_reason IS NOT NULL GROUP BY failure_reason"
5887
- ).all(row.id);
6123
+ ).all(card.id);
5888
6124
  for (const fr of failureRows) {
5889
6125
  failureBreakdown[fr.failure_reason] = fr.cnt;
5890
6126
  }
@@ -5892,12 +6128,12 @@ function createRegistryServer(opts) {
5892
6128
  }
5893
6129
  let reliability = null;
5894
6130
  if (opts.creditDb) {
5895
- const { getReliabilityMetrics } = await import("../../reliability-metrics-QG7WC5QK.js");
5896
- reliability = getReliabilityMetrics(opts.creditDb, owner);
6131
+ const { getReliabilityMetrics } = await import("../../reliability-metrics-G7LPUYJD.js");
6132
+ reliability = getReliabilityMetrics(opts.creditDb, providerIdentity);
5897
6133
  }
5898
6134
  agents.push({
5899
- id: row.id,
5900
- name: card.name ?? card.agent_name ?? owner,
6135
+ id: card.id,
6136
+ name: (typeof rawCard["name"] === "string" ? rawCard["name"] : void 0) ?? (typeof rawCard["agent_name"] === "string" ? rawCard["agent_name"] : void 0) ?? card.owner,
5901
6137
  online: card.availability?.online ?? false,
5902
6138
  current_load: 0,
5903
6139
  // Will be populated from relay heartbeat data in future
@@ -6175,6 +6411,32 @@ import { spawn as spawn2 } from "child_process";
6175
6411
  import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
6176
6412
  import { join as join5 } from "path";
6177
6413
  import { randomUUID as randomUUID8 } from "crypto";
6414
+ function buildFallbackRelayCard(owner) {
6415
+ return {
6416
+ id: randomUUID8(),
6417
+ owner,
6418
+ name: owner,
6419
+ description: "Agent registered via CLI",
6420
+ spec_version: "1.0",
6421
+ level: 1,
6422
+ inputs: [],
6423
+ outputs: [],
6424
+ pricing: { credits_per_call: 1 },
6425
+ availability: { online: true }
6426
+ };
6427
+ }
6428
+ function buildRelayRegistrationCards(owner, localCards) {
6429
+ if (localCards.length === 0) {
6430
+ return {
6431
+ primaryCard: buildFallbackRelayCard(owner),
6432
+ additionalCards: []
6433
+ };
6434
+ }
6435
+ return {
6436
+ primaryCard: localCards[0],
6437
+ additionalCards: localCards.slice(1)
6438
+ };
6439
+ }
6178
6440
  var ServiceCoordinator = class {
6179
6441
  config;
6180
6442
  guard;
@@ -6319,8 +6581,11 @@ var ServiceCoordinator = class {
6319
6581
  console.log("Conductor mode enabled \u2014 orchestrate/plan skills available via gateway");
6320
6582
  }
6321
6583
  if (opts.conductorEnabled && this.config.conductor?.public) {
6322
- const { buildConductorCard } = await import("../../card-EX2EYGCZ.js");
6323
- const conductorCard = buildConductorCard(this.config.owner);
6584
+ const { buildConductorCard } = await import("../../card-BN643ZOY.js");
6585
+ const conductorCard = attachCanonicalAgentId(
6586
+ this.runtime.registryDb,
6587
+ buildConductorCard(this.config.owner)
6588
+ );
6324
6589
  const now = (/* @__PURE__ */ new Date()).toISOString();
6325
6590
  const existing = this.runtime.registryDb.prepare("SELECT id FROM capability_cards WHERE id = ?").get(conductorCard.id);
6326
6591
  if (existing) {
@@ -6376,27 +6641,11 @@ var ServiceCoordinator = class {
6376
6641
  }
6377
6642
  }
6378
6643
  if (opts.registryUrl && opts.relay) {
6379
- const { RelayClient: RelayClient2 } = await import("../../websocket-client-4Z5P54RU.js");
6380
- const { executeCapabilityRequest: executeCapabilityRequest2 } = await import("../../execute-NNDCXTN4.js");
6381
- const cards = listCards(this.runtime.registryDb, this.config.owner);
6382
- const card = cards[0] ?? {
6383
- id: randomUUID8(),
6384
- owner: this.config.owner,
6385
- name: this.config.owner,
6386
- description: "Agent registered via CLI",
6387
- spec_version: "1.0",
6388
- level: 1,
6389
- inputs: [],
6390
- outputs: [],
6391
- pricing: { credits_per_call: 1 },
6392
- availability: { online: true }
6393
- };
6394
- const additionalCards = [];
6644
+ const { RelayClient: RelayClient2 } = await import("../../websocket-client-SNDF3B6N.js");
6645
+ const { executeCapabilityRequest: executeCapabilityRequest2 } = await import("../../execute-FZLQGIXB.js");
6646
+ const localCards = listCards(this.runtime.registryDb, this.config.owner);
6647
+ const { primaryCard, additionalCards } = buildRelayRegistrationCards(this.config.owner, localCards);
6395
6648
  if (this.config.conductor?.public) {
6396
- const { buildConductorCard } = await import("../../card-EX2EYGCZ.js");
6397
- additionalCards.push(
6398
- buildConductorCard(this.config.owner)
6399
- );
6400
6649
  console.log("Conductor card will be published to registry (conductor.public: true)");
6401
6650
  }
6402
6651
  this.relayClient = new RelayClient2({
@@ -6404,9 +6653,10 @@ var ServiceCoordinator = class {
6404
6653
  owner: this.config.owner,
6405
6654
  agent_id: this.config.agent_id,
6406
6655
  token: this.config.token,
6407
- card,
6656
+ card: primaryCard,
6408
6657
  cards: additionalCards.length > 0 ? additionalCards : void 0,
6409
6658
  onRequest: async (req) => {
6659
+ this.relayClient?.sendStarted(req.id, "provider acknowledged");
6410
6660
  const onProgress = (info) => {
6411
6661
  this.relayClient.sendProgress(req.id, info);
6412
6662
  };
@@ -6722,6 +6972,14 @@ function createSignedEscrowReceipt(db, privateKey, publicKey, opts) {
6722
6972
  return { escrowId, receipt };
6723
6973
  }
6724
6974
 
6975
+ // src/credit/settlement.ts
6976
+ function settleRequesterEscrow(requesterDb, escrowId) {
6977
+ confirmEscrowDebit(requesterDb, escrowId);
6978
+ }
6979
+ function releaseRequesterEscrow(requesterDb, escrowId) {
6980
+ releaseEscrow(requesterDb, escrowId);
6981
+ }
6982
+
6725
6983
  // src/app/agentbnb-service.ts
6726
6984
  var AgentBnBService = class {
6727
6985
  coordinator;
@@ -7131,8 +7389,35 @@ var requestInputSchema = {
7131
7389
  card_id: z10.string().optional().describe("Direct card ID to request (skips search)"),
7132
7390
  skill_id: z10.string().optional().describe("Specific skill within a v2.0 card"),
7133
7391
  params: z10.record(z10.unknown()).optional().describe("Input parameters for the capability"),
7134
- max_cost: z10.number().optional().default(50).describe("Maximum credits to spend")
7392
+ max_cost: z10.number().optional().default(50).describe("Maximum credits to spend"),
7393
+ timeout_ms: z10.number().positive().optional().describe("Requester timeout override in milliseconds")
7135
7394
  };
7395
+ function parsePositiveNumber(value) {
7396
+ return typeof value === "number" && value > 0 ? value : void 0;
7397
+ }
7398
+ function deriveTimeoutHintFromCard(remoteCard, skillId) {
7399
+ const topLevelHint = {
7400
+ expected_duration_ms: parsePositiveNumber(remoteCard["expected_duration_ms"]),
7401
+ hard_timeout_ms: parsePositiveNumber(remoteCard["hard_timeout_ms"])
7402
+ };
7403
+ const skills = remoteCard["skills"];
7404
+ if (!Array.isArray(skills)) {
7405
+ return topLevelHint.expected_duration_ms !== void 0 || topLevelHint.hard_timeout_ms !== void 0 ? topLevelHint : void 0;
7406
+ }
7407
+ const selectedSkill = skillId ? skills.find((candidate) => {
7408
+ if (!candidate || typeof candidate !== "object") return false;
7409
+ return candidate["id"] === skillId;
7410
+ }) : skills[0];
7411
+ if (!selectedSkill || typeof selectedSkill !== "object") {
7412
+ return topLevelHint.expected_duration_ms !== void 0 || topLevelHint.hard_timeout_ms !== void 0 ? topLevelHint : void 0;
7413
+ }
7414
+ const skillRecord = selectedSkill;
7415
+ const skillHint = {
7416
+ expected_duration_ms: parsePositiveNumber(skillRecord["expected_duration_ms"]) ?? topLevelHint.expected_duration_ms,
7417
+ hard_timeout_ms: parsePositiveNumber(skillRecord["hard_timeout_ms"]) ?? topLevelHint.hard_timeout_ms
7418
+ };
7419
+ return skillHint.expected_duration_ms !== void 0 || skillHint.hard_timeout_ms !== void 0 ? skillHint : void 0;
7420
+ }
7136
7421
  async function handleRequest(args, ctx) {
7137
7422
  try {
7138
7423
  const maxCost = args.max_cost ?? 50;
@@ -7196,6 +7481,7 @@ async function handleRequest(args, ctx) {
7196
7481
  token: ctx.config.token,
7197
7482
  cardId,
7198
7483
  params: { ...args.params ?? {}, ...args.skill_id ? { skill_id: args.skill_id } : {}, requester: ctx.config.owner },
7484
+ timeoutMs: args.timeout_ms,
7199
7485
  identity: identityAuth
7200
7486
  });
7201
7487
  return {
@@ -7224,7 +7510,9 @@ async function handleRequest(args, ctx) {
7224
7510
  };
7225
7511
  }
7226
7512
  const targetOwner = remoteCard["owner"] ?? remoteCard["agent_name"];
7513
+ const targetAgentId = typeof remoteCard["agent_id"] === "string" ? remoteCard["agent_id"] : void 0;
7227
7514
  const gatewayUrl = remoteCard["gateway_url"];
7515
+ const timeoutHint = deriveTimeoutHintFromCard(remoteCard, args.skill_id);
7228
7516
  if (gatewayUrl) {
7229
7517
  let remoteCost = 0;
7230
7518
  const remoteSkills = remoteCard["skills"];
@@ -7236,43 +7524,33 @@ async function handleRequest(args, ctx) {
7236
7524
  remoteCost = remotePricing?.credits_per_call ?? 0;
7237
7525
  }
7238
7526
  if (remoteCost > 0) {
7239
- const creditDb = openCreditDb(ctx.config.credit_db_path);
7240
- creditDb.pragma("busy_timeout = 5000");
7241
- try {
7242
- const keys = loadKeyPair(ctx.configDir);
7243
- const { escrowId, receipt } = createSignedEscrowReceipt(creditDb, keys.privateKey, keys.publicKey, {
7244
- owner: ctx.config.owner,
7245
- agent_id: ctx.identity.agent_id,
7246
- amount: remoteCost,
7247
- cardId,
7248
- skillId: args.skill_id
7249
- });
7250
- try {
7251
- const result = await requestCapability({
7252
- gatewayUrl,
7253
- token: ctx.config.token ?? "",
7254
- cardId,
7255
- params: { ...args.params ?? {}, ...args.skill_id ? { skill_id: args.skill_id } : {}, requester: ctx.config.owner },
7256
- identity: identityAuth,
7257
- escrowReceipt: receipt
7258
- });
7259
- confirmEscrowDebit(creditDb, escrowId);
7260
- return {
7261
- content: [{ type: "text", text: JSON.stringify({ success: true, result }, null, 2) }]
7262
- };
7263
- } catch (execErr) {
7264
- releaseEscrow(creditDb, escrowId);
7265
- throw execErr;
7266
- }
7267
- } finally {
7268
- creditDb.close();
7527
+ if (!targetOwner) {
7528
+ return {
7529
+ content: [{ type: "text", text: JSON.stringify({ success: false, error: "Paid remote request requires a target owner for relay routing" }) }]
7530
+ };
7269
7531
  }
7532
+ const result = await requestViaTemporaryRelay({
7533
+ registryUrl: ctx.config.registry,
7534
+ owner: ctx.config.owner,
7535
+ token: ctx.config.token ?? "",
7536
+ targetOwner,
7537
+ targetAgentId,
7538
+ cardId,
7539
+ skillId: args.skill_id,
7540
+ params: { ...args.params ?? {}, ...args.skill_id ? { skill_id: args.skill_id } : {}, requester: ctx.config.owner },
7541
+ timeoutMs: args.timeout_ms
7542
+ });
7543
+ return {
7544
+ content: [{ type: "text", text: JSON.stringify({ success: true, result }, null, 2) }]
7545
+ };
7270
7546
  } else {
7271
7547
  const result = await requestCapability({
7272
7548
  gatewayUrl,
7273
7549
  token: ctx.config.token ?? "",
7274
7550
  cardId,
7275
7551
  params: { ...args.params ?? {}, ...args.skill_id ? { skill_id: args.skill_id } : {}, requester: ctx.config.owner },
7552
+ timeoutMs: args.timeout_ms,
7553
+ timeoutHint,
7276
7554
  identity: identityAuth
7277
7555
  });
7278
7556
  return {
@@ -7281,30 +7559,20 @@ async function handleRequest(args, ctx) {
7281
7559
  }
7282
7560
  }
7283
7561
  if (targetOwner) {
7284
- const relay = new RelayClient({
7562
+ const result = await requestViaTemporaryRelay({
7285
7563
  registryUrl: ctx.config.registry,
7286
7564
  owner: ctx.config.owner,
7287
7565
  token: ctx.config.token ?? "",
7288
- card: { id: ctx.config.owner, owner: ctx.config.owner, name: "mcp-requester" },
7289
- onRequest: async () => ({ error: { code: -32601, message: "MCP client does not accept requests" } }),
7290
- silent: true
7566
+ targetOwner,
7567
+ targetAgentId,
7568
+ cardId,
7569
+ skillId: args.skill_id,
7570
+ params: { ...args.params ?? {}, ...args.skill_id ? { skill_id: args.skill_id } : {} },
7571
+ timeoutMs: args.timeout_ms
7291
7572
  });
7292
- try {
7293
- await relay.connect();
7294
- const result = await relay.request({
7295
- targetOwner,
7296
- cardId,
7297
- skillId: args.skill_id,
7298
- params: args.params ?? {},
7299
- requester: ctx.config.owner,
7300
- timeoutMs: 3e5
7301
- });
7302
- return {
7303
- content: [{ type: "text", text: JSON.stringify({ success: true, result }, null, 2) }]
7304
- };
7305
- } finally {
7306
- relay.disconnect();
7307
- }
7573
+ return {
7574
+ content: [{ type: "text", text: JSON.stringify({ success: true, result }, null, 2) }]
7575
+ };
7308
7576
  }
7309
7577
  return {
7310
7578
  content: [{ type: "text", text: JSON.stringify({ success: false, error: "Remote card has no gateway_url and no owner for relay routing" }) }]
@@ -7381,17 +7649,24 @@ async function conductAction(task, opts) {
7381
7649
  matchResults.map((m) => [m.subtask_id, m])
7382
7650
  );
7383
7651
  const resolveAgentUrl = (owner) => {
7384
- const peer = peers.find((p) => p.name.toLowerCase() === owner.toLowerCase());
7385
- if (peer) {
7386
- const execDb = openDatabase(config.db_path);
7387
- try {
7388
- const stmt = execDb.prepare("SELECT id FROM capability_cards WHERE owner = ? LIMIT 1");
7389
- const row = stmt.get(owner);
7390
- return { url: peer.url, cardId: row?.id ?? owner };
7391
- } finally {
7392
- execDb.close();
7652
+ const execDb = openDatabase(config.db_path);
7653
+ let matchingCards;
7654
+ try {
7655
+ matchingCards = listCards(execDb, owner);
7656
+ } finally {
7657
+ execDb.close();
7658
+ }
7659
+ const candidateNames = /* @__PURE__ */ new Set([owner.toLowerCase()]);
7660
+ for (const card of matchingCards) {
7661
+ candidateNames.add(card.owner.toLowerCase());
7662
+ if (typeof card.agent_id === "string" && card.agent_id.length > 0) {
7663
+ candidateNames.add(card.agent_id.toLowerCase());
7393
7664
  }
7394
7665
  }
7666
+ const peer = peers.find((p) => candidateNames.has(p.name.toLowerCase()));
7667
+ if (peer) {
7668
+ return { url: peer.url, cardId: matchingCards[0]?.id ?? owner };
7669
+ }
7395
7670
  if (config.registry) {
7396
7671
  let cardId = owner;
7397
7672
  for (const m of matchMap.values()) {