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
@@ -12,13 +12,13 @@ import {
12
12
  requestViaRelay,
13
13
  resolvePendingRequest,
14
14
  searchCards
15
- } from "../../chunk-ETGOKDFR.js";
15
+ } from "../../chunk-B2VJTKO5.js";
16
16
  import {
17
17
  executeCapabilityBatch,
18
18
  executeCapabilityRequest,
19
19
  releaseRequesterEscrow,
20
20
  settleRequesterEscrow
21
- } from "../../chunk-EAD4A4KG.js";
21
+ } from "../../chunk-ALX4WS3A.js";
22
22
  import {
23
23
  bootstrapAgent,
24
24
  generateKeyPair,
@@ -51,7 +51,7 @@ import {
51
51
  updateSkillAvailability,
52
52
  updateSkillIdleRate,
53
53
  verifyEscrowReceipt
54
- } from "../../chunk-K5FO42YF.js";
54
+ } from "../../chunk-7EF3HYVZ.js";
55
55
  import "../../chunk-NWIQJ2CL.js";
56
56
  import {
57
57
  getConfigDir,
@@ -60,16 +60,14 @@ import {
60
60
  import {
61
61
  AgentBnBError,
62
62
  AnyCardSchema
63
- } from "../../chunk-3CIMVISQ.js";
63
+ } from "../../chunk-WVY2W7AA.js";
64
64
  import {
65
65
  RelayClient,
66
66
  RelayMessageSchema
67
- } from "../../chunk-PU7LXOQ3.js";
67
+ } from "../../chunk-3LWBH7P3.js";
68
68
 
69
69
  // skills/agentbnb/bootstrap.ts
70
70
  import { join as join5 } from "path";
71
- import { dirname as dirname4, basename } from "path";
72
- import { existsSync as existsSync6 } from "fs";
73
71
  import { homedir as homedir2 } from "os";
74
72
  import { spawnSync } from "child_process";
75
73
  import { randomUUID as randomUUID10 } from "crypto";
@@ -918,7 +916,8 @@ var OpenClawBridge = class {
918
916
  };
919
917
 
920
918
  // src/skills/command-executor.ts
921
- import { execFile as execFile2 } from "child_process";
919
+ import { spawn } from "child_process";
920
+ var KILL_GRACE_MS = 5e3;
922
921
  function shellEscape2(value) {
923
922
  return "'" + value.replace(/'/g, "'\\''") + "'";
924
923
  }
@@ -944,29 +943,89 @@ function safeInterpolateCommand2(template, context) {
944
943
  return shellEscape2(String(current));
945
944
  });
946
945
  }
