agentbnb 8.2.2 → 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 +211 -278
  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-NX7GSPIG.js → request-KJNKR27T.js} +96 -43
  46. package/dist/{serve-skill-E6EJQYAK.js → serve-skill-GC6NIQ5T.js} +10 -11
  47. package/dist/{server-VBCT32FC.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 +550 -231
  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 +18 -12
  54. package/skills/agentbnb/install.sh +0 -0
  55. package/dist/chunk-MCED4GDW.js +0 -1572
  56. package/dist/chunk-NWIQJ2CL.js +0 -108
  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,29 +12,57 @@ 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,
37
41
  computeReputation,
42
+ confirmEscrowDebit,
38
43
  fetchRemoteCards,
39
44
  filterCards,
40
- getActivityFeed,
41
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,
42
66
  getCard,
43
67
  getCardsBySkillCapability,
44
68
  getEvolutionHistory,
@@ -47,41 +71,23 @@ import {
47
71
  getLatestEvolution,
48
72
  getRequestLog,
49
73
  getSkillRequestCount,
50
- getTransactions,
51
- holdEscrow,
52
74
  insertCard,
53
75
  insertEvolution,
54
76
  insertFeedback,
55
77
  insertRequestLog,
56
78
  listCards,
57
- lookupAgent,
58
- mergeResults,
59
- migrateOwner,
60
- openCreditDb,
61
79
  openDatabase,
62
- releaseEscrow,
63
- searchCards,
64
- settleEscrow,
65
80
  updateCard,
66
81
  updateSkillAvailability,
67
82
  updateSkillIdleRate
68
- } from "../../chunk-MCED4GDW.js";
69
- import "../../chunk-NWIQJ2CL.js";
83
+ } from "../../chunk-ZU2TP7CN.js";
70
84
  import {
71
- generateKeyPair,
72
- loadKeyPair,
73
- saveKeyPair,
74
- signEscrowReceipt,
75
- verifyEscrowReceipt
76
- } from "../../chunk-EJKW57ZV.js";
77
- import {
78
- getConfigDir,
79
- loadConfig
80
- } from "../../chunk-IVOYM3WG.js";
85
+ lookupAgent
86
+ } from "../../chunk-EE3V3DXK.js";
81
87
  import {
82
88
  AgentBnBError,
83
89
  AnyCardSchema
84
- } from "../../chunk-WVY2W7AA.js";
90
+ } from "../../chunk-I7KWA7OB.js";
85
91
 
86
92
  // skills/agentbnb/bootstrap.ts
87
93
  import { join as join7, basename as basename2, dirname as dirname3 } from "path";
