agentbnb 7.0.0-beta.1 → 7.0.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 (49) hide show
  1. package/dist/{card-REW7BSWW.js → card-EX2EYGCZ.js} +1 -1
  2. package/dist/{chunk-PU7LXOQ3.js → chunk-3LWBH7P3.js} +72 -3
  3. package/dist/{chunk-2HSUPCBT.js → chunk-5AAFG2V2.js} +3 -3
  4. package/dist/{chunk-GO4FVRVN.js → chunk-5GME4KJZ.js} +5 -5
  5. package/dist/{chunk-VPQ44XKE.js → chunk-64AK4FJM.js} +2 -2
  6. package/dist/{chunk-K5FO42YF.js → chunk-7EF3HYVZ.js} +24 -1
  7. package/dist/{chunk-EAD4A4KG.js → chunk-ALX4WS3A.js} +2 -2
  8. package/dist/{chunk-ETGOKDFR.js → chunk-B2VJTKO5.js} +2 -2
  9. package/dist/{chunk-PGDBUUGR.js → chunk-C537SFHV.js} +5 -5
  10. package/dist/{chunk-F53QQIM2.js → chunk-CUONY5TO.js} +1 -1
  11. package/dist/{chunk-J2K5S5MX.js → chunk-D6RKW2XG.js} +67 -1
  12. package/dist/{chunk-APEG4QIN.js → chunk-E2OKP5CY.js} +4 -4
  13. package/dist/{chunk-FK2MDNTB.js → chunk-FTZTEHYG.js} +1 -1
  14. package/dist/{chunk-Y7T6IMM3.js → chunk-GKVTD4EZ.js} +1 -1
  15. package/dist/{chunk-VMH2YS2I.js → chunk-KF3TZHA5.js} +1 -1
  16. package/dist/{chunk-574W3HHE.js → chunk-LJM7FHPM.js} +1 -1
  17. package/dist/{chunk-KA2VIEGM.js → chunk-O2OYBAVR.js} +1 -1
  18. package/dist/{chunk-PSQHUZ7X.js → chunk-OH7BP5NP.js} +1 -1
  19. package/dist/{chunk-EHSHB7TY.js → chunk-SSK653A6.js} +67 -2
  20. package/dist/{chunk-BP3L2TET.js → chunk-TBJ3FZKZ.js} +2 -2
  21. package/dist/{chunk-3CIMVISQ.js → chunk-WVY2W7AA.js} +4 -0
  22. package/dist/{chunk-DUW6RX6I.js → chunk-X32NE6V4.js} +1 -1
  23. package/dist/{chunk-CWYPTQRQ.js → chunk-YHY7OG6S.js} +5 -5
  24. package/dist/{chunk-TW65F5EU.js → chunk-Z4MCGKTL.js} +6 -2
  25. package/dist/cli/index.js +44 -23
  26. package/dist/{client-HRYRJKSA.js → client-HKV3QWZ3.js} +3 -3
  27. package/dist/{conduct-JNYJCDHQ.js → conduct-W6XF6DJW.js} +13 -13
  28. package/dist/conduct-YB64OHI6.js +22 -0
  29. package/dist/{conductor-mode-2VVFMKVE.js → conductor-mode-2GSLHVN6.js} +3 -3
  30. package/dist/{conductor-mode-VGUU54QI.js → conductor-mode-AKREGDIU.js} +10 -10
  31. package/dist/{execute-MOXSSA3Q.js → execute-AYQWORVH.js} +6 -6
  32. package/dist/{execute-I4PKSNJM.js → execute-EPE6MZLT.js} +3 -3
  33. package/dist/index.d.ts +262 -10
  34. package/dist/index.js +438 -26
  35. package/dist/{process-guard-QCCBGILS.js → process-guard-GH5LRNWO.js} +1 -1
  36. package/dist/{publish-capability-TS6CNR5G.js → publish-capability-AH2HDW54.js} +3 -3
  37. package/dist/{request-E7TA7COA.js → request-HCCXSKAY.js} +12 -12
  38. package/dist/{serve-skill-HIOWYKRU.js → serve-skill-SZAQT5T5.js} +8 -8
  39. package/dist/{server-I63CXFX3.js → server-MHMAYXWZ.js} +11 -11
  40. package/dist/{service-coordinator-XBNT3SMU.js → service-coordinator-WGH6B2VT.js} +375 -48
  41. package/dist/skills/agentbnb/bootstrap.js +393 -58
  42. package/dist/{websocket-client-PFGVTXNE.js → websocket-client-4Z5P54RU.js} +1 -1
  43. package/dist/websocket-client-QOVARTRN.js +7 -0
  44. package/package.json +17 -11
  45. package/skills/agentbnb/bootstrap.test.ts +9 -0
  46. package/skills/agentbnb/bootstrap.ts +51 -26
  47. package/skills/agentbnb/install.sh +0 -0
  48. package/dist/conduct-KJUD2RTB.js +0 -22
  49. package/dist/websocket-client-5MH6QRJK.js +0 -7
