agentbnb 6.0.0 → 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 (58) hide show
  1. package/README.md +245 -39
  2. package/dist/{card-REW7BSWW.js → card-EX2EYGCZ.js} +1 -1
  3. package/dist/{chunk-C6KPAFCC.js → chunk-3LWBH7P3.js} +94 -3
  4. package/dist/{chunk-YRRVFTDR.js → chunk-5AAFG2V2.js} +3 -3
  5. package/dist/{chunk-C2T4BMRW.js → chunk-5GME4KJZ.js} +8 -6
  6. package/dist/{chunk-VPQ44XKE.js → chunk-64AK4FJM.js} +2 -2
  7. package/dist/{chunk-2TLZ6G2B.js → chunk-7EF3HYVZ.js} +158 -18
  8. package/dist/{chunk-JR6TJDIF.js → chunk-ALX4WS3A.js} +9 -4
  9. package/dist/{chunk-7XHDSWRD.js → chunk-B2VJTKO5.js} +2 -2
  10. package/dist/{chunk-TR6UZDNX.js → chunk-C537SFHV.js} +8 -6
  11. package/dist/{chunk-F53QQIM2.js → chunk-CUONY5TO.js} +1 -1
  12. package/dist/chunk-D6RKW2XG.js +395 -0
  13. package/dist/{chunk-NYV3NE5Z.js → chunk-E2OKP5CY.js} +4 -4
  14. package/dist/{chunk-TQDV254A.js → chunk-FTZTEHYG.js} +117 -117
  15. package/dist/{chunk-Y7T6IMM3.js → chunk-GKVTD4EZ.js} +1 -1
  16. package/dist/{chunk-VMH2YS2I.js → chunk-KF3TZHA5.js} +1 -1
  17. package/dist/{chunk-574W3HHE.js → chunk-LJM7FHPM.js} +1 -1
  18. package/dist/chunk-NWIQJ2CL.js +108 -0
  19. package/dist/{chunk-KA2VIEGM.js → chunk-O2OYBAVR.js} +1 -1
  20. package/dist/{chunk-NQTE577Q.js → chunk-OCSU2S6W.js} +9 -0
  21. package/dist/{chunk-PSQHUZ7X.js → chunk-OH7BP5NP.js} +1 -1
  22. package/dist/chunk-SSK653A6.js +169 -0
  23. package/dist/{chunk-BP3L2TET.js → chunk-TBJ3FZKZ.js} +2 -2
  24. package/dist/{chunk-3CIMVISQ.js → chunk-WVY2W7AA.js} +4 -0
  25. package/dist/{chunk-OZXCRLP3.js → chunk-X32NE6V4.js} +5 -2
  26. package/dist/{chunk-7YLFLC5C.js → chunk-YHY7OG6S.js} +5 -5
  27. package/dist/{chunk-JOY533UH.js → chunk-Z4MCGKTL.js} +6 -2
  28. package/dist/cli/index.js +530 -269
  29. package/dist/{client-HRYRJKSA.js → client-HKV3QWZ3.js} +3 -3
  30. package/dist/{conduct-LF6FYPLD.js → conduct-W6XF6DJW.js} +14 -13
  31. package/dist/conduct-YB64OHI6.js +22 -0
  32. package/dist/{conductor-mode-NUDQLZFM.js → conductor-mode-2GSLHVN6.js} +7 -4
  33. package/dist/{conductor-mode-YQ6QSPPT.js → conductor-mode-AKREGDIU.js} +11 -10
  34. package/dist/execute-AYQWORVH.js +15 -0
  35. package/dist/{execute-ITHIYYOX.js → execute-EPE6MZLT.js} +4 -3
  36. package/dist/index.d.ts +502 -12
  37. package/dist/index.js +706 -53
  38. package/dist/{process-guard-QCCBGILS.js → process-guard-GH5LRNWO.js} +1 -1
  39. package/dist/{publish-capability-TS6CNR5G.js → publish-capability-AH2HDW54.js} +3 -3
  40. package/dist/reliability-metrics-QG7WC5QK.js +18 -0
  41. package/dist/{request-P6QCTCCG.js → request-HCCXSKAY.js} +15 -14
  42. package/dist/{serve-skill-EZOL7UYN.js → serve-skill-SZAQT5T5.js} +9 -8
  43. package/dist/{server-3G6ZTASA.js → server-MHMAYXWZ.js} +12 -11
  44. package/dist/{service-coordinator-CRSE4GWC.js → service-coordinator-WGH6B2VT.js} +572 -68
  45. package/dist/{skill-config-4W5W5O6T.js → skill-config-FETXPNVP.js} +1 -1
  46. package/dist/skills/agentbnb/bootstrap.js +594 -73
  47. package/dist/{websocket-client-WRN3HO73.js → websocket-client-4Z5P54RU.js} +1 -1
  48. package/dist/websocket-client-QOVARTRN.js +7 -0
  49. package/openclaw.plugin.json +2 -2
  50. package/package.json +18 -11
  51. package/skills/agentbnb/bootstrap.test.ts +9 -0
  52. package/skills/agentbnb/bootstrap.ts +51 -26
  53. package/skills/agentbnb/install.sh +0 -0
  54. package/dist/chunk-QT7TEVNV.js +0 -82
  55. package/dist/chunk-RVYQSC6L.js +0 -212
  56. package/dist/conduct-QAFZIEY6.js +0 -21
  57. package/dist/execute-PNJFABVJ.js +0 -14
  58. package/dist/websocket-client-6IIDGXKB.js +0 -7
