agentbnb 8.2.3 → 8.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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-GKVTD4EZ.js → chunk-77KGEDH4.js} +1 -1
  7. package/dist/{chunk-QCGIG7WW.js → chunk-7IQE34QK.js} +14 -7
  8. package/dist/{chunk-LKLKYXLV.js → chunk-BARHNIKG.js} +10 -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-YVLMQ2BO.js} +11 -10
  48. package/dist/{service-coordinator-KMSA6BST.js → service-coordinator-SCP2YIFT.js} +420 -171
  49. package/dist/{skill-config-FETXPNVP.js → skill-config-5O2VR546.js} +1 -1
  50. package/dist/skills/agentbnb/bootstrap.js +532 -256
  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 {
@@ -1942,10 +2042,11 @@ async function verifyIdentity(request, reply, options) {
1942
2042
  return false;
1943
2043
  }
1944
2044
  const claimedRequester = extractClaimedRequester(request);
1945
- if (claimedRequester) {
2045
+ if (claimedRequester && knownAgent !== null) {
1946
2046
  const matchesAgentId = claimedRequester === agentId;
1947
- const matchesLegacyOwner = knownAgent?.legacy_owner === claimedRequester;
1948
- if (!matchesAgentId && !matchesLegacyOwner) {
2047
+ const matchesDisplayName = knownAgent.display_name === claimedRequester;
2048
+ const matchesLegacyOwner = knownAgent.legacy_owner === claimedRequester;
2049
+ if (!matchesAgentId && !matchesDisplayName && !matchesLegacyOwner) {
1949
2050
  await reply.code(401).send({ error: "Identity does not match requester" });
1950
2051
  return false;
1951
2052
  }
@@ -2439,14 +2540,16 @@ function processEscrowSettle(creditDb, escrowId, success, providerAgentId, signa
2439
2540
  throw new Error("Invalid consumer signature on escrow settle");
2440
2541
  }
2441
2542
  }
2442
- const escrowRow = creditDb.prepare("SELECT amount, owner FROM credit_escrow WHERE id = ? AND status = ?").get(escrowId, "held");
2543
+ const escrowRow = creditDb.prepare(
2544
+ "SELECT amount, owner FROM credit_escrow WHERE id = ? AND status IN ('held', 'started', 'progressing', 'abandoned')"
2545
+ ).get(escrowId);
2443
2546
  if (!escrowRow) {
2444
2547
  throw new Error(`Escrow not found or already settled: ${escrowId}`);
2445
2548
  }
2446
- const NETWORK_FEE_RATE = 0.05;
2549
+ const NETWORK_FEE_RATE2 = 0.05;
2447
2550
  if (success) {
2448
2551
  settleEscrow(creditDb, escrowId, providerAgentId);
2449
- const networkFee = Math.floor(escrowRow.amount * NETWORK_FEE_RATE);
2552
+ const networkFee = Math.floor(escrowRow.amount * NETWORK_FEE_RATE2);
2450
2553
  const providerAmount = escrowRow.amount - networkFee;
2451
2554
  return {
2452
2555
  escrow_id: escrowId,
@@ -2770,7 +2873,16 @@ function handleJobRelayResponse(opts) {
2770
2873
  // src/relay/websocket-relay.ts
2771
2874
  var RATE_LIMIT_MAX = 60;
2772
2875
  var RATE_LIMIT_WINDOW_MS = 6e4;
2773
- var RELAY_TIMEOUT_MS = 3e5;
2876
+ var RELAY_IDLE_TIMEOUT_MS = 3e4;
2877
+ var RELAY_HARD_TIMEOUT_MS = 3e5;
2878
+ var RELAY_DISCONNECT_GRACE_MS = RELAY_HARD_TIMEOUT_MS + 3e4;
2879
+ function readTimeoutOverride(envKey, fallbackMs) {
2880
+ const raw = process.env[envKey];
2881
+ if (!raw) return fallbackMs;
2882
+ const parsed = Number(raw);
2883
+ if (!Number.isFinite(parsed) || parsed <= 0) return fallbackMs;
2884
+ return Math.floor(parsed);
2885
+ }
2774
2886
  function registerWebSocketRelay(server, db, creditDb) {
2775
2887
  const connections = /* @__PURE__ */ new Map();
2776
2888
  const agentIdToOwner = /* @__PURE__ */ new Map();
@@ -2784,6 +2896,136 @@ function registerWebSocketRelay(server, db, creditDb) {
2784
2896
  if (connections.has(target)) return target;
2785
2897
  return void 0;
2786
2898
  }
2899
+ function clearPendingTimers(pending) {
2900
+ clearTimeout(pending.timeout);
2901
+ if (pending.idleTimeout) clearTimeout(pending.idleTimeout);
2902
+ if (pending.hardTimeout) clearTimeout(pending.hardTimeout);
2903
+ if (pending.graceTimeout) clearTimeout(pending.graceTimeout);
2904
+ }
2905
+ function setPendingTimer(pending, timerType, timer) {
2906
+ const existing = pending[timerType];
2907
+ if (existing) clearTimeout(existing);
2908
+ pending[timerType] = timer;
2909
+ pending.timeout = timer;
2910
+ }
2911
+ function scheduleIdleTimeout(requestId, pending) {
2912
+ const timeoutMs = readTimeoutOverride("AGENTBNB_RELAY_IDLE_TIMEOUT_MS", RELAY_IDLE_TIMEOUT_MS);
2913
+ const timer = setTimeout(() => {
2914
+ const current = pendingRequests.get(requestId);
2915
+ if (!current || (current.lifecycle ?? "held") !== "held") return;
2916
+ pendingRequests.delete(requestId);
2917
+ clearPendingTimers(current);
2918
+ if (current.escrowId && creditDb) {
2919
+ try {
2920
+ releaseForRelay(creditDb, current.escrowId);
2921
+ } catch (e) {
2922
+ console.error("[relay] escrow release on idle timeout failed:", e);
2923
+ }
2924
+ }
2925
+ const originWs = connections.get(current.originOwner);
2926
+ if (originWs && originWs.readyState === 1) {
2927
+ sendMessage(originWs, {
2928
+ type: "response",
2929
+ id: requestId,
2930
+ error: { code: -32603, message: "Relay request timeout" }
2931
+ });
2932
+ }
2933
+ }, timeoutMs);
2934
+ setPendingTimer(pending, "idleTimeout", timer);
2935
+ }
2936
+ function scheduleHardTimeout(requestId, pending) {
2937
+ const timeoutMs = readTimeoutOverride("AGENTBNB_RELAY_HARD_TIMEOUT_MS", RELAY_HARD_TIMEOUT_MS);
2938
+ const timer = setTimeout(() => {
2939
+ const current = pendingRequests.get(requestId);
2940
+ if (!current || current.lifecycle === "abandoned") return;
2941
+ pendingRequests.delete(requestId);
2942
+ clearPendingTimers(current);
2943
+ if (current.escrowId && creditDb) {
2944
+ try {
2945
+ releaseForRelay(creditDb, current.escrowId);
2946
+ } catch (e) {
2947
+ console.error("[relay] escrow release on hard timeout failed:", e);
2948
+ }
2949
+ }
2950
+ const originWs = connections.get(current.originOwner);
2951
+ if (originWs && originWs.readyState === 1) {
2952
+ sendMessage(originWs, {
2953
+ type: "response",
2954
+ id: requestId,
2955
+ error: { code: -32603, message: "Relay request timeout" }
2956
+ });
2957
+ }
2958
+ }, timeoutMs);
2959
+ setPendingTimer(pending, "hardTimeout", timer);
2960
+ }
2961
+ function scheduleGraceTimeout(requestId, pending) {
2962
+ const timeoutMs = readTimeoutOverride(
2963
+ "AGENTBNB_RELAY_DISCONNECT_GRACE_MS",
2964
+ RELAY_DISCONNECT_GRACE_MS
2965
+ );
2966
+ const timer = setTimeout(() => {
2967
+ const current = pendingRequests.get(requestId);
2968
+ if (!current || current.lifecycle !== "abandoned") return;
2969
+ pendingRequests.delete(requestId);
2970
+ clearPendingTimers(current);
2971
+ if (current.escrowId && creditDb) {
2972
+ try {
2973
+ releaseForRelay(creditDb, current.escrowId);
2974
+ } catch (e) {
2975
+ console.error("[relay] escrow release after grace timeout failed:", e);
2976
+ }
2977
+ }
2978
+ }, timeoutMs);
2979
+ setPendingTimer(pending, "graceTimeout", timer);
2980
+ }
2981
+ function transitionPendingToStarted(requestId, pending) {
2982
+ if (pending.lifecycle === "started" || pending.lifecycle === "progressing" || pending.lifecycle === "abandoned") {
2983
+ return;
2984
+ }
2985
+ pending.lifecycle = "started";
2986
+ pending.startedAt = Date.now();
2987
+ if (pending.idleTimeout) clearTimeout(pending.idleTimeout);
2988
+ if (pending.graceTimeout) clearTimeout(pending.graceTimeout);
2989
+ if (pending.escrowId && creditDb) {
2990
+ try {
2991
+ markEscrowStarted(creditDb, pending.escrowId);
2992
+ } catch (e) {
2993
+ console.error("[relay] escrow transition to started failed:", e);
2994
+ }
2995
+ }
2996
+ scheduleHardTimeout(requestId, pending);
2997
+ }
2998
+ function transitionPendingToProgressing(requestId, pending) {
2999
+ if (pending.lifecycle === "abandoned") {
3000
+ return;
3001
+ }
3002
+ if ((pending.lifecycle ?? "held") === "held") {
3003
+ transitionPendingToStarted(requestId, pending);
3004
+ }
3005
+ pending.lifecycle = "progressing";
3006
+ if (pending.escrowId && creditDb) {
3007
+ try {
3008
+ markEscrowProgressing(creditDb, pending.escrowId);
3009
+ } catch (e) {
3010
+ console.error("[relay] escrow transition to progressing failed:", e);
3011
+ }
3012
+ }
3013
+ }
3014
+ function transitionPendingToAbandoned(requestId, pending) {
3015
+ if (pending.lifecycle === "abandoned") return;
3016
+ pending.lifecycle = "abandoned";
3017
+ pending.abandonedAt = Date.now();
3018
+ if (pending.idleTimeout) clearTimeout(pending.idleTimeout);
3019
+ if (pending.hardTimeout) clearTimeout(pending.hardTimeout);
3020
+ if (pending.escrowId && creditDb) {
3021
+ try {
3022
+ markEscrowAbandoned(creditDb, pending.escrowId);
3023
+ } catch (e) {
3024
+ console.error("[relay] escrow transition to abandoned failed:", e);
3025
+ }
3026
+ }
3027
+ scheduleGraceTimeout(requestId, pending);
3028
+ }
2787
3029
  function checkRateLimit(owner) {
2788
3030
  const now = Date.now();
2789
3031
  const entry = rateLimits.get(owner);
@@ -2833,15 +3075,20 @@ function registerWebSocketRelay(server, db, creditDb) {
2833
3075
  } catch {
2834
3076
  }
2835
3077
  }
2836
- function upsertCard(cardData, owner) {
2837
- const parsed = AnyCardSchema.safeParse(cardData);
3078
+ function upsertCard(cardData, owner, agentId) {
3079
+ const parsed = AnyCardSchema.safeParse(
3080
+ agentId ? { ...cardData, agent_id: agentId } : cardData
3081
+ );
2838
3082
  if (!parsed.success) {
2839
3083
  throw new AgentBnBError(
2840
3084
  `Card validation failed: ${parsed.error.message}`,
2841
3085
  "VALIDATION_ERROR"
2842
3086
  );
2843
3087
  }
2844
- const card = { ...parsed.data, availability: { ...parsed.data.availability, online: true } };
3088
+ const card = attachCanonicalAgentId(db, {
3089
+ ...parsed.data,
3090
+ availability: { ...parsed.data.availability, online: true }
3091
+ });
2845
3092
  const cardId = card.id;
2846
3093
  const now = (/* @__PURE__ */ new Date()).toISOString();
2847
3094
  const existing = db.prepare("SELECT id FROM capability_cards WHERE id = ?").get(cardId);
@@ -2895,7 +3142,7 @@ function registerWebSocketRelay(server, db, creditDb) {
2895
3142
  agentIdToOwner.set(agentEntry.agent_id, owner);
2896
3143
  for (const agentCard of agentEntry.cards) {
2897
3144
  try {
2898
- upsertCard(agentCard, owner);
3145
+ upsertCard(agentCard, owner, agentEntry.agent_id);
2899
3146
  } catch {
2900
3147
  }
2901
3148
  }
@@ -2909,7 +3156,7 @@ function registerWebSocketRelay(server, db, creditDb) {
2909
3156
  }
2910
3157
  let cardId;
2911
3158
  try {
2912
- cardId = upsertCard(card, owner);
3159
+ cardId = upsertCard(card, owner, msg.agent_id);
2913
3160
  } catch (err) {
2914
3161
  console.error(`[relay] card validation failed for ${owner}:`, err instanceof Error ? err.message : err);
2915
3162
  cardId = card.id ?? owner;
@@ -2919,7 +3166,7 @@ function registerWebSocketRelay(server, db, creditDb) {
2919
3166
  if (msg.cards && msg.cards.length > 0) {
2920
3167
  for (const extraCard of msg.cards) {
2921
3168
  try {
2922
- upsertCard(extraCard, owner);
3169
+ upsertCard(extraCard, owner, msg.agent_id);
2923
3170
  } catch {
2924
3171
  }
2925
3172
  }
@@ -2974,23 +3221,17 @@ function registerWebSocketRelay(server, db, creditDb) {
2974
3221
  console.error("[relay] credit hold error (non-fatal):", err);
2975
3222
  }
2976
3223
  }
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 });
3224
+ const pending = {
3225
+ originOwner: fromOwner,
3226
+ creditOwner,
3227
+ timeout: setTimeout(() => void 0, 1),
3228
+ escrowId,
3229
+ targetOwner: targetKey ?? msg.target_owner,
3230
+ lifecycle: "held",
3231
+ createdAt: Date.now()
3232
+ };
3233
+ pendingRequests.set(msg.id, pending);
3234
+ scheduleIdleTimeout(msg.id, pending);
2994
3235
  sendMessage(targetWs, {
2995
3236
  type: "incoming_request",
2996
3237
  id: msg.id,
@@ -3002,30 +3243,23 @@ function registerWebSocketRelay(server, db, creditDb) {
3002
3243
  escrow_receipt: msg.escrow_receipt
3003
3244
  });
3004
3245
  }
3246
+ function handleRelayStarted(msg) {
3247
+ const pending = pendingRequests.get(msg.id);
3248
+ if (!pending) return;
3249
+ transitionPendingToStarted(msg.id, pending);
3250
+ const originWs = connections.get(pending.originOwner);
3251
+ if (originWs && originWs.readyState === 1) {
3252
+ sendMessage(originWs, {
3253
+ type: "relay_started",
3254
+ id: msg.id,
3255
+ message: msg.message
3256
+ });
3257
+ }
3258
+ }
3005
3259
  function handleRelayProgress(msg) {
3006
3260
  const pending = pendingRequests.get(msg.id);
3007
3261
  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;
3262
+ transitionPendingToProgressing(msg.id, pending);
3029
3263
  const originWs = connections.get(pending.originOwner);
3030
3264
  if (originWs && originWs.readyState === 1) {
3031
3265
  sendMessage(originWs, {
@@ -3039,7 +3273,7 @@ function registerWebSocketRelay(server, db, creditDb) {
3039
3273
  function handleRelayResponse(msg) {
3040
3274
  const pending = pendingRequests.get(msg.id);
3041
3275
  if (!pending) return;
3042
- clearTimeout(pending.timeout);
3276
+ clearPendingTimers(pending);
3043
3277
  pendingRequests.delete(msg.id);
3044
3278
  if (pending.jobId && creditDb) {
3045
3279
  try {
@@ -3102,16 +3336,15 @@ function registerWebSocketRelay(server, db, creditDb) {
3102
3336
  connections.delete(owner);
3103
3337
  rateLimits.delete(owner);
3104
3338
  agentCapacities.delete(owner);
3105
- for (const [agentId, o] of agentIdToOwner) {
3106
- if (o === owner) {
3339
+ for (const [agentId, mappedOwner] of Array.from(agentIdToOwner.entries())) {
3340
+ if (mappedOwner === owner) {
3107
3341
  agentIdToOwner.delete(agentId);
3108
- break;
3109
3342
  }
3110
3343
  }
3111
3344
  markOwnerOffline(owner);
3112
3345
  for (const [reqId, pending] of pendingRequests) {
3113
3346
  if (pending.targetOwner === owner) {
3114
- clearTimeout(pending.timeout);
3347
+ clearPendingTimers(pending);
3115
3348
  pendingRequests.delete(reqId);
3116
3349
  if (pending.escrowId && creditDb) {
3117
3350
  try {
@@ -3129,14 +3362,19 @@ function registerWebSocketRelay(server, db, creditDb) {
3129
3362
  });
3130
3363
  }
3131
3364
  } 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);
3365
+ const lifecycle = pending.lifecycle ?? "held";
3366
+ if (lifecycle === "held") {
3367
+ clearPendingTimers(pending);
3368
+ pendingRequests.delete(reqId);
3369
+ if (pending.escrowId && creditDb) {
3370
+ try {
3371
+ releaseForRelay(creditDb, pending.escrowId);
3372
+ } catch (e) {
3373
+ console.error("[relay] escrow release on requester disconnect failed:", e);
3374
+ }
3139
3375
  }
3376
+ } else {
3377
+ transitionPendingToAbandoned(reqId, pending);
3140
3378
  }
3141
3379
  }
3142
3380
  }
@@ -3179,7 +3417,9 @@ function registerWebSocketRelay(server, db, creditDb) {
3179
3417
  return;
3180
3418
  }
3181
3419
  try {
3182
- const escrow = creditDb.prepare("SELECT card_id FROM credit_escrow WHERE id = ? AND status = ?").get(msg.escrow_id, "held");
3420
+ const escrow = creditDb.prepare(
3421
+ "SELECT card_id FROM credit_escrow WHERE id = ? AND status IN ('held', 'started', 'progressing', 'abandoned')"
3422
+ ).get(msg.escrow_id);
3183
3423
  if (!escrow) {
3184
3424
  sendMessage(ws, { type: "error", code: "escrow_not_found", message: `Escrow not found: ${msg.escrow_id}`, request_id: msg.request_id });
3185
3425
  return;
@@ -3276,6 +3516,9 @@ function registerWebSocketRelay(server, db, creditDb) {
3276
3516
  case "relay_response":
3277
3517
  handleRelayResponse(msg);
3278
3518
  break;
3519
+ case "relay_started":
3520
+ handleRelayStarted(msg);
3521
+ break;
3279
3522
  case "relay_progress":
3280
3523
  handleRelayProgress(msg);
3281
3524
  break;
@@ -3322,7 +3565,7 @@ function registerWebSocketRelay(server, db, creditDb) {
3322
3565
  }
3323
3566
  connections.clear();
3324
3567
  for (const [, pending] of pendingRequests) {
3325
- clearTimeout(pending.timeout);
3568
+ clearPendingTimers(pending);
3326
3569
  }
3327
3570
  pendingRequests.clear();
3328
3571
  rateLimits.clear();
@@ -4621,6 +4864,9 @@ function stripInternal(card) {
4621
4864
  const { _internal: _, ...publicCard } = card;
4622
4865
  return publicCard;
4623
4866
  }
4867
+ function buildSqlPlaceholders(count) {
4868
+ return Array.from({ length: count }, () => "?").join(", ");
4869
+ }
4624
4870
  function createRegistryServer(opts) {
4625
4871
  const { registryDb: db, silent = false } = opts;
4626
4872
  const server = Fastify2({ logger: !silent });
@@ -5030,11 +5276,11 @@ function createRegistryServer(opts) {
5030
5276
  const card = result.data;
5031
5277
  const now = (/* @__PURE__ */ new Date()).toISOString();
5032
5278
  if (card.spec_version === "2.0") {
5033
- const cardWithTimestamps = {
5279
+ const cardWithTimestamps = attachCanonicalAgentId(db, {
5034
5280
  ...card,
5035
5281
  created_at: card.created_at ?? now,
5036
5282
  updated_at: now
5037
- };
5283
+ });
5038
5284
  db.prepare(
5039
5285
  `INSERT OR REPLACE INTO capability_cards (id, owner, data, created_at, updated_at)
5040
5286
  VALUES (?, ?, ?, ?, ?)`
@@ -5155,19 +5401,16 @@ function createRegistryServer(opts) {
5155
5401
  if (ownerCards.length === 0) {
5156
5402
  return reply.status(404).send({ error: "Agent not found" });
5157
5403
  }
5158
- const memberStmt = db.prepare(
5159
- "SELECT MIN(created_at) as earliest, MAX(created_at) as latest FROM capability_cards WHERE owner = ?"
5404
+ const resolvedOwner = ownerCards[0]?.owner ?? owner;
5405
+ const ownerCardIds = ownerCards.map((card) => card.id);
5406
+ const cardIdPlaceholders = buildSqlPlaceholders(ownerCardIds.length);
5407
+ 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();
5408
+ 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;
5409
+ const lastActiveStmt = db.prepare(
5410
+ `SELECT MAX(created_at) as last_req FROM request_log WHERE card_id IN (${cardIdPlaceholders})`
5160
5411
  );
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;
5412
+ const lastActiveRow = lastActiveStmt.get(...ownerCardIds);
5413
+ const lastActive = lastActiveRow?.last_req ?? latestCardUpdate ?? joinedAt;
5171
5414
  const metricsStmt = db.prepare(`
5172
5415
  SELECT
5173
5416
  SUM(CASE WHEN rl.failure_reason IS NULL OR rl.failure_reason IN ('bad_execution','auth_error')
@@ -5177,10 +5420,9 @@ function createRegistryServer(opts) {
5177
5420
  COUNT(DISTINCT rl.requester) as unique_requesters,
5178
5421
  COUNT(DISTINCT CASE WHEN rl.status = 'success' THEN rl.requester END) as repeat_success_requesters
5179
5422
  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
5423
+ WHERE rl.card_id IN (${cardIdPlaceholders}) AND rl.action_type IS NULL
5182
5424
  `);
5183
- const metricsRow = metricsStmt.get(owner);
5425
+ const metricsRow = metricsStmt.get(...ownerCardIds);
5184
5426
  const totalExec = metricsRow?.total ?? 0;
5185
5427
  const successExec = metricsRow?.successes ?? 0;
5186
5428
  const successRate = totalExec > 0 ? successExec / totalExec : 0;
@@ -5194,25 +5436,23 @@ function createRegistryServer(opts) {
5194
5436
  COUNT(*) as count,
5195
5437
  SUM(CASE WHEN rl.status = 'success' THEN 1 ELSE 0 END) as success
5196
5438
  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
5439
+ WHERE rl.card_id IN (${cardIdPlaceholders}) AND rl.action_type IS NULL
5199
5440
  AND rl.created_at >= DATE('now', '-7 days')
5200
5441
  GROUP BY DATE(rl.created_at)
5201
5442
  ORDER BY day ASC
5202
5443
  `);
5203
- const trend_7d = trendStmt.all(owner).map((r) => ({ date: r.day, count: r.count, success: r.success }));
5444
+ const trend_7d = trendStmt.all(...ownerCardIds).map((r) => ({ date: r.day, count: r.count, success: r.success }));
5204
5445
  let performanceTier = 0;
5205
5446
  if (totalExec > 10) performanceTier = 1;
5206
5447
  if (totalExec > 50 && successRate >= 0.85) performanceTier = 2;
5207
5448
  const proofsStmt = db.prepare(`
5208
5449
  SELECT rl.card_name, rl.status, rl.latency_ms, rl.id, rl.created_at
5209
5450
  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
5451
+ WHERE rl.card_id IN (${cardIdPlaceholders}) AND rl.action_type IS NULL
5212
5452
  ORDER BY rl.created_at DESC
5213
5453
  LIMIT 10
5214
5454
  `);
5215
- const proofRows = proofsStmt.all(owner);
5455
+ const proofRows = proofsStmt.all(...ownerCardIds);
5216
5456
  const statusToOutcomeClass = (s) => {
5217
5457
  if (s === "success") return "completed";
5218
5458
  if (s === "timeout") return "cancelled";
@@ -5238,22 +5478,20 @@ function createRegistryServer(opts) {
5238
5478
  const activityStmt = db.prepare(`
5239
5479
  SELECT rl.id, rl.card_name, rl.requester, rl.status, rl.credits_charged, rl.created_at
5240
5480
  FROM request_log rl
5241
- INNER JOIN capability_cards cc ON rl.card_id = cc.id
5242
- WHERE cc.owner = ?
5481
+ WHERE rl.card_id IN (${cardIdPlaceholders})
5243
5482
  ORDER BY rl.created_at DESC
5244
5483
  LIMIT 10
5245
5484
  `);
5246
- const recentActivity = activityStmt.all(owner);
5485
+ const recentActivity = activityStmt.all(...ownerCardIds);
5247
5486
  const skillCount = ownerCards.reduce((sum, card) => sum + (card.skills?.length ?? 1), 0);
5248
5487
  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 = ?
5488
+ SELECT COALESCE(SUM(CASE WHEN rl.status = 'success' THEN rl.credits_charged ELSE 0 END), 0) as credits_earned
5489
+ FROM request_log rl
5490
+ WHERE rl.card_id IN (${cardIdPlaceholders})
5253
5491
  `);
5254
- const creditsRow = creditsStmt.get(owner);
5492
+ const creditsRow = creditsStmt.get(...ownerCardIds);
5255
5493
  const response = {
5256
- owner,
5494
+ owner: resolvedOwner,
5257
5495
  agent_name: v2Card?.agent_name,
5258
5496
  short_description: v2Card?.short_description,
5259
5497
  joined_at: joinedAt,
@@ -5285,7 +5523,7 @@ function createRegistryServer(opts) {
5285
5523
  return reply.send({
5286
5524
  ...response,
5287
5525
  profile: {
5288
- owner,
5526
+ owner: resolvedOwner,
5289
5527
  skill_count: skillCount,
5290
5528
  success_rate: successRate > 0 ? successRate : null,
5291
5529
  total_earned: creditsRow?.credits_earned ?? 0,
@@ -5557,7 +5795,7 @@ function createRegistryServer(opts) {
5557
5795
  return { card_id: target.cardId, skill_id: target.skillId };
5558
5796
  }
5559
5797
  if (!relayClient) {
5560
- const { RelayClient: RelayClient2 } = await import("../../websocket-client-4Z5P54RU.js");
5798
+ const { RelayClient: RelayClient2 } = await import("../../websocket-client-SNDF3B6N.js");
5561
5799
  relayClient = new RelayClient2({
5562
5800
  registryUrl: relayRegistryUrl,
5563
5801
  owner: relayRequesterOwner,
@@ -5579,7 +5817,7 @@ function createRegistryServer(opts) {
5579
5817
  });
5580
5818
  await relayClient.connect();
5581
5819
  }
5582
- const { requestViaRelay: requestViaRelay2 } = await import("../../client-XOLP5IUZ.js");
5820
+ const { requestViaRelay: requestViaRelay2 } = await import("../../client-OKJJ3UP2.js");
5583
5821
  return requestViaRelay2(relayClient, {
5584
5822
  targetOwner: target.owner,
5585
5823
  cardId: target.cardId,
@@ -5835,7 +6073,7 @@ function createRegistryServer(opts) {
5835
6073
  if (!opts.creditDb) {
5836
6074
  return reply.code(404).send({ error: "Credit system not enabled" });
5837
6075
  }
5838
- const { getReliabilityMetrics } = await import("../../reliability-metrics-QG7WC5QK.js");
6076
+ const { getReliabilityMetrics } = await import("../../reliability-metrics-G7LPUYJD.js");
5839
6077
  const metrics = getReliabilityMetrics(opts.creditDb, owner);
5840
6078
  if (!metrics) {
5841
6079
  return reply.code(404).send({ error: "No reliability data for this provider" });
@@ -5853,38 +6091,37 @@ function createRegistryServer(opts) {
5853
6091
  }
5854
6092
  }, async (request, reply) => {
5855
6093
  const { owner } = request.params;
5856
- const rows = db.prepare(
5857
- "SELECT id, data FROM capability_cards WHERE owner = ?"
5858
- ).all(owner);
6094
+ const cards = listCards(db, owner);
5859
6095
  const agents = [];
5860
- for (const row of rows) {
6096
+ for (const card of cards) {
5861
6097
  try {
5862
- const card = JSON.parse(row.data);
6098
+ const rawCard = card;
6099
+ const providerIdentity = typeof card.agent_id === "string" && card.agent_id.length > 0 ? card.agent_id : card.owner;
5863
6100
  let earnings = 0;
5864
6101
  let spend = 0;
5865
6102
  if (opts.creditDb) {
5866
6103
  const earningRow = opts.creditDb.prepare(
5867
6104
  "SELECT COALESCE(SUM(amount), 0) as total FROM credit_transactions WHERE owner = ? AND reason = 'settlement' AND amount > 0"
5868
- ).get(owner);
6105
+ ).get(providerIdentity);
5869
6106
  earnings = earningRow.total;
5870
6107
  const spendRow = opts.creditDb.prepare(
5871
6108
  "SELECT COALESCE(SUM(ABS(amount)), 0) as total FROM credit_transactions WHERE owner = ? AND reason = 'escrow_hold'"
5872
- ).get(owner);
6109
+ ).get(providerIdentity);
5873
6110
  spend = spendRow.total;
5874
6111
  }
5875
6112
  const successCount = db.prepare(
5876
6113
  "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;
6114
+ ).get(card.id).cnt;
5878
6115
  const failureCount = db.prepare(
5879
6116
  "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;
6117
+ ).get(card.id).cnt;
5881
6118
  const totalExec = successCount + failureCount;
5882
6119
  const successRate = totalExec > 0 ? successCount / totalExec : 0;
5883
6120
  let failureBreakdown = {};
5884
6121
  try {
5885
6122
  const failureRows = db.prepare(
5886
6123
  "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);
6124
+ ).all(card.id);
5888
6125
  for (const fr of failureRows) {
5889
6126
  failureBreakdown[fr.failure_reason] = fr.cnt;
5890
6127
  }
@@ -5892,12 +6129,12 @@ function createRegistryServer(opts) {
5892
6129
  }
5893
6130
  let reliability = null;
5894
6131
  if (opts.creditDb) {
5895
- const { getReliabilityMetrics } = await import("../../reliability-metrics-QG7WC5QK.js");
5896
- reliability = getReliabilityMetrics(opts.creditDb, owner);
6132
+ const { getReliabilityMetrics } = await import("../../reliability-metrics-G7LPUYJD.js");
6133
+ reliability = getReliabilityMetrics(opts.creditDb, providerIdentity);
5897
6134
  }
5898
6135
  agents.push({
5899
- id: row.id,
5900
- name: card.name ?? card.agent_name ?? owner,
6136
+ id: card.id,
6137
+ name: (typeof rawCard["name"] === "string" ? rawCard["name"] : void 0) ?? (typeof rawCard["agent_name"] === "string" ? rawCard["agent_name"] : void 0) ?? card.owner,
5901
6138
  online: card.availability?.online ?? false,
5902
6139
  current_load: 0,
5903
6140
  // Will be populated from relay heartbeat data in future
@@ -6175,6 +6412,32 @@ import { spawn as spawn2 } from "child_process";
6175
6412
  import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
6176
6413
  import { join as join5 } from "path";
6177
6414
  import { randomUUID as randomUUID8 } from "crypto";
6415
+ function buildFallbackRelayCard(owner) {
6416
+ return {
6417
+ id: randomUUID8(),
6418
+ owner,
6419
+ name: owner,
6420
+ description: "Agent registered via CLI",
6421
+ spec_version: "1.0",
6422
+ level: 1,
6423
+ inputs: [],
6424
+ outputs: [],
6425
+ pricing: { credits_per_call: 1 },
6426
+ availability: { online: true }
6427
+ };
6428
+ }
6429
+ function buildRelayRegistrationCards(owner, localCards) {
6430
+ if (localCards.length === 0) {
6431
+ return {
6432
+ primaryCard: buildFallbackRelayCard(owner),
6433
+ additionalCards: []
6434
+ };
6435
+ }
6436
+ return {
6437
+ primaryCard: localCards[0],
6438
+ additionalCards: localCards.slice(1)
6439
+ };
6440
+ }
6178
6441
  var ServiceCoordinator = class {
6179
6442
  config;
6180
6443
  guard;
@@ -6319,8 +6582,11 @@ var ServiceCoordinator = class {
6319
6582
  console.log("Conductor mode enabled \u2014 orchestrate/plan skills available via gateway");
6320
6583
  }
6321
6584
  if (opts.conductorEnabled && this.config.conductor?.public) {
6322
- const { buildConductorCard } = await import("../../card-EX2EYGCZ.js");
6323
- const conductorCard = buildConductorCard(this.config.owner);
6585
+ const { buildConductorCard } = await import("../../card-BN643ZOY.js");
6586
+ const conductorCard = attachCanonicalAgentId(
6587
+ this.runtime.registryDb,
6588
+ buildConductorCard(this.config.owner)
6589
+ );
6324
6590
  const now = (/* @__PURE__ */ new Date()).toISOString();
6325
6591
  const existing = this.runtime.registryDb.prepare("SELECT id FROM capability_cards WHERE id = ?").get(conductorCard.id);
6326
6592
  if (existing) {
@@ -6376,27 +6642,11 @@ var ServiceCoordinator = class {
6376
6642
  }
6377
6643
  }
6378
6644
  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 = [];
6645
+ const { RelayClient: RelayClient2 } = await import("../../websocket-client-SNDF3B6N.js");
6646
+ const { executeCapabilityRequest: executeCapabilityRequest2 } = await import("../../execute-FZLQGIXB.js");
6647
+ const localCards = listCards(this.runtime.registryDb, this.config.owner);
6648
+ const { primaryCard, additionalCards } = buildRelayRegistrationCards(this.config.owner, localCards);
6395
6649
  if (this.config.conductor?.public) {
6396
- const { buildConductorCard } = await import("../../card-EX2EYGCZ.js");
6397
- additionalCards.push(
6398
- buildConductorCard(this.config.owner)
6399
- );
6400
6650
  console.log("Conductor card will be published to registry (conductor.public: true)");
6401
6651
  }
6402
6652
  this.relayClient = new RelayClient2({
@@ -6404,9 +6654,10 @@ var ServiceCoordinator = class {
6404
6654
  owner: this.config.owner,
6405
6655
  agent_id: this.config.agent_id,
6406
6656
  token: this.config.token,
6407
- card,
6657
+ card: primaryCard,
6408
6658
  cards: additionalCards.length > 0 ? additionalCards : void 0,
6409
6659
  onRequest: async (req) => {
6660
+ this.relayClient?.sendStarted(req.id, "provider acknowledged");
6410
6661
  const onProgress = (info) => {
6411
6662
  this.relayClient.sendProgress(req.id, info);
6412
6663
  };
@@ -6722,6 +6973,14 @@ function createSignedEscrowReceipt(db, privateKey, publicKey, opts) {
6722
6973
  return { escrowId, receipt };
6723
6974
  }
6724
6975
 
6976
+ // src/credit/settlement.ts
6977
+ function settleRequesterEscrow(requesterDb, escrowId) {
6978
+ confirmEscrowDebit(requesterDb, escrowId);
6979
+ }
6980
+ function releaseRequesterEscrow(requesterDb, escrowId) {
6981
+ releaseEscrow(requesterDb, escrowId);
6982
+ }
6983
+
6725
6984
  // src/app/agentbnb-service.ts
6726
6985
  var AgentBnBService = class {
6727
6986
  coordinator;
@@ -7131,8 +7390,35 @@ var requestInputSchema = {
7131
7390
  card_id: z10.string().optional().describe("Direct card ID to request (skips search)"),
7132
7391
  skill_id: z10.string().optional().describe("Specific skill within a v2.0 card"),
7133
7392
  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")
7393
+ max_cost: z10.number().optional().default(50).describe("Maximum credits to spend"),
7394
+ timeout_ms: z10.number().positive().optional().describe("Requester timeout override in milliseconds")
7135
7395
  };
7396
+ function parsePositiveNumber(value) {
7397
+ return typeof value === "number" && value > 0 ? value : void 0;
7398
+ }
7399
+ function deriveTimeoutHintFromCard(remoteCard, skillId) {
7400
+ const topLevelHint = {
7401
+ expected_duration_ms: parsePositiveNumber(remoteCard["expected_duration_ms"]),
7402
+ hard_timeout_ms: parsePositiveNumber(remoteCard["hard_timeout_ms"])
7403
+ };
7404
+ const skills = remoteCard["skills"];
7405
+ if (!Array.isArray(skills)) {
7406
+ return topLevelHint.expected_duration_ms !== void 0 || topLevelHint.hard_timeout_ms !== void 0 ? topLevelHint : void 0;
7407
+ }
7408
+ const selectedSkill = skillId ? skills.find((candidate) => {
7409
+ if (!candidate || typeof candidate !== "object") return false;
7410
+ return candidate["id"] === skillId;
7411
+ }) : skills[0];
7412
+ if (!selectedSkill || typeof selectedSkill !== "object") {
7413
+ return topLevelHint.expected_duration_ms !== void 0 || topLevelHint.hard_timeout_ms !== void 0 ? topLevelHint : void 0;
7414
+ }
7415
+ const skillRecord = selectedSkill;
7416
+ const skillHint = {
7417
+ expected_duration_ms: parsePositiveNumber(skillRecord["expected_duration_ms"]) ?? topLevelHint.expected_duration_ms,
7418
+ hard_timeout_ms: parsePositiveNumber(skillRecord["hard_timeout_ms"]) ?? topLevelHint.hard_timeout_ms
7419
+ };
7420
+ return skillHint.expected_duration_ms !== void 0 || skillHint.hard_timeout_ms !== void 0 ? skillHint : void 0;
7421
+ }
7136
7422
  async function handleRequest(args, ctx) {
7137
7423
  try {
7138
7424
  const maxCost = args.max_cost ?? 50;
@@ -7196,6 +7482,7 @@ async function handleRequest(args, ctx) {
7196
7482
  token: ctx.config.token,
7197
7483
  cardId,
7198
7484
  params: { ...args.params ?? {}, ...args.skill_id ? { skill_id: args.skill_id } : {}, requester: ctx.config.owner },
7485
+ timeoutMs: args.timeout_ms,
7199
7486
  identity: identityAuth
7200
7487
  });
7201
7488
  return {
@@ -7224,7 +7511,9 @@ async function handleRequest(args, ctx) {
7224
7511
  };
7225
7512
  }
7226
7513
  const targetOwner = remoteCard["owner"] ?? remoteCard["agent_name"];
7514
+ const targetAgentId = typeof remoteCard["agent_id"] === "string" ? remoteCard["agent_id"] : void 0;
7227
7515
  const gatewayUrl = remoteCard["gateway_url"];
7516
+ const timeoutHint = deriveTimeoutHintFromCard(remoteCard, args.skill_id);
7228
7517
  if (gatewayUrl) {
7229
7518
  let remoteCost = 0;
7230
7519
  const remoteSkills = remoteCard["skills"];
@@ -7236,43 +7525,33 @@ async function handleRequest(args, ctx) {
7236
7525
  remoteCost = remotePricing?.credits_per_call ?? 0;
7237
7526
  }
7238
7527
  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();
7528
+ if (!targetOwner) {
7529
+ return {
7530
+ content: [{ type: "text", text: JSON.stringify({ success: false, error: "Paid remote request requires a target owner for relay routing" }) }]
7531
+ };
7269
7532
  }
7533
+ const result = await requestViaTemporaryRelay({
7534
+ registryUrl: ctx.config.registry,
7535
+ owner: ctx.config.owner,
7536
+ token: ctx.config.token ?? "",
7537
+ targetOwner,
7538
+ targetAgentId,
7539
+ cardId,
7540
+ skillId: args.skill_id,
7541
+ params: { ...args.params ?? {}, ...args.skill_id ? { skill_id: args.skill_id } : {}, requester: ctx.config.owner },
7542
+ timeoutMs: args.timeout_ms
7543
+ });
7544
+ return {
7545
+ content: [{ type: "text", text: JSON.stringify({ success: true, result }, null, 2) }]
7546
+ };
7270
7547
  } else {
7271
7548
  const result = await requestCapability({
7272
7549
  gatewayUrl,
7273
7550
  token: ctx.config.token ?? "",
7274
7551
  cardId,
7275
7552
  params: { ...args.params ?? {}, ...args.skill_id ? { skill_id: args.skill_id } : {}, requester: ctx.config.owner },
7553
+ timeoutMs: args.timeout_ms,
7554
+ timeoutHint,
7276
7555
  identity: identityAuth
7277
7556
  });
7278
7557
  return {
@@ -7281,30 +7560,20 @@ async function handleRequest(args, ctx) {
7281
7560
  }
7282
7561
  }
7283
7562
  if (targetOwner) {
7284
- const relay = new RelayClient({
7563
+ const result = await requestViaTemporaryRelay({
7285
7564
  registryUrl: ctx.config.registry,
7286
7565
  owner: ctx.config.owner,
7287
7566
  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
7567
+ targetOwner,
7568
+ targetAgentId,
7569
+ cardId,
7570
+ skillId: args.skill_id,
7571
+ params: { ...args.params ?? {}, ...args.skill_id ? { skill_id: args.skill_id } : {} },
7572
+ timeoutMs: args.timeout_ms
7291
7573
  });
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
- }
7574
+ return {
7575
+ content: [{ type: "text", text: JSON.stringify({ success: true, result }, null, 2) }]
7576
+ };
7308
7577
  }
7309
7578
  return {
7310
7579
  content: [{ type: "text", text: JSON.stringify({ success: false, error: "Remote card has no gateway_url and no owner for relay routing" }) }]
@@ -7381,17 +7650,24 @@ async function conductAction(task, opts) {
7381
7650
  matchResults.map((m) => [m.subtask_id, m])
7382
7651
  );
7383
7652
  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();
7653
+ const execDb = openDatabase(config.db_path);
7654
+ let matchingCards;
7655
+ try {
7656
+ matchingCards = listCards(execDb, owner);
7657
+ } finally {
7658
+ execDb.close();
7659
+ }
7660
+ const candidateNames = /* @__PURE__ */ new Set([owner.toLowerCase()]);
7661
+ for (const card of matchingCards) {
7662
+ candidateNames.add(card.owner.toLowerCase());
7663
+ if (typeof card.agent_id === "string" && card.agent_id.length > 0) {
7664
+ candidateNames.add(card.agent_id.toLowerCase());
7393
7665
  }
7394
7666
  }
7667
+ const peer = peers.find((p) => candidateNames.has(p.name.toLowerCase()));
7668
+ if (peer) {
7669
+ return { url: peer.url, cardId: matchingCards[0]?.id ?? owner };
7670
+ }
7395
7671
  if (config.registry) {
7396
7672
  let cardId = owner;
7397
7673
  for (const m of matchMap.values()) {