@@ -1,13 +1,13 @@
1
1
  import {
2
2
  executeCapabilityBatch,
3
3
  executeCapabilityRequest
4
- } from "./chunk-PGDBUUGR.js";
4
+ } from "./chunk-C537SFHV.js";
5
5
  import {
6
6
  StructuredFeedbackSchema
7
7
  } from "./chunk-AUBHR7HH.js";
8
8
  import {
9
9
  RelayMessageSchema
10
- } from "./chunk-EHSHB7TY.js";
10
+ } from "./chunk-SSK653A6.js";
11
11
  import {
12
12
  KNOWN_API_KEYS,
13
13
  announceGateway,
@@ -15,15 +15,15 @@ import {
15
15
  detectApiKeys,
16
16
  getPricingStats,
17
17
  stopAnnouncement
18
- } from "./chunk-FK2MDNTB.js";
18
+ } from "./chunk-FTZTEHYG.js";
19
19
  import {
20
20
  deriveAgentId
21
- } from "./chunk-PSQHUZ7X.js";
22
- import "./chunk-DUW6RX6I.js";
21
+ } from "./chunk-OH7BP5NP.js";
22
+ import "./chunk-X32NE6V4.js";
23
23
  import {
24
24
  createLedger,
25
25
  identityAuthPlugin
26
- } from "./chunk-2HSUPCBT.js";
26
+ } from "./chunk-5AAFG2V2.js";
27
27
  import {
28
28
  ApiSkillConfigSchema,
29
29
  parseSkillsFile
@@ -37,13 +37,13 @@ import {
37
37
  insertAuditEvent,
38
38
  listPendingRequests,
39
39
  resolvePendingRequest
40
- } from "./chunk-Y7T6IMM3.js";
40
+ } from "./chunk-GKVTD4EZ.js";
41
41
  import {
42
42
  buildReputationMap,
43
43
  computeReputation,
44
44
  filterCards,
45
45
  searchCards
46
- } from "./chunk-574W3HHE.js";
46
+ } from "./chunk-LJM7FHPM.js";
47
47
  import {
48
48
  bootstrapAgent,
49
49
  getBalance,
@@ -53,12 +53,12 @@ import {
53
53
  openCreditDb,
54
54
  releaseEscrow,
55
55
  settleEscrow
56
- } from "./chunk-J2K5S5MX.js";
56
+ } from "./chunk-D6RKW2XG.js";
57
57
  import "./chunk-NWIQJ2CL.js";
58
58
  import {
59
59
  generateKeyPair,
60
60
  verifyEscrowReceipt
61
- } from "./chunk-F53QQIM2.js";
61
+ } from "./chunk-CUONY5TO.js";
62
62
  import {
63
63
  getConfigDir
64
64
  } from "./chunk-75OC6E4F.js";
@@ -81,11 +81,11 @@ import {
81
81
  updateCard,
82
82
  updateSkillAvailability,
83
83
  updateSkillIdleRate
84
- } from "./chunk-KA2VIEGM.js";
84
+ } from "./chunk-O2OYBAVR.js";
85
85
  import {
86
86
  AgentBnBError,
87
87
  AnyCardSchema
88
- } from "./chunk-3CIMVISQ.js";
88
+ } from "./chunk-WVY2W7AA.js";
89
89
 
90
90
  // src/runtime/agent-runtime.ts
91
91
  import { readFileSync, existsSync } from "fs";
@@ -604,7 +604,8 @@ var OpenClawBridge = class {
604
604
  };
605
605
 
606
606
  // src/skills/command-executor.ts
607
- import { execFile as execFile2 } from "child_process";
607
+ import { spawn } from "child_process";
608
+ var KILL_GRACE_MS = 5e3;
608
609
  function shellEscape2(value) {
609
610
  return "'" + value.replace(/'/g, "'\\''") + "'";
610
611
  }
@@ -630,29 +631,89 @@ function safeInterpolateCommand2(template, context) {
630
631
  return shellEscape2(String(current));
631
632
  });
632
633
  }