@@ -1,10 +1,13 @@
1
+ import {
2
+ executeCapabilityBatch,
3
+ executeCapabilityRequest
4
+ } from "./chunk-C537SFHV.js";
1
5
  import {
2
6
  StructuredFeedbackSchema
3
7
  } from "./chunk-AUBHR7HH.js";
4
8
  import {
5
- executeCapabilityBatch,
6
- executeCapabilityRequest
7
- } from "./chunk-TR6UZDNX.js";
9
+ RelayMessageSchema
10
+ } from "./chunk-SSK653A6.js";
8
11
  import {
9
12
  KNOWN_API_KEYS,
10
13
  announceGateway,
@@ -12,22 +15,19 @@ import {
12
15
  detectApiKeys,
13
16
  getPricingStats,
14
17
  stopAnnouncement
15
- } from "./chunk-TQDV254A.js";
18
+ } from "./chunk-FTZTEHYG.js";
16
19
  import {
17
20
  deriveAgentId
18
- } from "./chunk-PSQHUZ7X.js";
21
+ } from "./chunk-OH7BP5NP.js";
22
+ import "./chunk-X32NE6V4.js";
19
23
  import {
20
24
  createLedger,
21
25
  identityAuthPlugin
22
- } from "./chunk-YRRVFTDR.js";
23
- import "./chunk-OZXCRLP3.js";
26
+ } from "./chunk-5AAFG2V2.js";
24
27
  import {
25
28
  ApiSkillConfigSchema,
26
29
  parseSkillsFile
27
- } from "./chunk-NQTE577Q.js";
28
- import {
29
- RelayMessageSchema
30
- } from "./chunk-QT7TEVNV.js";
30
+ } from "./chunk-OCSU2S6W.js";
31
31
  import {
32
32
  interpolateObject
33
33
  } from "./chunk-3MJT4PZG.js";
@@ -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,11 +53,12 @@ import {
53
53
  openCreditDb,
54
54
  releaseEscrow,
55
55
  settleEscrow
56
- } from "./chunk-RVYQSC6L.js";
56
+ } from "./chunk-D6RKW2XG.js";
57
+ import "./chunk-NWIQJ2CL.js";
57
58
  import {
58
59
  generateKeyPair,
59
60
  verifyEscrowReceipt
60
- } from "./chunk-F53QQIM2.js";
61
+ } from "./chunk-CUONY5TO.js";
61
62
  import {
62
63
  getConfigDir
63
64
  } from "./chunk-75OC6E4F.js";
@@ -80,11 +81,11 @@ import {
80
81
  updateCard,
81
82
  updateSkillAvailability,
82
83
  updateSkillIdleRate
83
- } from "./chunk-KA2VIEGM.js";
84
+ } from "./chunk-O2OYBAVR.js";
84
85
  import {
85
86
  AgentBnBError,
86
87
  AnyCardSchema
87
- } from "./chunk-3CIMVISQ.js";
88
+ } from "./chunk-WVY2W7AA.js";
88
89
 
89
90
  // src/runtime/agent-runtime.ts
90
91
  import { readFileSync, existsSync } from "fs";
