agentbnb 2.2.0 → 3.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.
package/dist/cli/index.js CHANGED
@@ -1,168 +1,69 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ getActivityFeed,
4
+ getCard,
5
+ getRequestLog,
6
+ getSkillRequestCount,
7
+ insertCard,
8
+ insertRequestLog,
9
+ listCards,
10
+ openDatabase,
11
+ updateCard,
12
+ updateReputation,
13
+ updateSkillAvailability,
14
+ updateSkillIdleRate
15
+ } from "../chunk-PJSYSVKN.js";
16
+ import {
17
+ AutoRequestor,
18
+ BudgetManager,
19
+ DEFAULT_AUTONOMY_CONFIG,
20
+ DEFAULT_BUDGET_CONFIG,
21
+ bootstrapAgent,
22
+ confirmEscrowDebit,
23
+ filterCards,
24
+ getAutonomyTier,
25
+ getBalance,
26
+ getTransactions,
27
+ holdEscrow,
28
+ insertAuditEvent,
29
+ interpolateObject,
30
+ listPendingRequests,
31
+ openCreditDb,
32
+ recordEarning,
33
+ releaseEscrow,
34
+ requestCapability,
35
+ resolvePendingRequest,
36
+ searchCards,
37
+ settleEscrow
38
+ } from "../chunk-V7M6GIJZ.js";
39
+ import {
40
+ findPeer,
41
+ getConfigDir,
42
+ loadConfig,
43
+ loadPeers,
44
+ removePeer,
45
+ saveConfig,
46
+ savePeer
47
+ } from "../chunk-BEI5MTNZ.js";
48
+ import {
49
+ AgentBnBError,
50
+ CapabilityCardSchema,
51
+ CapabilityCardV2Schema
52
+ } from "../chunk-TQMI73LL.js";
2
53
 
3
54
  // src/cli/index.ts
4
55
  import { Command } from "commander";
5
- import { readFileSync as readFileSync6 } from "fs";
56
+ import { readFileSync as readFileSync4 } from "fs";
6
57
  import { createRequire } from "module";
7
58
  import { randomBytes } from "crypto";
8
- import { join as join5 } from "path";
9
- import { networkInterfaces, homedir as homedir2 } from "os";
59
+ import { join as join3 } from "path";
60
+ import { networkInterfaces, homedir } from "os";
10
61
  import { createInterface } from "readline";
11
62
 
12
- // src/cli/config.ts
13
- import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
14
- import { homedir } from "os";
15
- import { join } from "path";
16
- function getConfigDir() {
17
- return process.env["AGENTBNB_DIR"] ?? join(homedir(), ".agentbnb");
18
- }
19
- function getConfigPath() {
20
- return join(getConfigDir(), "config.json");
21
- }
22
- function loadConfig() {
23
- const configPath = getConfigPath();
24
- if (!existsSync(configPath)) return null;
25
- try {
26
- const raw = readFileSync(configPath, "utf-8");
27
- return JSON.parse(raw);
28
- } catch {
29
- return null;
30
- }
31
- }
32
- function saveConfig(config) {
33
- const dir = getConfigDir();
34
- if (!existsSync(dir)) {
35
- mkdirSync(dir, { recursive: true });
36
- }
37
- writeFileSync(getConfigPath(), JSON.stringify(config, null, 2), "utf-8");
38
- }
39
-
40
63
  // src/credit/signing.ts
41
64
  import { generateKeyPairSync, sign, verify, createPublicKey, createPrivateKey } from "crypto";