633
- function execFileAsync2(file, args, options) {
634
+ function spawnWithKill(command, options, registry) {
634
635
  return new Promise((resolve2, reject) => {
635
- execFile2(file, args, options, (error, stdout, stderr) => {
636
- const stdoutStr = typeof stdout === "string" ? stdout : stdout.toString();
637
- const stderrStr = typeof stderr === "string" ? stderr : stderr.toString();
638
- if (error) {
639
- const enriched = Object.assign(error, { stderr: stderrStr });
640
- reject(enriched);
636
+ const child = spawn("/bin/sh", ["-c", `${command} < /dev/null`], {
637
+ cwd: options.cwd,
638
+ env: options.env,
639
+ stdio: ["ignore", "pipe", "pipe"],
640
+ detached: true
641
+ });
642
+ registry.add(child);
643
+ let stdout = "";
644
+ let stderr = "";
645
+ let timedOut = false;
646
+ let killed = false;
647
+ let killTimer;
648
+ child.stdout.on("data", (chunk) => {
649
+ if (stdout.length < options.maxBuffer) {
650
+ stdout += chunk.toString();
651
+ }
652
+ });
653
+ child.stderr.on("data", (chunk) => {
654
+ if (stderr.length < options.maxBuffer) {
655
+ stderr += chunk.toString();
656
+ }
657
+ });
658
+ const killGroup = (signal) => {
659
+ try {
660
+ process.kill(-child.pid, signal);
661
+ } catch {
662
+ try {
663
+ child.kill(signal);
664
+ } catch {
665
+ }
666
+ }
667
+ };
668
+ const timeoutId = setTimeout(() => {
669
+ timedOut = true;
670
+ killGroup("SIGTERM");
671
+ killTimer = setTimeout(() => {
672
+ if (!killed) {
673
+ killGroup("SIGKILL");
674
+ }
675
+ }, KILL_GRACE_MS);
676
+ killTimer.unref();
677
+ }, options.timeout);
678
+ child.on("close", (_code, _signal) => {
679
+ killed = true;
680
+ clearTimeout(timeoutId);
681
+ if (killTimer) clearTimeout(killTimer);
682
+ registry.delete(child);
683
+ if (timedOut) {
684
+ const err = new Error(`Command timed out after ${options.timeout}ms`);
685
+ err.code = "ETIMEDOUT";
686
+ reject(err);
687
+ } else if (_code !== 0) {
688
+ const err = new Error(stderr.trim() || `Process exited with code ${_code}`);
689
+ Object.assign(err, { stderr: stderr.trim() });
690
+ reject(err);
641
691
  } else {
642
- resolve2({ stdout: stdoutStr, stderr: stderrStr });
692
+ resolve2({ stdout, stderr });
643
693
  }
644
694
  });
695
+ child.on("error", (err) => {
696
+ killed = true;
697
+ clearTimeout(timeoutId);
698
+ registry.delete(child);
699
+ reject(err);
700
+ });
645
701
  });
646
702
  }
647
703
  var CommandExecutor = class {
704
+ /** Active child processes — killed on shutdown(). */
705
+ activeProcesses = /* @__PURE__ */ new Set();
706
+ /** In-flight execution count per skill ID for concurrency limiting. */
707
+ inflight = /* @__PURE__ */ new Map();
648
708
  /**
649
709
  * Execute a command skill with the provided parameters.
650
710
  *
651
711
  * Steps:
652
- * 1. Security check: base command must be in `allowed_commands` if set.
653
- * 2. Interpolate `config.command` using `{ params }` context.
654
- * 3. Run via `child_process.exec` with timeout and cwd.
655
- * 4. Parse stdout based on `output_type`: text | json | file.
712
+ * 1. Concurrency check: reject if at capacity.max_concurrent limit.
713
+ * 2. Security check: base command must be in `allowed_commands` if set.
714
+ * 3. Interpolate `config.command` using `{ params }` context.
715
+ * 4. Run via spawn with SIGTERM→SIGKILL timeout handling.
716
+ * 5. Parse stdout based on `output_type`: text | json | file.
656
717
  *
657
718
  * @param config - Validated CommandSkillConfig.
658
719
  * @param params - Input parameters passed by the caller.
@@ -660,6 +721,16 @@ var CommandExecutor = class {
660
721
  */
661
722
  async execute(config, params) {
662
723
  const cmdConfig = config;
724
+ const maxConcurrent = cmdConfig.capacity?.max_concurrent;
725
+ if (maxConcurrent !== void 0) {
726
+ const current = this.inflight.get(cmdConfig.id) ?? 0;
727
+ if (current >= maxConcurrent) {
728
+ return {
729
+ success: false,
730
+ error: `Skill "${cmdConfig.id}" at max concurrency (${maxConcurrent}). Try again later.`
731
+ };
732
+ }
733
+ }
663
734
  const baseCommand = cmdConfig.command.trim().split(/\s+/)[0] ?? "";
664
735
  if (cmdConfig.allowed_commands && cmdConfig.allowed_commands.length > 0) {
665
736
  const effectiveAllowed = cmdConfig.claude_code ? [.../* @__PURE__ */ new Set([...cmdConfig.allowed_commands, "claude"])] : cmdConfig.allowed_commands;
@@ -690,31 +761,33 @@ var CommandExecutor = class {
690
761
  }
691
762
  const timeout = cmdConfig.timeout_ms ?? 3e4;
692
763
  const cwd = cmdConfig.working_dir ?? process.cwd();
693
- let stdout;
694
764
  const env = { ...process.env };
695
765
  delete env["CLAUDECODE"];
766
+ this.inflight.set(cmdConfig.id, (this.inflight.get(cmdConfig.id) ?? 0) + 1);
767
+ let stdout;
696
768
  try {
697
- const result = await execFileAsync2("/bin/sh", ["-c", `${interpolatedCommand} < /dev/null`], {
769
+ const result = await spawnWithKill(interpolatedCommand, {
698
770
  timeout,
699
771
  cwd,
700
772
  env,
701
773
  maxBuffer: 10 * 1024 * 1024
702
774
  // 10 MB
703
- });
775
+ }, this.activeProcesses);
704
776
  stdout = result.stdout;
705
777
  } catch (err) {
778
+ this.decrementInflight(cmdConfig.id);
706
779
  if (err instanceof Error) {
707
- const message = err.message;
708
- const stderrContent = err.stderr ?? "";
709
- if (message.includes("timed out") || message.includes("ETIMEDOUT") || err.code === "ETIMEDOUT") {
780
+ const code = err.code;
781
+ if (code === "ETIMEDOUT" || err.message.includes("timed out")) {
710
782
  return {
711
783
  success: false,
712
784
  error: `Command timed out after ${timeout}ms`
713
785
  };
714
786
  }
787
+ const stderrContent = err.stderr ?? "";
715
788
  return {
716
789
  success: false,
717
- error: stderrContent.trim() || message
790
+ error: stderrContent.trim() || err.message
718
791
  };
719
792
  }
720
793
  return {
@@ -722,6 +795,7 @@ var CommandExecutor = class {
722
795
  error: String(err)
723
796
  };
724
797
  }
798
+ this.decrementInflight(cmdConfig.id);
725
799
  const rawOutput = stdout.trim();
726
800
  switch (cmdConfig.output_type) {
727
801
  case "text":
@@ -746,6 +820,48 @@ var CommandExecutor = class {
746
820
  };
747
821
  }
748
822
  }
823
+ /**
824
+ * Kill all active child processes. Called during service shutdown
825
+ * to prevent zombie processes.
826
+ */
827
+ shutdown() {
828
+ for (const child of this.activeProcesses) {
829
+ try {
830
+ process.kill(-child.pid, "SIGTERM");
831
+ } catch {
832
+ try {
833
+ child.kill("SIGTERM");
834
+ } catch {
835
+ }
836
+ }
837
+ const pid = child.pid;
838
+ const timer = setTimeout(() => {
839
+ try {
840
+ process.kill(-pid, "SIGKILL");
841
+ } catch {
842
+ }
843
+ }, KILL_GRACE_MS);
844
+ timer.unref();
845
+ }
846
+ this.activeProcesses.clear();
847
+ }
848
+ /** Returns the number of currently active child processes. */
849
+ get activeCount() {
850
+ return this.activeProcesses.size;
851
+ }
852
+ /** Returns the in-flight count for a specific skill ID. */
853
+ getInflight(skillId) {
854
+ return this.inflight.get(skillId) ?? 0;
855
+ }
856
+ /** Decrement the inflight counter for a skill ID. */
857
+ decrementInflight(skillId) {
858
+ const current = this.inflight.get(skillId) ?? 0;
859
+ if (current <= 1) {
860
+ this.inflight.delete(skillId);
861
+ } else {
862
+ this.inflight.set(skillId, current - 1);
863
+ }
864
+ }
749
865
  };
750
866
 
751
867
  // src/runtime/agent-runtime.ts
@@ -763,6 +879,8 @@ var AgentRuntime = class {
763
879
  * Undefined if no skills.yaml was provided or the file does not exist.
764
880
  */
765
881
  skillExecutor;
882
+ /** Reference to CommandExecutor for shutdown cleanup of child processes. */
883
+ commandExecutor;
766
884
  draining = false;
767
885
  orphanedEscrowAgeMinutes;
768
886
  skillsYamlPath;
@@ -828,8 +946,8 @@ var AgentRuntime = class {
828
946
  }
829
947
  const modes = /* @__PURE__ */ new Map();
830
948
  if (this.conductorEnabled) {
831
- const { ConductorMode } = await import("./conductor-mode-VGUU54QI.js");
832
- const { registerConductorCard, CONDUCTOR_OWNER } = await import("./card-REW7BSWW.js");
949
+ const { ConductorMode } = await import("./conductor-mode-AKREGDIU.js");
950
+ const { registerConductorCard, CONDUCTOR_OWNER } = await import("./card-EX2EYGCZ.js");
833
951
  const { loadPeers } = await import("./peers-K7FSHPN3.js");
834
952
  registerConductorCard(this.registryDb);
835
953
  const resolveAgentUrl = (owner) => {
@@ -876,10 +994,11 @@ var AgentRuntime = class {
876
994
  const executor = createSkillExecutor(configs, modes);
877
995
  if (hasSkillsYaml) {
878
996
  const pipelineExecutor = new PipelineExecutor(executor);
997
+ this.commandExecutor = new CommandExecutor();
879
998
  modes.set("api", new ApiExecutor());
880
999
  modes.set("pipeline", pipelineExecutor);
881
1000
  modes.set("openclaw", new OpenClawBridge());
882
- modes.set("command", new CommandExecutor());
1001
+ modes.set("command", this.commandExecutor);
883
1002
  }
884
1003
  this.skillExecutor = executor;
885
1004
  }
@@ -912,6 +1031,9 @@ var AgentRuntime = class {
912
1031
  return;
913
1032
  }
914
1033
  this.draining = true;
1034
+ if (this.commandExecutor) {
1035
+ this.commandExecutor.shutdown();
1036
+ }
915
1037
  for (const job of this.jobs) {
916
1038
  job.stop();
917
1039
  }
@@ -1152,6 +1274,78 @@ function releaseForRelay(creditDb, escrowId) {
1152
1274
  releaseEscrow(creditDb, escrowId);
1153
1275
  }
1154
1276
 
1277
+ // src/relay/relay-escrow.ts
1278
+ function verifyRelaySignature(data, signature, publicKeyHex) {
1279
+ try {
1280
+ const publicKeyBuf = Buffer.from(publicKeyHex, "hex");
1281
+ return verifyEscrowReceipt(data, signature, publicKeyBuf);
1282
+ } catch {
1283
+ return false;
1284
+ }
1285
+ }
1286
+ function processEscrowHold(creditDb, consumerAgentId, providerAgentId, skillId, amount, requestId, signature, publicKeyHex) {
1287
+ if (signature && publicKeyHex) {
1288
+ const signData = {
1289
+ consumer_agent_id: consumerAgentId,
1290
+ provider_agent_id: providerAgentId,
1291
+ skill_id: skillId,
1292
+ amount,
1293
+ request_id: requestId
1294
+ };
1295
+ if (!verifyRelaySignature(signData, signature, publicKeyHex)) {
1296
+ throw new Error("Invalid consumer signature on escrow hold");
1297
+ }
1298
+ }
1299
+ const escrowId = holdEscrow(creditDb, consumerAgentId, amount, `${providerAgentId}:${skillId}`);
1300
+ const remaining = getBalance(creditDb, consumerAgentId);
1301
+ return {
1302
+ escrow_id: escrowId,
1303
+ hold_amount: amount,
1304
+ consumer_remaining: remaining
1305
+ };
1306
+ }
1307
+ function processEscrowSettle(creditDb, escrowId, success, providerAgentId, signature, publicKeyHex, consumerAgentId) {
1308
+ if (signature && publicKeyHex && consumerAgentId) {
1309
+ const signData = {
1310
+ escrow_id: escrowId,
1311
+ success,
1312
+ consumer_agent_id: consumerAgentId
1313
+ };
1314
+ if (!verifyRelaySignature(signData, signature, publicKeyHex)) {
1315
+ throw new Error("Invalid consumer signature on escrow settle");
1316
+ }
1317
+ }
1318
+ const escrowRow = creditDb.prepare("SELECT amount, owner FROM credit_escrow WHERE id = ? AND status = ?").get(escrowId, "held");
1319
+ if (!escrowRow) {
1320
+ throw new Error(`Escrow not found or already settled: ${escrowId}`);
1321
+ }
1322
+ const NETWORK_FEE_RATE = 0.05;
1323
+ if (success) {
1324
+ settleEscrow(creditDb, escrowId, providerAgentId);
1325
+ const networkFee = Math.floor(escrowRow.amount * NETWORK_FEE_RATE);
1326
+ const providerAmount = escrowRow.amount - networkFee;
1327
+ return {
1328
+ escrow_id: escrowId,
1329
+ provider_earned: providerAmount,
1330
+ network_fee: networkFee,
1331
+ consumer_remaining: getBalance(creditDb, escrowRow.owner),
1332
+ provider_balance: getBalance(creditDb, providerAgentId)
1333
+ };
1334
+ } else {
1335
+ releaseEscrow(creditDb, escrowId);
1336
+ return {
1337
+ escrow_id: escrowId,
1338
+ provider_earned: 0,
1339
+ network_fee: 0,
1340
+ consumer_remaining: getBalance(creditDb, escrowRow.owner),
1341
+ provider_balance: getBalance(creditDb, providerAgentId)
1342
+ };
1343
+ }
1344
+ }
1345
+ function settleWithNetworkFee(creditDb, escrowId, providerOwner) {
1346
+ return processEscrowSettle(creditDb, escrowId, true, providerOwner);
1347
+ }
1348
+
1155
1349
  // src/hub-agent/relay-bridge.ts
1156
1350
  import { randomUUID as randomUUID3 } from "crypto";
1157
1351
 
@@ -1455,10 +1649,17 @@ var RATE_LIMIT_WINDOW_MS = 6e4;
1455
1649
  var RELAY_TIMEOUT_MS = 3e5;
1456
1650
  function registerWebSocketRelay(server, db, creditDb) {
1457
1651
  const connections = /* @__PURE__ */ new Map();
1652
+ const agentIdToOwner = /* @__PURE__ */ new Map();
1458
1653
  const pendingRequests = /* @__PURE__ */ new Map();
1459
1654
  const rateLimits = /* @__PURE__ */ new Map();
1460
1655
  const agentCapacities = /* @__PURE__ */ new Map();
1461
1656
  let onAgentOnlineCallback;
1657
+ function resolveConnectionKey(target) {
1658
+ const ownerFromAgentId = agentIdToOwner.get(target);
1659
+ if (ownerFromAgentId && connections.has(ownerFromAgentId)) return ownerFromAgentId;
1660
+ if (connections.has(target)) return target;
1661
+ return void 0;
1662
+ }
1462
1663
  function checkRateLimit(owner) {
1463
1664
  const now = Date.now();
1464
1665
  const entry = rateLimits.get(owner);
@@ -1562,6 +1763,20 @@ function registerWebSocketRelay(server, db, creditDb) {
1562
1763
  }
1563
1764
  }
1564
1765
  connections.set(owner, ws);
1766
+ if (msg.agent_id) {
1767
+ agentIdToOwner.set(msg.agent_id, owner);
1768
+ }
1769
+ if (msg.agents && msg.agents.length > 0) {
1770
+ for (const agentEntry of msg.agents) {
1771
+ agentIdToOwner.set(agentEntry.agent_id, owner);
1772
+ for (const agentCard of agentEntry.cards) {
1773
+ try {
1774
+ upsertCard(agentCard, owner);
1775
+ } catch {
1776
+ }
1777
+ }
1778
+ }
1779
+ }
1565
1780
  const isEphemeral = owner.includes(":req:");
1566
1781
  if (isEphemeral) {
1567
1782
  const cardId2 = card.id ?? owner;
@@ -1605,12 +1820,13 @@ function registerWebSocketRelay(server, db, creditDb) {
1605
1820
  });
1606
1821
  return;
1607
1822
  }
1608
- const targetWs = connections.get(msg.target_owner);
1823
+ const targetKey = resolveConnectionKey(msg.target_agent_id ?? msg.target_owner);
1824
+ const targetWs = targetKey ? connections.get(targetKey) : void 0;
1609
1825
  if (!targetWs || targetWs.readyState !== 1) {
1610
1826
  sendMessage(ws, {
1611
1827
  type: "response",
1612
1828
  id: msg.id,
1613
- error: { code: -32603, message: `Agent offline: ${msg.target_owner}` }
1829
+ error: { code: -32603, message: `Agent offline: ${msg.target_agent_id ?? msg.target_owner}` }
1614
1830
  });
1615
1831
  return;
1616
1832
  }
@@ -1724,7 +1940,7 @@ function registerWebSocketRelay(server, db, creditDb) {
1724
1940
  if (pending.escrowId && creditDb) {
1725
1941
  try {
1726
1942
  if (msg.error === void 0) {
1727
- settleForRelay(creditDb, pending.escrowId, pending.targetOwner);
1943
+ settleWithNetworkFee(creditDb, pending.escrowId, pending.targetOwner);
1728
1944
  } else {
1729
1945
  releaseForRelay(creditDb, pending.escrowId);
1730
1946
  }
@@ -1762,6 +1978,12 @@ function registerWebSocketRelay(server, db, creditDb) {
1762
1978
  connections.delete(owner);
1763
1979
  rateLimits.delete(owner);
1764
1980
  agentCapacities.delete(owner);
1981
+ for (const [agentId, o] of agentIdToOwner) {
1982
+ if (o === owner) {
1983
+ agentIdToOwner.delete(agentId);
1984
+ break;
1985
+ }
1986
+ }
1765
1987
  markOwnerOffline(owner);
1766
1988
  for (const [reqId, pending] of pendingRequests) {
1767
1989
  if (pending.targetOwner === owner) {
@@ -1798,6 +2020,96 @@ function registerWebSocketRelay(server, db, creditDb) {
1798
2020
  function handleHeartbeat(msg) {
1799
2021
  agentCapacities.set(msg.owner, msg.capacity);
1800
2022
  }
2023
+ function handleEscrowHold(ws, msg) {
2024
+ if (!creditDb) {
2025
+ sendMessage(ws, { type: "error", code: "no_credit_db", message: "Credit system not available" });
2026
+ return;
2027
+ }
2028
+ try {
2029
+ const result = processEscrowHold(
2030
+ creditDb,
2031
+ msg.consumer_agent_id,
2032
+ msg.provider_agent_id,
2033
+ msg.skill_id,
2034
+ msg.amount,
2035
+ msg.request_id,
2036
+ msg.signature,
2037
+ msg.public_key
2038
+ );
2039
+ sendMessage(ws, {
2040
+ type: "escrow_hold_confirmed",
2041
+ request_id: msg.request_id,
2042
+ escrow_id: result.escrow_id,
2043
+ hold_amount: result.hold_amount,
2044
+ consumer_remaining: result.consumer_remaining
2045
+ });
2046
+ } catch (err) {
2047
+ const errMsg = err instanceof Error ? err.message : "Escrow hold failed";
2048
+ const code = errMsg.includes("INSUFFICIENT_CREDITS") ? "insufficient_credits" : "escrow_hold_failed";
2049
+ sendMessage(ws, { type: "error", code, message: errMsg, request_id: msg.request_id });
2050
+ }
2051
+ }
2052
+ function handleEscrowSettle(ws, msg) {
2053
+ if (!creditDb) {
2054
+ sendMessage(ws, { type: "error", code: "no_credit_db", message: "Credit system not available" });
2055
+ return;
2056
+ }
2057
+ try {
2058
+ const escrow = creditDb.prepare("SELECT card_id FROM credit_escrow WHERE id = ? AND status = ?").get(msg.escrow_id, "held");
2059
+ if (!escrow) {
2060
+ sendMessage(ws, { type: "error", code: "escrow_not_found", message: `Escrow not found: ${msg.escrow_id}`, request_id: msg.request_id });
2061
+ return;
2062
+ }
2063
+ const providerAgentId = escrow.card_id.split(":")[0];
2064
+ const result = processEscrowSettle(
2065
+ creditDb,
2066
+ msg.escrow_id,
2067
+ msg.success,
2068
+ providerAgentId,
2069
+ msg.signature,
2070
+ msg.public_key,
2071
+ msg.consumer_agent_id
2072
+ );
2073
+ sendMessage(ws, {
2074
+ type: "escrow_settled",
2075
+ escrow_id: result.escrow_id,
2076
+ request_id: msg.request_id,
2077
+ provider_earned: result.provider_earned,
2078
+ network_fee: result.network_fee,
2079
+ consumer_remaining: result.consumer_remaining,
2080
+ provider_balance: result.provider_balance
2081
+ });
2082
+ const providerKey = resolveConnectionKey(providerAgentId);
2083
+ if (providerKey) {
2084
+ const providerWs = connections.get(providerKey);
2085
+ if (providerWs && providerWs.readyState === 1) {
2086
+ sendMessage(providerWs, {
2087
+ type: "escrow_settled",
2088
+ escrow_id: result.escrow_id,
2089
+ request_id: msg.request_id,
2090
+ provider_earned: result.provider_earned,
2091
+ network_fee: result.network_fee,
2092
+ consumer_remaining: result.consumer_remaining,
2093
+ provider_balance: result.provider_balance
2094
+ });
2095
+ }
2096
+ }
2097
+ } catch (err) {
2098
+ sendMessage(ws, { type: "error", code: "escrow_settle_failed", message: err instanceof Error ? err.message : "Settlement failed", request_id: msg.request_id });
2099
+ }
2100
+ }
2101
+ function handleBalanceSync(ws, msg) {
2102
+ if (!creditDb) {
2103
+ sendMessage(ws, { type: "error", code: "no_credit_db", message: "Credit system not available" });
2104
+ return;
2105
+ }
2106
+ const balance = getBalance(creditDb, msg.agent_id);
2107
+ sendMessage(ws, {
2108
+ type: "balance_sync_response",
2109
+ agent_id: msg.agent_id,
2110
+ balance
2111
+ });
2112
+ }
1801
2113
  void server.register(async (app) => {
1802
2114
  app.get("/ws", { websocket: true }, (rawSocket, _request) => {
1803
2115
  const socket = rawSocket;
@@ -1846,6 +2158,16 @@ function registerWebSocketRelay(server, db, creditDb) {
1846
2158
  case "heartbeat":
1847
2159
  handleHeartbeat(msg);
1848
2160
  break;
2161
+ // V8 Phase 2: Explicit escrow messages
2162
+ case "escrow_hold":
2163
+ handleEscrowHold(socket, msg);
2164
+ break;
2165
+ case "escrow_settle":
2166
+ handleEscrowSettle(socket, msg);
2167
+ break;
2168
+ case "balance_sync":
2169
+ handleBalanceSync(socket, msg);
2170
+ break;
1849
2171
  default:
1850
2172
  break;
1851
2173
  }
@@ -4499,7 +4821,7 @@ var IdleMonitor = class {
4499
4821
  };
4500
4822
 
4501
4823
  // src/runtime/service-coordinator.ts
4502
- import { spawn } from "child_process";
4824
+ import { spawn as spawn2 } from "child_process";
4503
4825
  import { createRequire } from "module";
4504
4826
  import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
4505
4827
  import { fileURLToPath as fileURLToPath2 } from "url";
@@ -4527,10 +4849,14 @@ var ServiceCoordinator = class {
4527
4849
  if (health.ok && health.agentbnb) {
4528
4850
  return "already_running";
4529
4851
  }
4530
- throw new AgentBnBError(
4531
- `AgentBnB lock exists but health check failed (pid=${running.pid}, port=${running.port})`,
4532
- "SERVICE_UNHEALTHY"
4533
- );
4852
+ if (opts?.foreground) {
4853
+ this.guard.release();
4854
+ } else {
4855
+ throw new AgentBnBError(
4856
+ `AgentBnB lock exists but health check failed (pid=${running.pid}, port=${running.port})`,
4857
+ "SERVICE_UNHEALTHY"
4858
+ );
4859
+ }
4534
4860
  }
4535
4861
  if (opts?.foreground) {
4536
4862
  return this.startInProcess(opts);
@@ -4645,7 +4971,7 @@ var ServiceCoordinator = class {
4645
4971
  console.log("Conductor mode enabled \u2014 orchestrate/plan skills available via gateway");
4646
4972
  }
4647
4973
  if (opts.conductorEnabled && this.config.conductor?.public) {
4648
- const { buildConductorCard } = await import("./card-REW7BSWW.js");
4974
+ const { buildConductorCard } = await import("./card-EX2EYGCZ.js");
4649
4975
  const conductorCard = buildConductorCard(this.config.owner);
4650
4976
  const now = (/* @__PURE__ */ new Date()).toISOString();
4651
4977
  const existing = this.runtime.registryDb.prepare("SELECT id FROM capability_cards WHERE id = ?").get(conductorCard.id);
@@ -4702,8 +5028,8 @@ var ServiceCoordinator = class {
4702
5028
  }
4703
5029
  }
4704
5030
  if (opts.registryUrl && opts.relay) {
4705
- const { RelayClient } = await import("./websocket-client-5MH6QRJK.js");
4706
- const { executeCapabilityRequest: executeCapabilityRequest2 } = await import("./execute-MOXSSA3Q.js");
5031
+ const { RelayClient } = await import("./websocket-client-QOVARTRN.js");
5032
+ const { executeCapabilityRequest: executeCapabilityRequest2 } = await import("./execute-AYQWORVH.js");
4707
5033
  const cards = listCards(this.runtime.registryDb, this.config.owner);
4708
5034
  const card = cards[0] ?? {
4709
5035
  id: randomUUID6(),
@@ -4719,7 +5045,7 @@ var ServiceCoordinator = class {
4719
5045
  };
4720
5046
  const additionalCards = [];
4721
5047
  if (this.config.conductor?.public) {
4722
- const { buildConductorCard } = await import("./card-REW7BSWW.js");
5048
+ const { buildConductorCard } = await import("./card-EX2EYGCZ.js");
4723
5049
  additionalCards.push(
4724
5050
  buildConductorCard(this.config.owner)
4725
5051
  );
@@ -4728,6 +5054,7 @@ var ServiceCoordinator = class {
4728
5054
  this.relayClient = new RelayClient({
4729
5055
  registryUrl: opts.registryUrl,
4730
5056
  owner: this.config.owner,
5057
+ agent_id: this.config.agent_id,
4731
5058
  token: this.config.token,
4732
5059
  card,
4733
5060
  cards: additionalCards.length > 0 ? additionalCards : void 0,
@@ -4810,7 +5137,7 @@ var ServiceCoordinator = class {
4810
5137
  const runtime = loadPersistedRuntime(getConfigDir());
4811
5138
  const nodeExec = resolveNodeExecutable(runtime);
4812
5139
  const cliArgs = resolveCliLaunchArgs(this.buildServeArgs(opts));
4813
- const child = spawn(nodeExec, cliArgs, {
5140
+ const child = spawn2(nodeExec, cliArgs, {
4814
5141
  detached: true,
4815
5142
  stdio: "ignore",
4816
5143
  env: { ...process.env }