@@ -93,13 +94,16 @@ import { readFileSync, existsSync } from "fs";
93
94
  var SkillExecutor = class {
94
95
  skillMap;
95
96
  modeMap;
97
+ concurrencyGuard;
96
98
  /**
97
99
  * @param configs - Parsed SkillConfig array (from parseSkillsFile).
98
100
  * @param modes - Map from skill type string to its executor implementation.
101
+ * @param concurrencyGuard - Optional ConcurrencyGuard to enforce max_concurrent limits.
99
102
  */
100
- constructor(configs, modes) {
103
+ constructor(configs, modes, concurrencyGuard) {
101
104
  this.skillMap = new Map(configs.map((c) => [c.id, c]));
102
105
  this.modeMap = modes;
106
+ this.concurrencyGuard = concurrencyGuard;
103
107
  }
104
108
  /**
105
109
  * Execute a skill by ID with the given input parameters.
@@ -132,6 +136,18 @@ var SkillExecutor = class {
132
136
  latency_ms: Date.now() - startTime
133
137
  };
134
138
  }
139
+ const maxConcurrent = config.capacity?.max_concurrent ?? Infinity;
140
+ if (this.concurrencyGuard && maxConcurrent !== Infinity) {
141
+ if (!this.concurrencyGuard.canAccept(skillId, maxConcurrent)) {
142
+ return {
143
+ success: false,
144
+ error: `Skill ${skillId} at capacity (${maxConcurrent} concurrent max)`,
145
+ latency_ms: Date.now() - startTime,
146
+ failure_reason: "overload"
147
+ };
148
+ }
149
+ this.concurrencyGuard.acquire(skillId);
150
+ }
135
151
  try {
136
152
  const modeResult = await mode.execute(config, params, onProgress);
137
153
  return {
@@ -145,6 +161,10 @@ var SkillExecutor = class {
145
161
  error: message,
146
162
  latency_ms: Date.now() - startTime
147
163
  };
164
+ } finally {
165
+ if (this.concurrencyGuard && maxConcurrent !== Infinity) {
166
+ this.concurrencyGuard.release(skillId);
167
+ }
148
168
  }
149
169
  }
150
170
  /**
@@ -165,8 +185,8 @@ var SkillExecutor = class {
165
185
  return this.skillMap.get(skillId);
166
186
  }
167
187
  };
168
- function createSkillExecutor(configs, modes) {
169
- return new SkillExecutor(configs, modes);
188
+ function createSkillExecutor(configs, modes, concurrencyGuard) {
189
+ return new SkillExecutor(configs, modes, concurrencyGuard);
170
190
  }
171
191
 
172
192
  // src/skills/api-executor.ts
@@ -584,7 +604,8 @@ var OpenClawBridge = class {
584
604
  };
585
605
 
586
606
  // src/skills/command-executor.ts
587
- import { execFile as execFile2 } from "child_process";
607
+ import { spawn } from "child_process";
608
+ var KILL_GRACE_MS = 5e3;
588
609
  function shellEscape2(value) {
589
610
  return "'" + value.replace(/'/g, "'\\''") + "'";
590
611
  }
@@ -610,29 +631,89 @@ function safeInterpolateCommand2(template, context) {
610
631
  return shellEscape2(String(current));
611
632
  });
612
633
  }
613
- function execFileAsync2(file, args, options) {
634
+ function spawnWithKill(command, options, registry) {
614
635
  return new Promise((resolve2, reject) => {
615
- execFile2(file, args, options, (error, stdout, stderr) => {
616
- const stdoutStr = typeof stdout === "string" ? stdout : stdout.toString();
617
- const stderrStr = typeof stderr === "string" ? stderr : stderr.toString();
618
- if (error) {
619
- const enriched = Object.assign(error, { stderr: stderrStr });
620
- 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);
621
691
  } else {
622
- resolve2({ stdout: stdoutStr, stderr: stderrStr });
692
+ resolve2({ stdout, stderr });
623
693
  }
624
694
  });
695
+ child.on("error", (err) => {
696
+ killed = true;
697
+ clearTimeout(timeoutId);
698
+ registry.delete(child);
699
+ reject(err);
700
+ });
625
701
  });
626
702
  }
627
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();
628
708
  /**
629
709
  * Execute a command skill with the provided parameters.
630
710
  *
631
711
  * Steps:
632
- * 1. Security check: base command must be in `allowed_commands` if set.
633
- * 2. Interpolate `config.command` using `{ params }` context.
634
- * 3. Run via `child_process.exec` with timeout and cwd.
635
- * 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.
636
717
  *
637
718
  * @param config - Validated CommandSkillConfig.
638
719
  * @param params - Input parameters passed by the caller.
@@ -640,43 +721,73 @@ var CommandExecutor = class {
640
721
  */
641
722
  async execute(config, params) {
642
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
+ }
643
734
  const baseCommand = cmdConfig.command.trim().split(/\s+/)[0] ?? "";
644
735
  if (cmdConfig.allowed_commands && cmdConfig.allowed_commands.length > 0) {
645
- if (!cmdConfig.allowed_commands.includes(baseCommand)) {
736
+ const effectiveAllowed = cmdConfig.claude_code ? [.../* @__PURE__ */ new Set([...cmdConfig.allowed_commands, "claude"])] : cmdConfig.allowed_commands;
737
+ const commandToCheck = cmdConfig.claude_code ? "claude" : baseCommand;
738
+ if (!effectiveAllowed.includes(commandToCheck)) {
646
739
  return {
647
740
  success: false,
648
- error: `Command not allowed: "${baseCommand}". Allowed: ${cmdConfig.allowed_commands.join(", ")}`
741
+ error: `Command not allowed: "${commandToCheck}". Allowed: ${effectiveAllowed.join(", ")}`
649
742
  };
650
743
  }
651
744
  }
652
- const interpolatedCommand = safeInterpolateCommand2(cmdConfig.command, { params });
745
+ let interpolatedCommand;
746
+ if (cmdConfig.claude_code) {
747
+ const parts = ["claude", "--print"];
748
+ if (cmdConfig.claude_code.auto_mode) {
749
+ parts.push("--dangerously-skip-permissions");
750
+ }
751
+ if (cmdConfig.claude_code.model) {
752
+ parts.push("--model", cmdConfig.claude_code.model);
753
+ }
754
+ if (cmdConfig.claude_code.system_prompt) {
755
+ parts.push("-p", shellEscape2(cmdConfig.claude_code.system_prompt));
756
+ }
757
+ const interpolatedPrompt = safeInterpolateCommand2(cmdConfig.command, { params });
758
+ interpolatedCommand = parts.join(" ") + " " + interpolatedPrompt;
759
+ } else {
760
+ interpolatedCommand = safeInterpolateCommand2(cmdConfig.command, { params });
761
+ }
653
762
  const timeout = cmdConfig.timeout_ms ?? 3e4;
654
763
  const cwd = cmdConfig.working_dir ?? process.cwd();
655
- let stdout;
656
764
  const env = { ...process.env };
657
765
  delete env["CLAUDECODE"];
766
+ this.inflight.set(cmdConfig.id, (this.inflight.get(cmdConfig.id) ?? 0) + 1);
767
+ let stdout;
658
768
  try {
659
- const result = await execFileAsync2("/bin/sh", ["-c", `${interpolatedCommand} < /dev/null`], {
769
+ const result = await spawnWithKill(interpolatedCommand, {
660
770
  timeout,
661
771
  cwd,
662
772
  env,
663
773
  maxBuffer: 10 * 1024 * 1024
664
774
  // 10 MB
665
- });
775
+ }, this.activeProcesses);
666
776
  stdout = result.stdout;
667
777
  } catch (err) {
778
+ this.decrementInflight(cmdConfig.id);
668
779
  if (err instanceof Error) {
669
- const message = err.message;
670
- const stderrContent = err.stderr ?? "";
671
- 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")) {
672
782
  return {
673
783
  success: false,
674
784
  error: `Command timed out after ${timeout}ms`
675
785
  };
676
786
  }
787
+ const stderrContent = err.stderr ?? "";
677
788
  return {
678
789
  success: false,
679
- error: stderrContent.trim() || message
790
+ error: stderrContent.trim() || err.message
680
791
  };
681
792
  }
682
793
  return {
@@ -684,6 +795,7 @@ var CommandExecutor = class {
684
795
  error: String(err)
685
796
  };
686
797
  }
798
+ this.decrementInflight(cmdConfig.id);
687
799
  const rawOutput = stdout.trim();
688
800
  switch (cmdConfig.output_type) {
689
801
  case "text":
@@ -708,6 +820,48 @@ var CommandExecutor = class {
708
820
  };
709
821
  }
710
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
+ }
711
865
  };
712
866
 
713
867
  // src/runtime/agent-runtime.ts
@@ -725,6 +879,8 @@ var AgentRuntime = class {
725
879
  * Undefined if no skills.yaml was provided or the file does not exist.
726
880
  */
727
881
  skillExecutor;
882
+ /** Reference to CommandExecutor for shutdown cleanup of child processes. */
883
+ commandExecutor;
728
884
  draining = false;
729
885
  orphanedEscrowAgeMinutes;
730
886
  skillsYamlPath;
@@ -790,8 +946,8 @@ var AgentRuntime = class {
790
946
  }
791
947
  const modes = /* @__PURE__ */ new Map();
792
948
  if (this.conductorEnabled) {
793
- const { ConductorMode } = await import("./conductor-mode-YQ6QSPPT.js");
794
- 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");
795
951
  const { loadPeers } = await import("./peers-K7FSHPN3.js");
796
952
  registerConductorCard(this.registryDb);
797
953
  const resolveAgentUrl = (owner) => {
@@ -838,10 +994,11 @@ var AgentRuntime = class {
838
994
  const executor = createSkillExecutor(configs, modes);
839
995
  if (hasSkillsYaml) {
840
996
  const pipelineExecutor = new PipelineExecutor(executor);
997
+ this.commandExecutor = new CommandExecutor();
841
998
  modes.set("api", new ApiExecutor());
842
999
  modes.set("pipeline", pipelineExecutor);
843
1000
  modes.set("openclaw", new OpenClawBridge());
844
- modes.set("command", new CommandExecutor());
1001
+ modes.set("command", this.commandExecutor);
845
1002
  }
846
1003
  this.skillExecutor = executor;
847
1004
  }
@@ -874,6 +1031,9 @@ var AgentRuntime = class {
874
1031
  return;
875
1032
  }
876
1033
  this.draining = true;
1034
+ if (this.commandExecutor) {
1035
+ this.commandExecutor.shutdown();
1036
+ }
877
1037
  for (const job of this.jobs) {
878
1038
  job.stop();
879
1039
  }
@@ -1114,6 +1274,78 @@ function releaseForRelay(creditDb, escrowId) {
1114
1274
  releaseEscrow(creditDb, escrowId);
1115
1275
  }
1116
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
+
1117
1349
  // src/hub-agent/relay-bridge.ts
1118
1350
  import { randomUUID as randomUUID3 } from "crypto";
1119
1351
 
@@ -1417,9 +1649,17 @@ var RATE_LIMIT_WINDOW_MS = 6e4;
1417
1649
  var RELAY_TIMEOUT_MS = 3e5;
1418
1650
  function registerWebSocketRelay(server, db, creditDb) {
1419
1651
  const connections = /* @__PURE__ */ new Map();
1652
+ const agentIdToOwner = /* @__PURE__ */ new Map();
1420
1653
  const pendingRequests = /* @__PURE__ */ new Map();
1421
1654
  const rateLimits = /* @__PURE__ */ new Map();
1655
+ const agentCapacities = /* @__PURE__ */ new Map();
1422
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
+ }
1423
1663
  function checkRateLimit(owner) {
1424
1664
  const now = Date.now();
1425
1665
  const entry = rateLimits.get(owner);
@@ -1523,6 +1763,20 @@ function registerWebSocketRelay(server, db, creditDb) {
1523
1763
  }
1524
1764
  }
1525
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
+ }
1526
1780
  const isEphemeral = owner.includes(":req:");
1527
1781
  if (isEphemeral) {
1528
1782
  const cardId2 = card.id ?? owner;
@@ -1566,12 +1820,13 @@ function registerWebSocketRelay(server, db, creditDb) {
1566
1820
  });
1567
1821
  return;
1568
1822
  }
1569
- 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;
1570
1825
  if (!targetWs || targetWs.readyState !== 1) {
1571
1826
  sendMessage(ws, {
1572
1827
  type: "response",
1573
1828
  id: msg.id,
1574
- error: { code: -32603, message: `Agent offline: ${msg.target_owner}` }
1829
+ error: { code: -32603, message: `Agent offline: ${msg.target_agent_id ?? msg.target_owner}` }
1575
1830
  });
1576
1831
  return;
1577
1832
  }
@@ -1685,7 +1940,7 @@ function registerWebSocketRelay(server, db, creditDb) {
1685
1940
  if (pending.escrowId && creditDb) {
1686
1941
  try {
1687
1942
  if (msg.error === void 0) {
1688
- settleForRelay(creditDb, pending.escrowId, pending.targetOwner);
1943
+ settleWithNetworkFee(creditDb, pending.escrowId, pending.targetOwner);
1689
1944
  } else {
1690
1945
  releaseForRelay(creditDb, pending.escrowId);
1691
1946
  }
@@ -1722,6 +1977,13 @@ function registerWebSocketRelay(server, db, creditDb) {
1722
1977
  if (!owner) return;
1723
1978
  connections.delete(owner);
1724
1979
  rateLimits.delete(owner);
1980
+ agentCapacities.delete(owner);
1981
+ for (const [agentId, o] of agentIdToOwner) {
1982
+ if (o === owner) {
1983
+ agentIdToOwner.delete(agentId);
1984
+ break;
1985
+ }
1986
+ }
1725
1987
  markOwnerOffline(owner);
1726
1988
  for (const [reqId, pending] of pendingRequests) {
1727
1989
  if (pending.targetOwner === owner) {
@@ -1755,6 +2017,99 @@ function registerWebSocketRelay(server, db, creditDb) {
1755
2017
  }
1756
2018
  }
1757
2019
  }
2020
+ function handleHeartbeat(msg) {
2021
+ agentCapacities.set(msg.owner, msg.capacity);
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
+ }
1758
2113
  void server.register(async (app) => {
1759
2114
  app.get("/ws", { websocket: true }, (rawSocket, _request) => {
1760
2115
  const socket = rawSocket;
@@ -1800,15 +2155,33 @@ function registerWebSocketRelay(server, db, creditDb) {
1800
2155
  case "relay_progress":
1801
2156
  handleRelayProgress(msg);
1802
2157
  break;
2158
+ case "heartbeat":
2159
+ handleHeartbeat(msg);
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;
1803
2171
  default:
1804
2172
  break;
1805
2173
  }
1806
2174
  })();
1807
2175
  });
2176
+ const pingInterval = setInterval(() => {
2177
+ if (socket.readyState === 1) socket.ping();
2178
+ }, 3e4);
1808
2179
  socket.on("close", () => {
2180
+ clearInterval(pingInterval);
1809
2181
  handleDisconnect(registeredOwner);
1810
2182
  });
1811
2183
  socket.on("error", () => {
2184
+ clearInterval(pingInterval);
1812
2185
  handleDisconnect(registeredOwner);
1813
2186
  });
1814
2187
  });
@@ -1829,6 +2202,7 @@ function registerWebSocketRelay(server, db, creditDb) {
1829
2202
  }
1830
2203
  pendingRequests.clear();
1831
2204
  rateLimits.clear();
2205
+ agentCapacities.clear();
1832
2206
  },
1833
2207
  setOnAgentOnline: (cb) => {
1834
2208
  onAgentOnlineCallback = cb;
@@ -1837,7 +2211,9 @@ function registerWebSocketRelay(server, db, creditDb) {
1837
2211
  getPendingRequests: () => pendingRequests,
1838
2212
  sendMessage: (ws, msg) => {
1839
2213
  sendMessage(ws, msg);
1840
- }
2214
+ },
2215
+ getAgentCapacity: (owner) => agentCapacities.get(owner),
2216
+ getAllCapacities: () => new Map(agentCapacities)
1841
2217
  };
1842
2218
  }
1843
2219
 
@@ -3108,7 +3484,7 @@ function createRegistryServer(opts) {
3108
3484
  openapi: "3.0.3",
3109
3485
  info: {
3110
3486
  title: "AgentBnB Registry API",
3111
- description: "P2P Agent Capability Sharing Protocol \u2014 discover, publish, and exchange agent capabilities",
3487
+ description: "Where AI agents hire AI agents \u2014 discover, publish, and coordinate agent capabilities",
3112
3488
  version: "3.1.6"
3113
3489
  },
3114
3490
  servers: [{ url: "/", description: "Registry server" }],
@@ -3140,7 +3516,7 @@ function createRegistryServer(opts) {
3140
3516
  });
3141
3517
  void server.register(cors, {
3142
3518
  origin: true,
3143
- methods: ["GET", "POST", "PATCH", "OPTIONS"],
3519
+ methods: ["GET", "POST", "PATCH", "DELETE", "OPTIONS"],
3144
3520
  allowedHeaders: ["Content-Type", "Authorization", "X-Agent-PublicKey", "X-Agent-Signature", "X-Agent-Timestamp"]
3145
3521
  });
3146
3522
  void server.register(fastifyWebsocket);
@@ -3303,7 +3679,9 @@ function createRegistryServer(opts) {
3303
3679
  const trustStmt = db.prepare(`
3304
3680
  SELECT cc.owner,
3305
3681
  COUNT(rl.id) as total_exec,
3306
- SUM(CASE WHEN rl.status IN ('success','failure','timeout','refunded') THEN 1 ELSE 0 END) as terminal_exec,
3682
+ SUM(CASE WHEN rl.status IN ('success','failure','timeout','refunded')
3683
+ AND (rl.failure_reason IS NULL OR rl.failure_reason IN ('bad_execution','auth_error'))
3684
+ THEN 1 ELSE 0 END) as terminal_exec,
3307
3685
  SUM(CASE WHEN rl.status = 'success' THEN 1 ELSE 0 END) as success_exec,
3308
3686
  AVG(CASE WHEN rl.status = 'success' THEN rl.latency_ms END) as avg_latency
3309
3687
  FROM capability_cards cc
@@ -3537,21 +3915,32 @@ function createRegistryServer(opts) {
3537
3915
  api.delete("/cards/:id", {
3538
3916
  schema: {
3539
3917
  tags: ["cards"],
3540
- summary: "Delete a capability card",
3918
+ summary: "Delete a capability card (requires Bearer auth)",
3919
+ security: [{ bearerAuth: [] }],
3541
3920
  params: { type: "object", properties: { id: { type: "string" } }, required: ["id"] },
3542
3921
  response: {
3543
- 200: { type: "object", properties: { ok: { type: "boolean" }, id: { type: "string" } } },
3922
+ 204: { type: "null", description: "Card deleted successfully" },
3923
+ 401: { type: "object", properties: { error: { type: "string" } } },
3924
+ 403: { type: "object", properties: { error: { type: "string" } } },
3544
3925
  404: { type: "object", properties: { error: { type: "string" } } }
3545
3926
  }
3546
3927
  }
3547
3928
  }, async (request, reply) => {
3929
+ const auth = request.headers.authorization;
3930
+ const token = auth?.startsWith("Bearer ") ? auth.slice(7).trim() : null;
3931
+ if (!token || !opts.ownerApiKey || token !== opts.ownerApiKey) {
3932
+ return reply.code(401).send({ error: "Unauthorized" });
3933
+ }
3548
3934
  const { id } = request.params;
3549
3935
  const card = getCard(db, id);
3550
3936
  if (!card) {
3551
3937
  return reply.code(404).send({ error: "Not found" });
3552
3938
  }
3939
+ if (opts.ownerName && card.owner !== opts.ownerName) {
3940
+ return reply.code(403).send({ error: "Forbidden" });
3941
+ }
3553
3942
  db.prepare("DELETE FROM capability_cards WHERE id = ?").run(id);
3554
- return reply.send({ ok: true, id });
3943
+ return reply.code(204).send();
3555
3944
  });
3556
3945
  api.get("/api/agents", {
3557
3946
  schema: {
@@ -3636,7 +4025,8 @@ function createRegistryServer(opts) {
3636
4025
  const lastActive = lastActiveRow?.last_req ?? memberRow?.latest ?? joinedAt;
3637
4026
  const metricsStmt = db.prepare(`
3638
4027
  SELECT
3639
- COUNT(*) as total,
4028
+ SUM(CASE WHEN rl.failure_reason IS NULL OR rl.failure_reason IN ('bad_execution','auth_error')
4029
+ THEN 1 ELSE 0 END) as total,
3640
4030
  SUM(CASE WHEN rl.status = 'success' THEN 1 ELSE 0 END) as successes,
3641
4031
  AVG(CASE WHEN rl.status = 'success' THEN rl.latency_ms END) as avg_latency,
3642
4032
  COUNT(DISTINCT rl.requester) as unique_requesters,
@@ -4229,6 +4619,110 @@ function createRegistryServer(opts) {
4229
4619
  });
4230
4620
  });
4231
4621
  }