42
- import { writeFileSync as writeFileSync2, readFileSync as readFileSync2, existsSync as existsSync2, chmodSync } from "fs";
43
- import { join as join2 } from "path";
44
-
45
- // src/types/index.ts
46
- import { z } from "zod";
47
- var IOSchemaSchema = z.object({
48
- name: z.string(),
49
- type: z.enum(["text", "json", "file", "audio", "image", "video", "stream"]),
50
- description: z.string().optional(),
51
- required: z.boolean().default(true),
52
- schema: z.record(z.unknown()).optional()
53
- // JSON Schema
54
- });
55
- var PoweredBySchema = z.object({
56
- provider: z.string().min(1),
57
- model: z.string().optional(),
58
- tier: z.string().optional()
59
- });
60
- var CapabilityCardSchema = z.object({
61
- spec_version: z.literal("1.0").default("1.0"),
62
- id: z.string().uuid(),
63
- owner: z.string().min(1),
64
- name: z.string().min(1).max(100),
65
- description: z.string().max(500),
66
- level: z.union([z.literal(1), z.literal(2), z.literal(3)]),
67
- inputs: z.array(IOSchemaSchema),
68
- outputs: z.array(IOSchemaSchema),
69
- pricing: z.object({
70
- credits_per_call: z.number().nonnegative(),
71
- credits_per_minute: z.number().nonnegative().optional(),
72
- /** Number of free monthly calls. Shown as a "N free/mo" badge in the Hub. */
73
- free_tier: z.number().nonnegative().optional()
74
- }),
75
- availability: z.object({
76
- online: z.boolean(),
77
- schedule: z.string().optional()
78
- // cron expression
79
- }),
80
- powered_by: z.array(PoweredBySchema).optional(),
81
- /**
82
- * Private per-card metadata. Stripped from all API and CLI responses —
83
- * never transmitted beyond the local store.
84
- */
85
- _internal: z.record(z.unknown()).optional(),
86
- metadata: z.object({
87
- apis_used: z.array(z.string()).optional(),
88
- avg_latency_ms: z.number().nonnegative().optional(),
89
- success_rate: z.number().min(0).max(1).optional(),
90
- tags: z.array(z.string()).optional()
91
- }).optional(),
92
- created_at: z.string().datetime().optional(),
93
- updated_at: z.string().datetime().optional()
94
- });
95
- var SkillSchema = z.object({
96
- /** Stable skill identifier, e.g. 'tts-elevenlabs'. Used for gateway routing and idle tracking. */
97
- id: z.string().min(1),
98
- name: z.string().min(1).max(100),
99
- description: z.string().max(500),
100
- level: z.union([z.literal(1), z.literal(2), z.literal(3)]),
101
- /** Optional grouping category, e.g. 'tts' | 'video_gen' | 'code_review'. */
102
- category: z.string().optional(),
103
- inputs: z.array(IOSchemaSchema),
104
- outputs: z.array(IOSchemaSchema),
105
- pricing: z.object({
106
- credits_per_call: z.number().nonnegative(),
107
- credits_per_minute: z.number().nonnegative().optional(),
108
- free_tier: z.number().nonnegative().optional()
109
- }),
110
- /** Per-skill online flag — overrides card-level availability for this skill. */
111
- availability: z.object({ online: z.boolean() }).optional(),
112
- powered_by: z.array(PoweredBySchema).optional(),
113
- metadata: z.object({
114
- apis_used: z.array(z.string()).optional(),
115
- avg_latency_ms: z.number().nonnegative().optional(),
116
- success_rate: z.number().min(0).max(1).optional(),
117
- tags: z.array(z.string()).optional(),
118
- capacity: z.object({
119
- calls_per_hour: z.number().positive().default(60)
120
- }).optional()
121
- }).optional(),
122
- /**
123
- * Private per-skill metadata. Stripped from all API and CLI responses —
124
- * never transmitted beyond the local store.
125
- */
126
- _internal: z.record(z.unknown()).optional()
127
- });
128
- var CapabilityCardV2Schema = z.object({
129
- spec_version: z.literal("2.0"),
130
- id: z.string().uuid(),
131
- owner: z.string().min(1),
132
- /** Agent display name — was 'name' in v1.0. */
133
- agent_name: z.string().min(1).max(100),
134
- /** At least one skill is required. */
135
- skills: z.array(SkillSchema).min(1),
136
- availability: z.object({
137
- online: z.boolean(),
138
- schedule: z.string().optional()
139
- }),
140
- /** Optional deployment environment metadata. */
141
- environment: z.object({
142
- runtime: z.string(),
143
- region: z.string().optional()
144
- }).optional(),
145
- /**
146
- * Private per-card metadata. Stripped from all API and CLI responses —
147
- * never transmitted beyond the local store.
148
- */
149
- _internal: z.record(z.unknown()).optional(),
150
- created_at: z.string().datetime().optional(),
151
- updated_at: z.string().datetime().optional()
152
- });
153
- var AnyCardSchema = z.discriminatedUnion("spec_version", [
154
- CapabilityCardSchema,
155
- CapabilityCardV2Schema
156
- ]);
157
- var AgentBnBError = class extends Error {
158
- constructor(message, code) {
159
- super(message);
160
- this.code = code;
161
- this.name = "AgentBnBError";
162
- }
163
- };
164
-
165
- // src/credit/signing.ts
65
+ import { writeFileSync, readFileSync, existsSync, chmodSync } from "fs";
66
+ import { join } from "path";
166
67
  function generateKeyPair() {
167
68
  const { publicKey, privateKey } = generateKeyPairSync("ed25519", {
168
69
  publicKeyEncoding: { type: "spki", format: "der" },
@@ -174,536 +75,95 @@ function generateKeyPair() {
174
75
  };
175
76
  }
176
77
  function saveKeyPair(configDir, keys) {
177
- const privatePath = join2(configDir, "private.key");
178
- const publicPath = join2(configDir, "public.key");
179
- writeFileSync2(privatePath, keys.privateKey);
78
+ const privatePath = join(configDir, "private.key");
79
+ const publicPath = join(configDir, "public.key");
80
+ writeFileSync(privatePath, keys.privateKey);
180
81
  chmodSync(privatePath, 384);
181
- writeFileSync2(publicPath, keys.publicKey);
82
+ writeFileSync(publicPath, keys.publicKey);
182
83
  }
183
84
  function loadKeyPair(configDir) {
184
- const privatePath = join2(configDir, "private.key");
185
- const publicPath = join2(configDir, "public.key");
186
- if (!existsSync2(privatePath) || !existsSync2(publicPath)) {
85
+ const privatePath = join(configDir, "private.key");
86
+ const publicPath = join(configDir, "public.key");
87
+ if (!existsSync(privatePath) || !existsSync(publicPath)) {
187
88
  throw new AgentBnBError("Keypair not found. Run `agentbnb init` to generate one.", "KEYPAIR_NOT_FOUND");
188
89
  }
189
90
  return {
190
- publicKey: readFileSync2(publicPath),
191
- privateKey: readFileSync2(privatePath)
91
+ publicKey: readFileSync(publicPath),
92
+ privateKey: readFileSync(privatePath)
192
93
  };
193
94
  }
194
-
195
- // src/autonomy/tiers.ts
196
- import { randomUUID } from "crypto";
197
- var DEFAULT_AUTONOMY_CONFIG = {
198
- tier1_max_credits: 0,
199
- tier2_max_credits: 0
200
- };
201
- function getAutonomyTier(creditAmount, config) {
202
- if (creditAmount < config.tier1_max_credits) return 1;
203
- if (creditAmount < config.tier2_max_credits) return 2;
204
- return 3;
95
+ function canonicalJson(data) {
96
+ return JSON.stringify(data, Object.keys(data).sort());
205
97
  }
206
- function insertAuditEvent(db, event) {
207
- const isShareEvent = event.type === "auto_share" || event.type === "auto_share_notify" || event.type === "auto_share_pending";
208
- const cardId = isShareEvent ? "system" : event.card_id;
209
- const creditsCharged = isShareEvent ? 0 : event.credits;
210
- const stmt = db.prepare(`
211
- INSERT INTO request_log (
212
- id, card_id, card_name, requester, status, latency_ms, credits_charged,
213
- created_at, skill_id, action_type, tier_invoked
214
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
215
- `);
216
- stmt.run(
217
- randomUUID(),
218
- cardId,
219
- "autonomy-audit",
220
- "self",
221
- "success",
222
- 0,
223
- creditsCharged,
224
- (/* @__PURE__ */ new Date()).toISOString(),
225
- event.skill_id,
226
- event.type,
227
- event.tier_invoked
228
- );
98
+ function signEscrowReceipt(data, privateKey) {
99
+ const message = Buffer.from(canonicalJson(data), "utf-8");
100
+ const keyObject = createPrivateKey({ key: privateKey, format: "der", type: "pkcs8" });
101
+ const signature = sign(null, message, keyObject);
102
+ return signature.toString("base64url");
229
103
  }
230
-
231
- // src/autonomy/idle-monitor.ts
232
- import { Cron } from "croner";
233
-
234
- // src/registry/store.ts
235
- import Database from "better-sqlite3";
236
-
237
- // src/registry/request-log.ts
238
- var SINCE_MS = {
239
- "24h": 864e5,
240
- "7d": 6048e5,
241
- "30d": 2592e6
242
- };
243
- function createRequestLogTable(db) {
244
- db.exec(`
245
- CREATE TABLE IF NOT EXISTS request_log (
246
- id TEXT PRIMARY KEY,
247
- card_id TEXT NOT NULL,
248
- card_name TEXT NOT NULL,
249
- requester TEXT NOT NULL,
250
- status TEXT NOT NULL CHECK(status IN ('success', 'failure', 'timeout')),
251
- latency_ms INTEGER NOT NULL,
252
- credits_charged INTEGER NOT NULL,
253
- created_at TEXT NOT NULL
254
- );
255
-
256
- CREATE INDEX IF NOT EXISTS request_log_created_at_idx
257
- ON request_log (created_at DESC);
258
- `);
104
+ function verifyEscrowReceipt(data, signature, publicKey) {
259
105
  try {
260
- db.exec("ALTER TABLE request_log ADD COLUMN skill_id TEXT");
261
- } catch {
262
- }
263
- try {
264
- db.exec("ALTER TABLE request_log ADD COLUMN action_type TEXT");
265
- } catch {
266
- }
267
- try {
268
- db.exec("ALTER TABLE request_log ADD COLUMN tier_invoked INTEGER");
106
+ const message = Buffer.from(canonicalJson(data), "utf-8");
107
+ const keyObject = createPublicKey({ key: publicKey, format: "der", type: "spki" });
108
+ const sigBuffer = Buffer.from(signature, "base64url");
109
+ return verify(null, message, keyObject, sigBuffer);
269
110
  } catch {
111
+ return false;
270
112
  }
271
113
  }
272
- function insertRequestLog(db, entry) {
273
- const stmt = db.prepare(`
274
- INSERT INTO request_log (id, card_id, card_name, requester, status, latency_ms, credits_charged, created_at, skill_id, action_type, tier_invoked)
275
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
276
- `);
277
- stmt.run(
278
- entry.id,
279
- entry.card_id,
280
- entry.card_name,
281
- entry.requester,
282
- entry.status,
283
- entry.latency_ms,
284
- entry.credits_charged,
285
- entry.created_at,
286
- entry.skill_id ?? null,
287
- entry.action_type ?? null,
288
- entry.tier_invoked ?? null
289
- );
290
- }
291
- function getSkillRequestCount(db, skillId, windowMs) {
292
- const cutoff = new Date(Date.now() - windowMs).toISOString();
293
- const stmt = db.prepare(
294
- `SELECT COUNT(*) as cnt FROM request_log
295
- WHERE skill_id = ? AND created_at >= ? AND status = 'success' AND action_type IS NULL`
296
- );
297
- const row = stmt.get(skillId, cutoff);
298
- return row.cnt;
299
- }
300
- function getActivityFeed(db, limit = 20, since) {
301
- const effectiveLimit = Math.min(limit, 100);
302
- if (since !== void 0) {
303
- const stmt2 = db.prepare(`
304
- SELECT r.id, r.card_name, r.requester, c.owner AS provider,
305
- r.status, r.credits_charged, r.latency_ms, r.created_at, r.action_type
306
- FROM request_log r
307
- LEFT JOIN capability_cards c ON r.card_id = c.id
308
- WHERE (r.action_type IS NULL OR r.action_type = 'auto_share')
309
- AND r.created_at > ?
310
- ORDER BY r.created_at DESC
311
- LIMIT ?
312
- `);
313
- return stmt2.all(since, effectiveLimit);
314
- }
315
- const stmt = db.prepare(`
316
- SELECT r.id, r.card_name, r.requester, c.owner AS provider,
317
- r.status, r.credits_charged, r.latency_ms, r.created_at, r.action_type
318
- FROM request_log r
319
- LEFT JOIN capability_cards c ON r.card_id = c.id
320
- WHERE (r.action_type IS NULL OR r.action_type = 'auto_share')
321
- ORDER BY r.created_at DESC
322
- LIMIT ?
323
- `);
324
- return stmt.all(effectiveLimit);
325
- }
326
- function getRequestLog(db, limit = 10, since) {
327
- if (since !== void 0) {
328
- const cutoff = new Date(Date.now() - SINCE_MS[since]).toISOString();
329
- const stmt2 = db.prepare(`
330
- SELECT id, card_id, card_name, requester, status, latency_ms, credits_charged, created_at, skill_id, action_type, tier_invoked
331
- FROM request_log
332
- WHERE created_at >= ?
333
- ORDER BY created_at DESC
334
- LIMIT ?
335
- `);
336
- return stmt2.all(cutoff, limit);
337
- }
338
- const stmt = db.prepare(`
339
- SELECT id, card_id, card_name, requester, status, latency_ms, credits_charged, created_at, skill_id, action_type, tier_invoked
340
- FROM request_log
341
- ORDER BY created_at DESC
342
- LIMIT ?
343
- `);
344
- return stmt.all(limit);
345
- }
346
114
 
347
- // src/registry/store.ts
348
- var V2_FTS_TRIGGERS = `
349
- DROP TRIGGER IF EXISTS cards_ai;
350
- DROP TRIGGER IF EXISTS cards_au;
351
- DROP TRIGGER IF EXISTS cards_ad;
352
-
353
- CREATE TRIGGER cards_ai AFTER INSERT ON capability_cards BEGIN
354
- INSERT INTO cards_fts(rowid, id, owner, name, description, tags)
355
- VALUES (
356
- new.rowid,
357
- new.id,
358
- new.owner,
359
- COALESCE(
360
- (SELECT group_concat(json_extract(value, '$.name'), ' ')
361
- FROM json_each(json_extract(new.data, '$.skills'))),
362
- json_extract(new.data, '$.name'),
363
- ''
364
- ),
365
- COALESCE(
366
- (SELECT group_concat(json_extract(value, '$.description'), ' ')
367
- FROM json_each(json_extract(new.data, '$.skills'))),
368
- json_extract(new.data, '$.description'),
369
- ''
370
- ),
371
- COALESCE(
372
- (SELECT group_concat(json_extract(value, '$.metadata.tags'), ' ')
373
- FROM json_each(json_extract(new.data, '$.skills'))),
374
- (SELECT group_concat(value, ' ')
375
- FROM json_each(json_extract(new.data, '$.metadata.tags'))),
376
- ''
377
- )
378
- );
379
- END;
380
-
381
- CREATE TRIGGER cards_au AFTER UPDATE ON capability_cards BEGIN
382
- INSERT INTO cards_fts(cards_fts, rowid, id, owner, name, description, tags)
383
- VALUES (
384
- 'delete',
385
- old.rowid,
386
- old.id,
387
- old.owner,
388
- COALESCE(
389
- (SELECT group_concat(json_extract(value, '$.name'), ' ')
390
- FROM json_each(json_extract(old.data, '$.skills'))),
391
- json_extract(old.data, '$.name'),
392
- ''
393
- ),
394
- COALESCE(
395
- (SELECT group_concat(json_extract(value, '$.description'), ' ')
396
- FROM json_each(json_extract(old.data, '$.skills'))),
397
- json_extract(old.data, '$.description'),
398
- ''
399
- ),
400
- COALESCE(
401
- (SELECT group_concat(json_extract(value, '$.metadata.tags'), ' ')
402
- FROM json_each(json_extract(old.data, '$.skills'))),
403
- (SELECT group_concat(value, ' ')
404
- FROM json_each(json_extract(old.data, '$.metadata.tags'))),
405
- ''
406
- )
407
- );
408
- INSERT INTO cards_fts(rowid, id, owner, name, description, tags)
409
- VALUES (
410
- new.rowid,
411
- new.id,
412
- new.owner,
413
- COALESCE(
414
- (SELECT group_concat(json_extract(value, '$.name'), ' ')
415
- FROM json_each(json_extract(new.data, '$.skills'))),
416
- json_extract(new.data, '$.name'),
417
- ''
418
- ),
419
- COALESCE(
420
- (SELECT group_concat(json_extract(value, '$.description'), ' ')
421
- FROM json_each(json_extract(new.data, '$.skills'))),
422
- json_extract(new.data, '$.description'),
423
- ''
424
- ),
425
- COALESCE(
426
- (SELECT group_concat(json_extract(value, '$.metadata.tags'), ' ')
427
- FROM json_each(json_extract(new.data, '$.skills'))),
428
- (SELECT group_concat(value, ' ')
429
- FROM json_each(json_extract(new.data, '$.metadata.tags'))),
430
- ''
431
- )
432
- );
433
- END;
434
-
435
- CREATE TRIGGER cards_ad AFTER DELETE ON capability_cards BEGIN
436
- INSERT INTO cards_fts(cards_fts, rowid, id, owner, name, description, tags)
437
- VALUES (
438
- 'delete',
439
- old.rowid,
440
- old.id,
441
- old.owner,
442
- COALESCE(
443
- (SELECT group_concat(json_extract(value, '$.name'), ' ')
444
- FROM json_each(json_extract(old.data, '$.skills'))),
445
- json_extract(old.data, '$.name'),
446
- ''
447
- ),
448
- COALESCE(
449
- (SELECT group_concat(json_extract(value, '$.description'), ' ')
450
- FROM json_each(json_extract(old.data, '$.skills'))),
451
- json_extract(old.data, '$.description'),
452
- ''
453
- ),
454
- COALESCE(
455
- (SELECT group_concat(json_extract(value, '$.metadata.tags'), ' ')
456
- FROM json_each(json_extract(old.data, '$.skills'))),
457
- (SELECT group_concat(value, ' ')
458
- FROM json_each(json_extract(old.data, '$.metadata.tags'))),
459
- ''
460
- )
461
- );
462
- END;
463
- `;
464
- function openDatabase(path = ":memory:") {
465
- const db = new Database(path);
466
- db.pragma("journal_mode = WAL");
467
- db.pragma("foreign_keys = ON");
468
- db.exec(`
469
- CREATE TABLE IF NOT EXISTS capability_cards (
470
- id TEXT PRIMARY KEY,
471
- owner TEXT NOT NULL,
472
- data TEXT NOT NULL,
473
- created_at TEXT NOT NULL,
474
- updated_at TEXT NOT NULL
475
- );
476
-
477
- CREATE TABLE IF NOT EXISTS pending_requests (
478
- id TEXT PRIMARY KEY,
479
- skill_query TEXT NOT NULL,
480
- max_cost_credits REAL NOT NULL,
481
- selected_peer TEXT,
482
- selected_card_id TEXT,
483
- selected_skill_id TEXT,
484
- credits REAL NOT NULL,
485
- status TEXT NOT NULL DEFAULT 'pending',
486
- params TEXT,
487
- created_at TEXT NOT NULL,
488
- resolved_at TEXT
489
- );
490
-
491
- CREATE VIRTUAL TABLE IF NOT EXISTS cards_fts USING fts5(
492
- id UNINDEXED,
493
- owner,
494
- name,
495
- description,
496
- tags,
497
- content=""
498
- );
499
- `);
500
- createRequestLogTable(db);
501
- runMigrations(db);
502
- return db;
503
- }
504
- function runMigrations(db) {
505
- const version = db.pragma("user_version")[0]?.user_version ?? 0;
506
- if (version < 2) {
507
- migrateV1toV2(db);
508
- }
509
- }
510
- function migrateV1toV2(db) {
511
- const migrate = db.transaction(() => {
512
- const rows = db.prepare("SELECT rowid, id, data FROM capability_cards").all();
513
- const now = (/* @__PURE__ */ new Date()).toISOString();
514
- for (const row of rows) {
515
- const parsed = JSON.parse(row.data);
516
- if (parsed["spec_version"] === "2.0") continue;
517
- const v1 = parsed;
518
- const v2 = {
519
- spec_version: "2.0",
520
- id: v1.id,
521
- owner: v1.owner,
522
- agent_name: v1.name,
523
- skills: [
524
- {
525
- id: `skill-${v1.id}`,
526
- name: v1.name,
527
- description: v1.description,
528
- level: v1.level,
529
- inputs: v1.inputs,
530
- outputs: v1.outputs,
531
- pricing: v1.pricing,
532
- availability: { online: v1.availability.online },
533
- powered_by: v1.powered_by,
534
- metadata: v1.metadata,
535
- _internal: v1._internal
536
- }
537
- ],
538
- availability: v1.availability,
539
- created_at: v1.created_at,
540
- updated_at: now
541
- };
542
- db.prepare("UPDATE capability_cards SET data = ?, updated_at = ? WHERE id = ?").run(
543
- JSON.stringify(v2),
544
- now,
545
- v2.id
546
- );
547
- }
548
- db.exec(V2_FTS_TRIGGERS);
549
- db.exec(`INSERT INTO cards_fts(cards_fts) VALUES('delete-all')`);
550
- const allRows = db.prepare("SELECT rowid, id, owner, data FROM capability_cards").all();
551
- const ftsInsert = db.prepare(
552
- "INSERT INTO cards_fts(rowid, id, owner, name, description, tags) VALUES (?, ?, ?, ?, ?, ?)"
553
- );
554
- for (const row of allRows) {
555
- const data = JSON.parse(row.data);
556
- const skills = data["skills"] ?? [];
557
- let name;
558
- let description;
559
- let tags;
560
- if (skills.length > 0) {
561
- name = skills.map((s) => String(s["name"] ?? "")).join(" ");
562
- description = skills.map((s) => String(s["description"] ?? "")).join(" ");
563
- tags = skills.flatMap((s) => {
564
- const meta = s["metadata"];
565
- return meta?.["tags"] ?? [];
566
- }).join(" ");
567
- } else {
568
- name = String(data["name"] ?? "");
569
- description = String(data["description"] ?? "");
570
- const meta = data["metadata"];
571
- const rawTags = meta?.["tags"] ?? [];
572
- tags = rawTags.join(" ");
573
- }
574
- ftsInsert.run(row.rowid, row.id, row.owner, name, description, tags);
575
- }
576
- db.pragma("user_version = 2");
577
- });
578
- migrate();
579
- }
580
- function insertCard(db, card) {
581
- const now = (/* @__PURE__ */ new Date()).toISOString();
582
- const withTimestamps = { ...card, created_at: card.created_at ?? now, updated_at: now };
583
- const parsed = CapabilityCardSchema.safeParse(withTimestamps);
584
- if (!parsed.success) {
585
- throw new AgentBnBError(
586
- `Card validation failed: ${parsed.error.message}`,
587
- "VALIDATION_ERROR"
588
- );
589
- }
590
- const stmt = db.prepare(`
591
- INSERT INTO capability_cards (id, owner, data, created_at, updated_at)
592
- VALUES (?, ?, ?, ?, ?)
593
- `);
594
- stmt.run(
595
- parsed.data.id,
596
- parsed.data.owner,
597
- JSON.stringify(parsed.data),
598
- parsed.data.created_at ?? now,
599
- parsed.data.updated_at ?? now
600
- );
601
- }
602
- function getCard(db, id) {
603
- const stmt = db.prepare("SELECT data FROM capability_cards WHERE id = ?");
604
- const row = stmt.get(id);
605
- if (!row) return null;
606
- return JSON.parse(row.data);
607
- }
608
- function updateCard(db, id, owner, updates) {
609
- const existing = getCard(db, id);
610
- if (!existing) {
611
- throw new AgentBnBError(`Card not found: ${id}`, "NOT_FOUND");
612
- }
613
- if (existing.owner !== owner) {
614
- throw new AgentBnBError("Forbidden: you do not own this card", "FORBIDDEN");
615
- }
616
- const now = (/* @__PURE__ */ new Date()).toISOString();
617
- const merged = { ...existing, ...updates, updated_at: now };
618
- const parsed = CapabilityCardSchema.safeParse(merged);
619
- if (!parsed.success) {
620
- throw new AgentBnBError(
621
- `Card validation failed: ${parsed.error.message}`,
622
- "VALIDATION_ERROR"
623
- );
624
- }
625
- const stmt = db.prepare(`
626
- UPDATE capability_cards
627
- SET data = ?, updated_at = ?
628
- WHERE id = ?
629
- `);
630
- stmt.run(JSON.stringify(parsed.data), now, id);
631
- }
632
- function updateReputation(db, cardId, success, latencyMs) {
633
- const existing = getCard(db, cardId);
634
- if (!existing) return;
635
- const ALPHA = 0.1;
636
- const observed = success ? 1 : 0;
637
- const prevSuccessRate = existing.metadata?.success_rate;
638
- const prevLatency = existing.metadata?.avg_latency_ms;
639
- const newSuccessRate = prevSuccessRate === void 0 ? observed : ALPHA * observed + (1 - ALPHA) * prevSuccessRate;
640
- const newLatency = prevLatency === void 0 ? latencyMs : ALPHA * latencyMs + (1 - ALPHA) * prevLatency;
641
- const now = (/* @__PURE__ */ new Date()).toISOString();
642
- const updatedMetadata = {
643
- ...existing.metadata,
644
- success_rate: Math.round(newSuccessRate * 1e3) / 1e3,
645
- avg_latency_ms: Math.round(newLatency)
115
+ // src/credit/escrow-receipt.ts
116
+ import { z } from "zod";
117
+ import { randomUUID } from "crypto";
118
+ var EscrowReceiptSchema = z.object({
119
+ requester_owner: z.string().min(1),
120
+ requester_public_key: z.string().min(1),
121
+ amount: z.number().positive(),
122
+ card_id: z.string().min(1),
123
+ skill_id: z.string().optional(),
124
+ timestamp: z.string(),
125
+ nonce: z.string().uuid(),
126
+ signature: z.string().min(1)
127
+ });
128
+ function createSignedEscrowReceipt(db, privateKey, publicKey, opts) {
129
+ const escrowId = holdEscrow(db, opts.owner, opts.amount, opts.cardId);
130
+ const receiptData = {
131
+ requester_owner: opts.owner,
132
+ requester_public_key: publicKey.toString("hex"),
133
+ amount: opts.amount,
134
+ card_id: opts.cardId,
135
+ ...opts.skillId ? { skill_id: opts.skillId } : {},
136
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
137
+ nonce: randomUUID()
646
138
  };
647
- const updatedCard = { ...existing, metadata: updatedMetadata, updated_at: now };
648
- const stmt = db.prepare(`
649
- UPDATE capability_cards
650
- SET data = ?, updated_at = ?
651
- WHERE id = ?
652
- `);
653
- stmt.run(JSON.stringify(updatedCard), now, cardId);
139
+ const signature = signEscrowReceipt(receiptData, privateKey);
140
+ const receipt = {
141
+ ...receiptData,
142
+ signature
143
+ };
144
+ return { escrowId, receipt };
654
145
  }
655
- function updateSkillAvailability(db, cardId, skillId, online) {
656
- const row = db.prepare("SELECT data FROM capability_cards WHERE id = ?").get(cardId);
657
- if (!row) return;
658
- const card = JSON.parse(row.data);
659
- const skills = card["skills"];
660
- if (!skills) return;
661
- const skill = skills.find((s) => s["id"] === skillId);
662
- if (!skill) return;
663
- const existing = skill["availability"] ?? {};
664
- skill["availability"] = { ...existing, online };
665
- const now = (/* @__PURE__ */ new Date()).toISOString();
666
- db.prepare("UPDATE capability_cards SET data = ?, updated_at = ? WHERE id = ?").run(
667
- JSON.stringify(card),
668
- now,
669
- cardId
146
+
147
+ // src/credit/settlement.ts
148
+ function settleProviderEarning(providerDb, providerOwner, receipt) {
149
+ recordEarning(
150
+ providerDb,
151
+ providerOwner,
152
+ receipt.amount,
153
+ receipt.card_id,
154
+ receipt.nonce
670
155
  );
156
+ return { settled: true };
671
157
  }
672
- function updateSkillIdleRate(db, cardId, skillId, idleRate) {
673
- const row = db.prepare("SELECT data FROM capability_cards WHERE id = ?").get(cardId);
674
- if (!row) return;
675
- const card = JSON.parse(row.data);
676
- const skills = card["skills"];
677
- if (!skills) return;
678
- const skill = skills.find((s) => s["id"] === skillId);
679
- if (!skill) return;
680
- const existing = skill["_internal"] ?? {};
681
- skill["_internal"] = {
682
- ...existing,
683
- idle_rate: idleRate,
684
- idle_rate_computed_at: (/* @__PURE__ */ new Date()).toISOString()
685
- };
686
- const now = (/* @__PURE__ */ new Date()).toISOString();
687
- db.prepare("UPDATE capability_cards SET data = ?, updated_at = ? WHERE id = ?").run(
688
- JSON.stringify(card),
689
- now,
690
- cardId
691
- );
158
+ function settleRequesterEscrow(requesterDb, escrowId) {
159
+ confirmEscrowDebit(requesterDb, escrowId);
692
160
  }
693
- function listCards(db, owner) {
694
- let stmt;
695
- let rows;
696
- if (owner !== void 0) {
697
- stmt = db.prepare("SELECT data FROM capability_cards WHERE owner = ?");
698
- rows = stmt.all(owner);
699
- } else {
700
- stmt = db.prepare("SELECT data FROM capability_cards");
701
- rows = stmt.all();
702
- }
703
- return rows.map((row) => JSON.parse(row.data));
161
+ function releaseRequesterEscrow(requesterDb, escrowId) {
162
+ releaseEscrow(requesterDb, escrowId);
704
163
  }
705
164
 
706
165
  // src/autonomy/idle-monitor.ts
166
+ import { Cron } from "croner";
707
167
  var IdleMonitor = class {
708
168
  job;
709
169
  owner;
@@ -795,566 +255,6 @@ var IdleMonitor = class {
795
255
  }
796
256
  };
797
257
 
798
- // src/credit/ledger.ts
799
- import Database2 from "better-sqlite3";
800
- import { randomUUID as randomUUID2 } from "crypto";
801
- var CREDIT_SCHEMA = `
802
- CREATE TABLE IF NOT EXISTS credit_balances (
803
- owner TEXT PRIMARY KEY,
804
- balance INTEGER NOT NULL DEFAULT 0,
805
- updated_at TEXT NOT NULL
806
- );
807
-
808
- CREATE TABLE IF NOT EXISTS credit_transactions (
809
- id TEXT PRIMARY KEY,
810
- owner TEXT NOT NULL,
811
- amount INTEGER NOT NULL,
812
- reason TEXT NOT NULL,
813
- reference_id TEXT,
814
- created_at TEXT NOT NULL
815
- );
816
-
817
- CREATE TABLE IF NOT EXISTS credit_escrow (
818
- id TEXT PRIMARY KEY,
819
- owner TEXT NOT NULL,
820
- amount INTEGER NOT NULL,
821
- card_id TEXT NOT NULL,
822
- status TEXT NOT NULL DEFAULT 'held',
823
- created_at TEXT NOT NULL,
824
- settled_at TEXT
825
- );
826
-
827
- CREATE INDEX IF NOT EXISTS idx_transactions_owner ON credit_transactions(owner, created_at);
828
- CREATE INDEX IF NOT EXISTS idx_escrow_owner ON credit_escrow(owner);
829
- `;
830
- function openCreditDb(path = ":memory:") {
831
- const db = new Database2(path);
832
- db.pragma("journal_mode = WAL");
833
- db.pragma("foreign_keys = ON");
834
- db.exec(CREDIT_SCHEMA);
835
- return db;
836
- }
837
- function bootstrapAgent(db, owner, amount = 100) {
838
- const now = (/* @__PURE__ */ new Date()).toISOString();
839
- db.transaction(() => {
840
- const result = db.prepare("INSERT OR IGNORE INTO credit_balances (owner, balance, updated_at) VALUES (?, ?, ?)").run(owner, amount, now);
841
- if (result.changes > 0) {
842
- db.prepare(
843
- "INSERT INTO credit_transactions (id, owner, amount, reason, reference_id, created_at) VALUES (?, ?, ?, ?, ?, ?)"
844
- ).run(randomUUID2(), owner, amount, "bootstrap", null, now);
845
- }
846
- })();
847
- }
848
- function getBalance(db, owner) {
849
- const row = db.prepare("SELECT balance FROM credit_balances WHERE owner = ?").get(owner);
850
- return row?.balance ?? 0;
851
- }
852
- function getTransactions(db, owner, limit = 100) {
853
- return db.prepare(
854
- "SELECT id, owner, amount, reason, reference_id, created_at FROM credit_transactions WHERE owner = ? ORDER BY created_at DESC LIMIT ?"
855
- ).all(owner, limit);
856
- }
857
-
858
- // src/credit/budget.ts
859
- var DEFAULT_BUDGET_CONFIG = {
860
- reserve_credits: 20
861
- };
862
- var BudgetManager = class {
863
- /**
864
- * Creates a new BudgetManager.
865
- *
866
- * @param creditDb - The credit SQLite database instance.
867
- * @param owner - Agent owner identifier.
868
- * @param config - Budget configuration. Defaults to DEFAULT_BUDGET_CONFIG (20 credit reserve).
869
- */
870
- constructor(creditDb, owner, config = DEFAULT_BUDGET_CONFIG) {
871
- this.creditDb = creditDb;
872
- this.owner = owner;
873
- this.config = config;
874
- }
875
- /**
876
- * Returns the number of credits available for spending.
877
- * Computed as: max(0, balance - reserve_credits).
878
- * Always returns a non-negative number — never goes below zero.
879
- *
880
- * @returns Available credits (balance minus reserve, floored at 0).
881
- */
882
- availableCredits() {
883
- const balance = getBalance(this.creditDb, this.owner);
884
- return Math.max(0, balance - this.config.reserve_credits);
885
- }
886
- /**
887
- * Returns true if spending `amount` credits is permitted by budget rules.
888
- *
889
- * Rules:
890
- * - Zero-cost calls (amount <= 0) always return true.
891
- * - Any positive amount requires availableCredits() >= amount.
892
- * - If balance is at or below the reserve floor, all positive-cost calls return false.
893
- *
894
- * @param amount - Number of credits to spend.
895
- * @returns true if the spend is allowed, false if it would breach the reserve floor.
896
- */
897
- canSpend(amount) {
898
- if (amount <= 0) return true;
899
- return this.availableCredits() >= amount;
900
- }
901
- };
902
-
903
- // src/registry/matcher.ts
904
- function searchCards(db, query, filters = {}) {
905
- const words = query.trim().split(/\s+/).map((w) => w.replace(/"/g, "")).filter((w) => w.length > 0);
906
- if (words.length === 0) return [];
907
- const ftsQuery = words.map((w) => `"${w}"`).join(" OR ");
908
- const conditions = [];
909
- const params = [ftsQuery];
910
- if (filters.level !== void 0) {
911
- conditions.push(`json_extract(cc.data, '$.level') = ?`);
912
- params.push(filters.level);
913
- }
914
- if (filters.online !== void 0) {
915
- conditions.push(`json_extract(cc.data, '$.availability.online') = ?`);
916
- params.push(filters.online ? 1 : 0);
917
- }
918
- const whereClause = conditions.length > 0 ? `AND ${conditions.join(" AND ")}` : "";
919
- const sql = `
920
- SELECT cc.data
921
- FROM capability_cards cc
922
- JOIN cards_fts ON cc.rowid = cards_fts.rowid
923
- WHERE cards_fts MATCH ?
924
- ${whereClause}
925
- ORDER BY bm25(cards_fts)
926
- LIMIT 50
927
- `;
928
- const stmt = db.prepare(sql);
929
- const rows = stmt.all(...params);
930
- const results = rows.map((row) => JSON.parse(row.data));
931
- if (filters.apis_used && filters.apis_used.length > 0) {
932
- const requiredApis = filters.apis_used;
933
- return results.filter((card) => {
934
- const cardApis = card.metadata?.apis_used ?? [];
935
- return requiredApis.every((api) => cardApis.includes(api));
936
- });
937
- }
938
- return results;
939
- }
940
- function filterCards(db, filters) {
941
- const conditions = [];
942
- const params = [];
943
- if (filters.level !== void 0) {
944
- conditions.push(`json_extract(data, '$.level') = ?`);
945
- params.push(filters.level);
946
- }
947
- if (filters.online !== void 0) {
948
- conditions.push(`json_extract(data, '$.availability.online') = ?`);
949
- params.push(filters.online ? 1 : 0);
950
- }
951
- const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
952
- const sql = `SELECT data FROM capability_cards ${whereClause}`;
953
- const stmt = db.prepare(sql);
954
- const rows = stmt.all(...params);
955
- return rows.map((row) => JSON.parse(row.data));
956
- }
957
-
958
- // src/credit/escrow.ts
959
- import { randomUUID as randomUUID3 } from "crypto";
960
- function holdEscrow(db, owner, amount, cardId) {
961
- const escrowId = randomUUID3();
962
- const now = (/* @__PURE__ */ new Date()).toISOString();
963
- const hold = db.transaction(() => {
964
- const row = db.prepare("SELECT balance FROM credit_balances WHERE owner = ?").get(owner);
965
- if (!row || row.balance < amount) {
966
- throw new AgentBnBError("Insufficient credits", "INSUFFICIENT_CREDITS");
967
- }
968
- db.prepare(
969
- "UPDATE credit_balances SET balance = balance - ?, updated_at = ? WHERE owner = ? AND balance >= ?"
970
- ).run(amount, now, owner, amount);
971
- db.prepare(
972
- "INSERT INTO credit_escrow (id, owner, amount, card_id, status, created_at) VALUES (?, ?, ?, ?, ?, ?)"
973
- ).run(escrowId, owner, amount, cardId, "held", now);
974
- db.prepare(
975
- "INSERT INTO credit_transactions (id, owner, amount, reason, reference_id, created_at) VALUES (?, ?, ?, ?, ?, ?)"
976
- ).run(randomUUID3(), owner, -amount, "escrow_hold", escrowId, now);
977
- });
978
- hold();
979
- return escrowId;
980
- }
981
- function settleEscrow(db, escrowId, recipientOwner) {
982
- const now = (/* @__PURE__ */ new Date()).toISOString();
983
- const settle = db.transaction(() => {
984
- const escrow = db.prepare("SELECT id, owner, amount, status FROM credit_escrow WHERE id = ?").get(escrowId);
985
- if (!escrow) {
986
- throw new AgentBnBError(`Escrow not found: ${escrowId}`, "ESCROW_NOT_FOUND");
987
- }
988
- if (escrow.status !== "held") {
989
- throw new AgentBnBError(
990
- `Escrow ${escrowId} is already ${escrow.status}`,
991
- "ESCROW_ALREADY_SETTLED"
992
- );
993
- }
994
- db.prepare(
995
- "INSERT OR IGNORE INTO credit_balances (owner, balance, updated_at) VALUES (?, 0, ?)"
996
- ).run(recipientOwner, now);
997
- db.prepare(
998
- "UPDATE credit_balances SET balance = balance + ?, updated_at = ? WHERE owner = ?"
999
- ).run(escrow.amount, now, recipientOwner);
1000
- db.prepare(
1001
- "UPDATE credit_escrow SET status = ?, settled_at = ? WHERE id = ?"
1002
- ).run("settled", now, escrowId);
1003
- db.prepare(
1004
- "INSERT INTO credit_transactions (id, owner, amount, reason, reference_id, created_at) VALUES (?, ?, ?, ?, ?, ?)"
1005
- ).run(randomUUID3(), recipientOwner, escrow.amount, "settlement", escrowId, now);
1006
- });
1007
- settle();
1008
- }
1009
- function releaseEscrow(db, escrowId) {
1010
- const now = (/* @__PURE__ */ new Date()).toISOString();
1011
- const release = db.transaction(() => {
1012
- const escrow = db.prepare("SELECT id, owner, amount, status FROM credit_escrow WHERE id = ?").get(escrowId);
1013
- if (!escrow) {
1014
- throw new AgentBnBError(`Escrow not found: ${escrowId}`, "ESCROW_NOT_FOUND");
1015
- }
1016
- if (escrow.status !== "held") {
1017
- throw new AgentBnBError(
1018
- `Escrow ${escrowId} is already ${escrow.status}`,
1019
- "ESCROW_ALREADY_SETTLED"
1020
- );
1021
- }
1022
- db.prepare(
1023
- "UPDATE credit_balances SET balance = balance + ?, updated_at = ? WHERE owner = ?"
1024
- ).run(escrow.amount, now, escrow.owner);
1025
- db.prepare(
1026
- "UPDATE credit_escrow SET status = ?, settled_at = ? WHERE id = ?"
1027
- ).run("released", now, escrowId);
1028
- db.prepare(
1029
- "INSERT INTO credit_transactions (id, owner, amount, reason, reference_id, created_at) VALUES (?, ?, ?, ?, ?, ?)"
1030
- ).run(randomUUID3(), escrow.owner, escrow.amount, "refund", escrowId, now);
1031
- });
1032
- release();
1033
- }
1034
-
1035
- // src/gateway/client.ts
1036
- import { randomUUID as randomUUID4 } from "crypto";
1037
- async function requestCapability(opts) {
1038
- const { gatewayUrl, token, cardId, params = {}, timeoutMs = 3e4 } = opts;
1039
- const id = randomUUID4();
1040
- const payload = {
1041
- jsonrpc: "2.0",
1042
- id,
1043
- method: "capability.execute",
1044
- params: { card_id: cardId, ...params }
1045
- };
1046
- const controller = new AbortController();
1047
- const timer = setTimeout(() => controller.abort(), timeoutMs);
1048
- let response;
1049
- try {
1050
- response = await fetch(`${gatewayUrl}/rpc`, {
1051
- method: "POST",
1052
- headers: {
1053
- "Content-Type": "application/json",
1054
- Authorization: `Bearer ${token}`
1055
- },
1056
- body: JSON.stringify(payload),
1057
- signal: controller.signal
1058
- });
1059
- } catch (err) {
1060
- clearTimeout(timer);
1061
- const isTimeout = err instanceof Error && err.name === "AbortError";
1062
- throw new AgentBnBError(
1063
- isTimeout ? "Request timed out" : `Network error: ${String(err)}`,
1064
- isTimeout ? "TIMEOUT" : "NETWORK_ERROR"
1065
- );
1066
- } finally {
1067
- clearTimeout(timer);
1068
- }
1069
- const body = await response.json();
1070
- if (body.error) {
1071
- throw new AgentBnBError(body.error.message, `RPC_ERROR_${body.error.code}`);
1072
- }
1073
- return body.result;
1074
- }
1075
-
1076
- // src/autonomy/pending-requests.ts
1077
- import { randomUUID as randomUUID5 } from "crypto";
1078
- function createPendingRequest(db, opts) {
1079
- const id = randomUUID5();
1080
- const now = (/* @__PURE__ */ new Date()).toISOString();
1081
- const paramsJson = opts.params !== void 0 ? JSON.stringify(opts.params) : null;
1082
- db.prepare(`
1083
- INSERT INTO pending_requests (
1084
- id, skill_query, max_cost_credits, selected_peer, selected_card_id,
1085
- selected_skill_id, credits, status, params, created_at, resolved_at
1086
- ) VALUES (?, ?, ?, ?, ?, ?, ?, 'pending', ?, ?, NULL)
1087
- `).run(
1088
- id,
1089
- opts.skill_query,
1090
- opts.max_cost_credits,
1091
- opts.selected_peer ?? null,
1092
- opts.selected_card_id ?? null,
1093
- opts.selected_skill_id ?? null,
1094
- opts.credits,
1095
- paramsJson,
1096
- now
1097
- );
1098
- return id;
1099
- }
1100
- function listPendingRequests(db) {
1101
- const rows = db.prepare(`SELECT * FROM pending_requests WHERE status = 'pending' ORDER BY created_at DESC`).all();
1102
- return rows;
1103
- }
1104
- function resolvePendingRequest(db, id, resolution) {
1105
- const now = (/* @__PURE__ */ new Date()).toISOString();
1106
- const result = db.prepare(
1107
- `UPDATE pending_requests SET status = ?, resolved_at = ? WHERE id = ?`
1108
- ).run(resolution, now, id);
1109
- if (result.changes === 0) {
1110
- throw new AgentBnBError(
1111
- `Pending request not found: ${id}`,
1112
- "NOT_FOUND"
1113
- );
1114
- }
1115
- }
1116
-
1117
- // src/cli/peers.ts
1118
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
1119
- import { join as join3 } from "path";
1120
- function getPeersPath() {
1121
- return join3(getConfigDir(), "peers.json");
1122
- }
1123
- function loadPeers() {
1124
- const peersPath = getPeersPath();
1125
- if (!existsSync3(peersPath)) {
1126
- return [];
1127
- }
1128
- try {
1129
- const raw = readFileSync3(peersPath, "utf-8");
1130
- return JSON.parse(raw);
1131
- } catch {
1132
- return [];
1133
- }
1134
- }
1135
- function writePeers(peers) {
1136
- const dir = getConfigDir();
1137
- if (!existsSync3(dir)) {
1138
- mkdirSync2(dir, { recursive: true });
1139
- }
1140
- writeFileSync3(getPeersPath(), JSON.stringify(peers, null, 2), "utf-8");
1141
- }
1142
- function savePeer(peer) {
1143
- const peers = loadPeers();
1144
- const lowerName = peer.name.toLowerCase();
1145
- const existing = peers.findIndex((p) => p.name.toLowerCase() === lowerName);
1146
- if (existing >= 0) {
1147
- peers[existing] = peer;
1148
- } else {
1149
- peers.push(peer);
1150
- }
1151
- writePeers(peers);
1152
- }
1153
- function removePeer(name) {
1154
- const peers = loadPeers();
1155
- const lowerName = name.toLowerCase();
1156
- const filtered = peers.filter((p) => p.name.toLowerCase() !== lowerName);
1157
- if (filtered.length === peers.length) {
1158
- return false;
1159
- }
1160
- writePeers(filtered);
1161
- return true;
1162
- }
1163
- function findPeer(name) {
1164
- const peers = loadPeers();
1165
- const lowerName = name.toLowerCase();
1166
- return peers.find((p) => p.name.toLowerCase() === lowerName) ?? null;
1167
- }
1168
-
1169
- // src/autonomy/auto-request.ts
1170
- function minMaxNormalize(values) {
1171
- if (values.length === 0) return [];
1172
- if (values.length === 1) return [1];
1173
- const min = Math.min(...values);
1174
- const max = Math.max(...values);
1175
- if (max === min) {
1176
- return values.map(() => 1);
1177
- }
1178
- return values.map((v) => (v - min) / (max - min));
1179
- }
1180
- function scorePeers(candidates, selfOwner) {
1181
- const eligible = candidates.filter((c) => c.card.owner !== selfOwner);
1182
- if (eligible.length === 0) return [];
1183
- const successRates = eligible.map((c) => c.card.metadata?.success_rate ?? 0.5);
1184
- const costEfficiencies = eligible.map((c) => c.cost === 0 ? 1 : 1 / c.cost);
1185
- const idleRates = eligible.map((c) => {
1186
- const internal = c.card._internal;
1187
- const idleRate = internal?.idle_rate;
1188
- return typeof idleRate === "number" ? idleRate : 1;
1189
- });
1190
- const normSuccess = minMaxNormalize(successRates);
1191
- const normCost = minMaxNormalize(costEfficiencies);
1192
- const normIdle = minMaxNormalize(idleRates);
1193
- const scored = eligible.map((c, i) => ({
1194
- ...c,
1195
- rawScore: (normSuccess[i] ?? 0) * (normCost[i] ?? 0) * (normIdle[i] ?? 0)
1196
- }));
1197
- scored.sort((a, b) => b.rawScore - a.rawScore);
1198
- return scored;
1199
- }
1200
- var AutoRequestor = class {
1201
- owner;
1202
- registryDb;
1203
- creditDb;
1204
- autonomyConfig;
1205
- budgetManager;
1206
- /**
1207
- * Creates a new AutoRequestor.
1208
- *
1209
- * @param opts - Configuration for this AutoRequestor instance.
1210
- */
1211
- constructor(opts) {
1212
- this.owner = opts.owner;
1213
- this.registryDb = opts.registryDb;
1214
- this.creditDb = opts.creditDb;
1215
- this.autonomyConfig = opts.autonomyConfig;
1216
- this.budgetManager = opts.budgetManager;
1217
- }
1218
- /**
1219
- * Executes an autonomous capability request.
1220
- *
1221
- * Performs the full flow:
1222
- * 1. Search for matching capability cards
1223
- * 2. Filter self-owned and over-budget candidates
1224
- * 3. Score candidates using min-max normalized composite scoring
1225
- * 4. Resolve peer gateway config
1226
- * 5. Check autonomy tier (Tier 3 queues to pending_requests)
1227
- * 6. Check budget reserve
1228
- * 7. Hold escrow
1229
- * 8. Execute via peer gateway
1230
- * 9. Settle or release escrow based on outcome
1231
- * 10. Log audit event (for Tier 2 notifications and all failures)
1232
- *
1233
- * @param need - The capability need to fulfill.
1234
- * @returns The result of the auto-request attempt.
1235
- */
1236
- async requestWithAutonomy(need) {
1237
- const cards = searchCards(this.registryDb, need.query, { online: true });
1238
- const candidates = [];
1239
- for (const card of cards) {
1240
- const cardAsV2 = card;
1241
- if (Array.isArray(cardAsV2.skills)) {
1242
- for (const skill of cardAsV2.skills) {
1243
- const cost = skill.pricing.credits_per_call;
1244
- if (cost <= need.maxCostCredits) {
1245
- candidates.push({ card, cost, skillId: skill.id });
1246
- }
1247
- }
1248
- } else {
1249
- const cost = card.pricing.credits_per_call;
1250
- if (cost <= need.maxCostCredits) {
1251
- candidates.push({ card, cost, skillId: void 0 });
1252
- }
1253
- }
1254
- }
1255
- const scored = scorePeers(candidates, this.owner);
1256
- if (scored.length === 0) {
1257
- this.logFailure("auto_request_failed", "system", "none", 3, 0, "none", "No eligible peer found");
1258
- return { status: "no_peer", reason: "No eligible peer found" };
1259
- }
1260
- const top = scored[0];
1261
- const peerConfig = findPeer(top.card.owner);
1262
- if (!peerConfig) {
1263
- this.logFailure("auto_request_failed", top.card.id, top.skillId ?? "none", 3, top.cost, top.card.owner, "No gateway config for peer");
1264
- return { status: "no_peer", reason: "No gateway config for peer" };
1265
- }
1266
- const tier = getAutonomyTier(top.cost, this.autonomyConfig);
1267
- if (tier === 3) {
1268
- createPendingRequest(this.registryDb, {
1269
- skill_query: need.query,
1270
- max_cost_credits: need.maxCostCredits,
1271
- credits: top.cost,
1272
- selected_peer: top.card.owner,
1273
- selected_card_id: top.card.id,
1274
- selected_skill_id: top.skillId,
1275
- params: need.params
1276
- });
1277
- insertAuditEvent(this.registryDb, {
1278
- type: "auto_request_pending",
1279
- card_id: top.card.id,
1280
- skill_id: top.skillId ?? top.card.id,
1281
- tier_invoked: 3,
1282
- credits: top.cost,
1283
- peer: top.card.owner
1284
- });
1285
- return {
1286
- status: "tier_blocked",
1287
- reason: "Tier 3: owner approval required",
1288
- peer: top.card.owner
1289
- };
1290
- }
1291
- if (!this.budgetManager.canSpend(top.cost)) {
1292
- this.logFailure("auto_request_failed", top.card.id, top.skillId ?? "none", tier, top.cost, top.card.owner, "Budget reserve would be breached");
1293
- return { status: "budget_blocked", reason: "Insufficient credits \u2014 reserve floor would be breached" };
1294
- }
1295
- const escrowId = holdEscrow(this.creditDb, this.owner, top.cost, top.card.id);
1296
- try {
1297
- const execResult = await requestCapability({
1298
- gatewayUrl: peerConfig.url,
1299
- token: peerConfig.token,
1300
- cardId: top.card.id,
1301
- params: top.skillId ? { skill_id: top.skillId, ...need.params } : need.params
1302
- });
1303
- settleEscrow(this.creditDb, escrowId, top.card.owner);
1304
- if (tier === 2) {
1305
- insertAuditEvent(this.registryDb, {
1306
- type: "auto_request_notify",
1307
- card_id: top.card.id,
1308
- skill_id: top.skillId ?? top.card.id,
1309
- tier_invoked: 2,
1310
- credits: top.cost,
1311
- peer: top.card.owner
1312
- });
1313
- } else {
1314
- insertAuditEvent(this.registryDb, {
1315
- type: "auto_request",
1316
- card_id: top.card.id,
1317
- skill_id: top.skillId ?? top.card.id,
1318
- tier_invoked: 1,
1319
- credits: top.cost,
1320
- peer: top.card.owner
1321
- });
1322
- }
1323
- return {
1324
- status: "success",
1325
- result: execResult,
1326
- escrowId,
1327
- peer: top.card.owner,
1328
- creditsSpent: top.cost
1329
- };
1330
- } catch (err) {
1331
- releaseEscrow(this.creditDb, escrowId);
1332
- const reason = err instanceof Error ? err.message : String(err);
1333
- this.logFailure("auto_request_failed", top.card.id, top.skillId ?? "none", tier, top.cost, top.card.owner, `Execution failed: ${reason}`);
1334
- return {
1335
- status: "failed",
1336
- reason: `Execution failed: ${reason}`,
1337
- peer: top.card.owner
1338
- };
1339
- }
1340
- }
1341
- /**
1342
- * Logs a failure audit event to request_log.
1343
- * Used for all non-success paths to satisfy REQ-06.
1344
- */
1345
- logFailure(type, cardId, skillId, tier, credits, peer, reason) {
1346
- insertAuditEvent(this.registryDb, {
1347
- type,
1348
- card_id: cardId,
1349
- skill_id: skillId,
1350
- tier_invoked: tier,
1351
- credits,
1352
- peer,
1353
- reason
1354
- });
1355
- }
1356
- };
1357
-
1358
258
  // src/cli/remote-registry.ts
1359
259
  var RegistryTimeoutError = class extends AgentBnBError {
1360
260
  constructor(url) {
@@ -1439,7 +339,7 @@ function mergeResults(localCards, remoteCards, hasQuery) {
1439
339
  }
1440
340
 
1441
341
  // src/cli/onboarding.ts
1442
- import { randomUUID as randomUUID6 } from "crypto";
342
+ import { randomUUID as randomUUID2 } from "crypto";
1443
343
  import { createConnection } from "net";
1444
344
  var KNOWN_API_KEYS = [
1445
345
  "OPENAI_API_KEY",
@@ -1588,7 +488,7 @@ function buildDraftCard(apiKey, owner) {
1588
488
  const now = (/* @__PURE__ */ new Date()).toISOString();
1589
489
  return {
1590
490
  spec_version: "1.0",
1591
- id: randomUUID6(),
491
+ id: randomUUID2(),
1592
492
  owner,
1593
493
  name: template.name,
1594
494
  description: template.description,
@@ -1608,7 +508,7 @@ function buildDraftCard(apiKey, owner) {
1608
508
  }
1609
509
 
1610
510
  // src/runtime/agent-runtime.ts
1611
- import { readFileSync as readFileSync4, existsSync as existsSync4 } from "fs";
511
+ import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
1612
512
 
1613
513
  // src/skills/executor.ts
1614
514
  var SkillExecutor = class {
@@ -1766,17 +666,29 @@ var CommandSkillConfigSchema = z2.object({
1766
666
  timeout_ms: z2.number().positive().default(3e4),
1767
667
  pricing: PricingSchema
1768
668
  });
669
+ var ConductorSkillConfigSchema = z2.object({
670
+ id: z2.string().min(1),
671
+ type: z2.literal("conductor"),
672
+ name: z2.string().min(1),
673
+ conductor_skill: z2.enum(["orchestrate", "plan"]),
674
+ pricing: PricingSchema,
675
+ timeout_ms: z2.number().positive().optional()
676
+ });
1769
677
  var SkillConfigSchema = z2.discriminatedUnion("type", [
1770
678
  ApiSkillConfigSchema,
1771
679
  PipelineSkillConfigSchema,
1772
680
  OpenClawSkillConfigSchema,
1773
- CommandSkillConfigSchema
681
+ CommandSkillConfigSchema,
682
+ ConductorSkillConfigSchema
1774
683
  ]);
1775
684
  var SkillsFileSchema = z2.object({
1776
685
  skills: z2.array(SkillConfigSchema)
1777
686
  });
1778
687
  function expandEnvVars(value) {
1779
688
  return value.replace(/\$\{([^}]+)\}/g, (_match, varName) => {
689
+ if (/[.a-z]/.test(varName)) {
690
+ return _match;
691
+ }
1780
692
  const envValue = process.env[varName];
1781
693
  if (envValue === void 0) {
1782
694
  throw new Error(`Environment variable "${varName}" is not defined`);
@@ -1867,7 +779,7 @@ function applyInputMapping(params, mapping) {
1867
779
  pathParams[key] = String(value);
1868
780
  break;
1869
781
  case "header":
1870
- headers[key] = String(value);
782
+ headers[key] = String(value).replace(/[\r\n]/g, "");
1871
783
  break;
1872
784
  }
1873
785
  }
@@ -1962,58 +874,34 @@ var ApiExecutor = class {
1962
874
  };
1963
875
 
1964
876
  // src/skills/pipeline-executor.ts
1965
- import { exec } from "child_process";
877
+ import { execFile } from "child_process";
1966
878
  import { promisify } from "util";
1967
-
1968
- // src/utils/interpolation.ts
1969
- function resolvePath(obj, path) {
1970
- const segments = path.replace(/\[(\d+)\]/g, ".$1").split(".").filter((s) => s.length > 0);
1971
- let current = obj;
1972
- for (const segment of segments) {
1973
- if (current === null || current === void 0) {
1974
- return void 0;
1975
- }
1976
- if (typeof current !== "object") {
1977
- return void 0;
1978
- }
1979
- current = current[segment];
1980
- }
1981
- return current;
1982
- }
1983
- function interpolate(template, context) {
1984
- return template.replace(/\$\{([^}]+)\}/g, (_match, expression) => {
1985
- const resolved = resolvePath(context, expression.trim());
1986
- if (resolved === void 0 || resolved === null) {
1987
- return "";
1988
- }
1989
- if (typeof resolved === "object") {
1990
- return JSON.stringify(resolved);
879
+ var execFileAsync = promisify(execFile);
880
+ function shellEscape(value) {
881
+ return "'" + value.replace(/'/g, "'\\''") + "'";
882
+ }
883
+ function safeInterpolateCommand(template, context) {
884
+ return template.replace(/\$\{([^}]+)\}/g, (_match, expr) => {
885
+ const parts = expr.split(".");
886
+ let current = context;
887
+ for (const part of parts) {
888
+ if (current === null || typeof current !== "object") return "";
889
+ const bracketMatch = part.match(/^(\w+)\[(\d+)\]$/);
890
+ if (bracketMatch) {
891
+ current = current[bracketMatch[1]];
892
+ if (Array.isArray(current)) {
893
+ current = current[parseInt(bracketMatch[2], 10)];
894
+ } else {
895
+ return "";
896
+ }
897
+ } else {
898
+ current = current[part];
899
+ }
1991
900
  }
1992
- return String(resolved);
901
+ if (current === void 0 || current === null) return "";
902
+ return shellEscape(String(current));
1993
903
  });
1994
904
  }
1995
- function interpolateObject(obj, context) {
1996
- const result = {};
1997
- for (const [key, value] of Object.entries(obj)) {
1998
- result[key] = interpolateValue(value, context);
1999
- }
2000
- return result;
2001
- }
2002
- function interpolateValue(value, context) {
2003
- if (typeof value === "string") {
2004
- return interpolate(value, context);
2005
- }
2006
- if (Array.isArray(value)) {
2007
- return value.map((item) => interpolateValue(item, context));
2008
- }
2009
- if (value !== null && typeof value === "object") {
2010
- return interpolateObject(value, context);
2011
- }
2012
- return value;
2013
- }
2014
-
2015
- // src/skills/pipeline-executor.ts
2016
- var execAsync = promisify(exec);
2017
905
  var PipelineExecutor = class {
2018
906
  /**
2019
907
  * @param skillExecutor - The parent SkillExecutor used to dispatch sub-skill calls.
@@ -2074,12 +962,12 @@ var PipelineExecutor = class {
2074
962
  }
2075
963
  stepResult = subResult.result;
2076
964
  } else if ("command" in step && step.command) {
2077
- const interpolatedCommand = interpolate(
965
+ const interpolatedCommand = safeInterpolateCommand(
2078
966
  step.command,
2079
967
  context
2080
968
  );
2081
969
  try {
2082
- const { stdout } = await execAsync(interpolatedCommand, { timeout: 3e4 });
970
+ const { stdout } = await execFileAsync("/bin/sh", ["-c", interpolatedCommand], { timeout: 3e4 });
2083
971
  stepResult = stdout.trim();
2084
972
  } catch (err) {
2085
973
  const message = err instanceof Error ? err.message : String(err);
@@ -2106,7 +994,7 @@ var PipelineExecutor = class {
2106
994
  };
2107
995
 
2108
996
  // src/skills/openclaw-bridge.ts
2109
- import { execSync } from "child_process";
997
+ import { execFileSync } from "child_process";
2110
998
  var DEFAULT_BASE_URL = "http://localhost:3000";
2111
999
  var DEFAULT_TIMEOUT_MS = 6e4;
2112
1000
  function buildPayload(config, params) {
@@ -2151,12 +1039,22 @@ async function executeWebhook(config, payload) {
2151
1039
  clearTimeout(timer);
2152
1040
  }
2153
1041
  }
1042
+ function validateAgentName(name) {
1043
+ return /^[a-zA-Z0-9._-]+$/.test(name);
1044
+ }
2154
1045
  function executeProcess(config, payload) {
2155
1046
  const timeoutMs = config.timeout_ms ?? DEFAULT_TIMEOUT_MS;
1047
+ if (!validateAgentName(config.agent_name)) {
1048
+ return {
1049
+ success: false,
1050
+ error: `Invalid agent name: "${config.agent_name}" (only alphanumeric, hyphens, underscores, dots allowed)`
1051
+ };
1052
+ }
2156
1053
  const inputJson = JSON.stringify(payload);
2157
- const cmd = `openclaw run ${config.agent_name} --input '${inputJson}'`;
2158
1054
  try {
2159
- const stdout = execSync(cmd, { timeout: timeoutMs });
1055
+ const stdout = execFileSync("openclaw", ["run", config.agent_name, "--input", inputJson], {
1056
+ timeout: timeoutMs
1057
+ });
2160
1058
  const text = stdout.toString().trim();
2161
1059
  const result = JSON.parse(text);
2162
1060
  return { success: true, result };
@@ -2229,10 +1127,35 @@ var OpenClawBridge = class {
2229
1127
  };
2230
1128
 
2231
1129
  // src/skills/command-executor.ts
2232
- import { exec as exec2 } from "child_process";
2233
- function execAsync2(command, options) {
1130
+ import { execFile as execFile2 } from "child_process";
1131
+ function shellEscape2(value) {
1132
+ return "'" + value.replace(/'/g, "'\\''") + "'";
1133
+ }
1134
+ function safeInterpolateCommand2(template, context) {
1135
+ return template.replace(/\$\{([^}]+)\}/g, (_match, expr) => {
1136
+ const parts = expr.split(".");
1137
+ let current = context;
1138
+ for (const part of parts) {
1139
+ if (current === null || typeof current !== "object") return "";
1140
+ const bracketMatch = part.match(/^(\w+)\[(\d+)\]$/);
1141
+ if (bracketMatch) {
1142
+ current = current[bracketMatch[1]];
1143
+ if (Array.isArray(current)) {
1144
+ current = current[parseInt(bracketMatch[2], 10)];
1145
+ } else {
1146
+ return "";
1147
+ }
1148
+ } else {
1149
+ current = current[part];
1150
+ }
1151
+ }
1152
+ if (current === void 0 || current === null) return "";
1153
+ return shellEscape2(String(current));
1154
+ });
1155
+ }
1156
+ function execFileAsync2(file, args, options) {
2234
1157
  return new Promise((resolve, reject) => {
2235
- exec2(command, options, (error, stdout, stderr) => {
1158
+ execFile2(file, args, options, (error, stdout, stderr) => {
2236
1159
  const stdoutStr = typeof stdout === "string" ? stdout : stdout.toString();
2237
1160
  const stderrStr = typeof stderr === "string" ? stderr : stderr.toString();
2238
1161
  if (error) {
@@ -2269,17 +1192,16 @@ var CommandExecutor = class {
2269
1192
  };
2270
1193
  }
2271
1194
  }
2272
- const interpolatedCommand = interpolate(cmdConfig.command, { params });
1195
+ const interpolatedCommand = safeInterpolateCommand2(cmdConfig.command, { params });
2273
1196
  const timeout = cmdConfig.timeout_ms ?? 3e4;
2274
1197
  const cwd = cmdConfig.working_dir ?? process.cwd();
2275
1198
  let stdout;
2276
1199
  try {
2277
- const result = await execAsync2(interpolatedCommand, {
1200
+ const result = await execFileAsync2("/bin/sh", ["-c", interpolatedCommand], {
2278
1201
  timeout,
2279
1202
  cwd,
2280
- maxBuffer: 10 * 1024 * 1024,
1203
+ maxBuffer: 10 * 1024 * 1024
2281
1204
  // 10 MB
2282
- shell: "/bin/sh"
2283
1205
  });
2284
1206
  stdout = result.stdout;
2285
1207
  } catch (err) {
@@ -2346,6 +1268,8 @@ var AgentRuntime = class {
2346
1268
  draining = false;
2347
1269
  orphanedEscrowAgeMinutes;
2348
1270
  skillsYamlPath;
1271
+ conductorEnabled;
1272
+ conductorToken;
2349
1273
  /**
2350
1274
  * Creates a new AgentRuntime instance.
2351
1275
  * Opens both databases with WAL mode, foreign_keys=ON, and busy_timeout=5000.
@@ -2357,6 +1281,8 @@ var AgentRuntime = class {
2357
1281
  this.owner = options.owner;
2358
1282
  this.orphanedEscrowAgeMinutes = options.orphanedEscrowAgeMinutes ?? 10;
2359
1283
  this.skillsYamlPath = options.skillsYamlPath;
1284
+ this.conductorEnabled = options.conductorEnabled ?? false;
1285
+ this.conductorToken = options.conductorToken ?? "";
2360
1286
  this.registryDb = openDatabase(options.registryDbPath);
2361
1287
  this.creditDb = openCreditDb(options.creditDbPath);
2362
1288
  this.registryDb.pragma("busy_timeout = 5000");
@@ -2393,18 +1319,70 @@ var AgentRuntime = class {
2393
1319
  * 3. Populate the Map with all 4 modes — SkillExecutor sees them via reference.
2394
1320
  */
2395
1321
  async initSkillExecutor() {
2396
- if (!this.skillsYamlPath || !existsSync4(this.skillsYamlPath)) {
1322
+ const hasSkillsYaml = this.skillsYamlPath && existsSync2(this.skillsYamlPath);
1323
+ if (!hasSkillsYaml && !this.conductorEnabled) {
2397
1324
  return;
2398
1325
  }
2399
- const yamlContent = readFileSync4(this.skillsYamlPath, "utf8");
2400
- const configs = parseSkillsFile(yamlContent);
1326
+ let configs = [];
1327
+ if (hasSkillsYaml) {
1328
+ const yamlContent = readFileSync2(this.skillsYamlPath, "utf8");
1329
+ configs = parseSkillsFile(yamlContent);
1330
+ }
2401
1331
  const modes = /* @__PURE__ */ new Map();
1332
+ if (this.conductorEnabled) {
1333
+ const { ConductorMode } = await import("../conductor-mode-DJ3RIJ5T.js");
1334
+ const { registerConductorCard, CONDUCTOR_OWNER } = await import("../card-P5C36VBD.js");
1335
+ const { loadPeers: loadPeers2 } = await import("../peers-G36URZYB.js");
1336
+ registerConductorCard(this.registryDb);
1337
+ const resolveAgentUrl = (owner) => {
1338
+ const peers = loadPeers2();
1339
+ const peer = peers.find((p) => p.name.toLowerCase() === owner.toLowerCase());
1340
+ if (!peer) {
1341
+ throw new Error(
1342
+ `No peer found for agent owner "${owner}". Add with: agentbnb peers add ${owner} <url> <token>`
1343
+ );
1344
+ }
1345
+ const stmt = this.registryDb.prepare(
1346
+ "SELECT id FROM capability_cards WHERE owner = ? LIMIT 1"
1347
+ );
1348
+ const row = stmt.get(owner);
1349
+ const cardId = row?.id ?? owner;
1350
+ return { url: peer.url, cardId };
1351
+ };
1352
+ const conductorMode = new ConductorMode({
1353
+ db: this.registryDb,
1354
+ creditDb: this.creditDb,
1355
+ conductorOwner: CONDUCTOR_OWNER,
1356
+ gatewayToken: this.conductorToken,
1357
+ resolveAgentUrl,
1358
+ maxBudget: 100
1359
+ });
1360
+ modes.set("conductor", conductorMode);
1361
+ configs.push(
1362
+ {
1363
+ id: "orchestrate",
1364
+ type: "conductor",
1365
+ name: "Orchestrate",
1366
+ conductor_skill: "orchestrate",
1367
+ pricing: { credits_per_call: 5 }
1368
+ },
1369
+ {
1370
+ id: "plan",
1371
+ type: "conductor",
1372
+ name: "Plan",
1373
+ conductor_skill: "plan",
1374
+ pricing: { credits_per_call: 1 }
1375
+ }
1376
+ );
1377
+ }
2402
1378
  const executor = createSkillExecutor(configs, modes);
2403
- const pipelineExecutor = new PipelineExecutor(executor);
2404
- modes.set("api", new ApiExecutor());
2405
- modes.set("pipeline", pipelineExecutor);
2406
- modes.set("openclaw", new OpenClawBridge());
2407
- modes.set("command", new CommandExecutor());
1379
+ if (hasSkillsYaml) {
1380
+ const pipelineExecutor = new PipelineExecutor(executor);
1381
+ modes.set("api", new ApiExecutor());
1382
+ modes.set("pipeline", pipelineExecutor);
1383
+ modes.set("openclaw", new OpenClawBridge());
1384
+ modes.set("command", new CommandExecutor());
1385
+ }
2408
1386
  this.skillExecutor = executor;
2409
1387
  }
2410
1388
  /**
@@ -2459,7 +1437,7 @@ var AgentRuntime = class {
2459
1437
 
2460
1438
  // src/gateway/server.ts
2461
1439
  import Fastify from "fastify";
2462
- import { randomUUID as randomUUID7 } from "crypto";
1440
+ import { randomUUID as randomUUID3 } from "crypto";
2463
1441
  var VERSION = "0.0.1";
2464
1442
  function createGatewayServer(opts) {
2465
1443
  const {
@@ -2553,24 +1531,55 @@ function createGatewayServer(opts) {
2553
1531
  creditsNeeded = card.pricing.credits_per_call;
2554
1532
  cardName = card.name;
2555
1533
  }
2556
- let escrowId;
2557
- try {
2558
- const balance = getBalance(creditDb, requester);
2559
- if (balance < creditsNeeded) {
1534
+ const receipt = params.escrow_receipt;
1535
+ let escrowId = null;
1536
+ let isRemoteEscrow = false;
1537
+ if (receipt) {
1538
+ const { signature, ...receiptData } = receipt;
1539
+ const publicKeyBuf = Buffer.from(receipt.requester_public_key, "hex");
1540
+ const valid = verifyEscrowReceipt(receiptData, signature, publicKeyBuf);
1541
+ if (!valid) {
2560
1542
  return reply.send({
2561
1543
  jsonrpc: "2.0",
2562
1544
  id,
2563
- error: { code: -32603, message: "Insufficient credits" }
1545
+ error: { code: -32603, message: "Invalid escrow receipt signature" }
1546
+ });
1547
+ }
1548
+ if (receipt.amount < creditsNeeded) {
1549
+ return reply.send({
1550
+ jsonrpc: "2.0",
1551
+ id,
1552
+ error: { code: -32603, message: "Insufficient escrow amount" }
1553
+ });
1554
+ }
1555
+ const receiptAge = Date.now() - new Date(receipt.timestamp).getTime();
1556
+ if (receiptAge > 5 * 60 * 1e3) {
1557
+ return reply.send({
1558
+ jsonrpc: "2.0",
1559
+ id,
1560
+ error: { code: -32603, message: "Escrow receipt expired" }
1561
+ });
1562
+ }
1563
+ isRemoteEscrow = true;
1564
+ } else {
1565
+ try {
1566
+ const balance = getBalance(creditDb, requester);
1567
+ if (balance < creditsNeeded) {
1568
+ return reply.send({
1569
+ jsonrpc: "2.0",
1570
+ id,
1571
+ error: { code: -32603, message: "Insufficient credits" }
1572
+ });
1573
+ }
1574
+ escrowId = holdEscrow(creditDb, requester, creditsNeeded, cardId);
1575
+ } catch (err) {
1576
+ const msg = err instanceof AgentBnBError ? err.message : "Failed to hold escrow";
1577
+ return reply.send({
1578
+ jsonrpc: "2.0",
1579
+ id,
1580
+ error: { code: -32603, message: msg }
2564
1581
  });
2565
1582
  }
2566
- escrowId = holdEscrow(creditDb, requester, creditsNeeded, cardId);
2567
- } catch (err) {
2568
- const msg = err instanceof AgentBnBError ? err.message : "Failed to hold escrow";
2569
- return reply.send({
2570
- jsonrpc: "2.0",
2571
- id,
2572
- error: { code: -32603, message: msg }
2573
- });
2574
1583
  }
2575
1584
  const startMs = Date.now();
2576
1585
  if (skillExecutor) {
@@ -2579,11 +1588,11 @@ function createGatewayServer(opts) {
2579
1588
  try {
2580
1589
  execResult = await skillExecutor.execute(targetSkillId, params);
2581
1590
  } catch (err) {
2582
- releaseEscrow(creditDb, escrowId);
1591
+ if (!isRemoteEscrow && escrowId) releaseEscrow(creditDb, escrowId);
2583
1592
  updateReputation(registryDb, cardId, false, Date.now() - startMs);
2584
1593
  try {
2585
1594
  insertRequestLog(registryDb, {
2586
- id: randomUUID7(),
1595
+ id: randomUUID3(),
2587
1596
  card_id: cardId,
2588
1597
  card_name: cardName,
2589
1598
  skill_id: resolvedSkillId,
@@ -2599,15 +1608,19 @@ function createGatewayServer(opts) {
2599
1608
  return reply.send({
2600
1609
  jsonrpc: "2.0",
2601
1610
  id,
2602
- error: { code: -32603, message }
1611
+ error: {
1612
+ code: -32603,
1613
+ message,
1614
+ ...isRemoteEscrow ? { data: { receipt_released: true } } : {}
1615
+ }
2603
1616
  });
2604
1617
  }
2605
1618
  if (!execResult.success) {
2606
- releaseEscrow(creditDb, escrowId);
1619
+ if (!isRemoteEscrow && escrowId) releaseEscrow(creditDb, escrowId);
2607
1620
  updateReputation(registryDb, cardId, false, execResult.latency_ms);
2608
1621
  try {
2609
1622
  insertRequestLog(registryDb, {
2610
- id: randomUUID7(),
1623
+ id: randomUUID3(),
2611
1624
  card_id: cardId,
2612
1625
  card_name: cardName,
2613
1626
  skill_id: resolvedSkillId,
@@ -2622,14 +1635,22 @@ function createGatewayServer(opts) {
2622
1635
  return reply.send({
2623
1636
  jsonrpc: "2.0",
2624
1637
  id,
2625
- error: { code: -32603, message: execResult.error ?? "Execution failed" }
1638
+ error: {
1639
+ code: -32603,
1640
+ message: execResult.error ?? "Execution failed",
1641
+ ...isRemoteEscrow ? { data: { receipt_released: true } } : {}
1642
+ }
2626
1643
  });
2627
1644
  }
2628
- settleEscrow(creditDb, escrowId, card.owner);
1645
+ if (isRemoteEscrow && receipt) {
1646
+ settleProviderEarning(creditDb, card.owner, receipt);
1647
+ } else if (escrowId) {
1648
+ settleEscrow(creditDb, escrowId, card.owner);
1649
+ }
2629
1650
  updateReputation(registryDb, cardId, true, execResult.latency_ms);
2630
1651
  try {
2631
1652
  insertRequestLog(registryDb, {
2632
- id: randomUUID7(),
1653
+ id: randomUUID3(),
2633
1654
  card_id: cardId,
2634
1655
  card_name: cardName,
2635
1656
  skill_id: resolvedSkillId,
@@ -2641,7 +1662,8 @@ function createGatewayServer(opts) {
2641
1662
  });
2642
1663
  } catch {
2643
1664
  }
2644
- return reply.send({ jsonrpc: "2.0", id, result: execResult.result });
1665
+ const successResult = isRemoteEscrow ? { ...typeof execResult.result === "object" && execResult.result !== null ? execResult.result : { data: execResult.result }, receipt_settled: true, receipt_nonce: receipt.nonce } : execResult.result;
1666
+ return reply.send({ jsonrpc: "2.0", id, result: successResult });
2645
1667
  }
2646
1668
  const controller = new AbortController();
2647
1669
  const timer = setTimeout(() => controller.abort(), timeoutMs);
@@ -2654,11 +1676,11 @@ function createGatewayServer(opts) {
2654
1676
  });
2655
1677
  clearTimeout(timer);
2656
1678
  if (!response.ok) {
2657
- releaseEscrow(creditDb, escrowId);
1679
+ if (!isRemoteEscrow && escrowId) releaseEscrow(creditDb, escrowId);
2658
1680
  updateReputation(registryDb, cardId, false, Date.now() - startMs);
2659
1681
  try {
2660
1682
  insertRequestLog(registryDb, {
2661
- id: randomUUID7(),
1683
+ id: randomUUID3(),
2662
1684
  card_id: cardId,
2663
1685
  card_name: cardName,
2664
1686
  skill_id: resolvedSkillId,
@@ -2673,15 +1695,23 @@ function createGatewayServer(opts) {
2673
1695
  return reply.send({
2674
1696
  jsonrpc: "2.0",
2675
1697
  id,
2676
- error: { code: -32603, message: `Handler returned ${response.status}` }
1698
+ error: {
1699
+ code: -32603,
1700
+ message: `Handler returned ${response.status}`,
1701
+ ...isRemoteEscrow ? { data: { receipt_released: true } } : {}
1702
+ }
2677
1703
  });
2678
1704
  }
2679
1705
  const result = await response.json();
2680
- settleEscrow(creditDb, escrowId, card.owner);
1706
+ if (isRemoteEscrow && receipt) {
1707
+ settleProviderEarning(creditDb, card.owner, receipt);
1708
+ } else if (escrowId) {
1709
+ settleEscrow(creditDb, escrowId, card.owner);
1710
+ }
2681
1711
  updateReputation(registryDb, cardId, true, Date.now() - startMs);
2682
1712
  try {
2683
1713
  insertRequestLog(registryDb, {
2684
- id: randomUUID7(),
1714
+ id: randomUUID3(),
2685
1715
  card_id: cardId,
2686
1716
  card_name: cardName,
2687
1717
  skill_id: resolvedSkillId,
@@ -2693,15 +1723,16 @@ function createGatewayServer(opts) {
2693
1723
  });
2694
1724
  } catch {
2695
1725
  }
2696
- return reply.send({ jsonrpc: "2.0", id, result });
1726
+ const successResult = isRemoteEscrow ? { ...typeof result === "object" && result !== null ? result : { data: result }, receipt_settled: true, receipt_nonce: receipt.nonce } : result;
1727
+ return reply.send({ jsonrpc: "2.0", id, result: successResult });
2697
1728
  } catch (err) {
2698
1729
  clearTimeout(timer);
2699
- releaseEscrow(creditDb, escrowId);
1730
+ if (!isRemoteEscrow && escrowId) releaseEscrow(creditDb, escrowId);
2700
1731
  updateReputation(registryDb, cardId, false, Date.now() - startMs);
2701
1732
  const isTimeout = err instanceof Error && err.name === "AbortError";
2702
1733
  try {
2703
1734
  insertRequestLog(registryDb, {
2704
- id: randomUUID7(),
1735
+ id: randomUUID3(),
2705
1736
  card_id: cardId,
2706
1737
  card_name: cardName,
2707
1738
  skill_id: resolvedSkillId,
@@ -2716,7 +1747,11 @@ function createGatewayServer(opts) {
2716
1747
  return reply.send({
2717
1748
  jsonrpc: "2.0",
2718
1749
  id,
2719
- error: { code: -32603, message: isTimeout ? "Execution timeout" : "Handler error" }
1750
+ error: {
1751
+ code: -32603,
1752
+ message: isTimeout ? "Execution timeout" : "Handler error",
1753
+ ...isRemoteEscrow ? { data: { receipt_released: true } } : {}
1754
+ }
2720
1755
  });
2721
1756
  }
2722
1757
  });
@@ -2727,9 +1762,9 @@ function createGatewayServer(opts) {
2727
1762
  import Fastify2 from "fastify";
2728
1763
  import cors from "@fastify/cors";
2729
1764
  import fastifyStatic from "@fastify/static";
2730
- import { join as join4, dirname } from "path";
1765
+ import { join as join2, dirname } from "path";
2731
1766
  import { fileURLToPath } from "url";
2732
- import { existsSync as existsSync5 } from "fs";
1767
+ import { existsSync as existsSync3 } from "fs";
2733
1768
  function stripInternal(card) {
2734
1769
  const { _internal: _, ...publicCard } = card;
2735
1770
  return publicCard;
@@ -2745,12 +1780,12 @@ function createRegistryServer(opts) {
2745
1780
  const __filename = fileURLToPath(import.meta.url);
2746
1781
  const __dirname = dirname(__filename);
2747
1782
  const hubDistCandidates = [
2748
- join4(__dirname, "../../hub/dist"),
1783
+ join2(__dirname, "../../hub/dist"),
2749
1784
  // When running from dist/registry/server.js
2750
- join4(__dirname, "../../../hub/dist")
1785
+ join2(__dirname, "../../../hub/dist")
2751
1786
  // Fallback for alternative layouts
2752
1787
  ];
2753
- const hubDistDir = hubDistCandidates.find((p) => existsSync5(p));
1788
+ const hubDistDir = hubDistCandidates.find((p) => existsSync3(p));
2754
1789
  if (hubDistDir) {
2755
1790
  void server.register(fastifyStatic, {
2756
1791
  root: hubDistDir,
@@ -3100,10 +2135,10 @@ async function stopAnnouncement() {
3100
2135
  }
3101
2136
 
3102
2137
  // src/openclaw/soul-sync.ts
3103
- import { randomUUID as randomUUID9 } from "crypto";
2138
+ import { randomUUID as randomUUID5 } from "crypto";
3104
2139
 
3105
2140
  // src/skills/publish-capability.ts
3106
- import { randomUUID as randomUUID8 } from "crypto";
2141
+ import { randomUUID as randomUUID4 } from "crypto";
3107
2142
  function parseSoulMd(content) {
3108
2143
  const lines = content.split("\n");
3109
2144
  let name = "";
@@ -3174,7 +2209,7 @@ function parseSoulMdV2(content) {
3174
2209
  const parsed = parseSoulMd(content);
3175
2210
  const skills = parsed.capabilities.map((cap) => {
3176
2211
  const sanitizedId = cap.name.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
3177
- const id = sanitizedId.length > 0 ? sanitizedId : randomUUID9();
2212
+ const id = sanitizedId.length > 0 ? sanitizedId : randomUUID5();
3178
2213
  return {
3179
2214
  id,
3180
2215
  name: cap.name,
@@ -3216,7 +2251,7 @@ function publishFromSoulV2(db, soulContent, owner) {
3216
2251
  (c) => c.spec_version === "2.0"
3217
2252
  );
3218
2253
  const now = (/* @__PURE__ */ new Date()).toISOString();
3219
- const cardId = existingV2?.id ?? randomUUID9();
2254
+ const cardId = existingV2?.id ?? randomUUID5();
3220
2255
  const card = {
3221
2256
  spec_version: "2.0",
3222
2257
  id: cardId,
@@ -3241,7 +2276,7 @@ function publishFromSoulV2(db, soulContent, owner) {
3241
2276
  }
3242
2277
 
3243
2278
  // src/openclaw/heartbeat-writer.ts
3244
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync6 } from "fs";
2279
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync4 } from "fs";
3245
2280
  var HEARTBEAT_MARKER_START = "<!-- agentbnb:start -->";
3246
2281
  var HEARTBEAT_MARKER_END = "<!-- agentbnb:end -->";
3247
2282
  function generateHeartbeatSection(autonomy, budget) {
@@ -3277,11 +2312,11 @@ function generateHeartbeatSection(autonomy, budget) {
3277
2312
  ].join("\n");
3278
2313
  }
3279
2314
  function injectHeartbeatSection(heartbeatPath, section) {
3280
- if (!existsSync6(heartbeatPath)) {
3281
- writeFileSync4(heartbeatPath, section + "\n", "utf-8");
2315
+ if (!existsSync4(heartbeatPath)) {
2316
+ writeFileSync2(heartbeatPath, section + "\n", "utf-8");
3282
2317
  return;
3283
2318
  }
3284
- let content = readFileSync5(heartbeatPath, "utf-8");
2319
+ let content = readFileSync3(heartbeatPath, "utf-8");
3285
2320
  const startIdx = content.indexOf(HEARTBEAT_MARKER_START);
3286
2321
  const endIdx = content.indexOf(HEARTBEAT_MARKER_END);
3287
2322
  if (startIdx !== -1 && endIdx !== -1) {
@@ -3289,7 +2324,7 @@ function injectHeartbeatSection(heartbeatPath, section) {
3289
2324
  } else {
3290
2325
  content = content + "\n" + section + "\n";
3291
2326
  }
3292
- writeFileSync4(heartbeatPath, content, "utf-8");
2327
+ writeFileSync2(heartbeatPath, content, "utf-8");
3293
2328
  }
3294
2329
 
3295
2330
  // src/openclaw/skill.ts
@@ -3356,8 +2391,8 @@ program.command("init").description("Initialize AgentBnB config and create agent
3356
2391
  const owner = opts.owner ?? `agent-${randomBytes(4).toString("hex")}`;
3357
2392
  const token = randomBytes(32).toString("hex");
3358
2393
  const configDir = getConfigDir();
3359
- const dbPath = join5(configDir, "registry.db");
3360
- const creditDbPath = join5(configDir, "credit.db");
2394
+ const dbPath = join3(configDir, "registry.db");
2395
+ const creditDbPath = join3(configDir, "credit.db");
3361
2396
  const port = parseInt(opts.port, 10);
3362
2397
  const ip = opts.host ?? getLanIp();
3363
2398
  const existingConfig = loadConfig();
@@ -3471,7 +2506,7 @@ program.command("publish <card.json>").description("Publish a Capability Card to
3471
2506
  }
3472
2507
  let raw;
3473
2508
  try {
3474
- raw = readFileSync6(cardPath, "utf-8");
2509
+ raw = readFileSync4(cardPath, "utf-8");
3475
2510
  } catch {
3476
2511
  console.error(`Error: cannot read file: ${cardPath}`);
3477
2512
  process.exit(1);
@@ -3622,7 +2657,7 @@ ${discovered.length} agent(s) found on local network`);
3622
2657
  console.log(`
3623
2658
  ${outputCards.length} result(s)`);
3624
2659
  });
3625
- program.command("request [card-id]").description("Request a capability from another agent \u2014 direct (card-id) or auto (--query)").option("--params <json>", "Input parameters as JSON string", "{}").option("--peer <name>", "Peer name to send request to (resolves URL+token from peer registry)").option("--query <text>", "Search query for capability gap (triggers auto-request flow)").option("--max-cost <credits>", "Maximum credits to spend on auto-request (default: 50)").option("--json", "Output as JSON").action(async (cardId, opts) => {
2660
+ program.command("request [card-id]").description("Request a capability from another agent \u2014 direct (card-id) or auto (--query)").option("--params <json>", "Input parameters as JSON string", "{}").option("--peer <name>", "Peer name to send request to (resolves URL+token from peer registry)").option("--skill <id>", "Skill ID within a v2.0 card").option("--cost <credits>", "Credits to commit (required for cross-machine peer requests)").option("--query <text>", "Search query for capability gap (triggers auto-request flow)").option("--max-cost <credits>", "Maximum credits to spend on auto-request (default: 50)").option("--no-receipt", "Skip signed escrow receipt (local-only mode)").option("--json", "Output as JSON").action(async (cardId, opts) => {
3626
2661
  const config = loadConfig();
3627
2662
  if (!config) {
3628
2663
  console.error("Error: not initialized. Run `agentbnb init` first.");
@@ -3638,8 +2673,8 @@ program.command("request [card-id]").description("Request a capability from anot
3638
2673
  process.exit(1);
3639
2674
  }
3640
2675
  }
3641
- const registryDb = openDatabase(join5(getConfigDir(), "registry.db"));
3642
- const creditDb = openCreditDb(join5(getConfigDir(), "credit.db"));
2676
+ const registryDb = openDatabase(join3(getConfigDir(), "registry.db"));
2677
+ const creditDb = openCreditDb(join3(getConfigDir(), "credit.db"));
3643
2678
  registryDb.pragma("busy_timeout = 5000");
3644
2679
  creditDb.pragma("busy_timeout = 5000");
3645
2680
  try {
@@ -3676,6 +2711,7 @@ program.command("request [card-id]").description("Request a capability from anot
3676
2711
  }
3677
2712
  let gatewayUrl;
3678
2713
  let token;
2714
+ const isPeerRequest = !!opts.peer;
3679
2715
  if (opts.peer) {
3680
2716
  const peer = findPeer(opts.peer);
3681
2717
  if (!peer) {
@@ -3688,20 +2724,87 @@ program.command("request [card-id]").description("Request a capability from anot
3688
2724
  gatewayUrl = config.gateway_url;
3689
2725
  token = config.token;
3690
2726
  }
2727
+ const useReceipt = isPeerRequest && opts.receipt !== false;
2728
+ if (useReceipt && !opts.cost) {
2729
+ console.error("Error: --cost <credits> is required for peer requests. Specify the credits to commit.");
2730
+ process.exit(1);
2731
+ }
2732
+ let escrowId;
2733
+ let escrowReceipt;
2734
+ if (useReceipt) {
2735
+ const configDir = getConfigDir();
2736
+ const creditDb = openCreditDb(join3(configDir, "credit.db"));
2737
+ creditDb.pragma("busy_timeout = 5000");
2738
+ try {
2739
+ const keys = loadKeyPair(configDir);
2740
+ const amount = Number(opts.cost);
2741
+ if (isNaN(amount) || amount <= 0) {
2742
+ console.error("Error: --cost must be a positive number.");
2743
+ process.exit(1);
2744
+ }
2745
+ const receiptResult = createSignedEscrowReceipt(creditDb, keys.privateKey, keys.publicKey, {
2746
+ owner: config.owner,
2747
+ amount,
2748
+ cardId,
2749
+ skillId: opts.skill
2750
+ });
2751
+ escrowId = receiptResult.escrowId;
2752
+ escrowReceipt = receiptResult.receipt;
2753
+ if (!opts.json) {
2754
+ console.log(`Escrow: ${amount} credits held (ID: ${escrowId.slice(0, 8)}...)`);
2755
+ }
2756
+ } catch (err) {
2757
+ creditDb.close();
2758
+ const msg = err instanceof Error ? err.message : String(err);
2759
+ if (opts.json) {
2760
+ console.log(JSON.stringify({ success: false, error: msg }, null, 2));
2761
+ } else {
2762
+ console.error(`Error creating escrow receipt: ${msg}`);
2763
+ }
2764
+ process.exit(1);
2765
+ }
2766
+ }
3691
2767
  try {
3692
2768
  const result = await requestCapability({
3693
2769
  gatewayUrl,
3694
2770
  token,
3695
2771
  cardId,
3696
- params
2772
+ params: { ...params, ...opts.skill ? { skill_id: opts.skill } : {} },
2773
+ escrowReceipt
3697
2774
  });
2775
+ if (useReceipt && escrowId) {
2776
+ const configDir = getConfigDir();
2777
+ const creditDb = openCreditDb(join3(configDir, "credit.db"));
2778
+ creditDb.pragma("busy_timeout = 5000");
2779
+ try {
2780
+ settleRequesterEscrow(creditDb, escrowId);
2781
+ if (!opts.json) {
2782
+ console.log(`Escrow settled: ${opts.cost} credits deducted.`);
2783
+ }
2784
+ } finally {
2785
+ creditDb.close();
2786
+ }
2787
+ }
3698
2788
  if (opts.json) {
3699
2789
  console.log(JSON.stringify({ success: true, result }, null, 2));
3700
2790
  } else {
3701
2791
  console.log("Result:");
3702
- console.log(JSON.stringify(result, null, 2));
2792
+ console.log(typeof result === "string" ? result : JSON.stringify(result, null, 2));
3703
2793
  }
3704
2794
  } catch (err) {
2795
+ if (useReceipt && escrowId) {
2796
+ const configDir = getConfigDir();
2797
+ const creditDb = openCreditDb(join3(configDir, "credit.db"));
2798
+ creditDb.pragma("busy_timeout = 5000");
2799
+ try {
2800
+ releaseRequesterEscrow(creditDb, escrowId);
2801
+ if (!opts.json) {
2802
+ console.log("Escrow released: credits refunded.");
2803
+ }
2804
+ } finally {
2805
+ creditDb.close();
2806
+ }
2807
+ }
3705
2808
  const msg = err instanceof Error ? err.message : String(err);
3706
2809
  if (opts.json) {
3707
2810
  console.log(JSON.stringify({ success: false, error: msg }, null, 2));
@@ -3751,7 +2854,7 @@ Active Escrows (${heldEscrows.length}):`);
3751
2854
  }
3752
2855
  }
3753
2856
  });
3754
- program.command("serve").description("Start the AgentBnB gateway server").option("--port <port>", "Port to listen on (overrides config)").option("--handler-url <url>", "Local capability handler URL", "http://localhost:8080").option("--skills-yaml <path>", "Path to skills.yaml (default: ~/.agentbnb/skills.yaml)").option("--registry-port <port>", "Public registry API port (0 to disable)", "7701").option("--announce", "Announce this gateway on the local network via mDNS").action(async (opts) => {
2857
+ program.command("serve").description("Start the AgentBnB gateway server").option("--port <port>", "Port to listen on (overrides config)").option("--handler-url <url>", "Local capability handler URL", "http://localhost:8080").option("--skills-yaml <path>", "Path to skills.yaml (default: ~/.agentbnb/skills.yaml)").option("--registry-port <port>", "Public registry API port (0 to disable)", "7701").option("--conductor", "Enable Conductor orchestration mode").option("--announce", "Announce this gateway on the local network via mDNS").action(async (opts) => {
3755
2858
  const config = loadConfig();
3756
2859
  if (!config) {
3757
2860
  console.error("Error: not initialized. Run `agentbnb init` first.");
@@ -3759,17 +2862,22 @@ program.command("serve").description("Start the AgentBnB gateway server").option
3759
2862
  }
3760
2863
  const port = opts.port ? parseInt(opts.port, 10) : config.gateway_port;
3761
2864
  const registryPort = parseInt(opts.registryPort, 10);
3762
- const skillsYamlPath = opts.skillsYaml ?? join5(homedir2(), ".agentbnb", "skills.yaml");
2865
+ const skillsYamlPath = opts.skillsYaml ?? join3(homedir(), ".agentbnb", "skills.yaml");
3763
2866
  const runtime = new AgentRuntime({
3764
2867
  registryDbPath: config.db_path,
3765
2868
  creditDbPath: config.credit_db_path,
3766
2869
  owner: config.owner,
3767
- skillsYamlPath
2870
+ skillsYamlPath,
2871
+ conductorEnabled: opts.conductor ?? false,
2872
+ conductorToken: config.token
3768
2873
  });
3769
2874
  await runtime.start();
3770
2875
  if (runtime.skillExecutor) {
3771
2876
  console.log(`SkillExecutor initialized from ${skillsYamlPath}`);
3772
2877
  }
2878
+ if (opts.conductor) {
2879
+ console.log("Conductor mode enabled \u2014 orchestrate/plan skills available via gateway");
2880
+ }
3773
2881
  const autonomyConfig = config.autonomy ?? DEFAULT_AUTONOMY_CONFIG;
3774
2882
  const idleMonitor = new IdleMonitor({
3775
2883
  owner: config.owner,
@@ -3985,7 +3093,7 @@ openclaw.command("sync").description("Read SOUL.md and publish/update a v2.0 cap
3985
3093
  }
3986
3094
  let content;
3987
3095
  try {
3988
- content = readFileSync6(opts.soulPath, "utf-8");
3096
+ content = readFileSync4(opts.soulPath, "utf-8");
3989
3097
  } catch {
3990
3098
  console.error(`Error: cannot read SOUL.md at ${opts.soulPath}`);
3991
3099
  process.exit(1);
@@ -4045,4 +3153,38 @@ openclaw.command("rules").description("Print HEARTBEAT.md rules block (or inject
4045
3153
  console.log(section);
4046
3154
  }
4047
3155
  });
3156
+ program.command("conduct <task>").description("Orchestrate a complex task across the AgentBnB network").option("--plan-only", "Show execution plan without executing").option("--max-budget <credits>", "Maximum credits to spend", "100").option("--json", "Output as JSON").action(async (task, opts) => {
3157
+ const { conductAction } = await import("../conduct-JZJS2ZHA.js");
3158
+ const result = await conductAction(task, opts);
3159
+ if (opts.json) {
3160
+ console.log(JSON.stringify(result, null, 2));
3161
+ if (!result.success) process.exit(1);
3162
+ return;
3163
+ }
3164
+ if (!result.success) {
3165
+ console.error(`Error: ${result.error}`);
3166
+ process.exit(1);
3167
+ }
3168
+ const plan = result.plan;
3169
+ console.log("\nExecution Plan:");
3170
+ for (const step of plan.steps) {
3171
+ const deps = step.depends_on.length > 0 ? ` [depends on prior steps]` : "";
3172
+ console.log(` Step ${step.step}: ${step.description} (${step.capability}) -> @${step.agent} (${step.credits} cr)${deps}`);
3173
+ }
3174
+ console.log(` Orchestration fee: ${plan.orchestration_fee} cr`);
3175
+ console.log(` Total estimated: ${plan.estimated_total} cr`);
3176
+ if (result.execution) {
3177
+ console.log("\nResults:");
3178
+ console.log(JSON.stringify(result.execution, null, 2));
3179
+ console.log(`
3180
+ Total credits spent: ${result.total_credits ?? 0} cr`);
3181
+ console.log(`Latency: ${result.latency_ms ?? 0} ms`);
3182
+ }
3183
+ if (result.errors && result.errors.length > 0) {
3184
+ console.log("\nErrors:");
3185
+ for (const err of result.errors) {
3186
+ console.log(` - ${err}`);
3187
+ }
3188
+ }
3189
+ });
4048
3190
  await program.parseAsync(process.argv);