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
package/dist/index.js CHANGED
@@ -17,6 +17,8 @@ var CapabilityCardSchema = z.object({
17
17
  spec_version: z.literal("1.0").default("1.0"),
18
18
  id: z.string().uuid(),
19
19
  owner: z.string().min(1),
20
+ /** V8: Cryptographic agent identity (Ed25519 public key hash). */
21
+ agent_id: z.string().optional(),
20
22
  name: z.string().min(1).max(100),
21
23
  description: z.string().max(500),
22
24
  level: z.union([z.literal(1), z.literal(2), z.literal(3)]),
@@ -139,6 +141,8 @@ var CapabilityCardV2Schema = z.object({
139
141
  spec_version: z.literal("2.0"),
140
142
  id: z.string().uuid(),
141
143
  owner: z.string().min(1),
144
+ /** V8: Cryptographic agent identity (Ed25519 public key hash). */
145
+ agent_id: z.string().optional(),
142
146
  /** Agent display name — was 'name' in v1.0. */
143
147
  agent_name: z.string().min(1).max(100),
144
148
  /** Short one-liner shown in Hub v2 Identity Header. */
@@ -856,6 +860,26 @@ function recordSuccessfulHire(db, providerOwner, consumerOwner) {
856
860
  ).run(repeatIncrement, now, providerOwner);
857
861
  }
858
862
 
863
+ // src/identity/agent-identity.ts
864
+ var AGENTS_SCHEMA = `
865
+ CREATE TABLE IF NOT EXISTS agents (
866
+ agent_id TEXT PRIMARY KEY,
867
+ display_name TEXT NOT NULL,
868
+ public_key TEXT NOT NULL,
869
+ operator_id TEXT,
870
+ server_id TEXT,
871
+ legacy_owner TEXT,
872
+ created_at TEXT NOT NULL,
873
+ updated_at TEXT NOT NULL
874
+ );
875
+
876
+ CREATE INDEX IF NOT EXISTS idx_agents_operator ON agents(operator_id);
877
+ CREATE INDEX IF NOT EXISTS idx_agents_legacy_owner ON agents(legacy_owner);
878
+ `;
879
+ function ensureAgentsTable(db) {
880
+ db.exec(AGENTS_SCHEMA);
881
+ }
882
+
859
883
  // src/credit/ledger.ts
860
884
  var CREDIT_SCHEMA = `
861
885
  CREATE TABLE IF NOT EXISTS credit_balances (
@@ -912,6 +936,7 @@ function openCreditDb(path = ":memory:") {
912
936
  } catch {
913
937
  }
914
938
  ensureReliabilityTable(db);
939
+ ensureAgentsTable(db);
915
940
  return db;
916
941
  }
917
942
  function getBalance(db, owner) {
@@ -2309,7 +2334,8 @@ var OpenClawBridge = class {
2309
2334
  };
2310
2335
 
2311
2336
  // src/skills/command-executor.ts
2312
- import { execFile as execFile2 } from "child_process";
2337
+ import { spawn } from "child_process";
2338
+ var KILL_GRACE_MS = 5e3;
2313
2339
  function shellEscape2(value) {
2314
2340
  return "'" + value.replace(/'/g, "'\\''") + "'";
2315
2341
  }
@@ -2335,29 +2361,89 @@ function safeInterpolateCommand2(template, context) {
2335
2361
  return shellEscape2(String(current));
2336
2362
  });
2337
2363
  }
2338
- function execFileAsync2(file, args, options) {
2364
+ function spawnWithKill(command, options, registry) {
2339
2365
  return new Promise((resolve, reject) => {
2340
- execFile2(file, args, options, (error, stdout, stderr) => {
2341
- const stdoutStr = typeof stdout === "string" ? stdout : stdout.toString();
2342
- const stderrStr = typeof stderr === "string" ? stderr : stderr.toString();
2343
- if (error) {
2344
- const enriched = Object.assign(error, { stderr: stderrStr });
2345
- reject(enriched);
2366
+ const child = spawn("/bin/sh", ["-c", `${command} < /dev/null`], {
2367
+ cwd: options.cwd,
2368
+ env: options.env,
2369
+ stdio: ["ignore", "pipe", "pipe"],
2370
+ detached: true
2371
+ });
2372
+ registry.add(child);
2373
+ let stdout = "";
2374
+ let stderr = "";
2375
+ let timedOut = false;
2376
+ let killed = false;
2377
+ let killTimer;
2378
+ child.stdout.on("data", (chunk) => {
2379
+ if (stdout.length < options.maxBuffer) {
2380
+ stdout += chunk.toString();
2381
+ }
2382
+ });
2383
+ child.stderr.on("data", (chunk) => {
2384
+ if (stderr.length < options.maxBuffer) {
2385
+ stderr += chunk.toString();
2386
+ }
2387
+ });
2388
+ const killGroup = (signal) => {
2389
+ try {
2390
+ process.kill(-child.pid, signal);
2391
+ } catch {
2392
+ try {
2393
+ child.kill(signal);
2394
+ } catch {
2395
+ }
2396
+ }
2397
+ };
2398
+ const timeoutId = setTimeout(() => {
2399
+ timedOut = true;
2400
+ killGroup("SIGTERM");
2401
+ killTimer = setTimeout(() => {
2402
+ if (!killed) {
2403
+ killGroup("SIGKILL");
2404
+ }
2405
+ }, KILL_GRACE_MS);
2406
+ killTimer.unref();
2407
+ }, options.timeout);
2408
+ child.on("close", (_code, _signal) => {
2409
+ killed = true;
2410
+ clearTimeout(timeoutId);
2411
+ if (killTimer) clearTimeout(killTimer);
2412
+ registry.delete(child);
2413
+ if (timedOut) {
2414
+ const err = new Error(`Command timed out after ${options.timeout}ms`);
2415
+ err.code = "ETIMEDOUT";
2416
+ reject(err);
2417
+ } else if (_code !== 0) {
2418
+ const err = new Error(stderr.trim() || `Process exited with code ${_code}`);
2419
+ Object.assign(err, { stderr: stderr.trim() });
2420
+ reject(err);
2346
2421
  } else {
2347
- resolve({ stdout: stdoutStr, stderr: stderrStr });
2422
+ resolve({ stdout, stderr });
2348
2423
  }
2349
2424
  });
2425
+ child.on("error", (err) => {
2426
+ killed = true;
2427
+ clearTimeout(timeoutId);
2428
+ registry.delete(child);
2429
+ reject(err);
2430
+ });
2350
2431
  });
2351
2432
  }
2352
2433
  var CommandExecutor = class {
2434
+ /** Active child processes — killed on shutdown(). */
2435
+ activeProcesses = /* @__PURE__ */ new Set();
2436
+ /** In-flight execution count per skill ID for concurrency limiting. */
2437
+ inflight = /* @__PURE__ */ new Map();
2353
2438
  /**
2354
2439
  * Execute a command skill with the provided parameters.
2355
2440
  *
2356
2441
  * Steps:
2357
- * 1. Security check: base command must be in `allowed_commands` if set.
2358
- * 2. Interpolate `config.command` using `{ params }` context.
2359
- * 3. Run via `child_process.exec` with timeout and cwd.
2360
- * 4. Parse stdout based on `output_type`: text | json | file.
2442
+ * 1. Concurrency check: reject if at capacity.max_concurrent limit.
2443
+ * 2. Security check: base command must be in `allowed_commands` if set.
2444
+ * 3. Interpolate `config.command` using `{ params }` context.
2445
+ * 4. Run via spawn with SIGTERM→SIGKILL timeout handling.
2446
+ * 5. Parse stdout based on `output_type`: text | json | file.
2361
2447
  *
2362
2448
  * @param config - Validated CommandSkillConfig.
2363
2449
  * @param params - Input parameters passed by the caller.
@@ -2365,6 +2451,16 @@ var CommandExecutor = class {
2365
2451
  */
2366
2452
  async execute(config, params) {
2367
2453
  const cmdConfig = config;
2454
+ const maxConcurrent = cmdConfig.capacity?.max_concurrent;
2455
+ if (maxConcurrent !== void 0) {
2456
+ const current = this.inflight.get(cmdConfig.id) ?? 0;
2457
+ if (current >= maxConcurrent) {
2458
+ return {
2459
+ success: false,
2460
+ error: `Skill "${cmdConfig.id}" at max concurrency (${maxConcurrent}). Try again later.`
2461
+ };
2462
+ }
2463
+ }
2368
2464
  const baseCommand = cmdConfig.command.trim().split(/\s+/)[0] ?? "";
2369
2465
  if (cmdConfig.allowed_commands && cmdConfig.allowed_commands.length > 0) {
2370
2466
  const effectiveAllowed = cmdConfig.claude_code ? [.../* @__PURE__ */ new Set([...cmdConfig.allowed_commands, "claude"])] : cmdConfig.allowed_commands;
@@ -2395,31 +2491,33 @@ var CommandExecutor = class {
2395
2491
  }
2396
2492
  const timeout = cmdConfig.timeout_ms ?? 3e4;
2397
2493
  const cwd = cmdConfig.working_dir ?? process.cwd();
2398
- let stdout;
2399
2494
  const env = { ...process.env };
2400
2495
  delete env["CLAUDECODE"];
2496
+ this.inflight.set(cmdConfig.id, (this.inflight.get(cmdConfig.id) ?? 0) + 1);
2497
+ let stdout;
2401
2498
  try {
2402
- const result = await execFileAsync2("/bin/sh", ["-c", `${interpolatedCommand} < /dev/null`], {
2499
+ const result = await spawnWithKill(interpolatedCommand, {
2403
2500
  timeout,
2404
2501
  cwd,
2405
2502
  env,
2406
2503
  maxBuffer: 10 * 1024 * 1024
2407
2504
  // 10 MB
2408
- });
2505
+ }, this.activeProcesses);
2409
2506
  stdout = result.stdout;
2410
2507
  } catch (err) {
2508
+ this.decrementInflight(cmdConfig.id);
2411
2509
  if (err instanceof Error) {
2412
- const message = err.message;
2413
- const stderrContent = err.stderr ?? "";
2414
- if (message.includes("timed out") || message.includes("ETIMEDOUT") || err.code === "ETIMEDOUT") {
2510
+ const code = err.code;
2511
+ if (code === "ETIMEDOUT" || err.message.includes("timed out")) {
2415
2512
  return {
2416
2513
  success: false,
2417
2514
  error: `Command timed out after ${timeout}ms`
2418
2515
  };
2419
2516
  }
2517
+ const stderrContent = err.stderr ?? "";
2420
2518
  return {
2421
2519
  success: false,
2422
- error: stderrContent.trim() || message
2520
+ error: stderrContent.trim() || err.message
2423
2521
  };
2424
2522
  }
2425
2523
  return {
@@ -2427,6 +2525,7 @@ var CommandExecutor = class {
2427
2525
  error: String(err)
2428
2526
  };
2429
2527
  }
2528
+ this.decrementInflight(cmdConfig.id);
2430
2529
  const rawOutput = stdout.trim();
2431
2530
  switch (cmdConfig.output_type) {
2432
2531
  case "text":
@@ -2451,6 +2550,48 @@ var CommandExecutor = class {
2451
2550
  };
2452
2551
  }
2453
2552
  }
2553
+ /**
2554
+ * Kill all active child processes. Called during service shutdown
2555
+ * to prevent zombie processes.
2556
+ */
2557
+ shutdown() {
2558
+ for (const child of this.activeProcesses) {
2559
+ try {
2560
+ process.kill(-child.pid, "SIGTERM");
2561
+ } catch {
2562
+ try {
2563
+ child.kill("SIGTERM");
2564
+ } catch {
2565
+ }
2566
+ }
2567
+ const pid = child.pid;
2568
+ const timer = setTimeout(() => {
2569
+ try {
2570
+ process.kill(-pid, "SIGKILL");
2571
+ } catch {
2572
+ }
2573
+ }, KILL_GRACE_MS);
2574
+ timer.unref();
2575
+ }
2576
+ this.activeProcesses.clear();
2577
+ }
2578
+ /** Returns the number of currently active child processes. */
2579
+ get activeCount() {
2580
+ return this.activeProcesses.size;
2581
+ }
2582
+ /** Returns the in-flight count for a specific skill ID. */
2583
+ getInflight(skillId) {
2584
+ return this.inflight.get(skillId) ?? 0;
2585
+ }
2586
+ /** Decrement the inflight counter for a skill ID. */
2587
+ decrementInflight(skillId) {
2588
+ const current = this.inflight.get(skillId) ?? 0;
2589
+ if (current <= 1) {
2590
+ this.inflight.delete(skillId);
2591
+ } else {
2592
+ this.inflight.set(skillId, current - 1);
2593
+ }
2594
+ }
2454
2595
  };
2455
2596
 
2456
2597
  // src/conductor/task-decomposer.ts
@@ -3561,6 +3702,7 @@ import { z as z3 } from "zod";
3561
3702
  import { randomUUID as randomUUID12 } from "crypto";
3562
3703
  var EscrowReceiptSchema = z3.object({
3563
3704
  requester_owner: z3.string().min(1),
3705
+ requester_agent_id: z3.string().optional(),
3564
3706
  requester_public_key: z3.string().min(1),
3565
3707
  amount: z3.number().positive(),
3566
3708
  card_id: z3.string().min(1),
@@ -3573,6 +3715,7 @@ function createSignedEscrowReceipt(db, privateKey, publicKey, opts) {
3573
3715
  const escrowId = holdEscrow(db, opts.owner, opts.amount, opts.cardId);
3574
3716
  const receiptData = {
3575
3717
  requester_owner: opts.owner,
3718
+ ...opts.agent_id ? { requester_agent_id: opts.agent_id } : {},
3576
3719
  requester_public_key: publicKey.toString("hex"),
3577
3720
  amount: opts.amount,
3578
3721
  card_id: opts.cardId,
@@ -4071,11 +4214,22 @@ import { z as z6 } from "zod";
4071
4214
  var RegisterMessageSchema = z6.object({
4072
4215
  type: z6.literal("register"),
4073
4216
  owner: z6.string().min(1),
4217
+ /** V8: Cryptographic agent identity. When present, used as the canonical key. */
4218
+ agent_id: z6.string().optional(),
4219
+ /** V8 Phase 3: Server identifier for multi-agent delegation. */
4220
+ server_id: z6.string().optional(),
4074
4221
  token: z6.string().min(1),
4075
4222
  card: z6.record(z6.unknown()),
4076
4223
  // CapabilityCard (validated separately)
4077
- cards: z6.array(z6.record(z6.unknown())).optional()
4224
+ cards: z6.array(z6.record(z6.unknown())).optional(),
4078
4225
  // Additional cards (e.g., conductor card)
4226
+ /** V8 Phase 3: Additional agents served by this server (multi-agent registration). */
4227
+ agents: z6.array(z6.object({
4228
+ agent_id: z6.string().min(1),
4229
+ display_name: z6.string().min(1),
4230
+ cards: z6.array(z6.record(z6.unknown())),
4231
+ delegation_token: z6.record(z6.unknown()).optional()
4232
+ })).optional()
4079
4233
  });
4080
4234
  var RegisteredMessageSchema = z6.object({
4081
4235
  type: z6.literal("registered"),
@@ -4085,6 +4239,8 @@ var RelayRequestMessageSchema = z6.object({
4085
4239
  type: z6.literal("relay_request"),
4086
4240
  id: z6.string().uuid(),
4087
4241
  target_owner: z6.string().min(1),
4242
+ /** V8: Target agent's cryptographic identity. Preferred over target_owner. */
4243
+ target_agent_id: z6.string().optional(),
4088
4244
  card_id: z6.string(),
4089
4245
  skill_id: z6.string().optional(),
4090
4246
  params: z6.record(z6.unknown()).default({}),
@@ -4155,6 +4311,52 @@ var HeartbeatMessageSchema = z6.object({
4155
4311
  })
4156
4312
  })
4157
4313
  });
4314
+ var EscrowHoldMessageSchema = z6.object({
4315
+ type: z6.literal("escrow_hold"),
4316
+ consumer_agent_id: z6.string().min(1),
4317
+ provider_agent_id: z6.string().min(1),
4318
+ skill_id: z6.string().min(1),
4319
+ amount: z6.number().positive(),
4320
+ request_id: z6.string().uuid(),
4321
+ signature: z6.string().optional(),
4322
+ public_key: z6.string().optional()
4323
+ });
4324
+ var EscrowHoldConfirmedMessageSchema = z6.object({
4325
+ type: z6.literal("escrow_hold_confirmed"),
4326
+ request_id: z6.string(),
4327
+ escrow_id: z6.string(),
4328
+ hold_amount: z6.number(),
4329
+ consumer_remaining: z6.number()
4330
+ });
4331
+ var EscrowSettleMessageSchema = z6.object({
4332
+ type: z6.literal("escrow_settle"),
4333
+ escrow_id: z6.string().min(1),
4334
+ request_id: z6.string().uuid(),
4335
+ success: z6.boolean(),
4336
+ failure_reason: z6.enum(["bad_execution", "overload", "timeout", "auth_error", "not_found"]).optional(),
4337
+ result_hash: z6.string().optional(),
4338
+ signature: z6.string().optional(),
4339
+ public_key: z6.string().optional(),
4340
+ consumer_agent_id: z6.string().optional()
4341
+ });
4342
+ var EscrowSettledMessageSchema = z6.object({
4343
+ type: z6.literal("escrow_settled"),
4344
+ escrow_id: z6.string(),
4345
+ request_id: z6.string(),
4346
+ provider_earned: z6.number(),
4347
+ network_fee: z6.number(),
4348
+ consumer_remaining: z6.number(),
4349
+ provider_balance: z6.number()
4350
+ });
4351
+ var BalanceSyncMessageSchema = z6.object({
4352
+ type: z6.literal("balance_sync"),
4353
+ agent_id: z6.string().min(1)
4354
+ });
4355
+ var BalanceSyncResponseMessageSchema = z6.object({
4356
+ type: z6.literal("balance_sync_response"),
4357
+ agent_id: z6.string(),
4358
+ balance: z6.number()
4359
+ });
4158
4360
  var RelayMessageSchema = z6.discriminatedUnion("type", [
4159
4361
  RegisterMessageSchema,
4160
4362
  RegisteredMessageSchema,
@@ -4164,7 +4366,13 @@ var RelayMessageSchema = z6.discriminatedUnion("type", [
4164
4366
  ResponseMessageSchema,
4165
4367
  ErrorMessageSchema,
4166
4368
  RelayProgressMessageSchema,
4167
- HeartbeatMessageSchema
4369
+ HeartbeatMessageSchema,
4370
+ EscrowHoldMessageSchema,
4371
+ EscrowHoldConfirmedMessageSchema,
4372
+ EscrowSettleMessageSchema,
4373
+ EscrowSettledMessageSchema,
4374
+ BalanceSyncMessageSchema,
4375
+ BalanceSyncResponseMessageSchema
4168
4376
  ]);
4169
4377
 
4170
4378
  // src/relay/websocket-relay.ts
@@ -4225,6 +4433,78 @@ function releaseForRelay(creditDb, escrowId) {
4225
4433
  releaseEscrow(creditDb, escrowId);
4226
4434
  }
4227
4435
 
4436
+ // src/relay/relay-escrow.ts
4437
+ function verifyRelaySignature(data, signature, publicKeyHex) {
4438
+ try {
4439
+ const publicKeyBuf = Buffer.from(publicKeyHex, "hex");
4440
+ return verifyEscrowReceipt(data, signature, publicKeyBuf);
4441
+ } catch {
4442
+ return false;
4443
+ }
4444
+ }
4445
+ function processEscrowHold(creditDb, consumerAgentId, providerAgentId, skillId, amount, requestId, signature, publicKeyHex) {
4446
+ if (signature && publicKeyHex) {
4447
+ const signData = {
4448
+ consumer_agent_id: consumerAgentId,
4449
+ provider_agent_id: providerAgentId,
4450
+ skill_id: skillId,
4451
+ amount,
4452
+ request_id: requestId
4453
+ };
4454
+ if (!verifyRelaySignature(signData, signature, publicKeyHex)) {
4455
+ throw new Error("Invalid consumer signature on escrow hold");
4456
+ }
4457
+ }
4458
+ const escrowId = holdEscrow(creditDb, consumerAgentId, amount, `${providerAgentId}:${skillId}`);
4459
+ const remaining = getBalance(creditDb, consumerAgentId);
4460
+ return {
4461
+ escrow_id: escrowId,
4462
+ hold_amount: amount,
4463
+ consumer_remaining: remaining
4464
+ };
4465
+ }
4466
+ function processEscrowSettle(creditDb, escrowId, success, providerAgentId, signature, publicKeyHex, consumerAgentId) {
4467
+ if (signature && publicKeyHex && consumerAgentId) {
4468
+ const signData = {
4469
+ escrow_id: escrowId,
4470
+ success,
4471
+ consumer_agent_id: consumerAgentId
4472
+ };
4473
+ if (!verifyRelaySignature(signData, signature, publicKeyHex)) {
4474
+ throw new Error("Invalid consumer signature on escrow settle");
4475
+ }
4476
+ }
4477
+ const escrowRow = creditDb.prepare("SELECT amount, owner FROM credit_escrow WHERE id = ? AND status = ?").get(escrowId, "held");
4478
+ if (!escrowRow) {
4479
+ throw new Error(`Escrow not found or already settled: ${escrowId}`);
4480
+ }
4481
+ const NETWORK_FEE_RATE2 = 0.05;
4482
+ if (success) {
4483
+ settleEscrow(creditDb, escrowId, providerAgentId);
4484
+ const networkFee = Math.floor(escrowRow.amount * NETWORK_FEE_RATE2);
4485
+ const providerAmount = escrowRow.amount - networkFee;
4486
+ return {
4487
+ escrow_id: escrowId,
4488
+ provider_earned: providerAmount,
4489
+ network_fee: networkFee,
4490
+ consumer_remaining: getBalance(creditDb, escrowRow.owner),
4491
+ provider_balance: getBalance(creditDb, providerAgentId)
4492
+ };
4493
+ } else {
4494
+ releaseEscrow(creditDb, escrowId);
4495
+ return {
4496
+ escrow_id: escrowId,
4497
+ provider_earned: 0,
4498
+ network_fee: 0,
4499
+ consumer_remaining: getBalance(creditDb, escrowRow.owner),
4500
+ provider_balance: getBalance(creditDb, providerAgentId)
4501
+ };
4502
+ }
4503
+ }
4504
+ function settleWithNetworkFee(creditDb, escrowId, providerOwner) {
4505
+ return processEscrowSettle(creditDb, escrowId, true, providerOwner);
4506
+ }
4507
+
4228
4508
  // src/hub-agent/relay-bridge.ts
4229
4509
  import { randomUUID as randomUUID15 } from "crypto";
4230
4510
 
@@ -4272,10 +4552,17 @@ var RATE_LIMIT_WINDOW_MS = 6e4;
4272
4552
  var RELAY_TIMEOUT_MS = 3e5;
4273
4553
  function registerWebSocketRelay(server, db, creditDb) {
4274
4554
  const connections = /* @__PURE__ */ new Map();
4555
+ const agentIdToOwner = /* @__PURE__ */ new Map();
4275
4556
  const pendingRequests = /* @__PURE__ */ new Map();
4276
4557
  const rateLimits = /* @__PURE__ */ new Map();
4277
4558
  const agentCapacities = /* @__PURE__ */ new Map();
4278
4559
  let onAgentOnlineCallback;
4560
+ function resolveConnectionKey(target) {
4561
+ const ownerFromAgentId = agentIdToOwner.get(target);
4562
+ if (ownerFromAgentId && connections.has(ownerFromAgentId)) return ownerFromAgentId;
4563
+ if (connections.has(target)) return target;
4564
+ return void 0;
4565
+ }
4279
4566
  function checkRateLimit(owner) {
4280
4567
  const now = Date.now();
4281
4568
  const entry = rateLimits.get(owner);
@@ -4379,6 +4666,20 @@ function registerWebSocketRelay(server, db, creditDb) {
4379
4666
  }
4380
4667
  }
4381
4668
  connections.set(owner, ws);
4669
+ if (msg.agent_id) {
4670
+ agentIdToOwner.set(msg.agent_id, owner);
4671
+ }
4672
+ if (msg.agents && msg.agents.length > 0) {
4673
+ for (const agentEntry of msg.agents) {
4674
+ agentIdToOwner.set(agentEntry.agent_id, owner);
4675
+ for (const agentCard of agentEntry.cards) {
4676
+ try {
4677
+ upsertCard(agentCard, owner);
4678
+ } catch {
4679
+ }
4680
+ }
4681
+ }
4682
+ }
4382
4683
  const isEphemeral = owner.includes(":req:");
4383
4684
  if (isEphemeral) {
4384
4685
  const cardId2 = card.id ?? owner;
@@ -4422,12 +4723,13 @@ function registerWebSocketRelay(server, db, creditDb) {
4422
4723
  });
4423
4724
  return;
4424
4725
  }
4425
- const targetWs = connections.get(msg.target_owner);
4726
+ const targetKey = resolveConnectionKey(msg.target_agent_id ?? msg.target_owner);
4727
+ const targetWs = targetKey ? connections.get(targetKey) : void 0;
4426
4728
  if (!targetWs || targetWs.readyState !== 1) {
4427
4729
  sendMessage(ws, {
4428
4730
  type: "response",
4429
4731
  id: msg.id,
4430
- error: { code: -32603, message: `Agent offline: ${msg.target_owner}` }
4732
+ error: { code: -32603, message: `Agent offline: ${msg.target_agent_id ?? msg.target_owner}` }
4431
4733
  });
4432
4734
  return;
4433
4735
  }
@@ -4541,7 +4843,7 @@ function registerWebSocketRelay(server, db, creditDb) {
4541
4843
  if (pending.escrowId && creditDb) {
4542
4844
  try {
4543
4845
  if (msg.error === void 0) {
4544
- settleForRelay(creditDb, pending.escrowId, pending.targetOwner);
4846
+ settleWithNetworkFee(creditDb, pending.escrowId, pending.targetOwner);
4545
4847
  } else {
4546
4848
  releaseForRelay(creditDb, pending.escrowId);
4547
4849
  }
@@ -4579,6 +4881,12 @@ function registerWebSocketRelay(server, db, creditDb) {
4579
4881
  connections.delete(owner);
4580
4882
  rateLimits.delete(owner);
4581
4883
  agentCapacities.delete(owner);
4884
+ for (const [agentId, o] of agentIdToOwner) {
4885
+ if (o === owner) {
4886
+ agentIdToOwner.delete(agentId);
4887
+ break;
4888
+ }
4889
+ }
4582
4890
  markOwnerOffline(owner);
4583
4891
  for (const [reqId, pending] of pendingRequests) {
4584
4892
  if (pending.targetOwner === owner) {
@@ -4615,6 +4923,96 @@ function registerWebSocketRelay(server, db, creditDb) {
4615
4923
  function handleHeartbeat(msg) {
4616
4924
  agentCapacities.set(msg.owner, msg.capacity);
4617
4925
  }
4926
+ function handleEscrowHold(ws, msg) {
4927
+ if (!creditDb) {
4928
+ sendMessage(ws, { type: "error", code: "no_credit_db", message: "Credit system not available" });
4929
+ return;
4930
+ }
4931
+ try {
4932
+ const result = processEscrowHold(
4933
+ creditDb,
4934
+ msg.consumer_agent_id,
4935
+ msg.provider_agent_id,
4936
+ msg.skill_id,
4937
+ msg.amount,
4938
+ msg.request_id,
4939
+ msg.signature,
4940
+ msg.public_key
4941
+ );
4942
+ sendMessage(ws, {
4943
+ type: "escrow_hold_confirmed",
4944
+ request_id: msg.request_id,
4945
+ escrow_id: result.escrow_id,
4946
+ hold_amount: result.hold_amount,
4947
+ consumer_remaining: result.consumer_remaining
4948
+ });
4949
+ } catch (err) {
4950
+ const errMsg = err instanceof Error ? err.message : "Escrow hold failed";
4951
+ const code = errMsg.includes("INSUFFICIENT_CREDITS") ? "insufficient_credits" : "escrow_hold_failed";
4952
+ sendMessage(ws, { type: "error", code, message: errMsg, request_id: msg.request_id });
4953
+ }
4954
+ }
4955
+ function handleEscrowSettle(ws, msg) {
4956
+ if (!creditDb) {
4957
+ sendMessage(ws, { type: "error", code: "no_credit_db", message: "Credit system not available" });
4958
+ return;
4959
+ }
4960
+ try {
4961
+ const escrow = creditDb.prepare("SELECT card_id FROM credit_escrow WHERE id = ? AND status = ?").get(msg.escrow_id, "held");
4962
+ if (!escrow) {
4963
+ sendMessage(ws, { type: "error", code: "escrow_not_found", message: `Escrow not found: ${msg.escrow_id}`, request_id: msg.request_id });
4964
+ return;
4965
+ }
4966
+ const providerAgentId = escrow.card_id.split(":")[0];
4967
+ const result = processEscrowSettle(
4968
+ creditDb,
4969
+ msg.escrow_id,
4970
+ msg.success,
4971
+ providerAgentId,
4972
+ msg.signature,
4973
+ msg.public_key,
4974
+ msg.consumer_agent_id
4975
+ );
4976
+ sendMessage(ws, {
4977
+ type: "escrow_settled",
4978
+ escrow_id: result.escrow_id,
4979
+ request_id: msg.request_id,
4980
+ provider_earned: result.provider_earned,
4981
+ network_fee: result.network_fee,
4982
+ consumer_remaining: result.consumer_remaining,
4983
+ provider_balance: result.provider_balance
4984
+ });
4985
+ const providerKey = resolveConnectionKey(providerAgentId);
4986
+ if (providerKey) {
4987
+ const providerWs = connections.get(providerKey);
4988
+ if (providerWs && providerWs.readyState === 1) {
4989
+ sendMessage(providerWs, {
4990
+ type: "escrow_settled",
4991
+ escrow_id: result.escrow_id,
4992
+ request_id: msg.request_id,
4993
+ provider_earned: result.provider_earned,
4994
+ network_fee: result.network_fee,
4995
+ consumer_remaining: result.consumer_remaining,
4996
+ provider_balance: result.provider_balance
4997
+ });
4998
+ }
4999
+ }
5000
+ } catch (err) {
5001
+ sendMessage(ws, { type: "error", code: "escrow_settle_failed", message: err instanceof Error ? err.message : "Settlement failed", request_id: msg.request_id });
5002
+ }
5003
+ }
5004
+ function handleBalanceSync(ws, msg) {
5005
+ if (!creditDb) {
5006
+ sendMessage(ws, { type: "error", code: "no_credit_db", message: "Credit system not available" });
5007
+ return;
5008
+ }
5009
+ const balance = getBalance(creditDb, msg.agent_id);
5010
+ sendMessage(ws, {
5011
+ type: "balance_sync_response",
5012
+ agent_id: msg.agent_id,
5013
+ balance
5014
+ });
5015
+ }
4618
5016
  void server.register(async (app) => {
4619
5017
  app.get("/ws", { websocket: true }, (rawSocket, _request) => {
4620
5018
  const socket = rawSocket;
@@ -4663,6 +5061,16 @@ function registerWebSocketRelay(server, db, creditDb) {
4663
5061
  case "heartbeat":
4664
5062
  handleHeartbeat(msg);
4665
5063
  break;
5064
+ // V8 Phase 2: Explicit escrow messages
5065
+ case "escrow_hold":
5066
+ handleEscrowHold(socket, msg);
5067
+ break;
5068
+ case "escrow_settle":
5069
+ handleEscrowSettle(socket, msg);
5070
+ break;
5071
+ case "balance_sync":
5072
+ handleBalanceSync(socket, msg);
5073
+ break;
4666
5074
  default:
4667
5075
  break;
4668
5076
  }
@@ -4745,9 +5153,12 @@ var RelayClient = class {
4745
5153
  this.send({
4746
5154
  type: "register",
4747
5155
  owner: this.opts.owner,
5156
+ ...this.opts.agent_id ? { agent_id: this.opts.agent_id } : {},
5157
+ ...this.opts.server_id ? { server_id: this.opts.server_id } : {},
4748
5158
  token: this.opts.token,
4749
5159
  card: this.opts.card,
4750
- ...this.opts.cards && this.opts.cards.length > 0 ? { cards: this.opts.cards } : {}
5160
+ ...this.opts.cards && this.opts.cards.length > 0 ? { cards: this.opts.cards } : {},
5161
+ ...this.opts.agents && this.opts.agents.length > 0 ? { agents: this.opts.agents } : {}
4751
5162
  });
4752
5163
  });
4753
5164
  this.ws.on("message", (raw) => {
@@ -4823,6 +5234,7 @@ var RelayClient = class {
4823
5234
  type: "relay_request",
4824
5235
  id,
4825
5236
  target_owner: opts.targetOwner,
5237
+ ...opts.targetAgentId ? { target_agent_id: opts.targetAgentId } : {},
4826
5238
  card_id: opts.cardId,
4827
5239
  skill_id: opts.skillId,
4828
5240
  params: opts.params,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  AgentBnBError
3
- } from "./chunk-3CIMVISQ.js";
3
+ } from "./chunk-WVY2W7AA.js";
4
4
 
5
5
  // src/runtime/process-guard.ts
6
6
  import { dirname, join } from "path";