4622
+ api.get("/api/providers/:owner/reliability", {
4623
+ schema: {
4624
+ tags: ["agents"],
4625
+ summary: "Get provider reliability metrics",
4626
+ params: { type: "object", properties: { owner: { type: "string" } }, required: ["owner"] },
4627
+ response: {
4628
+ 200: {
4629
+ type: "object",
4630
+ properties: {
4631
+ current_streak: { type: "integer" },
4632
+ longest_streak: { type: "integer" },
4633
+ total_hires: { type: "integer" },
4634
+ repeat_hires: { type: "integer" },
4635
+ repeat_hire_rate: { type: "number" },
4636
+ avg_feedback_score: { type: "number" },
4637
+ availability_rate: { type: "number" }
4638
+ }
4639
+ },
4640
+ 404: { type: "object", properties: { error: { type: "string" } } }
4641
+ }
4642
+ }
4643
+ }, async (request, reply) => {
4644
+ const { owner } = request.params;
4645
+ if (!opts.creditDb) {
4646
+ return reply.code(404).send({ error: "Credit system not enabled" });
4647
+ }
4648
+ const { getReliabilityMetrics } = await import("./reliability-metrics-QG7WC5QK.js");
4649
+ const metrics = getReliabilityMetrics(opts.creditDb, owner);
4650
+ if (!metrics) {
4651
+ return reply.code(404).send({ error: "No reliability data for this provider" });
4652
+ }
4653
+ return reply.send(metrics);
4654
+ });
4655
+ api.get("/api/fleet/:owner", {
4656
+ schema: {
4657
+ tags: ["agents"],
4658
+ summary: "Agent fleet overview for an owner",
4659
+ params: { type: "object", properties: { owner: { type: "string" } }, required: ["owner"] },
4660
+ response: {
4661
+ 200: { type: "object", properties: { agents: { type: "array" } } }
4662
+ }
4663
+ }
4664
+ }, async (request, reply) => {
4665
+ const { owner } = request.params;
4666
+ const rows = db.prepare(
4667
+ "SELECT id, data FROM capability_cards WHERE owner = ?"
4668
+ ).all(owner);
4669
+ const agents = [];
4670
+ for (const row of rows) {
4671
+ try {
4672
+ const card = JSON.parse(row.data);
4673
+ let earnings = 0;
4674
+ let spend = 0;
4675
+ if (opts.creditDb) {
4676
+ const earningRow = opts.creditDb.prepare(
4677
+ "SELECT COALESCE(SUM(amount), 0) as total FROM credit_transactions WHERE owner = ? AND reason = 'settlement' AND amount > 0"
4678
+ ).get(owner);
4679
+ earnings = earningRow.total;
4680
+ const spendRow = opts.creditDb.prepare(
4681
+ "SELECT COALESCE(SUM(ABS(amount)), 0) as total FROM credit_transactions WHERE owner = ? AND reason = 'escrow_hold'"
4682
+ ).get(owner);
4683
+ spend = spendRow.total;
4684
+ }
4685
+ const successCount = db.prepare(
4686
+ "SELECT COUNT(*) as cnt FROM request_log WHERE card_id = ? AND status = 'success' AND (action_type IS NULL OR action_type = 'auto_share')"
4687
+ ).get(row.id).cnt;
4688
+ const failureCount = db.prepare(
4689
+ "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')"
4690
+ ).get(row.id).cnt;
4691
+ const totalExec = successCount + failureCount;
4692
+ const successRate = totalExec > 0 ? successCount / totalExec : 0;
4693
+ let failureBreakdown = {};
4694
+ try {
4695
+ const failureRows = db.prepare(
4696
+ "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"
4697
+ ).all(row.id);
4698
+ for (const fr of failureRows) {
4699
+ failureBreakdown[fr.failure_reason] = fr.cnt;
4700
+ }
4701
+ } catch {
4702
+ }
4703
+ let reliability = null;
4704
+ if (opts.creditDb) {
4705
+ const { getReliabilityMetrics } = await import("./reliability-metrics-QG7WC5QK.js");
4706
+ reliability = getReliabilityMetrics(opts.creditDb, owner);
4707
+ }
4708
+ agents.push({
4709
+ id: row.id,
4710
+ name: card.name ?? card.agent_name ?? owner,
4711
+ online: card.availability?.online ?? false,
4712
+ current_load: 0,
4713
+ // Will be populated from relay heartbeat data in future
4714
+ success_rate: successRate,
4715
+ total_executions: totalExec,
4716
+ earnings,
4717
+ spend,
4718
+ failure_breakdown: failureBreakdown,
4719
+ reliability
4720
+ });
4721
+ } catch {
4722
+ }
4723
+ }
4724
+ return reply.send({ agents });
4725
+ });
4232
4726
  });