@@ -264,6 +270,9 @@ function isPidFileContent(value) {
264
270
  import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
265
271
 
266
272
  // src/skills/executor.ts
273
+ function buildTimeoutError(skillId, timeoutMs) {
274
+ return `Skill "${skillId}" timed out after ${timeoutMs}ms`;
275
+ }
267
276
  var SkillExecutor = class {
268
277
  skillMap;
269
278
  modeMap;
@@ -322,7 +331,20 @@ var SkillExecutor = class {
322
331
  this.concurrencyGuard.acquire(skillId);
323
332
  }
324
333
  try {
325
- 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
+ });
326
348
  return {
327
349
  ...modeResult,
328
350
  latency_ms: Date.now() - startTime
@@ -393,7 +415,8 @@ var CapabilityDeclarationSchema = {
393
415
  description: z.string().optional(),
394
416
  capability_types: z.array(z.string()).optional(),
395
417
  requires_capabilities: z.array(z.string()).optional(),
396
- visibility: z.enum(["public", "private"]).optional()
418
+ visibility: z.enum(["public", "private"]).optional(),
419
+ expected_duration_ms: z.number().positive().optional()
397
420
  };
398
421
  var ApiSkillConfigSchema = z.object({
399
422
  id: z.string().min(1),
@@ -677,6 +700,15 @@ var ApiExecutor = class {
677
700
  import { execFile } from "child_process";
678
701
  import { promisify } from "util";
679
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
+ }
680
712
  function shellEscape(value) {
681
713
  return "'" + value.replace(/'/g, "'\\''") + "'";
682
714
  }
@@ -728,6 +760,8 @@ var PipelineExecutor = class {
728
760
  async execute(config, params, onProgress) {
729
761
  const pipelineConfig = config;
730
762
  const steps = pipelineConfig.steps ?? [];
763
+ const pipelineTimeoutMs = pipelineConfig.timeout_ms;
764
+ const deadline = typeof pipelineTimeoutMs === "number" ? Date.now() + pipelineTimeoutMs : void 0;
731
765
  if (steps.length === 0) {
732
766
  return { success: true, result: null };
733
767
  }
@@ -749,11 +783,41 @@ var PipelineExecutor = class {
749
783
  context
750
784
  );
751
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
+ };
752
807
  if ("skill_id" in step && step.skill_id) {
753
- const subResult = await this.skillExecutor.execute(
754
- step.skill_id,
755
- resolvedInputs
756
- );
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
+ }
757
821
  if (!subResult.success) {
758
822
  return {
759
823
  success: false,
@@ -767,10 +831,16 @@ var PipelineExecutor = class {
767
831
  context
768
832
  );
769
833
  try {
770
- 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
+ });
771
841
  stepResult = stdout.trim();
772
842
  } catch (err) {
773
- 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);
774
844
  return {
775
845
  success: false,
776
846
  error: `Step ${i} failed: ${message}`
@@ -857,17 +927,43 @@ function executeProcess(config, payload) {
857
927
  error: `Invalid agent name: "${config.agent_name}" (only alphanumeric, hyphens, underscores, dots allowed)`
858
928
  };
859
929
  }
860
- 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"}`;
861
942
  try {
862
- 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
+ ], {
863
952
  timeout: timeoutMs
864
953
  });
865
954
  const text = stdout.toString().trim();
866
- const result = JSON.parse(text);
867
- 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
+ }
868
964
  } catch (err) {
869
- const message = err instanceof Error ? err.message : String(err);
870
- return { success: false, error: message };
965
+ const message2 = err instanceof Error ? err.message : String(err);
966
+ return { success: false, error: message2 };
871
967
  }
872
968
  }
873
969
  async function executeTelegram(config, payload) {
@@ -1276,23 +1372,27 @@ var AgentRuntime = class {
1276
1372
  }
1277
1373
  const modes = /* @__PURE__ */ new Map();
1278
1374
  if (this.conductorEnabled) {
1279
- const { ConductorMode } = await import("../../conductor-mode-WKB42PYM.js");
1280
- 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");
1281
1377
  const { loadPeers: loadPeers2 } = await import("../../peers-CJ7T4RJO.js");
1282
1378
  registerConductorCard(this.registryDb);
1283
1379
  const resolveAgentUrl = (owner) => {
1284
1380
  const peers = loadPeers2();
1285
- 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()));
1286
1390
  if (!peer) {
1287
1391
  throw new Error(
1288
1392
  `No peer found for agent owner "${owner}". Add with: agentbnb connect ${owner} <url> <token>`
1289
1393
  );
1290
1394
  }
1291
- const stmt = this.registryDb.prepare(
1292
- "SELECT id FROM capability_cards WHERE owner = ? LIMIT 1"
1293
- );
1294
- const row = stmt.get(owner);
1295
- const cardId = row?.id ?? owner;
1395
+ const cardId = matchingCards[0]?.id ?? owner;
1296
1396
  return { url: peer.url, cardId };
1297
1397
  };
1298
1398
  const conductorMode = new ConductorMode({
@@ -1334,7 +1434,8 @@ var AgentRuntime = class {
1334
1434
  }
1335
1435
  /**
1336
1436
  * Recovers orphaned escrows by releasing them.
1337
- * 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.
1338
1439
  * Errors during individual release are swallowed (escrow may have settled between query and release).
1339
1440
  */
1340
1441
  async recoverOrphanedEscrows() {
@@ -1342,7 +1443,7 @@ var AgentRuntime = class {
1342
1443
  Date.now() - this.orphanedEscrowAgeMinutes * 60 * 1e3
1343
1444
  ).toISOString();
1344
1445
  const orphaned = this.creditDb.prepare(
1345
- "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 < ?"
1346
1447
  ).all(cutoff);
1347
1448
  for (const row of orphaned) {
1348
1449
  try {
@@ -2438,14 +2539,16 @@ function processEscrowSettle(creditDb, escrowId, success, providerAgentId, signa
2438
2539
  throw new Error("Invalid consumer signature on escrow settle");
2439
2540
  }
2440
2541
  }
2441
- 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);
2442
2545
  if (!escrowRow) {
2443
2546
  throw new Error(`Escrow not found or already settled: ${escrowId}`);
2444
2547
  }
2445
- const NETWORK_FEE_RATE = 0.05;
2548
+ const NETWORK_FEE_RATE2 = 0.05;
2446
2549
  if (success) {
2447
2550
  settleEscrow(creditDb, escrowId, providerAgentId);
2448
- const networkFee = Math.floor(escrowRow.amount * NETWORK_FEE_RATE);
2551
+ const networkFee = Math.floor(escrowRow.amount * NETWORK_FEE_RATE2);
2449
2552
  const providerAmount = escrowRow.amount - networkFee;
2450
2553
  return {
2451
2554
  escrow_id: escrowId,
@@ -2769,7 +2872,16 @@ function handleJobRelayResponse(opts) {
2769
2872
  // src/relay/websocket-relay.ts
2770
2873
  var RATE_LIMIT_MAX = 60;
2771
2874
  var RATE_LIMIT_WINDOW_MS = 6e4;
2772
- 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
+ }
2773
2885
  function registerWebSocketRelay(server, db, creditDb) {
2774
2886
  const connections = /* @__PURE__ */ new Map();
2775
2887
  const agentIdToOwner = /* @__PURE__ */ new Map();
@@ -2783,6 +2895,136 @@ function registerWebSocketRelay(server, db, creditDb) {
2783
2895
  if (connections.has(target)) return target;
2784
2896
  return void 0;
2785
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
+ }
2786
3028
  function checkRateLimit(owner) {
2787
3029
  const now = Date.now();
2788
3030
  const entry = rateLimits.get(owner);
@@ -2832,15 +3074,20 @@ function registerWebSocketRelay(server, db, creditDb) {
2832
3074
  } catch {
2833
3075
  }
2834
3076
  }
2835
- function upsertCard(cardData, owner) {
2836
- const parsed = AnyCardSchema.safeParse(cardData);
3077
+ function upsertCard(cardData, owner, agentId) {
3078
+ const parsed = AnyCardSchema.safeParse(
3079
+ agentId ? { ...cardData, agent_id: agentId } : cardData
3080
+ );
2837
3081
  if (!parsed.success) {
2838
3082
  throw new AgentBnBError(
2839
3083
  `Card validation failed: ${parsed.error.message}`,
2840
3084
  "VALIDATION_ERROR"
2841
3085
  );
2842
3086
  }
2843
- 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
+ });
2844
3091
  const cardId = card.id;
2845
3092
  const now = (/* @__PURE__ */ new Date()).toISOString();
2846
3093
  const existing = db.prepare("SELECT id FROM capability_cards WHERE id = ?").get(cardId);
@@ -2894,7 +3141,7 @@ function registerWebSocketRelay(server, db, creditDb) {
2894
3141
  agentIdToOwner.set(agentEntry.agent_id, owner);
2895
3142
  for (const agentCard of agentEntry.cards) {
2896
3143
  try {
2897
- upsertCard(agentCard, owner);
3144
+ upsertCard(agentCard, owner, agentEntry.agent_id);
2898
3145
  } catch {
2899
3146
  }
2900
3147
  }
@@ -2908,7 +3155,7 @@ function registerWebSocketRelay(server, db, creditDb) {
2908
3155
  }
2909
3156
  let cardId;
2910
3157
  try {
2911
- cardId = upsertCard(card, owner);
3158
+ cardId = upsertCard(card, owner, msg.agent_id);
2912
3159
  } catch (err) {
2913
3160
  console.error(`[relay] card validation failed for ${owner}:`, err instanceof Error ? err.message : err);
2914
3161
  cardId = card.id ?? owner;
@@ -2918,7 +3165,7 @@ function registerWebSocketRelay(server, db, creditDb) {
2918
3165
  if (msg.cards && msg.cards.length > 0) {
2919
3166
  for (const extraCard of msg.cards) {
2920
3167
  try {
2921
- upsertCard(extraCard, owner);
3168
+ upsertCard(extraCard, owner, msg.agent_id);
2922
3169
  } catch {
2923
3170
  }
2924
3171
  }
@@ -2973,23 +3220,17 @@ function registerWebSocketRelay(server, db, creditDb) {
2973
3220
  console.error("[relay] credit hold error (non-fatal):", err);
2974
3221
  }
2975
3222
  }
2976
- const timeout = setTimeout(() => {
2977
- const pending = pendingRequests.get(msg.id);
2978
- pendingRequests.delete(msg.id);
2979
- if (pending?.escrowId && creditDb) {
2980
- try {
2981
- releaseForRelay(creditDb, pending.escrowId);
2982
- } catch (e) {
2983
- console.error("[relay] escrow release on timeout failed:", e);
2984
- }
2985
- }
2986
- sendMessage(ws, {
2987
- type: "response",
2988
- id: msg.id,
2989
- error: { code: -32603, message: "Relay request timeout" }
2990
- });
2991
- }, RELAY_TIMEOUT_MS);
2992
- 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);
2993
3234
  sendMessage(targetWs, {
2994
3235
  type: "incoming_request",
2995
3236
  id: msg.id,
@@ -3001,30 +3242,23 @@ function registerWebSocketRelay(server, db, creditDb) {
3001
3242
  escrow_receipt: msg.escrow_receipt
3002
3243
  });
3003
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
+ }
3004
3258
  function handleRelayProgress(msg) {
3005
3259
  const pending = pendingRequests.get(msg.id);
3006
3260
  if (!pending) return;
3007
- clearTimeout(pending.timeout);
3008
- const newTimeout = setTimeout(() => {
3009
- const p = pendingRequests.get(msg.id);
3010
- pendingRequests.delete(msg.id);
3011
- if (p?.escrowId && creditDb) {
3012
- try {
3013
- releaseForRelay(creditDb, p.escrowId);
3014
- } catch (e) {
3015
- console.error("[relay] escrow release on progress timeout failed:", e);
3016
- }
3017
- }
3018
- const originWs2 = connections.get(pending.originOwner);
3019
- if (originWs2 && originWs2.readyState === 1) {
3020
- sendMessage(originWs2, {
3021
- type: "response",
3022
- id: msg.id,
3023
- error: { code: -32603, message: "Relay request timeout" }
3024
- });
3025
- }
3026
- }, RELAY_TIMEOUT_MS);
3027
- pending.timeout = newTimeout;
3261
+ transitionPendingToProgressing(msg.id, pending);
3028
3262
  const originWs = connections.get(pending.originOwner);
3029
3263
  if (originWs && originWs.readyState === 1) {
3030
3264
  sendMessage(originWs, {
@@ -3038,7 +3272,7 @@ function registerWebSocketRelay(server, db, creditDb) {
3038
3272
  function handleRelayResponse(msg) {
3039
3273
  const pending = pendingRequests.get(msg.id);
3040
3274
  if (!pending) return;
3041
- clearTimeout(pending.timeout);
3275
+ clearPendingTimers(pending);
3042
3276
  pendingRequests.delete(msg.id);
3043
3277
  if (pending.jobId && creditDb) {
3044
3278
  try {
@@ -3101,16 +3335,15 @@ function registerWebSocketRelay(server, db, creditDb) {
3101
3335
  connections.delete(owner);
3102
3336
  rateLimits.delete(owner);
3103
3337
  agentCapacities.delete(owner);
3104
- for (const [agentId, o] of agentIdToOwner) {
3105
- if (o === owner) {
3338
+ for (const [agentId, mappedOwner] of Array.from(agentIdToOwner.entries())) {
3339
+ if (mappedOwner === owner) {
3106
3340
  agentIdToOwner.delete(agentId);
3107
- break;
3108
3341
  }
3109
3342
  }
3110
3343
  markOwnerOffline(owner);
3111
3344
  for (const [reqId, pending] of pendingRequests) {
3112
3345
  if (pending.targetOwner === owner) {
3113
- clearTimeout(pending.timeout);
3346
+ clearPendingTimers(pending);
3114
3347
  pendingRequests.delete(reqId);
3115
3348
  if (pending.escrowId && creditDb) {
3116
3349
  try {
@@ -3128,14 +3361,19 @@ function registerWebSocketRelay(server, db, creditDb) {
3128
3361
  });
3129
3362
  }
3130
3363
  } else if (pending.originOwner === owner) {
3131
- clearTimeout(pending.timeout);
3132
- pendingRequests.delete(reqId);
3133
- if (pending.escrowId && creditDb) {
3134
- try {
3135
- releaseForRelay(creditDb, pending.escrowId);
3136
- } catch (e) {
3137
- 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
+ }
3138
3374
  }
3375
+ } else {
3376
+ transitionPendingToAbandoned(reqId, pending);
3139
3377
  }
3140
3378
  }
3141
3379
  }
@@ -3178,7 +3416,9 @@ function registerWebSocketRelay(server, db, creditDb) {
3178
3416
  return;
3179
3417
  }
3180
3418
  try {
3181
- 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);
3182
3422
  if (!escrow) {
3183
3423
  sendMessage(ws, { type: "error", code: "escrow_not_found", message: `Escrow not found: ${msg.escrow_id}`, request_id: msg.request_id });
3184
3424
  return;
@@ -3275,6 +3515,9 @@ function registerWebSocketRelay(server, db, creditDb) {
3275
3515
  case "relay_response":
3276
3516
  handleRelayResponse(msg);
3277
3517
  break;
3518
+ case "relay_started":
3519
+ handleRelayStarted(msg);
3520
+ break;
3278
3521
  case "relay_progress":
3279
3522
  handleRelayProgress(msg);
3280
3523
  break;
@@ -3321,7 +3564,7 @@ function registerWebSocketRelay(server, db, creditDb) {
3321
3564
  }
3322
3565
  connections.clear();
3323
3566
  for (const [, pending] of pendingRequests) {
3324
- clearTimeout(pending.timeout);
3567
+ clearPendingTimers(pending);
3325
3568
  }
3326
3569
  pendingRequests.clear();
3327
3570
  rateLimits.clear();
@@ -4620,6 +4863,9 @@ function stripInternal(card) {
4620
4863
  const { _internal: _, ...publicCard } = card;
4621
4864
  return publicCard;
4622
4865
  }
4866
+ function buildSqlPlaceholders(count) {
4867
+ return Array.from({ length: count }, () => "?").join(", ");
4868
+ }
4623
4869
  function createRegistryServer(opts) {
4624
4870
  const { registryDb: db, silent = false } = opts;
4625
4871
  const server = Fastify2({ logger: !silent });
@@ -5029,11 +5275,11 @@ function createRegistryServer(opts) {
5029
5275
  const card = result.data;
5030
5276
  const now = (/* @__PURE__ */ new Date()).toISOString();
5031
5277
  if (card.spec_version === "2.0") {
5032
- const cardWithTimestamps = {
5278
+ const cardWithTimestamps = attachCanonicalAgentId(db, {
5033
5279
  ...card,
5034
5280
  created_at: card.created_at ?? now,
5035
5281
  updated_at: now
5036
- };
5282
+ });
5037
5283
  db.prepare(
5038
5284
  `INSERT OR REPLACE INTO capability_cards (id, owner, data, created_at, updated_at)
5039
5285
  VALUES (?, ?, ?, ?, ?)`
@@ -5154,19 +5400,16 @@ function createRegistryServer(opts) {
5154
5400
  if (ownerCards.length === 0) {
5155
5401
  return reply.status(404).send({ error: "Agent not found" });
5156
5402
  }
5157
- const memberStmt = db.prepare(
5158
- "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})`
5159
5410
  );
5160
- const memberRow = memberStmt.get(owner);
5161
- const joinedAt = memberRow?.earliest ?? (/* @__PURE__ */ new Date()).toISOString();
5162
- const lastActiveStmt = db.prepare(`
5163
- SELECT MAX(rl.created_at) as last_req
5164
- FROM request_log rl
5165
- INNER JOIN capability_cards cc ON rl.card_id = cc.id
5166
- WHERE cc.owner = ?
5167
- `);
5168
- const lastActiveRow = lastActiveStmt.get(owner);
5169
- const lastActive = lastActiveRow?.last_req ?? memberRow?.latest ?? joinedAt;
5411
+ const lastActiveRow = lastActiveStmt.get(...ownerCardIds);
5412
+ const lastActive = lastActiveRow?.last_req ?? latestCardUpdate ?? joinedAt;
5170
5413
  const metricsStmt = db.prepare(`
5171
5414
  SELECT
5172
5415
  SUM(CASE WHEN rl.failure_reason IS NULL OR rl.failure_reason IN ('bad_execution','auth_error')
@@ -5176,10 +5419,9 @@ function createRegistryServer(opts) {
5176
5419
  COUNT(DISTINCT rl.requester) as unique_requesters,
5177
5420
  COUNT(DISTINCT CASE WHEN rl.status = 'success' THEN rl.requester END) as repeat_success_requesters
5178
5421
  FROM request_log rl
5179
- INNER JOIN capability_cards cc ON rl.card_id = cc.id
5180
- WHERE cc.owner = ? AND rl.action_type IS NULL
5422
+ WHERE rl.card_id IN (${cardIdPlaceholders}) AND rl.action_type IS NULL
5181
5423
  `);
5182
- const metricsRow = metricsStmt.get(owner);
5424
+ const metricsRow = metricsStmt.get(...ownerCardIds);
5183
5425
  const totalExec = metricsRow?.total ?? 0;
5184
5426
  const successExec = metricsRow?.successes ?? 0;
5185
5427
  const successRate = totalExec > 0 ? successExec / totalExec : 0;
@@ -5193,25 +5435,23 @@ function createRegistryServer(opts) {
5193
5435
  COUNT(*) as count,
5194
5436
  SUM(CASE WHEN rl.status = 'success' THEN 1 ELSE 0 END) as success
5195
5437
  FROM request_log rl
5196
- INNER JOIN capability_cards cc ON rl.card_id = cc.id
5197
- WHERE cc.owner = ? AND rl.action_type IS NULL
5438
+ WHERE rl.card_id IN (${cardIdPlaceholders}) AND rl.action_type IS NULL
5198
5439
  AND rl.created_at >= DATE('now', '-7 days')
5199
5440
  GROUP BY DATE(rl.created_at)
5200
5441
  ORDER BY day ASC
5201
5442
  `);
5202
- 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 }));
5203
5444
  let performanceTier = 0;
5204
5445
  if (totalExec > 10) performanceTier = 1;
5205
5446
  if (totalExec > 50 && successRate >= 0.85) performanceTier = 2;
5206
5447
  const proofsStmt = db.prepare(`
5207
5448
  SELECT rl.card_name, rl.status, rl.latency_ms, rl.id, rl.created_at
5208
5449
  FROM request_log rl
5209
- INNER JOIN capability_cards cc ON rl.card_id = cc.id
5210
- WHERE cc.owner = ? AND rl.action_type IS NULL
5450
+ WHERE rl.card_id IN (${cardIdPlaceholders}) AND rl.action_type IS NULL
5211
5451
  ORDER BY rl.created_at DESC
5212
5452
  LIMIT 10
5213
5453
  `);
5214
- const proofRows = proofsStmt.all(owner);
5454
+ const proofRows = proofsStmt.all(...ownerCardIds);
5215
5455
  const statusToOutcomeClass = (s) => {
5216
5456
  if (s === "success") return "completed";
5217
5457
  if (s === "timeout") return "cancelled";
@@ -5237,22 +5477,20 @@ function createRegistryServer(opts) {
5237
5477
  const activityStmt = db.prepare(`
5238
5478
  SELECT rl.id, rl.card_name, rl.requester, rl.status, rl.credits_charged, rl.created_at
5239
5479
  FROM request_log rl
5240
- INNER JOIN capability_cards cc ON rl.card_id = cc.id
5241
- WHERE cc.owner = ?
5480
+ WHERE rl.card_id IN (${cardIdPlaceholders})
5242
5481
  ORDER BY rl.created_at DESC
5243
5482
  LIMIT 10
5244
5483
  `);
5245
- const recentActivity = activityStmt.all(owner);
5484
+ const recentActivity = activityStmt.all(...ownerCardIds);
5246
5485
  const skillCount = ownerCards.reduce((sum, card) => sum + (card.skills?.length ?? 1), 0);
5247
5486
  const creditsStmt = db.prepare(`
5248
- SELECT SUM(CASE WHEN rl.status = 'success' THEN rl.credits_charged ELSE 0 END) as credits_earned
5249
- FROM capability_cards cc
5250
- LEFT JOIN request_log rl ON rl.card_id = cc.id
5251
- 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})
5252
5490
  `);
5253
- const creditsRow = creditsStmt.get(owner);
5491
+ const creditsRow = creditsStmt.get(...ownerCardIds);
5254
5492
  const response = {
5255
- owner,
5493
+ owner: resolvedOwner,
5256
5494
  agent_name: v2Card?.agent_name,
5257
5495
  short_description: v2Card?.short_description,
5258
5496
  joined_at: joinedAt,
@@ -5284,7 +5522,7 @@ function createRegistryServer(opts) {
5284
5522
  return reply.send({
5285
5523
  ...response,
5286
5524
  profile: {
5287
- owner,
5525
+ owner: resolvedOwner,
5288
5526
  skill_count: skillCount,
5289
5527
  success_rate: successRate > 0 ? successRate : null,
5290
5528
  total_earned: creditsRow?.credits_earned ?? 0,
@@ -5556,7 +5794,7 @@ function createRegistryServer(opts) {
5556
5794
  return { card_id: target.cardId, skill_id: target.skillId };
5557
5795
  }
5558
5796
  if (!relayClient) {
5559
- const { RelayClient: RelayClient2 } = await import("../../websocket-client-4Z5P54RU.js");
5797
+ const { RelayClient: RelayClient2 } = await import("../../websocket-client-SNDF3B6N.js");
5560
5798
  relayClient = new RelayClient2({
5561
5799
  registryUrl: relayRegistryUrl,
5562
5800
  owner: relayRequesterOwner,
@@ -5578,7 +5816,7 @@ function createRegistryServer(opts) {
5578
5816
  });
5579
5817
  await relayClient.connect();
5580
5818
  }
5581
- const { requestViaRelay: requestViaRelay2 } = await import("../../client-XOLP5IUZ.js");
5819
+ const { requestViaRelay: requestViaRelay2 } = await import("../../client-OKJJ3UP2.js");
5582
5820
  return requestViaRelay2(relayClient, {
5583
5821
  targetOwner: target.owner,
5584
5822
  cardId: target.cardId,
@@ -5834,7 +6072,7 @@ function createRegistryServer(opts) {
5834
6072
  if (!opts.creditDb) {
5835
6073
  return reply.code(404).send({ error: "Credit system not enabled" });
5836
6074
  }
5837
- const { getReliabilityMetrics } = await import("../../reliability-metrics-QG7WC5QK.js");
6075
+ const { getReliabilityMetrics } = await import("../../reliability-metrics-G7LPUYJD.js");
5838
6076
  const metrics = getReliabilityMetrics(opts.creditDb, owner);
5839
6077
  if (!metrics) {
5840
6078
  return reply.code(404).send({ error: "No reliability data for this provider" });
@@ -5852,38 +6090,37 @@ function createRegistryServer(opts) {
5852
6090
  }
5853
6091
  }, async (request, reply) => {
5854
6092
  const { owner } = request.params;
5855
- const rows = db.prepare(
5856
- "SELECT id, data FROM capability_cards WHERE owner = ?"
5857
- ).all(owner);
6093
+ const cards = listCards(db, owner);
5858
6094
  const agents = [];
5859
- for (const row of rows) {
6095
+ for (const card of cards) {
5860
6096
  try {
5861
- 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;
5862
6099
  let earnings = 0;
5863
6100
  let spend = 0;
5864
6101
  if (opts.creditDb) {
5865
6102
  const earningRow = opts.creditDb.prepare(
5866
6103
  "SELECT COALESCE(SUM(amount), 0) as total FROM credit_transactions WHERE owner = ? AND reason = 'settlement' AND amount > 0"
5867
- ).get(owner);
6104
+ ).get(providerIdentity);
5868
6105
  earnings = earningRow.total;
5869
6106
  const spendRow = opts.creditDb.prepare(
5870
6107
  "SELECT COALESCE(SUM(ABS(amount)), 0) as total FROM credit_transactions WHERE owner = ? AND reason = 'escrow_hold'"
5871
- ).get(owner);
6108
+ ).get(providerIdentity);
5872
6109
  spend = spendRow.total;
5873
6110
  }
5874
6111
  const successCount = db.prepare(
5875
6112
  "SELECT COUNT(*) as cnt FROM request_log WHERE card_id = ? AND status = 'success' AND (action_type IS NULL OR action_type = 'auto_share')"
5876
- ).get(row.id).cnt;
6113
+ ).get(card.id).cnt;
5877
6114
  const failureCount = db.prepare(
5878
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')"
5879
- ).get(row.id).cnt;
6116
+ ).get(card.id).cnt;
5880
6117
  const totalExec = successCount + failureCount;
5881
6118
  const successRate = totalExec > 0 ? successCount / totalExec : 0;
5882
6119
  let failureBreakdown = {};
5883
6120
  try {
5884
6121
  const failureRows = db.prepare(
5885
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"
5886
- ).all(row.id);
6123
+ ).all(card.id);
5887
6124
  for (const fr of failureRows) {
5888
6125
  failureBreakdown[fr.failure_reason] = fr.cnt;
5889
6126
  }
@@ -5891,12 +6128,12 @@ function createRegistryServer(opts) {
5891
6128
  }
5892
6129
  let reliability = null;
5893
6130
  if (opts.creditDb) {
5894
- const { getReliabilityMetrics } = await import("../../reliability-metrics-QG7WC5QK.js");
5895
- reliability = getReliabilityMetrics(opts.creditDb, owner);
6131
+ const { getReliabilityMetrics } = await import("../../reliability-metrics-G7LPUYJD.js");
6132
+ reliability = getReliabilityMetrics(opts.creditDb, providerIdentity);
5896
6133
  }
5897
6134
  agents.push({
5898
- id: row.id,
5899
- 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,
5900
6137
  online: card.availability?.online ?? false,
5901
6138
  current_load: 0,
5902
6139
  // Will be populated from relay heartbeat data in future
@@ -6174,6 +6411,32 @@ import { spawn as spawn2 } from "child_process";
6174
6411
  import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
6175
6412
  import { join as join5 } from "path";
6176
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
+ }
6177
6440
  var ServiceCoordinator = class {
6178
6441
  config;
6179
6442
  guard;
@@ -6318,8 +6581,11 @@ var ServiceCoordinator = class {
6318
6581
  console.log("Conductor mode enabled \u2014 orchestrate/plan skills available via gateway");
6319
6582
  }
6320
6583
  if (opts.conductorEnabled && this.config.conductor?.public) {
6321
- const { buildConductorCard } = await import("../../card-EX2EYGCZ.js");
6322
- 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
+ );
6323
6589
  const now = (/* @__PURE__ */ new Date()).toISOString();
6324
6590
  const existing = this.runtime.registryDb.prepare("SELECT id FROM capability_cards WHERE id = ?").get(conductorCard.id);
6325
6591
  if (existing) {
@@ -6375,27 +6641,11 @@ var ServiceCoordinator = class {
6375
6641
  }
6376
6642
  }
6377
6643
  if (opts.registryUrl && opts.relay) {
6378
- const { RelayClient: RelayClient2 } = await import("../../websocket-client-4Z5P54RU.js");
6379
- const { executeCapabilityRequest: executeCapabilityRequest2 } = await import("../../execute-NNDCXTN4.js");
6380
- const cards = listCards(this.runtime.registryDb, this.config.owner);
6381
- const card = cards[0] ?? {
6382
- id: randomUUID8(),
6383
- owner: this.config.owner,
6384
- name: this.config.owner,
6385
- description: "Agent registered via CLI",
6386
- spec_version: "1.0",
6387
- level: 1,
6388
- inputs: [],
6389
- outputs: [],
6390
- pricing: { credits_per_call: 1 },
6391
- availability: { online: true }
6392
- };
6393
- 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);
6394
6648
  if (this.config.conductor?.public) {
6395
- const { buildConductorCard } = await import("../../card-EX2EYGCZ.js");
6396
- additionalCards.push(
6397
- buildConductorCard(this.config.owner)
6398
- );
6399
6649
  console.log("Conductor card will be published to registry (conductor.public: true)");
6400
6650
  }
6401
6651
  this.relayClient = new RelayClient2({
@@ -6403,9 +6653,10 @@ var ServiceCoordinator = class {
6403
6653
  owner: this.config.owner,
6404
6654
  agent_id: this.config.agent_id,
6405
6655
  token: this.config.token,
6406
- card,
6656
+ card: primaryCard,
6407
6657
  cards: additionalCards.length > 0 ? additionalCards : void 0,
6408
6658
  onRequest: async (req) => {
6659
+ this.relayClient?.sendStarted(req.id, "provider acknowledged");
6409
6660
  const onProgress = (info) => {
6410
6661
  this.relayClient.sendProgress(req.id, info);
6411
6662
  };
@@ -6721,6 +6972,14 @@ function createSignedEscrowReceipt(db, privateKey, publicKey, opts) {
6721
6972
  return { escrowId, receipt };
6722
6973
  }
6723
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
+
6724
6983
  // src/app/agentbnb-service.ts
6725
6984
  var AgentBnBService = class {
6726
6985
  coordinator;
@@ -7130,8 +7389,35 @@ var requestInputSchema = {
7130
7389
  card_id: z10.string().optional().describe("Direct card ID to request (skips search)"),
7131
7390
  skill_id: z10.string().optional().describe("Specific skill within a v2.0 card"),
7132
7391
  params: z10.record(z10.unknown()).optional().describe("Input parameters for the capability"),
7133
- 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")
7134
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
+ }
7135
7421
  async function handleRequest(args, ctx) {
7136
7422
  try {
7137
7423
  const maxCost = args.max_cost ?? 50;
@@ -7195,6 +7481,7 @@ async function handleRequest(args, ctx) {
7195
7481
  token: ctx.config.token,
7196
7482
  cardId,
7197
7483
  params: { ...args.params ?? {}, ...args.skill_id ? { skill_id: args.skill_id } : {}, requester: ctx.config.owner },
7484
+ timeoutMs: args.timeout_ms,
7198
7485
  identity: identityAuth
7199
7486
  });
7200
7487
  return {
@@ -7223,45 +7510,70 @@ async function handleRequest(args, ctx) {
7223
7510
  };
7224
7511
  }
7225
7512
  const targetOwner = remoteCard["owner"] ?? remoteCard["agent_name"];
7513
+ const targetAgentId = typeof remoteCard["agent_id"] === "string" ? remoteCard["agent_id"] : void 0;
7226
7514
  const gatewayUrl = remoteCard["gateway_url"];
7515
+ const timeoutHint = deriveTimeoutHintFromCard(remoteCard, args.skill_id);
7227
7516
  if (gatewayUrl) {
7228
- const result = await requestCapability({
7229
- gatewayUrl,
7230
- token: "",
7231
- cardId,
7232
- params: { ...args.params ?? {}, ...args.skill_id ? { skill_id: args.skill_id } : {}, requester: ctx.config.owner },
7233
- identity: identityAuth
7234
- });
7235
- return {
7236
- content: [{ type: "text", text: JSON.stringify({ success: true, result }, null, 2) }]
7237
- };
7238
- }
7239
- if (targetOwner) {
7240
- const relay = new RelayClient({
7241
- registryUrl: ctx.config.registry,
7242
- owner: ctx.config.owner,
7243
- token: ctx.config.token ?? "",
7244
- card: { id: ctx.config.owner, owner: ctx.config.owner, name: "mcp-requester" },
7245
- onRequest: async () => ({ error: { code: -32601, message: "MCP client does not accept requests" } }),
7246
- silent: true
7247
- });
7248
- try {
7249
- await relay.connect();
7250
- const result = await relay.request({
7517
+ let remoteCost = 0;
7518
+ const remoteSkills = remoteCard["skills"];
7519
+ if (Array.isArray(remoteSkills)) {
7520
+ const matchedSkill = args.skill_id ? remoteSkills.find((s) => s.id === args.skill_id) : remoteSkills[0];
7521
+ remoteCost = matchedSkill?.pricing?.credits_per_call ?? 0;
7522
+ } else {
7523
+ const remotePricing = remoteCard["pricing"];
7524
+ remoteCost = remotePricing?.credits_per_call ?? 0;
7525
+ }
7526
+ if (remoteCost > 0) {
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
+ };
7531
+ }
7532
+ const result = await requestViaTemporaryRelay({
7533
+ registryUrl: ctx.config.registry,
7534
+ owner: ctx.config.owner,
7535
+ token: ctx.config.token ?? "",
7251
7536
  targetOwner,
7537
+ targetAgentId,
7252
7538
  cardId,
7253
7539
  skillId: args.skill_id,
7254
- params: args.params ?? {},
7255
- requester: ctx.config.owner,
7256
- timeoutMs: 3e5
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
+ };
7546
+ } else {
7547
+ const result = await requestCapability({
7548
+ gatewayUrl,
7549
+ token: ctx.config.token ?? "",
7550
+ cardId,
7551
+ params: { ...args.params ?? {}, ...args.skill_id ? { skill_id: args.skill_id } : {}, requester: ctx.config.owner },
7552
+ timeoutMs: args.timeout_ms,
7553
+ timeoutHint,
7554
+ identity: identityAuth
7257
7555
  });
7258
7556
  return {
7259
7557
  content: [{ type: "text", text: JSON.stringify({ success: true, result }, null, 2) }]
7260
7558
  };
7261
- } finally {
7262
- relay.disconnect();
7263
7559
  }
7264
7560
  }
7561
+ if (targetOwner) {
7562
+ const result = await requestViaTemporaryRelay({
7563
+ registryUrl: ctx.config.registry,
7564
+ owner: ctx.config.owner,
7565
+ token: ctx.config.token ?? "",
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
7572
+ });
7573
+ return {
7574
+ content: [{ type: "text", text: JSON.stringify({ success: true, result }, null, 2) }]
7575
+ };
7576
+ }
7265
7577
  return {
7266
7578
  content: [{ type: "text", text: JSON.stringify({ success: false, error: "Remote card has no gateway_url and no owner for relay routing" }) }]
7267
7579
  };
@@ -7337,17 +7649,24 @@ async function conductAction(task, opts) {
7337
7649
  matchResults.map((m) => [m.subtask_id, m])
7338
7650
  );
7339
7651
  const resolveAgentUrl = (owner) => {
7340
- const peer = peers.find((p) => p.name.toLowerCase() === owner.toLowerCase());
7341
- if (peer) {
7342
- const execDb = openDatabase(config.db_path);
7343
- try {
7344
- const stmt = execDb.prepare("SELECT id FROM capability_cards WHERE owner = ? LIMIT 1");
7345
- const row = stmt.get(owner);
7346
- return { url: peer.url, cardId: row?.id ?? owner };
7347
- } finally {
7348
- 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());
7349
7664
  }
7350
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
+ }
7351
7670
  if (config.registry) {
7352
7671
  let cardId = owner;
7353
7672
  for (const m of matchMap.values()) {