947
- function execFileAsync2(file, args, options) {
946
+ function spawnWithKill(command, options, registry) {
948
947
  return new Promise((resolve2, reject) => {
949
- execFile2(file, args, options, (error, stdout, stderr) => {
950
- const stdoutStr = typeof stdout === "string" ? stdout : stdout.toString();
951
- const stderrStr = typeof stderr === "string" ? stderr : stderr.toString();
952
- if (error) {
953
- const enriched = Object.assign(error, { stderr: stderrStr });
954
- reject(enriched);
948
+ const child = spawn("/bin/sh", ["-c", `${command} < /dev/null`], {
949
+ cwd: options.cwd,
950
+ env: options.env,
951
+ stdio: ["ignore", "pipe", "pipe"],
952
+ detached: true
953
+ });
954
+ registry.add(child);
955
+ let stdout = "";
956
+ let stderr = "";
957
+ let timedOut = false;
958
+ let killed = false;
959
+ let killTimer;
960
+ child.stdout.on("data", (chunk) => {
961
+ if (stdout.length < options.maxBuffer) {
962
+ stdout += chunk.toString();
963
+ }
964
+ });
965
+ child.stderr.on("data", (chunk) => {
966
+ if (stderr.length < options.maxBuffer) {
967
+ stderr += chunk.toString();
968
+ }
969
+ });
970
+ const killGroup = (signal) => {
971
+ try {
972
+ process.kill(-child.pid, signal);
973
+ } catch {
974
+ try {
975
+ child.kill(signal);
976
+ } catch {
977
+ }
978
+ }
979
+ };
980
+ const timeoutId = setTimeout(() => {
981
+ timedOut = true;
982
+ killGroup("SIGTERM");
983
+ killTimer = setTimeout(() => {
984
+ if (!killed) {
985
+ killGroup("SIGKILL");
986
+ }
987
+ }, KILL_GRACE_MS);
988
+ killTimer.unref();
989
+ }, options.timeout);
990
+ child.on("close", (_code, _signal) => {
991
+ killed = true;
992
+ clearTimeout(timeoutId);
993
+ if (killTimer) clearTimeout(killTimer);
994
+ registry.delete(child);
995
+ if (timedOut) {
996
+ const err = new Error(`Command timed out after ${options.timeout}ms`);
997
+ err.code = "ETIMEDOUT";
998
+ reject(err);
999
+ } else if (_code !== 0) {
1000
+ const err = new Error(stderr.trim() || `Process exited with code ${_code}`);
1001
+ Object.assign(err, { stderr: stderr.trim() });
1002
+ reject(err);
955
1003
  } else {
956
- resolve2({ stdout: stdoutStr, stderr: stderrStr });
1004
+ resolve2({ stdout, stderr });
957
1005
  }
958
1006
  });
1007
+ child.on("error", (err) => {
1008
+ killed = true;
1009
+ clearTimeout(timeoutId);
1010
+ registry.delete(child);
1011
+ reject(err);
1012
+ });
959
1013
  });
960
1014
  }
961
1015
  var CommandExecutor = class {
1016
+ /** Active child processes — killed on shutdown(). */
1017
+ activeProcesses = /* @__PURE__ */ new Set();
1018
+ /** In-flight execution count per skill ID for concurrency limiting. */
1019
+ inflight = /* @__PURE__ */ new Map();
962
1020
  /**
963
1021
  * Execute a command skill with the provided parameters.
964
1022
  *
965
1023
  * Steps:
966
- * 1. Security check: base command must be in `allowed_commands` if set.
967
- * 2. Interpolate `config.command` using `{ params }` context.
968
- * 3. Run via `child_process.exec` with timeout and cwd.
969
- * 4. Parse stdout based on `output_type`: text | json | file.
1024
+ * 1. Concurrency check: reject if at capacity.max_concurrent limit.
1025
+ * 2. Security check: base command must be in `allowed_commands` if set.
1026
+ * 3. Interpolate `config.command` using `{ params }` context.
1027
+ * 4. Run via spawn with SIGTERM→SIGKILL timeout handling.
1028
+ * 5. Parse stdout based on `output_type`: text | json | file.
970
1029
  *
971
1030
  * @param config - Validated CommandSkillConfig.
972
1031
  * @param params - Input parameters passed by the caller.
@@ -974,6 +1033,16 @@ var CommandExecutor = class {
974
1033
  */
975
1034
  async execute(config, params) {
976
1035
  const cmdConfig = config;
1036
+ const maxConcurrent = cmdConfig.capacity?.max_concurrent;
1037
+ if (maxConcurrent !== void 0) {
1038
+ const current = this.inflight.get(cmdConfig.id) ?? 0;
1039
+ if (current >= maxConcurrent) {
1040
+ return {
1041
+ success: false,
1042
+ error: `Skill "${cmdConfig.id}" at max concurrency (${maxConcurrent}). Try again later.`
1043
+ };
1044
+ }
1045
+ }
977
1046
  const baseCommand = cmdConfig.command.trim().split(/\s+/)[0] ?? "";
978
1047
  if (cmdConfig.allowed_commands && cmdConfig.allowed_commands.length > 0) {
979
1048
  const effectiveAllowed = cmdConfig.claude_code ? [.../* @__PURE__ */ new Set([...cmdConfig.allowed_commands, "claude"])] : cmdConfig.allowed_commands;
@@ -1004,31 +1073,33 @@ var CommandExecutor = class {
1004
1073
  }
1005
1074
  const timeout = cmdConfig.timeout_ms ?? 3e4;
1006
1075
  const cwd = cmdConfig.working_dir ?? process.cwd();
1007
- let stdout;
1008
1076
  const env = { ...process.env };
1009
1077
  delete env["CLAUDECODE"];
1078
+ this.inflight.set(cmdConfig.id, (this.inflight.get(cmdConfig.id) ?? 0) + 1);
1079
+ let stdout;
1010
1080
  try {
1011
- const result = await execFileAsync2("/bin/sh", ["-c", `${interpolatedCommand} < /dev/null`], {
1081
+ const result = await spawnWithKill(interpolatedCommand, {
1012
1082
  timeout,
1013
1083
  cwd,
1014
1084
  env,
1015
1085
  maxBuffer: 10 * 1024 * 1024
1016
1086
  // 10 MB
1017
- });
1087
+ }, this.activeProcesses);
1018
1088
  stdout = result.stdout;
1019
1089
  } catch (err) {
1090
+ this.decrementInflight(cmdConfig.id);
1020
1091
  if (err instanceof Error) {
1021
- const message = err.message;
1022
- const stderrContent = err.stderr ?? "";
1023
- if (message.includes("timed out") || message.includes("ETIMEDOUT") || err.code === "ETIMEDOUT") {
1092
+ const code = err.code;
1093
+ if (code === "ETIMEDOUT" || err.message.includes("timed out")) {
1024
1094
  return {
1025
1095
  success: false,
1026
1096
  error: `Command timed out after ${timeout}ms`
1027
1097
  };
1028
1098
  }
1099
+ const stderrContent = err.stderr ?? "";
1029
1100
  return {
1030
1101
  success: false,
1031
- error: stderrContent.trim() || message
1102
+ error: stderrContent.trim() || err.message
1032
1103
  };
1033
1104
  }
1034
1105
  return {
@@ -1036,6 +1107,7 @@ var CommandExecutor = class {
1036
1107
  error: String(err)
1037
1108
  };
1038
1109
  }
1110
+ this.decrementInflight(cmdConfig.id);
1039
1111
  const rawOutput = stdout.trim();
1040
1112
  switch (cmdConfig.output_type) {
1041
1113
  case "text":
@@ -1060,6 +1132,48 @@ var CommandExecutor = class {
1060
1132
  };
1061
1133
  }
1062
1134
  }
1135
+ /**
1136
+ * Kill all active child processes. Called during service shutdown
1137
+ * to prevent zombie processes.
1138
+ */
1139
+ shutdown() {
1140
+ for (const child of this.activeProcesses) {
1141
+ try {
1142
+ process.kill(-child.pid, "SIGTERM");
1143
+ } catch {
1144
+ try {
1145
+ child.kill("SIGTERM");
1146
+ } catch {
1147
+ }
1148
+ }
1149
+ const pid = child.pid;
1150
+ const timer = setTimeout(() => {
1151
+ try {
1152
+ process.kill(-pid, "SIGKILL");
1153
+ } catch {
1154
+ }
1155
+ }, KILL_GRACE_MS);
1156
+ timer.unref();
1157
+ }
1158
+ this.activeProcesses.clear();
1159
+ }
1160
+ /** Returns the number of currently active child processes. */
1161
+ get activeCount() {
1162
+ return this.activeProcesses.size;
1163
+ }
1164
+ /** Returns the in-flight count for a specific skill ID. */
1165
+ getInflight(skillId) {
1166
+ return this.inflight.get(skillId) ?? 0;
1167
+ }
1168
+ /** Decrement the inflight counter for a skill ID. */
1169
+ decrementInflight(skillId) {
1170
+ const current = this.inflight.get(skillId) ?? 0;
1171
+ if (current <= 1) {
1172
+ this.inflight.delete(skillId);
1173
+ } else {
1174
+ this.inflight.set(skillId, current - 1);
1175
+ }
1176
+ }
1063
1177
  };
1064
1178
 
1065
1179
  // src/runtime/agent-runtime.ts
@@ -1077,6 +1191,8 @@ var AgentRuntime = class {
1077
1191
  * Undefined if no skills.yaml was provided or the file does not exist.
1078
1192
  */
1079
1193
  skillExecutor;
1194
+ /** Reference to CommandExecutor for shutdown cleanup of child processes. */
1195
+ commandExecutor;
1080
1196
  draining = false;
1081
1197
  orphanedEscrowAgeMinutes;
1082
1198
  skillsYamlPath;
@@ -1142,8 +1258,8 @@ var AgentRuntime = class {
1142
1258
  }
1143
1259
  const modes = /* @__PURE__ */ new Map();
1144
1260
  if (this.conductorEnabled) {
1145
- const { ConductorMode } = await import("../../conductor-mode-2VVFMKVE.js");
1146
- const { registerConductorCard, CONDUCTOR_OWNER } = await import("../../card-REW7BSWW.js");
1261
+ const { ConductorMode } = await import("../../conductor-mode-2GSLHVN6.js");
1262
+ const { registerConductorCard, CONDUCTOR_OWNER } = await import("../../card-EX2EYGCZ.js");
1147
1263
  const { loadPeers } = await import("../../peers-CJ7T4RJO.js");
1148
1264
  registerConductorCard(this.registryDb);
1149
1265
  const resolveAgentUrl = (owner) => {
@@ -1190,10 +1306,11 @@ var AgentRuntime = class {
1190
1306
  const executor = createSkillExecutor(configs, modes);
1191
1307
  if (hasSkillsYaml) {
1192
1308
  const pipelineExecutor = new PipelineExecutor(executor);
1309
+ this.commandExecutor = new CommandExecutor();
1193
1310
  modes.set("api", new ApiExecutor());
1194
1311
  modes.set("pipeline", pipelineExecutor);
1195
1312
  modes.set("openclaw", new OpenClawBridge());
1196
- modes.set("command", new CommandExecutor());
1313
+ modes.set("command", this.commandExecutor);
1197
1314
  }
1198
1315
  this.skillExecutor = executor;
1199
1316
  }
@@ -1226,6 +1343,9 @@ var AgentRuntime = class {
1226
1343
  return;
1227
1344
  }
1228
1345
  this.draining = true;
1346
+ if (this.commandExecutor) {
1347
+ this.commandExecutor.shutdown();
1348
+ }
1229
1349
  for (const job of this.jobs) {
1230
1350
  job.stop();
1231
1351
  }
@@ -2003,6 +2123,78 @@ function releaseForRelay(creditDb, escrowId) {
2003
2123
  releaseEscrow(creditDb, escrowId);
2004
2124
  }
2005
2125
 
2126
+ // src/relay/relay-escrow.ts
2127
+ function verifyRelaySignature(data, signature, publicKeyHex) {
2128
+ try {
2129
+ const publicKeyBuf = Buffer.from(publicKeyHex, "hex");
2130
+ return verifyEscrowReceipt(data, signature, publicKeyBuf);
2131
+ } catch {
2132
+ return false;
2133
+ }
2134
+ }
2135
+ function processEscrowHold(creditDb, consumerAgentId, providerAgentId, skillId, amount, requestId, signature, publicKeyHex) {
2136
+ if (signature && publicKeyHex) {
2137
+ const signData = {
2138
+ consumer_agent_id: consumerAgentId,
2139
+ provider_agent_id: providerAgentId,
2140
+ skill_id: skillId,
2141
+ amount,
2142
+ request_id: requestId
2143
+ };
2144
+ if (!verifyRelaySignature(signData, signature, publicKeyHex)) {
2145
+ throw new Error("Invalid consumer signature on escrow hold");
2146
+ }
2147
+ }
2148
+ const escrowId = holdEscrow(creditDb, consumerAgentId, amount, `${providerAgentId}:${skillId}`);
2149
+ const remaining = getBalance(creditDb, consumerAgentId);
2150
+ return {
2151
+ escrow_id: escrowId,
2152
+ hold_amount: amount,
2153
+ consumer_remaining: remaining
2154
+ };
2155
+ }
2156
+ function processEscrowSettle(creditDb, escrowId, success, providerAgentId, signature, publicKeyHex, consumerAgentId) {
2157
+ if (signature && publicKeyHex && consumerAgentId) {
2158
+ const signData = {
2159
+ escrow_id: escrowId,
2160
+ success,
2161
+ consumer_agent_id: consumerAgentId
2162
+ };
2163
+ if (!verifyRelaySignature(signData, signature, publicKeyHex)) {
2164
+ throw new Error("Invalid consumer signature on escrow settle");
2165
+ }
2166
+ }
2167
+ const escrowRow = creditDb.prepare("SELECT amount, owner FROM credit_escrow WHERE id = ? AND status = ?").get(escrowId, "held");
2168
+ if (!escrowRow) {
2169
+ throw new Error(`Escrow not found or already settled: ${escrowId}`);
2170
+ }
2171
+ const NETWORK_FEE_RATE = 0.05;
2172
+ if (success) {
2173
+ settleEscrow(creditDb, escrowId, providerAgentId);
2174
+ const networkFee = Math.floor(escrowRow.amount * NETWORK_FEE_RATE);
2175
+ const providerAmount = escrowRow.amount - networkFee;
2176
+ return {
2177
+ escrow_id: escrowId,
2178
+ provider_earned: providerAmount,
2179
+ network_fee: networkFee,
2180
+ consumer_remaining: getBalance(creditDb, escrowRow.owner),
2181
+ provider_balance: getBalance(creditDb, providerAgentId)
2182
+ };
2183
+ } else {
2184
+ releaseEscrow(creditDb, escrowId);
2185
+ return {
2186
+ escrow_id: escrowId,
2187
+ provider_earned: 0,
2188
+ network_fee: 0,
2189
+ consumer_remaining: getBalance(creditDb, escrowRow.owner),
2190
+ provider_balance: getBalance(creditDb, providerAgentId)
2191
+ };
2192
+ }
2193
+ }
2194
+ function settleWithNetworkFee(creditDb, escrowId, providerOwner) {
2195
+ return processEscrowSettle(creditDb, escrowId, true, providerOwner);
2196
+ }
2197
+
2006
2198
  // src/hub-agent/relay-bridge.ts
2007
2199
  import { randomUUID as randomUUID4 } from "crypto";
2008
2200
 
@@ -2392,10 +2584,17 @@ var RATE_LIMIT_WINDOW_MS = 6e4;
2392
2584
  var RELAY_TIMEOUT_MS = 3e5;
2393
2585
  function registerWebSocketRelay(server, db, creditDb) {
2394
2586
  const connections = /* @__PURE__ */ new Map();
2587
+ const agentIdToOwner = /* @__PURE__ */ new Map();
2395
2588
  const pendingRequests = /* @__PURE__ */ new Map();
2396
2589
  const rateLimits = /* @__PURE__ */ new Map();
2397
2590
  const agentCapacities = /* @__PURE__ */ new Map();
2398
2591
  let onAgentOnlineCallback;
2592
+ function resolveConnectionKey(target) {
2593
+ const ownerFromAgentId = agentIdToOwner.get(target);
2594
+ if (ownerFromAgentId && connections.has(ownerFromAgentId)) return ownerFromAgentId;
2595
+ if (connections.has(target)) return target;
2596
+ return void 0;
2597
+ }
2399
2598
  function checkRateLimit(owner) {
2400
2599
  const now = Date.now();
2401
2600
  const entry = rateLimits.get(owner);
@@ -2499,6 +2698,20 @@ function registerWebSocketRelay(server, db, creditDb) {
2499
2698
  }
2500
2699
  }
2501
2700
  connections.set(owner, ws);
2701
+ if (msg.agent_id) {
2702
+ agentIdToOwner.set(msg.agent_id, owner);
2703
+ }
2704
+ if (msg.agents && msg.agents.length > 0) {
2705
+ for (const agentEntry of msg.agents) {
2706
+ agentIdToOwner.set(agentEntry.agent_id, owner);
2707
+ for (const agentCard of agentEntry.cards) {
2708
+ try {
2709
+ upsertCard(agentCard, owner);
2710
+ } catch {
2711
+ }
2712
+ }
2713
+ }
2714
+ }
2502
2715
  const isEphemeral = owner.includes(":req:");
2503
2716
  if (isEphemeral) {
2504
2717
  const cardId2 = card.id ?? owner;
@@ -2542,12 +2755,13 @@ function registerWebSocketRelay(server, db, creditDb) {
2542
2755
  });
2543
2756
  return;
2544
2757
  }
2545
- const targetWs = connections.get(msg.target_owner);
2758
+ const targetKey = resolveConnectionKey(msg.target_agent_id ?? msg.target_owner);
2759
+ const targetWs = targetKey ? connections.get(targetKey) : void 0;
2546
2760
  if (!targetWs || targetWs.readyState !== 1) {
2547
2761
  sendMessage(ws, {
2548
2762
  type: "response",
2549
2763
  id: msg.id,
2550
- error: { code: -32603, message: `Agent offline: ${msg.target_owner}` }
2764
+ error: { code: -32603, message: `Agent offline: ${msg.target_agent_id ?? msg.target_owner}` }
2551
2765
  });
2552
2766
  return;
2553
2767
  }
@@ -2661,7 +2875,7 @@ function registerWebSocketRelay(server, db, creditDb) {
2661
2875
  if (pending.escrowId && creditDb) {
2662
2876
  try {
2663
2877
  if (msg.error === void 0) {
2664
- settleForRelay(creditDb, pending.escrowId, pending.targetOwner);
2878
+ settleWithNetworkFee(creditDb, pending.escrowId, pending.targetOwner);
2665
2879
  } else {
2666
2880
  releaseForRelay(creditDb, pending.escrowId);
2667
2881
  }
@@ -2699,6 +2913,12 @@ function registerWebSocketRelay(server, db, creditDb) {
2699
2913
  connections.delete(owner);
2700
2914
  rateLimits.delete(owner);
2701
2915
  agentCapacities.delete(owner);
2916
+ for (const [agentId, o] of agentIdToOwner) {
2917
+ if (o === owner) {
2918
+ agentIdToOwner.delete(agentId);
2919
+ break;
2920
+ }
2921
+ }
2702
2922
  markOwnerOffline(owner);
2703
2923
  for (const [reqId, pending] of pendingRequests) {
2704
2924
  if (pending.targetOwner === owner) {
@@ -2735,6 +2955,96 @@ function registerWebSocketRelay(server, db, creditDb) {
2735
2955
  function handleHeartbeat(msg) {
2736
2956
  agentCapacities.set(msg.owner, msg.capacity);
2737
2957
  }
2958
+ function handleEscrowHold(ws, msg) {
2959
+ if (!creditDb) {
2960
+ sendMessage(ws, { type: "error", code: "no_credit_db", message: "Credit system not available" });
2961
+ return;
2962
+ }
2963
+ try {
2964
+ const result = processEscrowHold(
2965
+ creditDb,
2966
+ msg.consumer_agent_id,
2967
+ msg.provider_agent_id,
2968
+ msg.skill_id,
2969
+ msg.amount,
2970
+ msg.request_id,
2971
+ msg.signature,
2972
+ msg.public_key
2973
+ );
2974
+ sendMessage(ws, {
2975
+ type: "escrow_hold_confirmed",
2976
+ request_id: msg.request_id,
2977
+ escrow_id: result.escrow_id,
2978
+ hold_amount: result.hold_amount,
2979
+ consumer_remaining: result.consumer_remaining
2980
+ });
2981
+ } catch (err) {
2982
+ const errMsg = err instanceof Error ? err.message : "Escrow hold failed";
2983
+ const code = errMsg.includes("INSUFFICIENT_CREDITS") ? "insufficient_credits" : "escrow_hold_failed";
2984
+ sendMessage(ws, { type: "error", code, message: errMsg, request_id: msg.request_id });
2985
+ }
2986
+ }
2987
+ function handleEscrowSettle(ws, msg) {
2988
+ if (!creditDb) {
2989
+ sendMessage(ws, { type: "error", code: "no_credit_db", message: "Credit system not available" });
2990
+ return;
2991
+ }
2992
+ try {
2993
+ const escrow = creditDb.prepare("SELECT card_id FROM credit_escrow WHERE id = ? AND status = ?").get(msg.escrow_id, "held");
2994
+ if (!escrow) {
2995
+ sendMessage(ws, { type: "error", code: "escrow_not_found", message: `Escrow not found: ${msg.escrow_id}`, request_id: msg.request_id });
2996
+ return;
2997
+ }
2998
+ const providerAgentId = escrow.card_id.split(":")[0];
2999
+ const result = processEscrowSettle(
3000
+ creditDb,
3001
+ msg.escrow_id,
3002
+ msg.success,
3003
+ providerAgentId,
3004
+ msg.signature,
3005
+ msg.public_key,
3006
+ msg.consumer_agent_id
3007
+ );
3008
+ sendMessage(ws, {
3009
+ type: "escrow_settled",
3010
+ escrow_id: result.escrow_id,
3011
+ request_id: msg.request_id,
3012
+ provider_earned: result.provider_earned,
3013
+ network_fee: result.network_fee,
3014
+ consumer_remaining: result.consumer_remaining,
3015
+ provider_balance: result.provider_balance
3016
+ });
3017
+ const providerKey = resolveConnectionKey(providerAgentId);
3018
+ if (providerKey) {
3019
+ const providerWs = connections.get(providerKey);
3020
+ if (providerWs && providerWs.readyState === 1) {
3021
+ sendMessage(providerWs, {
3022
+ type: "escrow_settled",
3023
+ escrow_id: result.escrow_id,
3024
+ request_id: msg.request_id,
3025
+ provider_earned: result.provider_earned,
3026
+ network_fee: result.network_fee,
3027
+ consumer_remaining: result.consumer_remaining,
3028
+ provider_balance: result.provider_balance
3029
+ });
3030
+ }
3031
+ }
3032
+ } catch (err) {
3033
+ sendMessage(ws, { type: "error", code: "escrow_settle_failed", message: err instanceof Error ? err.message : "Settlement failed", request_id: msg.request_id });
3034
+ }
3035
+ }
3036
+ function handleBalanceSync(ws, msg) {
3037
+ if (!creditDb) {
3038
+ sendMessage(ws, { type: "error", code: "no_credit_db", message: "Credit system not available" });
3039
+ return;
3040
+ }
3041
+ const balance = getBalance(creditDb, msg.agent_id);
3042
+ sendMessage(ws, {
3043
+ type: "balance_sync_response",
3044
+ agent_id: msg.agent_id,
3045
+ balance
3046
+ });
3047
+ }
2738
3048
  void server.register(async (app) => {
2739
3049
  app.get("/ws", { websocket: true }, (rawSocket, _request) => {
2740
3050
  const socket = rawSocket;
@@ -2783,6 +3093,16 @@ function registerWebSocketRelay(server, db, creditDb) {
2783
3093
  case "heartbeat":
2784
3094
  handleHeartbeat(msg);
2785
3095
  break;
3096
+ // V8 Phase 2: Explicit escrow messages
3097
+ case "escrow_hold":
3098
+ handleEscrowHold(socket, msg);
3099
+ break;
3100
+ case "escrow_settle":
3101
+ handleEscrowSettle(socket, msg);
3102
+ break;
3103
+ case "balance_sync":
3104
+ handleBalanceSync(socket, msg);
3105
+ break;
2786
3106
  default:
2787
3107
  break;
2788
3108
  }
@@ -5494,7 +5814,7 @@ async function stopAnnouncement() {
5494
5814
  }
5495
5815
 
5496
5816
  // src/runtime/service-coordinator.ts
5497
- import { spawn } from "child_process";
5817
+ import { spawn as spawn2 } from "child_process";
5498
5818
  import { createRequire } from "module";
5499
5819
  import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
5500
5820
  import { fileURLToPath as fileURLToPath2 } from "url";
@@ -5522,10 +5842,14 @@ var ServiceCoordinator = class {
5522
5842
  if (health.ok && health.agentbnb) {
5523
5843
  return "already_running";
5524
5844
  }
5525
- throw new AgentBnBError(
5526
- `AgentBnB lock exists but health check failed (pid=${running.pid}, port=${running.port})`,
5527
- "SERVICE_UNHEALTHY"
5528
- );
5845
+ if (opts?.foreground) {
5846
+ this.guard.release();
5847
+ } else {
5848
+ throw new AgentBnBError(
5849
+ `AgentBnB lock exists but health check failed (pid=${running.pid}, port=${running.port})`,
5850
+ "SERVICE_UNHEALTHY"
5851
+ );
5852
+ }
5529
5853
  }
5530
5854
  if (opts?.foreground) {
5531
5855
  return this.startInProcess(opts);
@@ -5640,7 +5964,7 @@ var ServiceCoordinator = class {
5640
5964
  console.log("Conductor mode enabled \u2014 orchestrate/plan skills available via gateway");
5641
5965
  }
5642
5966
  if (opts.conductorEnabled && this.config.conductor?.public) {
5643
- const { buildConductorCard } = await import("../../card-REW7BSWW.js");
5967
+ const { buildConductorCard } = await import("../../card-EX2EYGCZ.js");
5644
5968
  const conductorCard = buildConductorCard(this.config.owner);
5645
5969
  const now = (/* @__PURE__ */ new Date()).toISOString();
5646
5970
  const existing = this.runtime.registryDb.prepare("SELECT id FROM capability_cards WHERE id = ?").get(conductorCard.id);
@@ -5697,8 +6021,8 @@ var ServiceCoordinator = class {
5697
6021
  }
5698
6022
  }
5699
6023
  if (opts.registryUrl && opts.relay) {
5700
- const { RelayClient: RelayClient2 } = await import("../../websocket-client-PFGVTXNE.js");
5701
- const { executeCapabilityRequest: executeCapabilityRequest2 } = await import("../../execute-I4PKSNJM.js");
6024
+ const { RelayClient: RelayClient2 } = await import("../../websocket-client-4Z5P54RU.js");
6025
+ const { executeCapabilityRequest: executeCapabilityRequest2 } = await import("../../execute-EPE6MZLT.js");
5702
6026
  const cards = listCards(this.runtime.registryDb, this.config.owner);
5703
6027
  const card = cards[0] ?? {
5704
6028
  id: randomUUID7(),
@@ -5714,7 +6038,7 @@ var ServiceCoordinator = class {
5714
6038
  };
5715
6039
  const additionalCards = [];
5716
6040
  if (this.config.conductor?.public) {
5717
- const { buildConductorCard } = await import("../../card-REW7BSWW.js");
6041
+ const { buildConductorCard } = await import("../../card-EX2EYGCZ.js");
5718
6042
  additionalCards.push(
5719
6043
  buildConductorCard(this.config.owner)
5720
6044
  );
@@ -5723,6 +6047,7 @@ var ServiceCoordinator = class {
5723
6047
  this.relayClient = new RelayClient2({
5724
6048
  registryUrl: opts.registryUrl,
5725
6049
  owner: this.config.owner,
6050
+ agent_id: this.config.agent_id,
5726
6051
  token: this.config.token,
5727
6052
  card,
5728
6053
  cards: additionalCards.length > 0 ? additionalCards : void 0,
@@ -5805,7 +6130,7 @@ var ServiceCoordinator = class {
5805
6130
  const runtime = loadPersistedRuntime(getConfigDir());
5806
6131
  const nodeExec = resolveNodeExecutable(runtime);
5807
6132
  const cliArgs = resolveCliLaunchArgs(this.buildServeArgs(opts));
5808
- const child = spawn(nodeExec, cliArgs, {
6133
+ const child = spawn2(nodeExec, cliArgs, {
5809
6134
  detached: true,
5810
6135
  stdio: "ignore",
5811
6136
  env: { ...process.env }
@@ -6034,6 +6359,7 @@ import { z as z8 } from "zod";
6034
6359
  import { randomUUID as randomUUID8 } from "crypto";
6035
6360
  var EscrowReceiptSchema = z8.object({
6036
6361
  requester_owner: z8.string().min(1),
6362
+ requester_agent_id: z8.string().optional(),
6037
6363
  requester_public_key: z8.string().min(1),
6038
6364
  amount: z8.number().positive(),
6039
6365
  card_id: z8.string().min(1),
@@ -6046,6 +6372,7 @@ function createSignedEscrowReceipt(db, privateKey, publicKey, opts) {
6046
6372
  const escrowId = holdEscrow(db, opts.owner, opts.amount, opts.cardId);
6047
6373
  const receiptData = {
6048
6374
  requester_owner: opts.owner,
6375
+ ...opts.agent_id ? { requester_agent_id: opts.agent_id } : {},
6049
6376
  requester_public_key: publicKey.toString("hex"),
6050
6377
  amount: opts.amount,
6051
6378
  card_id: opts.cardId,
@@ -6408,21 +6735,16 @@ function isNetworkError(err) {
6408
6735
  }
6409
6736
 
6410
6737
  // skills/agentbnb/bootstrap.ts
6411
- function findSoulMd(startDir) {
6412
- let dir = startDir;
6413
- while (true) {
6414
- const candidate = join5(dir, "SOUL.md");
6415
- if (existsSync6(candidate)) return candidate;
6416
- const parent = dirname4(dir);
6417
- if (parent === dir) return null;
6418
- dir = parent;
6419
- }
6420
- }
6421
6738
  function resolveWorkspaceDir() {
6422
- const soulPath = findSoulMd(process.cwd());
6423
- if (soulPath) {
6424
- const workspaceName = basename(dirname4(soulPath));
6425
- return join5(homedir2(), ".agentbnb", workspaceName);
6739
+ if (process.env["AGENTBNB_DIR"]) {
6740
+ return process.env["AGENTBNB_DIR"];
6741
+ }
6742
+ const openclawAgentsDir = join5(homedir2(), ".openclaw", "agents");
6743
+ const cwd = process.cwd();
6744
+ if (cwd.startsWith(openclawAgentsDir + "/")) {
6745
+ const relative = cwd.slice(openclawAgentsDir.length + 1);
6746
+ const agentName = relative.split("/")[0];
6747
+ return join5(openclawAgentsDir, agentName, ".agentbnb");
6426
6748
  }
6427
6749
  return join5(homedir2(), ".agentbnb");
6428
6750
  }
@@ -6486,7 +6808,20 @@ function registerDecomposerCard(configDir, owner) {
6486
6808
  }
6487
6809
  }
6488
6810
  async function activate(config = {}) {
6489
- if (!process.env["AGENTBNB_DIR"]) {
6811
+ if (config.agentDir) {
6812
+ process.env["AGENTBNB_DIR"] = config.agentDir;
6813
+ process.stderr.write(
6814
+ `[agentbnb] AGENTBNB_DIR set from config.agentDir: ${config.agentDir}
6815
+ `
6816
+ );
6817
+ } else if (config.workspaceDir) {
6818
+ const derived = join5(config.workspaceDir, ".agentbnb");
6819
+ process.env["AGENTBNB_DIR"] = derived;
6820
+ process.stderr.write(
6821
+ `[agentbnb] AGENTBNB_DIR derived from config.workspaceDir: ${derived}
6822
+ `
6823
+ );
6824
+ } else if (!process.env["AGENTBNB_DIR"]) {
6490
6825
  const workspaceDir = resolveWorkspaceDir();
6491
6826
  process.env["AGENTBNB_DIR"] = workspaceDir;
6492
6827
  process.stderr.write(