4233
4727
  return { server, relayState };
4234
4728
  }
@@ -4327,7 +4821,7 @@ var IdleMonitor = class {
4327
4821
  };
4328
4822
 
4329
4823
  // src/runtime/service-coordinator.ts
4330
- import { spawn } from "child_process";
4824
+ import { spawn as spawn2 } from "child_process";
4331
4825
  import { createRequire } from "module";
4332
4826
  import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
4333
4827
  import { fileURLToPath as fileURLToPath2 } from "url";
@@ -4355,10 +4849,14 @@ var ServiceCoordinator = class {
4355
4849
  if (health.ok && health.agentbnb) {
4356
4850
  return "already_running";
4357
4851
  }
4358
- throw new AgentBnBError(
4359
- `AgentBnB lock exists but health check failed (pid=${running.pid}, port=${running.port})`,
4360
- "SERVICE_UNHEALTHY"
4361
- );
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
+ }
4362
4860
  }
4363
4861
  if (opts?.foreground) {
4364
4862
  return this.startInProcess(opts);
@@ -4473,7 +4971,7 @@ var ServiceCoordinator = class {
4473
4971
  console.log("Conductor mode enabled \u2014 orchestrate/plan skills available via gateway");
4474
4972
  }
4475
4973
  if (opts.conductorEnabled && this.config.conductor?.public) {
4476
- const { buildConductorCard } = await import("./card-REW7BSWW.js");
4974
+ const { buildConductorCard } = await import("./card-EX2EYGCZ.js");
4477
4975
  const conductorCard = buildConductorCard(this.config.owner);
4478
4976
  const now = (/* @__PURE__ */ new Date()).toISOString();
4479
4977
  const existing = this.runtime.registryDb.prepare("SELECT id FROM capability_cards WHERE id = ?").get(conductorCard.id);
@@ -4530,8 +5028,8 @@ var ServiceCoordinator = class {
4530
5028
  }
4531
5029
  }
4532
5030
  if (opts.registryUrl && opts.relay) {
4533
- const { RelayClient } = await import("./websocket-client-6IIDGXKB.js");
4534
- const { executeCapabilityRequest: executeCapabilityRequest2 } = await import("./execute-PNJFABVJ.js");
5031
+ const { RelayClient } = await import("./websocket-client-QOVARTRN.js");
5032
+ const { executeCapabilityRequest: executeCapabilityRequest2 } = await import("./execute-AYQWORVH.js");
4535
5033
  const cards = listCards(this.runtime.registryDb, this.config.owner);
4536
5034
  const card = cards[0] ?? {
4537
5035
  id: randomUUID6(),
@@ -4547,7 +5045,7 @@ var ServiceCoordinator = class {
4547
5045
  };
4548
5046
  const additionalCards = [];
4549
5047
  if (this.config.conductor?.public) {
4550
- const { buildConductorCard } = await import("./card-REW7BSWW.js");
5048
+ const { buildConductorCard } = await import("./card-EX2EYGCZ.js");
4551
5049
  additionalCards.push(
4552
5050
  buildConductorCard(this.config.owner)
4553
5051
  );
@@ -4556,6 +5054,7 @@ var ServiceCoordinator = class {
4556
5054
  this.relayClient = new RelayClient({
4557
5055
  registryUrl: opts.registryUrl,
4558
5056
  owner: this.config.owner,
5057
+ agent_id: this.config.agent_id,
4559
5058
  token: this.config.token,
4560
5059
  card,
4561
5060
  cards: additionalCards.length > 0 ? additionalCards : void 0,
@@ -4638,7 +5137,7 @@ var ServiceCoordinator = class {
4638
5137
  const runtime = loadPersistedRuntime(getConfigDir());
4639
5138
  const nodeExec = resolveNodeExecutable(runtime);
4640
5139
  const cliArgs = resolveCliLaunchArgs(this.buildServeArgs(opts));
4641
- const child = spawn(nodeExec, cliArgs, {
5140
+ const child = spawn2(nodeExec, cliArgs, {
4642
5141
  detached: true,
4643
5142
  stdio: "ignore",
4644
5143
  env: { ...process.env }
@@ -4797,7 +5296,6 @@ var ServiceCoordinator = class {
4797
5296
  })();
4798
5297
  };
4799
5298
  };
4800
- var require2 = createRequire(import.meta.url);
4801
5299
  function loadPersistedRuntime(configDir) {
4802
5300
  const runtimePath = join2(configDir, "runtime.json");
4803
5301
  if (!existsSync3(runtimePath)) return null;
@@ -4825,6 +5323,12 @@ function resolveNodeExecutable(runtime) {
4825
5323
  return process.execPath;
4826
5324
  }
4827
5325
  function resolveCliLaunchArgs(serveArgs) {
5326
+ const require2 = createRequire(import.meta.url);
5327
+ try {
5328
+ const distCli2 = require2.resolve("agentbnb/dist/cli/index.js");
5329
+ return [distCli2, "serve", ...serveArgs];
5330
+ } catch {
5331
+ }
4828
5332
  const projectRoot = resolve(dirname2(fileURLToPath2(import.meta.url)), "..", "..");
4829
5333
  const distCli = join2(projectRoot, "dist", "cli", "index.js");
4830
5334
  if (existsSync3(distCli)) {