agentbnb 2.2.0 → 3.1.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,709 +1,109 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ executeCapabilityRequest,
4
+ generateKeyPair,
5
+ loadKeyPair,
6
+ releaseRequesterEscrow,
7
+ saveKeyPair,
8
+ settleRequesterEscrow,
9
+ signEscrowReceipt
10
+ } from "../chunk-4Q7D24DP.js";
11
+ import {
12
+ RelayMessageSchema
13
+ } from "../chunk-3Y36WQDV.js";
14
+ import {
15
+ AutoRequestor,
16
+ BudgetManager,
17
+ DEFAULT_AUTONOMY_CONFIG,
18
+ DEFAULT_BUDGET_CONFIG,
19
+ filterCards,
20
+ getAutonomyTier,
21
+ insertAuditEvent,
22
+ interpolateObject,
23
+ listPendingRequests,
24
+ requestCapability,
25
+ resolvePendingRequest,
26
+ searchCards
27
+ } from "../chunk-QVIGMCHA.js";
28
+ import {
29
+ findPeer,
30
+ getConfigDir,
31
+ loadConfig,
32
+ loadPeers,
33
+ removePeer,
34
+ saveConfig,
35
+ savePeer
36
+ } from "../chunk-BEI5MTNZ.js";
37
+ import {
38
+ getActivityFeed,
39
+ getCard,
40
+ getRequestLog,
41
+ getSkillRequestCount,
42
+ insertCard,
43
+ insertRequestLog,
44
+ listCards,
45
+ openDatabase,
46
+ updateCard,
47
+ updateSkillAvailability,
48
+ updateSkillIdleRate
49
+ } from "../chunk-2LLXUKMY.js";
50
+ import {
51
+ bootstrapAgent,
52
+ getBalance,
53
+ getTransactions,
54
+ holdEscrow,
55
+ openCreditDb,
56
+ releaseEscrow
57
+ } from "../chunk-ZJCIBK6O.js";
58
+ import {
59
+ AgentBnBError,
60
+ CapabilityCardSchema,
61
+ CapabilityCardV2Schema
62
+ } from "../chunk-TQMI73LL.js";
2
63
 
3
64
  // src/cli/index.ts
4
65
  import { Command } from "commander";
5
- import { readFileSync as readFileSync6 } from "fs";
66
+ import { readFileSync as readFileSync3 } from "fs";
6
67
  import { createRequire } from "module";
7
68
  import { randomBytes } from "crypto";
8
- import { join as join5 } from "path";
9
- import { networkInterfaces, homedir as homedir2 } from "os";
10
- import { createInterface } from "readline";
11
-
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
- // src/credit/signing.ts
41
- import { generateKeyPairSync, sign, verify, createPublicKey, createPrivateKey } from "crypto";
42
- import { writeFileSync as writeFileSync2, readFileSync as readFileSync2, existsSync as existsSync2, chmodSync } from "fs";
43
69
  import { join as join2 } from "path";
70
+ import { networkInterfaces, homedir } from "os";
71
+ import { createInterface } from "readline";
44
72
 
45
- // src/types/index.ts
73
+ // src/credit/escrow-receipt.ts
46
74
  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()
75
+ import { randomUUID } from "crypto";
76
+ var EscrowReceiptSchema = z.object({
77
+ requester_owner: z.string().min(1),
78
+ requester_public_key: z.string().min(1),
79
+ amount: z.number().positive(),
80
+ card_id: z.string().min(1),
81
+ skill_id: z.string().optional(),
82
+ timestamp: z.string(),
83
+ nonce: z.string().uuid(),
84
+ signature: z.string().min(1)
152
85
  });
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
166
- function generateKeyPair() {
167
- const { publicKey, privateKey } = generateKeyPairSync("ed25519", {
168
- publicKeyEncoding: { type: "spki", format: "der" },
169
- privateKeyEncoding: { type: "pkcs8", format: "der" }
170
- });
171
- return {
172
- publicKey: Buffer.from(publicKey),
173
- privateKey: Buffer.from(privateKey)
86
+ function createSignedEscrowReceipt(db, privateKey, publicKey, opts) {
87
+ const escrowId = holdEscrow(db, opts.owner, opts.amount, opts.cardId);
88
+ const receiptData = {
89
+ requester_owner: opts.owner,
90
+ requester_public_key: publicKey.toString("hex"),
91
+ amount: opts.amount,
92
+ card_id: opts.cardId,
93
+ ...opts.skillId ? { skill_id: opts.skillId } : {},
94
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
95
+ nonce: randomUUID()
174
96
  };
175
- }
176
- function saveKeyPair(configDir, keys) {
177
- const privatePath = join2(configDir, "private.key");
178
- const publicPath = join2(configDir, "public.key");
179
- writeFileSync2(privatePath, keys.privateKey);
180
- chmodSync(privatePath, 384);
181
- writeFileSync2(publicPath, keys.publicKey);
182
- }
183
- function loadKeyPair(configDir) {
184
- const privatePath = join2(configDir, "private.key");
185
- const publicPath = join2(configDir, "public.key");
186
- if (!existsSync2(privatePath) || !existsSync2(publicPath)) {
187
- throw new AgentBnBError("Keypair not found. Run `agentbnb init` to generate one.", "KEYPAIR_NOT_FOUND");
188
- }
189
- return {
190
- publicKey: readFileSync2(publicPath),
191
- privateKey: readFileSync2(privatePath)
97
+ const signature = signEscrowReceipt(receiptData, privateKey);
98
+ const receipt = {
99
+ ...receiptData,
100
+ signature
192
101
  };
193
- }
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;
205
- }
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
- );
102
+ return { escrowId, receipt };
229
103
  }
230
104
 
231
105
  // src/autonomy/idle-monitor.ts
232
106
  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
- `);
259
- 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");
269
- } catch {
270
- }
271
- }
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
-
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)
646
- };
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);
654
- }
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
670
- );
671
- }
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
- );
692
- }
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));
704
- }
705
-
706
- // src/autonomy/idle-monitor.ts
707
107
  var IdleMonitor = class {
708
108
  job;
709
109
  owner;
@@ -795,566 +195,6 @@ var IdleMonitor = class {
795
195
  }
796
196
  };
797
197
 
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
198
  // src/cli/remote-registry.ts
1359
199
  var RegistryTimeoutError = class extends AgentBnBError {
1360
200
  constructor(url) {
@@ -1439,7 +279,7 @@ function mergeResults(localCards, remoteCards, hasQuery) {
1439
279
  }
1440
280
 
1441
281
  // src/cli/onboarding.ts
1442
- import { randomUUID as randomUUID6 } from "crypto";
282
+ import { randomUUID as randomUUID2 } from "crypto";
1443
283
  import { createConnection } from "net";
1444
284
  var KNOWN_API_KEYS = [
1445
285
  "OPENAI_API_KEY",
@@ -1588,7 +428,7 @@ function buildDraftCard(apiKey, owner) {
1588
428
  const now = (/* @__PURE__ */ new Date()).toISOString();
1589
429
  return {
1590
430
  spec_version: "1.0",
1591
- id: randomUUID6(),
431
+ id: randomUUID2(),
1592
432
  owner,
1593
433
  name: template.name,
1594
434
  description: template.description,
@@ -1608,7 +448,7 @@ function buildDraftCard(apiKey, owner) {
1608
448
  }
1609
449
 
1610
450
  // src/runtime/agent-runtime.ts
1611
- import { readFileSync as readFileSync4, existsSync as existsSync4 } from "fs";
451
+ import { readFileSync, existsSync } from "fs";
1612
452
 
1613
453
  // src/skills/executor.ts
1614
454
  var SkillExecutor = class {
@@ -1766,17 +606,29 @@ var CommandSkillConfigSchema = z2.object({
1766
606
  timeout_ms: z2.number().positive().default(3e4),
1767
607
  pricing: PricingSchema
1768
608
  });
609
+ var ConductorSkillConfigSchema = z2.object({
610
+ id: z2.string().min(1),
611
+ type: z2.literal("conductor"),
612
+ name: z2.string().min(1),
613
+ conductor_skill: z2.enum(["orchestrate", "plan"]),
614
+ pricing: PricingSchema,
615
+ timeout_ms: z2.number().positive().optional()
616
+ });
1769
617
  var SkillConfigSchema = z2.discriminatedUnion("type", [
1770
618
  ApiSkillConfigSchema,
1771
619
  PipelineSkillConfigSchema,
1772
620
  OpenClawSkillConfigSchema,
1773
- CommandSkillConfigSchema
621
+ CommandSkillConfigSchema,
622
+ ConductorSkillConfigSchema
1774
623
  ]);
1775
624
  var SkillsFileSchema = z2.object({
1776
625
  skills: z2.array(SkillConfigSchema)
1777
626
  });
1778
627
  function expandEnvVars(value) {
1779
628
  return value.replace(/\$\{([^}]+)\}/g, (_match, varName) => {
629
+ if (/[.a-z]/.test(varName)) {
630
+ return _match;
631
+ }
1780
632
  const envValue = process.env[varName];
1781
633
  if (envValue === void 0) {
1782
634
  throw new Error(`Environment variable "${varName}" is not defined`);
@@ -1867,7 +719,7 @@ function applyInputMapping(params, mapping) {
1867
719
  pathParams[key] = String(value);
1868
720
  break;
1869
721
  case "header":
1870
- headers[key] = String(value);
722
+ headers[key] = String(value).replace(/[\r\n]/g, "");
1871
723
  break;
1872
724
  }
1873
725
  }
@@ -1962,58 +814,34 @@ var ApiExecutor = class {
1962
814
  };
1963
815
 
1964
816
  // src/skills/pipeline-executor.ts
1965
- import { exec } from "child_process";
817
+ import { execFile } from "child_process";
1966
818
  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);
819
+ var execFileAsync = promisify(execFile);
820
+ function shellEscape(value) {
821
+ return "'" + value.replace(/'/g, "'\\''") + "'";
822
+ }
823
+ function safeInterpolateCommand(template, context) {
824
+ return template.replace(/\$\{([^}]+)\}/g, (_match, expr) => {
825
+ const parts = expr.split(".");
826
+ let current = context;
827
+ for (const part of parts) {
828
+ if (current === null || typeof current !== "object") return "";
829
+ const bracketMatch = part.match(/^(\w+)\[(\d+)\]$/);
830
+ if (bracketMatch) {
831
+ current = current[bracketMatch[1]];
832
+ if (Array.isArray(current)) {
833
+ current = current[parseInt(bracketMatch[2], 10)];
834
+ } else {
835
+ return "";
836
+ }
837
+ } else {
838
+ current = current[part];
839
+ }
1991
840
  }
1992
- return String(resolved);
841
+ if (current === void 0 || current === null) return "";
842
+ return shellEscape(String(current));
1993
843
  });
1994
844
  }
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
845
  var PipelineExecutor = class {
2018
846
  /**
2019
847
  * @param skillExecutor - The parent SkillExecutor used to dispatch sub-skill calls.
@@ -2074,12 +902,12 @@ var PipelineExecutor = class {
2074
902
  }
2075
903
  stepResult = subResult.result;
2076
904
  } else if ("command" in step && step.command) {
2077
- const interpolatedCommand = interpolate(
905
+ const interpolatedCommand = safeInterpolateCommand(
2078
906
  step.command,
2079
907
  context
2080
908
  );
2081
909
  try {
2082
- const { stdout } = await execAsync(interpolatedCommand, { timeout: 3e4 });
910
+ const { stdout } = await execFileAsync("/bin/sh", ["-c", interpolatedCommand], { timeout: 3e4 });
2083
911
  stepResult = stdout.trim();
2084
912
  } catch (err) {
2085
913
  const message = err instanceof Error ? err.message : String(err);
@@ -2106,7 +934,7 @@ var PipelineExecutor = class {
2106
934
  };
2107
935
 
2108
936
  // src/skills/openclaw-bridge.ts
2109
- import { execSync } from "child_process";
937
+ import { execFileSync } from "child_process";
2110
938
  var DEFAULT_BASE_URL = "http://localhost:3000";
2111
939
  var DEFAULT_TIMEOUT_MS = 6e4;
2112
940
  function buildPayload(config, params) {
@@ -2151,12 +979,22 @@ async function executeWebhook(config, payload) {
2151
979
  clearTimeout(timer);
2152
980
  }
2153
981
  }
982
+ function validateAgentName(name) {
983
+ return /^[a-zA-Z0-9._-]+$/.test(name);
984
+ }
2154
985
  function executeProcess(config, payload) {
2155
986
  const timeoutMs = config.timeout_ms ?? DEFAULT_TIMEOUT_MS;
987
+ if (!validateAgentName(config.agent_name)) {
988
+ return {
989
+ success: false,
990
+ error: `Invalid agent name: "${config.agent_name}" (only alphanumeric, hyphens, underscores, dots allowed)`
991
+ };
992
+ }
2156
993
  const inputJson = JSON.stringify(payload);
2157
- const cmd = `openclaw run ${config.agent_name} --input '${inputJson}'`;
2158
994
  try {
2159
- const stdout = execSync(cmd, { timeout: timeoutMs });
995
+ const stdout = execFileSync("openclaw", ["run", config.agent_name, "--input", inputJson], {
996
+ timeout: timeoutMs
997
+ });
2160
998
  const text = stdout.toString().trim();
2161
999
  const result = JSON.parse(text);
2162
1000
  return { success: true, result };
@@ -2229,10 +1067,35 @@ var OpenClawBridge = class {
2229
1067
  };
2230
1068
 
2231
1069
  // src/skills/command-executor.ts
2232
- import { exec as exec2 } from "child_process";
2233
- function execAsync2(command, options) {
1070
+ import { execFile as execFile2 } from "child_process";
1071
+ function shellEscape2(value) {
1072
+ return "'" + value.replace(/'/g, "'\\''") + "'";
1073
+ }
1074
+ function safeInterpolateCommand2(template, context) {
1075
+ return template.replace(/\$\{([^}]+)\}/g, (_match, expr) => {
1076
+ const parts = expr.split(".");
1077
+ let current = context;
1078
+ for (const part of parts) {
1079
+ if (current === null || typeof current !== "object") return "";
1080
+ const bracketMatch = part.match(/^(\w+)\[(\d+)\]$/);
1081
+ if (bracketMatch) {
1082
+ current = current[bracketMatch[1]];
1083
+ if (Array.isArray(current)) {
1084
+ current = current[parseInt(bracketMatch[2], 10)];
1085
+ } else {
1086
+ return "";
1087
+ }
1088
+ } else {
1089
+ current = current[part];
1090
+ }
1091
+ }
1092
+ if (current === void 0 || current === null) return "";
1093
+ return shellEscape2(String(current));
1094
+ });
1095
+ }
1096
+ function execFileAsync2(file, args, options) {
2234
1097
  return new Promise((resolve, reject) => {
2235
- exec2(command, options, (error, stdout, stderr) => {
1098
+ execFile2(file, args, options, (error, stdout, stderr) => {
2236
1099
  const stdoutStr = typeof stdout === "string" ? stdout : stdout.toString();
2237
1100
  const stderrStr = typeof stderr === "string" ? stderr : stderr.toString();
2238
1101
  if (error) {
@@ -2269,17 +1132,16 @@ var CommandExecutor = class {
2269
1132
  };
2270
1133
  }
2271
1134
  }
2272
- const interpolatedCommand = interpolate(cmdConfig.command, { params });
1135
+ const interpolatedCommand = safeInterpolateCommand2(cmdConfig.command, { params });
2273
1136
  const timeout = cmdConfig.timeout_ms ?? 3e4;
2274
1137
  const cwd = cmdConfig.working_dir ?? process.cwd();
2275
1138
  let stdout;
2276
1139
  try {
2277
- const result = await execAsync2(interpolatedCommand, {
1140
+ const result = await execFileAsync2("/bin/sh", ["-c", interpolatedCommand], {
2278
1141
  timeout,
2279
1142
  cwd,
2280
- maxBuffer: 10 * 1024 * 1024,
1143
+ maxBuffer: 10 * 1024 * 1024
2281
1144
  // 10 MB
2282
- shell: "/bin/sh"
2283
1145
  });
2284
1146
  stdout = result.stdout;
2285
1147
  } catch (err) {
@@ -2346,6 +1208,8 @@ var AgentRuntime = class {
2346
1208
  draining = false;
2347
1209
  orphanedEscrowAgeMinutes;
2348
1210
  skillsYamlPath;
1211
+ conductorEnabled;
1212
+ conductorToken;
2349
1213
  /**
2350
1214
  * Creates a new AgentRuntime instance.
2351
1215
  * Opens both databases with WAL mode, foreign_keys=ON, and busy_timeout=5000.
@@ -2357,6 +1221,8 @@ var AgentRuntime = class {
2357
1221
  this.owner = options.owner;
2358
1222
  this.orphanedEscrowAgeMinutes = options.orphanedEscrowAgeMinutes ?? 10;
2359
1223
  this.skillsYamlPath = options.skillsYamlPath;
1224
+ this.conductorEnabled = options.conductorEnabled ?? false;
1225
+ this.conductorToken = options.conductorToken ?? "";
2360
1226
  this.registryDb = openDatabase(options.registryDbPath);
2361
1227
  this.creditDb = openCreditDb(options.creditDbPath);
2362
1228
  this.registryDb.pragma("busy_timeout = 5000");
@@ -2393,18 +1259,70 @@ var AgentRuntime = class {
2393
1259
  * 3. Populate the Map with all 4 modes — SkillExecutor sees them via reference.
2394
1260
  */
2395
1261
  async initSkillExecutor() {
2396
- if (!this.skillsYamlPath || !existsSync4(this.skillsYamlPath)) {
1262
+ const hasSkillsYaml = this.skillsYamlPath && existsSync(this.skillsYamlPath);
1263
+ if (!hasSkillsYaml && !this.conductorEnabled) {
2397
1264
  return;
2398
1265
  }
2399
- const yamlContent = readFileSync4(this.skillsYamlPath, "utf8");
2400
- const configs = parseSkillsFile(yamlContent);
1266
+ let configs = [];
1267
+ if (hasSkillsYaml) {
1268
+ const yamlContent = readFileSync(this.skillsYamlPath, "utf8");
1269
+ configs = parseSkillsFile(yamlContent);
1270
+ }
2401
1271
  const modes = /* @__PURE__ */ new Map();
1272
+ if (this.conductorEnabled) {
1273
+ const { ConductorMode } = await import("../conductor-mode-CF6PSRRA.js");
1274
+ const { registerConductorCard, CONDUCTOR_OWNER } = await import("../card-P5C36VBD.js");
1275
+ const { loadPeers: loadPeers2 } = await import("../peers-G36URZYB.js");
1276
+ registerConductorCard(this.registryDb);
1277
+ const resolveAgentUrl = (owner) => {
1278
+ const peers = loadPeers2();
1279
+ const peer = peers.find((p) => p.name.toLowerCase() === owner.toLowerCase());
1280
+ if (!peer) {
1281
+ throw new Error(
1282
+ `No peer found for agent owner "${owner}". Add with: agentbnb peers add ${owner} <url> <token>`
1283
+ );
1284
+ }
1285
+ const stmt = this.registryDb.prepare(
1286
+ "SELECT id FROM capability_cards WHERE owner = ? LIMIT 1"
1287
+ );
1288
+ const row = stmt.get(owner);
1289
+ const cardId = row?.id ?? owner;
1290
+ return { url: peer.url, cardId };
1291
+ };
1292
+ const conductorMode = new ConductorMode({
1293
+ db: this.registryDb,
1294
+ creditDb: this.creditDb,
1295
+ conductorOwner: CONDUCTOR_OWNER,
1296
+ gatewayToken: this.conductorToken,
1297
+ resolveAgentUrl,
1298
+ maxBudget: 100
1299
+ });
1300
+ modes.set("conductor", conductorMode);
1301
+ configs.push(
1302
+ {
1303
+ id: "orchestrate",
1304
+ type: "conductor",
1305
+ name: "Orchestrate",
1306
+ conductor_skill: "orchestrate",
1307
+ pricing: { credits_per_call: 5 }
1308
+ },
1309
+ {
1310
+ id: "plan",
1311
+ type: "conductor",
1312
+ name: "Plan",
1313
+ conductor_skill: "plan",
1314
+ pricing: { credits_per_call: 1 }
1315
+ }
1316
+ );
1317
+ }
2402
1318
  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());
1319
+ if (hasSkillsYaml) {
1320
+ const pipelineExecutor = new PipelineExecutor(executor);
1321
+ modes.set("api", new ApiExecutor());
1322
+ modes.set("pipeline", pipelineExecutor);
1323
+ modes.set("openclaw", new OpenClawBridge());
1324
+ modes.set("command", new CommandExecutor());
1325
+ }
2408
1326
  this.skillExecutor = executor;
2409
1327
  }
2410
1328
  /**
@@ -2459,7 +1377,6 @@ var AgentRuntime = class {
2459
1377
 
2460
1378
  // src/gateway/server.ts
2461
1379
  import Fastify from "fastify";
2462
- import { randomUUID as randomUUID7 } from "crypto";
2463
1380
  var VERSION = "0.0.1";
2464
1381
  function createGatewayServer(opts) {
2465
1382
  const {
@@ -2523,213 +1440,282 @@ function createGatewayServer(opts) {
2523
1440
  error: { code: -32602, message: "Invalid params: card_id required" }
2524
1441
  });
2525
1442
  }
2526
- const card = getCard(registryDb, cardId);
2527
- if (!card) {
2528
- return reply.send({
2529
- jsonrpc: "2.0",
2530
- id,
2531
- error: { code: -32602, message: `Card not found: ${cardId}` }
2532
- });
2533
- }
2534
1443
  const requester = params.requester ?? "unknown";
2535
- let creditsNeeded;
2536
- let cardName;
2537
- let resolvedSkillId;
2538
- const rawCard = card;
2539
- if (Array.isArray(rawCard["skills"])) {
2540
- const v2card = card;
2541
- const skill = skillId ? v2card.skills.find((s) => s.id === skillId) : v2card.skills[0];
2542
- if (!skill) {
2543
- return reply.send({
2544
- jsonrpc: "2.0",
2545
- id,
2546
- error: { code: -32602, message: `Skill not found: ${skillId}` }
2547
- });
2548
- }
2549
- creditsNeeded = skill.pricing.credits_per_call;
2550
- cardName = skill.name;
2551
- resolvedSkillId = skill.id;
1444
+ const receipt = params.escrow_receipt;
1445
+ const result = await executeCapabilityRequest({
1446
+ registryDb,
1447
+ creditDb,
1448
+ cardId,
1449
+ skillId,
1450
+ params,
1451
+ requester,
1452
+ escrowReceipt: receipt,
1453
+ skillExecutor,
1454
+ handlerUrl,
1455
+ timeoutMs
1456
+ });
1457
+ if (result.success) {
1458
+ return reply.send({ jsonrpc: "2.0", id, result: result.result });
2552
1459
  } else {
2553
- creditsNeeded = card.pricing.credits_per_call;
2554
- cardName = card.name;
1460
+ return reply.send({ jsonrpc: "2.0", id, error: result.error });
2555
1461
  }
2556
- let escrowId;
1462
+ });
1463
+ return fastify;
1464
+ }
1465
+
1466
+ // src/registry/server.ts
1467
+ import Fastify2 from "fastify";
1468
+ import cors from "@fastify/cors";
1469
+ import fastifyStatic from "@fastify/static";
1470
+ import fastifyWebsocket from "@fastify/websocket";
1471
+ import { join, dirname } from "path";
1472
+ import { fileURLToPath } from "url";
1473
+ import { existsSync as existsSync2 } from "fs";
1474
+
1475
+ // src/relay/websocket-relay.ts
1476
+ import { randomUUID as randomUUID3 } from "crypto";
1477
+ var RATE_LIMIT_MAX = 60;
1478
+ var RATE_LIMIT_WINDOW_MS = 6e4;
1479
+ var RELAY_TIMEOUT_MS = 3e4;
1480
+ function registerWebSocketRelay(server, db) {
1481
+ const connections = /* @__PURE__ */ new Map();
1482
+ const pendingRequests = /* @__PURE__ */ new Map();
1483
+ const rateLimits = /* @__PURE__ */ new Map();
1484
+ function checkRateLimit(owner) {
1485
+ const now = Date.now();
1486
+ const entry = rateLimits.get(owner);
1487
+ if (!entry || now >= entry.resetTime) {
1488
+ rateLimits.set(owner, { count: 1, resetTime: now + RATE_LIMIT_WINDOW_MS });
1489
+ return true;
1490
+ }
1491
+ if (entry.count >= RATE_LIMIT_MAX) {
1492
+ return false;
1493
+ }
1494
+ entry.count++;
1495
+ return true;
1496
+ }
1497
+ function markOwnerOffline(owner) {
2557
1498
  try {
2558
- const balance = getBalance(creditDb, requester);
2559
- if (balance < creditsNeeded) {
2560
- return reply.send({
2561
- jsonrpc: "2.0",
2562
- id,
2563
- error: { code: -32603, message: "Insufficient credits" }
2564
- });
2565
- }
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
- }
2575
- const startMs = Date.now();
2576
- if (skillExecutor) {
2577
- const targetSkillId = resolvedSkillId ?? skillId ?? cardId;
2578
- let execResult;
2579
- try {
2580
- execResult = await skillExecutor.execute(targetSkillId, params);
2581
- } catch (err) {
2582
- releaseEscrow(creditDb, escrowId);
2583
- updateReputation(registryDb, cardId, false, Date.now() - startMs);
1499
+ const stmt = db.prepare("SELECT id, data FROM capability_cards WHERE owner = ?");
1500
+ const rows = stmt.all(owner);
1501
+ for (const row of rows) {
2584
1502
  try {
2585
- insertRequestLog(registryDb, {
2586
- id: randomUUID7(),
2587
- card_id: cardId,
2588
- card_name: cardName,
2589
- skill_id: resolvedSkillId,
2590
- requester,
2591
- status: "failure",
2592
- latency_ms: Date.now() - startMs,
2593
- credits_charged: 0,
2594
- created_at: (/* @__PURE__ */ new Date()).toISOString()
2595
- });
1503
+ const card = JSON.parse(row.data);
1504
+ if (card.availability?.online) {
1505
+ card.availability.online = false;
1506
+ card.updated_at = (/* @__PURE__ */ new Date()).toISOString();
1507
+ const updateStmt = db.prepare("UPDATE capability_cards SET data = ?, updated_at = ? WHERE id = ?");
1508
+ updateStmt.run(JSON.stringify(card), card.updated_at, row.id);
1509
+ }
2596
1510
  } catch {
2597
1511
  }
2598
- const message = err instanceof Error ? err.message : "Execution error";
2599
- return reply.send({
2600
- jsonrpc: "2.0",
2601
- id,
2602
- error: { code: -32603, message }
2603
- });
2604
1512
  }
2605
- if (!execResult.success) {
2606
- releaseEscrow(creditDb, escrowId);
2607
- updateReputation(registryDb, cardId, false, execResult.latency_ms);
1513
+ } catch {
1514
+ }
1515
+ }
1516
+ function markOwnerOnline(owner) {
1517
+ try {
1518
+ const stmt = db.prepare("SELECT id, data FROM capability_cards WHERE owner = ?");
1519
+ const rows = stmt.all(owner);
1520
+ for (const row of rows) {
2608
1521
  try {
2609
- insertRequestLog(registryDb, {
2610
- id: randomUUID7(),
2611
- card_id: cardId,
2612
- card_name: cardName,
2613
- skill_id: resolvedSkillId,
2614
- requester,
2615
- status: "failure",
2616
- latency_ms: execResult.latency_ms,
2617
- credits_charged: 0,
2618
- created_at: (/* @__PURE__ */ new Date()).toISOString()
2619
- });
1522
+ const card = JSON.parse(row.data);
1523
+ card.availability = { ...card.availability, online: true };
1524
+ card.updated_at = (/* @__PURE__ */ new Date()).toISOString();
1525
+ const updateStmt = db.prepare("UPDATE capability_cards SET data = ?, updated_at = ? WHERE id = ?");
1526
+ updateStmt.run(JSON.stringify(card), card.updated_at, row.id);
2620
1527
  } catch {
2621
1528
  }
2622
- return reply.send({
2623
- jsonrpc: "2.0",
2624
- id,
2625
- error: { code: -32603, message: execResult.error ?? "Execution failed" }
2626
- });
2627
1529
  }
2628
- settleEscrow(creditDb, escrowId, card.owner);
2629
- updateReputation(registryDb, cardId, true, execResult.latency_ms);
1530
+ } catch {
1531
+ }
1532
+ }
1533
+ function upsertCard(cardData, owner) {
1534
+ const cardId = cardData.id;
1535
+ const existing = getCard(db, cardId);
1536
+ if (existing) {
1537
+ const updates = { ...cardData, availability: { ...cardData.availability ?? {}, online: true } };
1538
+ updateCard(db, cardId, owner, updates);
1539
+ } else {
1540
+ const card = { ...cardData, availability: { ...cardData.availability ?? {}, online: true } };
1541
+ insertCard(db, card);
1542
+ }
1543
+ return cardId;
1544
+ }
1545
+ function logAgentJoined(owner, cardName, cardId) {
1546
+ try {
1547
+ insertRequestLog(db, {
1548
+ id: randomUUID3(),
1549
+ card_id: cardId,
1550
+ card_name: cardName,
1551
+ requester: owner,
1552
+ status: "success",
1553
+ latency_ms: 0,
1554
+ credits_charged: 0,
1555
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
1556
+ action_type: "agent_joined"
1557
+ });
1558
+ } catch {
1559
+ }
1560
+ }
1561
+ function sendMessage(ws, msg) {
1562
+ if (ws.readyState === 1) {
1563
+ ws.send(JSON.stringify(msg));
1564
+ }
1565
+ }
1566
+ function handleRegister(ws, msg) {
1567
+ const { owner, card } = msg;
1568
+ const existing = connections.get(owner);
1569
+ if (existing && existing !== ws) {
2630
1570
  try {
2631
- insertRequestLog(registryDb, {
2632
- id: randomUUID7(),
2633
- card_id: cardId,
2634
- card_name: cardName,
2635
- skill_id: resolvedSkillId,
2636
- requester,
2637
- status: "success",
2638
- latency_ms: execResult.latency_ms,
2639
- credits_charged: creditsNeeded,
2640
- created_at: (/* @__PURE__ */ new Date()).toISOString()
2641
- });
1571
+ existing.close(1e3, "Replaced by new connection");
2642
1572
  } catch {
2643
1573
  }
2644
- return reply.send({ jsonrpc: "2.0", id, result: execResult.result });
2645
1574
  }
2646
- const controller = new AbortController();
2647
- const timer = setTimeout(() => controller.abort(), timeoutMs);
2648
- try {
2649
- const response = await fetch(handlerUrl, {
2650
- method: "POST",
2651
- headers: { "Content-Type": "application/json" },
2652
- body: JSON.stringify({ card_id: cardId, skill_id: resolvedSkillId, params }),
2653
- signal: controller.signal
1575
+ connections.set(owner, ws);
1576
+ const cardId = upsertCard(card, owner);
1577
+ const cardName = card.name ?? card.agent_name ?? owner;
1578
+ logAgentJoined(owner, cardName, cardId);
1579
+ markOwnerOnline(owner);
1580
+ sendMessage(ws, { type: "registered", agent_id: cardId });
1581
+ }
1582
+ function handleRelayRequest(ws, msg, fromOwner) {
1583
+ if (!checkRateLimit(fromOwner)) {
1584
+ sendMessage(ws, {
1585
+ type: "error",
1586
+ code: "rate_limited",
1587
+ message: `Rate limit exceeded: max ${RATE_LIMIT_MAX} requests per minute`,
1588
+ request_id: msg.id
2654
1589
  });
2655
- clearTimeout(timer);
2656
- if (!response.ok) {
2657
- releaseEscrow(creditDb, escrowId);
2658
- updateReputation(registryDb, cardId, false, Date.now() - startMs);
2659
- try {
2660
- insertRequestLog(registryDb, {
2661
- id: randomUUID7(),
2662
- card_id: cardId,
2663
- card_name: cardName,
2664
- skill_id: resolvedSkillId,
2665
- requester,
2666
- status: "failure",
2667
- latency_ms: Date.now() - startMs,
2668
- credits_charged: 0,
2669
- created_at: (/* @__PURE__ */ new Date()).toISOString()
2670
- });
2671
- } catch {
2672
- }
2673
- return reply.send({
2674
- jsonrpc: "2.0",
2675
- id,
2676
- error: { code: -32603, message: `Handler returned ${response.status}` }
2677
- });
1590
+ return;
1591
+ }
1592
+ const targetWs = connections.get(msg.target_owner);
1593
+ if (!targetWs || targetWs.readyState !== 1) {
1594
+ sendMessage(ws, {
1595
+ type: "response",
1596
+ id: msg.id,
1597
+ error: { code: -32603, message: `Agent offline: ${msg.target_owner}` }
1598
+ });
1599
+ return;
1600
+ }
1601
+ const timeout = setTimeout(() => {
1602
+ pendingRequests.delete(msg.id);
1603
+ sendMessage(ws, {
1604
+ type: "response",
1605
+ id: msg.id,
1606
+ error: { code: -32603, message: "Relay request timeout" }
1607
+ });
1608
+ }, RELAY_TIMEOUT_MS);
1609
+ pendingRequests.set(msg.id, { originOwner: fromOwner, timeout });
1610
+ sendMessage(targetWs, {
1611
+ type: "incoming_request",
1612
+ id: msg.id,
1613
+ from_owner: fromOwner,
1614
+ card_id: msg.card_id,
1615
+ skill_id: msg.skill_id,
1616
+ params: msg.params,
1617
+ requester: msg.requester ?? fromOwner,
1618
+ escrow_receipt: msg.escrow_receipt
1619
+ });
1620
+ }
1621
+ function handleRelayResponse(msg) {
1622
+ const pending = pendingRequests.get(msg.id);
1623
+ if (!pending) return;
1624
+ clearTimeout(pending.timeout);
1625
+ pendingRequests.delete(msg.id);
1626
+ const originWs = connections.get(pending.originOwner);
1627
+ if (originWs && originWs.readyState === 1) {
1628
+ sendMessage(originWs, {
1629
+ type: "response",
1630
+ id: msg.id,
1631
+ result: msg.result,
1632
+ error: msg.error
1633
+ });
1634
+ }
1635
+ }
1636
+ function handleDisconnect(owner) {
1637
+ if (!owner) return;
1638
+ connections.delete(owner);
1639
+ rateLimits.delete(owner);
1640
+ markOwnerOffline(owner);
1641
+ for (const [reqId, pending] of pendingRequests) {
1642
+ if (pending.originOwner === owner) {
1643
+ clearTimeout(pending.timeout);
1644
+ pendingRequests.delete(reqId);
2678
1645
  }
2679
- const result = await response.json();
2680
- settleEscrow(creditDb, escrowId, card.owner);
2681
- updateReputation(registryDb, cardId, true, Date.now() - startMs);
1646
+ }
1647
+ }
1648
+ server.get("/ws", { websocket: true }, (socket) => {
1649
+ let registeredOwner;
1650
+ socket.on("message", (raw) => {
1651
+ let data;
2682
1652
  try {
2683
- insertRequestLog(registryDb, {
2684
- id: randomUUID7(),
2685
- card_id: cardId,
2686
- card_name: cardName,
2687
- skill_id: resolvedSkillId,
2688
- requester,
2689
- status: "success",
2690
- latency_ms: Date.now() - startMs,
2691
- credits_charged: creditsNeeded,
2692
- created_at: (/* @__PURE__ */ new Date()).toISOString()
2693
- });
1653
+ data = JSON.parse(typeof raw === "string" ? raw : raw.toString("utf-8"));
2694
1654
  } catch {
1655
+ sendMessage(socket, { type: "error", code: "invalid_json", message: "Invalid JSON" });
1656
+ return;
2695
1657
  }
2696
- return reply.send({ jsonrpc: "2.0", id, result });
2697
- } catch (err) {
2698
- clearTimeout(timer);
2699
- releaseEscrow(creditDb, escrowId);
2700
- updateReputation(registryDb, cardId, false, Date.now() - startMs);
2701
- const isTimeout = err instanceof Error && err.name === "AbortError";
2702
- try {
2703
- insertRequestLog(registryDb, {
2704
- id: randomUUID7(),
2705
- card_id: cardId,
2706
- card_name: cardName,
2707
- skill_id: resolvedSkillId,
2708
- requester,
2709
- status: isTimeout ? "timeout" : "failure",
2710
- latency_ms: Date.now() - startMs,
2711
- credits_charged: 0,
2712
- created_at: (/* @__PURE__ */ new Date()).toISOString()
1658
+ const parsed = RelayMessageSchema.safeParse(data);
1659
+ if (!parsed.success) {
1660
+ sendMessage(socket, {
1661
+ type: "error",
1662
+ code: "invalid_message",
1663
+ message: `Invalid message: ${parsed.error.issues[0]?.message ?? "unknown error"}`
2713
1664
  });
2714
- } catch {
1665
+ return;
2715
1666
  }
2716
- return reply.send({
2717
- jsonrpc: "2.0",
2718
- id,
2719
- error: { code: -32603, message: isTimeout ? "Execution timeout" : "Handler error" }
2720
- });
2721
- }
1667
+ const msg = parsed.data;
1668
+ switch (msg.type) {
1669
+ case "register":
1670
+ registeredOwner = msg.owner;
1671
+ handleRegister(socket, msg);
1672
+ break;
1673
+ case "relay_request":
1674
+ if (!registeredOwner) {
1675
+ sendMessage(socket, {
1676
+ type: "error",
1677
+ code: "not_registered",
1678
+ message: "Must send register message before relay requests"
1679
+ });
1680
+ return;
1681
+ }
1682
+ handleRelayRequest(socket, msg, registeredOwner);
1683
+ break;
1684
+ case "relay_response":
1685
+ handleRelayResponse(msg);
1686
+ break;
1687
+ default:
1688
+ break;
1689
+ }
1690
+ });
1691
+ socket.on("close", () => {
1692
+ handleDisconnect(registeredOwner);
1693
+ });
1694
+ socket.on("error", () => {
1695
+ handleDisconnect(registeredOwner);
1696
+ });
2722
1697
  });
2723
- return fastify;
1698
+ return {
1699
+ getOnlineCount: () => connections.size,
1700
+ getOnlineOwners: () => Array.from(connections.keys()),
1701
+ shutdown: () => {
1702
+ for (const [, ws] of connections) {
1703
+ try {
1704
+ ws.close(1001, "Server shutting down");
1705
+ } catch {
1706
+ }
1707
+ }
1708
+ connections.clear();
1709
+ for (const [, pending] of pendingRequests) {
1710
+ clearTimeout(pending.timeout);
1711
+ }
1712
+ pendingRequests.clear();
1713
+ rateLimits.clear();
1714
+ }
1715
+ };
2724
1716
  }
2725
1717
 
2726
1718
  // src/registry/server.ts
2727
- import Fastify2 from "fastify";
2728
- import cors from "@fastify/cors";
2729
- import fastifyStatic from "@fastify/static";
2730
- import { join as join4, dirname } from "path";
2731
- import { fileURLToPath } from "url";
2732
- import { existsSync as existsSync5 } from "fs";
2733
1719
  function stripInternal(card) {
2734
1720
  const { _internal: _, ...publicCard } = card;
2735
1721
  return publicCard;
@@ -2742,15 +1728,20 @@ function createRegistryServer(opts) {
2742
1728
  methods: ["GET", "POST", "PATCH", "OPTIONS"],
2743
1729
  allowedHeaders: ["Content-Type", "Authorization"]
2744
1730
  });
1731
+ void server.register(fastifyWebsocket);
1732
+ let relayState = null;
1733
+ if (opts.creditDb) {
1734
+ relayState = registerWebSocketRelay(server, db);
1735
+ }
2745
1736
  const __filename = fileURLToPath(import.meta.url);
2746
1737
  const __dirname = dirname(__filename);
2747
1738
  const hubDistCandidates = [
2748
- join4(__dirname, "../../hub/dist"),
1739
+ join(__dirname, "../../hub/dist"),
2749
1740
  // When running from dist/registry/server.js
2750
- join4(__dirname, "../../../hub/dist")
1741
+ join(__dirname, "../../../hub/dist")
2751
1742
  // Fallback for alternative layouts
2752
1743
  ];
2753
- const hubDistDir = hubDistCandidates.find((p) => existsSync5(p));
1744
+ const hubDistDir = hubDistCandidates.find((p) => existsSync2(p));
2754
1745
  if (hubDistDir) {
2755
1746
  void server.register(fastifyStatic, {
2756
1747
  root: hubDistDir,
@@ -2920,6 +1911,29 @@ function createRegistryServer(opts) {
2920
1911
  const items = getActivityFeed(db, limit, since);
2921
1912
  return reply.send({ items, total: items.length, limit });
2922
1913
  });
1914
+ server.get("/api/stats", async (_request, reply) => {
1915
+ const allCards = listCards(db);
1916
+ const onlineOwners = /* @__PURE__ */ new Set();
1917
+ if (relayState) {
1918
+ for (const owner of relayState.getOnlineOwners()) {
1919
+ onlineOwners.add(owner);
1920
+ }
1921
+ }
1922
+ for (const card of allCards) {
1923
+ if (card.availability.online) {
1924
+ onlineOwners.add(card.owner);
1925
+ }
1926
+ }
1927
+ const exchangeStmt = db.prepare(
1928
+ "SELECT COUNT(*) as count FROM request_log WHERE status = 'success' AND (action_type IS NULL OR action_type = 'auto_share')"
1929
+ );
1930
+ const exchangeRow = exchangeStmt.get();
1931
+ return reply.send({
1932
+ agents_online: onlineOwners.size,
1933
+ total_capabilities: allCards.length,
1934
+ total_exchanges: exchangeRow.count
1935
+ });
1936
+ });
2923
1937
  if (opts.ownerApiKey && opts.ownerName) {
2924
1938
  const ownerApiKey = opts.ownerApiKey;
2925
1939
  const ownerName = opts.ownerName;
@@ -3026,7 +2040,7 @@ function createRegistryServer(opts) {
3026
2040
  });
3027
2041
  });
3028
2042
  }
3029
- return server;
2043
+ return { server, relayState };
3030
2044
  }
3031
2045
 
3032
2046
  // src/discovery/mdns.ts
@@ -3100,10 +2114,10 @@ async function stopAnnouncement() {
3100
2114
  }
3101
2115
 
3102
2116
  // src/openclaw/soul-sync.ts
3103
- import { randomUUID as randomUUID9 } from "crypto";
2117
+ import { randomUUID as randomUUID5 } from "crypto";
3104
2118
 
3105
2119
  // src/skills/publish-capability.ts
3106
- import { randomUUID as randomUUID8 } from "crypto";
2120
+ import { randomUUID as randomUUID4 } from "crypto";
3107
2121
  function parseSoulMd(content) {
3108
2122
  const lines = content.split("\n");
3109
2123
  let name = "";
@@ -3174,7 +2188,7 @@ function parseSoulMdV2(content) {
3174
2188
  const parsed = parseSoulMd(content);
3175
2189
  const skills = parsed.capabilities.map((cap) => {
3176
2190
  const sanitizedId = cap.name.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
3177
- const id = sanitizedId.length > 0 ? sanitizedId : randomUUID9();
2191
+ const id = sanitizedId.length > 0 ? sanitizedId : randomUUID5();
3178
2192
  return {
3179
2193
  id,
3180
2194
  name: cap.name,
@@ -3216,7 +2230,7 @@ function publishFromSoulV2(db, soulContent, owner) {
3216
2230
  (c) => c.spec_version === "2.0"
3217
2231
  );
3218
2232
  const now = (/* @__PURE__ */ new Date()).toISOString();
3219
- const cardId = existingV2?.id ?? randomUUID9();
2233
+ const cardId = existingV2?.id ?? randomUUID5();
3220
2234
  const card = {
3221
2235
  spec_version: "2.0",
3222
2236
  id: cardId,
@@ -3241,7 +2255,7 @@ function publishFromSoulV2(db, soulContent, owner) {
3241
2255
  }
3242
2256
 
3243
2257
  // src/openclaw/heartbeat-writer.ts
3244
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync6 } from "fs";
2258
+ import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync3 } from "fs";
3245
2259
  var HEARTBEAT_MARKER_START = "<!-- agentbnb:start -->";
3246
2260
  var HEARTBEAT_MARKER_END = "<!-- agentbnb:end -->";
3247
2261
  function generateHeartbeatSection(autonomy, budget) {
@@ -3277,11 +2291,11 @@ function generateHeartbeatSection(autonomy, budget) {
3277
2291
  ].join("\n");
3278
2292
  }
3279
2293
  function injectHeartbeatSection(heartbeatPath, section) {
3280
- if (!existsSync6(heartbeatPath)) {
3281
- writeFileSync4(heartbeatPath, section + "\n", "utf-8");
2294
+ if (!existsSync3(heartbeatPath)) {
2295
+ writeFileSync(heartbeatPath, section + "\n", "utf-8");
3282
2296
  return;
3283
2297
  }
3284
- let content = readFileSync5(heartbeatPath, "utf-8");
2298
+ let content = readFileSync2(heartbeatPath, "utf-8");
3285
2299
  const startIdx = content.indexOf(HEARTBEAT_MARKER_START);
3286
2300
  const endIdx = content.indexOf(HEARTBEAT_MARKER_END);
3287
2301
  if (startIdx !== -1 && endIdx !== -1) {
@@ -3289,7 +2303,7 @@ function injectHeartbeatSection(heartbeatPath, section) {
3289
2303
  } else {
3290
2304
  content = content + "\n" + section + "\n";
3291
2305
  }
3292
- writeFileSync4(heartbeatPath, content, "utf-8");
2306
+ writeFileSync(heartbeatPath, content, "utf-8");
3293
2307
  }
3294
2308
 
3295
2309
  // src/openclaw/skill.ts
@@ -3356,8 +2370,8 @@ program.command("init").description("Initialize AgentBnB config and create agent
3356
2370
  const owner = opts.owner ?? `agent-${randomBytes(4).toString("hex")}`;
3357
2371
  const token = randomBytes(32).toString("hex");
3358
2372
  const configDir = getConfigDir();
3359
- const dbPath = join5(configDir, "registry.db");
3360
- const creditDbPath = join5(configDir, "credit.db");
2373
+ const dbPath = join2(configDir, "registry.db");
2374
+ const creditDbPath = join2(configDir, "credit.db");
3361
2375
  const port = parseInt(opts.port, 10);
3362
2376
  const ip = opts.host ?? getLanIp();
3363
2377
  const existingConfig = loadConfig();
@@ -3471,7 +2485,7 @@ program.command("publish <card.json>").description("Publish a Capability Card to
3471
2485
  }
3472
2486
  let raw;
3473
2487
  try {
3474
- raw = readFileSync6(cardPath, "utf-8");
2488
+ raw = readFileSync3(cardPath, "utf-8");
3475
2489
  } catch {
3476
2490
  console.error(`Error: cannot read file: ${cardPath}`);
3477
2491
  process.exit(1);
@@ -3622,7 +2636,7 @@ ${discovered.length} agent(s) found on local network`);
3622
2636
  console.log(`
3623
2637
  ${outputCards.length} result(s)`);
3624
2638
  });
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) => {
2639
+ 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
2640
  const config = loadConfig();
3627
2641
  if (!config) {
3628
2642
  console.error("Error: not initialized. Run `agentbnb init` first.");
@@ -3638,8 +2652,8 @@ program.command("request [card-id]").description("Request a capability from anot
3638
2652
  process.exit(1);
3639
2653
  }
3640
2654
  }
3641
- const registryDb = openDatabase(join5(getConfigDir(), "registry.db"));
3642
- const creditDb = openCreditDb(join5(getConfigDir(), "credit.db"));
2655
+ const registryDb = openDatabase(join2(getConfigDir(), "registry.db"));
2656
+ const creditDb = openCreditDb(join2(getConfigDir(), "credit.db"));
3643
2657
  registryDb.pragma("busy_timeout = 5000");
3644
2658
  creditDb.pragma("busy_timeout = 5000");
3645
2659
  try {
@@ -3676,6 +2690,7 @@ program.command("request [card-id]").description("Request a capability from anot
3676
2690
  }
3677
2691
  let gatewayUrl;
3678
2692
  let token;
2693
+ const isPeerRequest = !!opts.peer;
3679
2694
  if (opts.peer) {
3680
2695
  const peer = findPeer(opts.peer);
3681
2696
  if (!peer) {
@@ -3688,20 +2703,87 @@ program.command("request [card-id]").description("Request a capability from anot
3688
2703
  gatewayUrl = config.gateway_url;
3689
2704
  token = config.token;
3690
2705
  }
2706
+ const useReceipt = isPeerRequest && opts.receipt !== false;
2707
+ if (useReceipt && !opts.cost) {
2708
+ console.error("Error: --cost <credits> is required for peer requests. Specify the credits to commit.");
2709
+ process.exit(1);
2710
+ }
2711
+ let escrowId;
2712
+ let escrowReceipt;
2713
+ if (useReceipt) {
2714
+ const configDir = getConfigDir();
2715
+ const creditDb = openCreditDb(join2(configDir, "credit.db"));
2716
+ creditDb.pragma("busy_timeout = 5000");
2717
+ try {
2718
+ const keys = loadKeyPair(configDir);
2719
+ const amount = Number(opts.cost);
2720
+ if (isNaN(amount) || amount <= 0) {
2721
+ console.error("Error: --cost must be a positive number.");
2722
+ process.exit(1);
2723
+ }
2724
+ const receiptResult = createSignedEscrowReceipt(creditDb, keys.privateKey, keys.publicKey, {
2725
+ owner: config.owner,
2726
+ amount,
2727
+ cardId,
2728
+ skillId: opts.skill
2729
+ });
2730
+ escrowId = receiptResult.escrowId;
2731
+ escrowReceipt = receiptResult.receipt;
2732
+ if (!opts.json) {
2733
+ console.log(`Escrow: ${amount} credits held (ID: ${escrowId.slice(0, 8)}...)`);
2734
+ }
2735
+ } catch (err) {
2736
+ creditDb.close();
2737
+ const msg = err instanceof Error ? err.message : String(err);
2738
+ if (opts.json) {
2739
+ console.log(JSON.stringify({ success: false, error: msg }, null, 2));
2740
+ } else {
2741
+ console.error(`Error creating escrow receipt: ${msg}`);
2742
+ }
2743
+ process.exit(1);
2744
+ }
2745
+ }
3691
2746
  try {
3692
2747
  const result = await requestCapability({
3693
2748
  gatewayUrl,
3694
2749
  token,
3695
2750
  cardId,
3696
- params
2751
+ params: { ...params, ...opts.skill ? { skill_id: opts.skill } : {} },
2752
+ escrowReceipt
3697
2753
  });
2754
+ if (useReceipt && escrowId) {
2755
+ const configDir = getConfigDir();
2756
+ const creditDb = openCreditDb(join2(configDir, "credit.db"));
2757
+ creditDb.pragma("busy_timeout = 5000");
2758
+ try {
2759
+ settleRequesterEscrow(creditDb, escrowId);
2760
+ if (!opts.json) {
2761
+ console.log(`Escrow settled: ${opts.cost} credits deducted.`);
2762
+ }
2763
+ } finally {
2764
+ creditDb.close();
2765
+ }
2766
+ }
3698
2767
  if (opts.json) {
3699
2768
  console.log(JSON.stringify({ success: true, result }, null, 2));
3700
2769
  } else {
3701
2770
  console.log("Result:");
3702
- console.log(JSON.stringify(result, null, 2));
2771
+ console.log(typeof result === "string" ? result : JSON.stringify(result, null, 2));
3703
2772
  }
3704
2773
  } catch (err) {
2774
+ if (useReceipt && escrowId) {
2775
+ const configDir = getConfigDir();
2776
+ const creditDb = openCreditDb(join2(configDir, "credit.db"));
2777
+ creditDb.pragma("busy_timeout = 5000");
2778
+ try {
2779
+ releaseRequesterEscrow(creditDb, escrowId);
2780
+ if (!opts.json) {
2781
+ console.log("Escrow released: credits refunded.");
2782
+ }
2783
+ } finally {
2784
+ creditDb.close();
2785
+ }
2786
+ }
3705
2787
  const msg = err instanceof Error ? err.message : String(err);
3706
2788
  if (opts.json) {
3707
2789
  console.log(JSON.stringify({ success: false, error: msg }, null, 2));
@@ -3746,12 +2828,12 @@ Active Escrows (${heldEscrows.length}):`);
3746
2828
  if (transactions.length > 0) {
3747
2829
  console.log("\nRecent Transactions:");
3748
2830
  for (const tx of transactions) {
3749
- const sign2 = tx.amount > 0 ? "+" : "";
3750
- console.log(` ${tx.created_at.slice(0, 19)} ${sign2}${tx.amount} ${tx.reason}`);
2831
+ const sign = tx.amount > 0 ? "+" : "";
2832
+ console.log(` ${tx.created_at.slice(0, 19)} ${sign}${tx.amount} ${tx.reason}`);
3751
2833
  }
3752
2834
  }
3753
2835
  });
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) => {
2836
+ 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("--registry <url>", "Connect to remote registry via WebSocket relay (e.g., hub.agentbnb.dev)").option("--conductor", "Enable Conductor orchestration mode").option("--announce", "Announce this gateway on the local network via mDNS").action(async (opts) => {
3755
2837
  const config = loadConfig();
3756
2838
  if (!config) {
3757
2839
  console.error("Error: not initialized. Run `agentbnb init` first.");
@@ -3759,17 +2841,22 @@ program.command("serve").description("Start the AgentBnB gateway server").option
3759
2841
  }
3760
2842
  const port = opts.port ? parseInt(opts.port, 10) : config.gateway_port;
3761
2843
  const registryPort = parseInt(opts.registryPort, 10);
3762
- const skillsYamlPath = opts.skillsYaml ?? join5(homedir2(), ".agentbnb", "skills.yaml");
2844
+ const skillsYamlPath = opts.skillsYaml ?? join2(homedir(), ".agentbnb", "skills.yaml");
3763
2845
  const runtime = new AgentRuntime({
3764
2846
  registryDbPath: config.db_path,
3765
2847
  creditDbPath: config.credit_db_path,
3766
2848
  owner: config.owner,
3767
- skillsYamlPath
2849
+ skillsYamlPath,
2850
+ conductorEnabled: opts.conductor ?? false,
2851
+ conductorToken: config.token
3768
2852
  });
3769
2853
  await runtime.start();
3770
2854
  if (runtime.skillExecutor) {
3771
2855
  console.log(`SkillExecutor initialized from ${skillsYamlPath}`);
3772
2856
  }
2857
+ if (opts.conductor) {
2858
+ console.log("Conductor mode enabled \u2014 orchestrate/plan skills available via gateway");
2859
+ }
3773
2860
  const autonomyConfig = config.autonomy ?? DEFAULT_AUTONOMY_CONFIG;
3774
2861
  const idleMonitor = new IdleMonitor({
3775
2862
  owner: config.owner,
@@ -3787,14 +2874,18 @@ program.command("serve").description("Start the AgentBnB gateway server").option
3787
2874
  handlerUrl: opts.handlerUrl,
3788
2875
  skillExecutor: runtime.skillExecutor
3789
2876
  });
3790
- let registryServer = null;
2877
+ let registryFastify = null;
2878
+ let relayClient = null;
3791
2879
  const gracefulShutdown = async () => {
3792
2880
  console.log("\nShutting down...");
2881
+ if (relayClient) {
2882
+ relayClient.disconnect();
2883
+ }
3793
2884
  if (opts.announce) {
3794
2885
  await stopAnnouncement();
3795
2886
  }
3796
- if (registryServer) {
3797
- await registryServer.close();
2887
+ if (registryFastify) {
2888
+ await registryFastify.close();
3798
2889
  }
3799
2890
  await server.close();
3800
2891
  await runtime.shutdown();
@@ -3813,15 +2904,66 @@ program.command("serve").description("Start the AgentBnB gateway server").option
3813
2904
  if (!config.api_key) {
3814
2905
  console.warn("No API key found. Run `agentbnb init` to enable dashboard features.");
3815
2906
  }
3816
- registryServer = createRegistryServer({
2907
+ const { server: regServer, relayState } = createRegistryServer({
3817
2908
  registryDb: runtime.registryDb,
3818
2909
  silent: false,
3819
2910
  ownerName: config.owner,
3820
2911
  ownerApiKey: config.api_key,
3821
2912
  creditDb: runtime.creditDb
3822
2913
  });
3823
- await registryServer.listen({ port: registryPort, host: "0.0.0.0" });
2914
+ registryFastify = regServer;
2915
+ await registryFastify.listen({ port: registryPort, host: "0.0.0.0" });
3824
2916
  console.log(`Registry API: http://0.0.0.0:${registryPort}/cards`);
2917
+ if (relayState) {
2918
+ console.log(`WebSocket relay active on /ws`);
2919
+ }
2920
+ }
2921
+ if (opts.registry) {
2922
+ const { RelayClient } = await import("../websocket-client-5TIQDYQ4.js");
2923
+ const { executeCapabilityRequest: executeCapabilityRequest2 } = await import("../execute-3T5RF3DP.js");
2924
+ const cards = listCards(runtime.registryDb, config.owner);
2925
+ const card = cards[0] ?? {
2926
+ id: config.owner,
2927
+ owner: config.owner,
2928
+ name: config.owner,
2929
+ description: "Agent registered via CLI",
2930
+ spec_version: "1.0",
2931
+ level: 1,
2932
+ inputs: [],
2933
+ outputs: [],
2934
+ pricing: { credits_per_call: 0 },
2935
+ availability: { online: true }
2936
+ };
2937
+ relayClient = new RelayClient({
2938
+ registryUrl: opts.registry,
2939
+ owner: config.owner,
2940
+ token: config.token,
2941
+ card,
2942
+ onRequest: async (req) => {
2943
+ const result = await executeCapabilityRequest2({
2944
+ registryDb: runtime.registryDb,
2945
+ creditDb: runtime.creditDb,
2946
+ cardId: req.card_id,
2947
+ skillId: req.skill_id,
2948
+ params: req.params,
2949
+ requester: req.requester ?? req.from_owner,
2950
+ escrowReceipt: req.escrow_receipt,
2951
+ skillExecutor: runtime.skillExecutor,
2952
+ handlerUrl: opts.handlerUrl
2953
+ });
2954
+ if (result.success) {
2955
+ return { result: result.result };
2956
+ }
2957
+ return { error: { code: result.error.code, message: result.error.message } };
2958
+ }
2959
+ });
2960
+ try {
2961
+ await relayClient.connect();
2962
+ console.log(`Connected to registry: ${opts.registry}`);
2963
+ } catch (err) {
2964
+ console.warn(`Warning: could not connect to registry ${opts.registry}: ${err instanceof Error ? err.message : err}`);
2965
+ console.warn("Will auto-reconnect in background...");
2966
+ }
3825
2967
  }
3826
2968
  if (opts.announce) {
3827
2969
  announceGateway(config.owner, port);
@@ -3829,8 +2971,9 @@ program.command("serve").description("Start the AgentBnB gateway server").option
3829
2971
  }
3830
2972
  } catch (err) {
3831
2973
  console.error("Failed to start:", err);
3832
- if (registryServer) {
3833
- await registryServer.close().catch(() => {
2974
+ if (relayClient) relayClient.disconnect();
2975
+ if (registryFastify) {
2976
+ await registryFastify.close().catch(() => {
3834
2977
  });
3835
2978
  }
3836
2979
  await runtime.shutdown();
@@ -3985,7 +3128,7 @@ openclaw.command("sync").description("Read SOUL.md and publish/update a v2.0 cap
3985
3128
  }
3986
3129
  let content;
3987
3130
  try {
3988
- content = readFileSync6(opts.soulPath, "utf-8");
3131
+ content = readFileSync3(opts.soulPath, "utf-8");
3989
3132
  } catch {
3990
3133
  console.error(`Error: cannot read SOUL.md at ${opts.soulPath}`);
3991
3134
  process.exit(1);
@@ -4045,4 +3188,38 @@ openclaw.command("rules").description("Print HEARTBEAT.md rules block (or inject
4045
3188
  console.log(section);
4046
3189
  }
4047
3190
  });
3191
+ 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) => {
3192
+ const { conductAction } = await import("../conduct-M57F72RK.js");
3193
+ const result = await conductAction(task, opts);
3194
+ if (opts.json) {
3195
+ console.log(JSON.stringify(result, null, 2));
3196
+ if (!result.success) process.exit(1);
3197
+ return;
3198
+ }
3199
+ if (!result.success) {
3200
+ console.error(`Error: ${result.error}`);
3201
+ process.exit(1);
3202
+ }
3203
+ const plan = result.plan;
3204
+ console.log("\nExecution Plan:");
3205
+ for (const step of plan.steps) {
3206
+ const deps = step.depends_on.length > 0 ? ` [depends on prior steps]` : "";
3207
+ console.log(` Step ${step.step}: ${step.description} (${step.capability}) -> @${step.agent} (${step.credits} cr)${deps}`);
3208
+ }
3209
+ console.log(` Orchestration fee: ${plan.orchestration_fee} cr`);
3210
+ console.log(` Total estimated: ${plan.estimated_total} cr`);
3211
+ if (result.execution) {
3212
+ console.log("\nResults:");
3213
+ console.log(JSON.stringify(result.execution, null, 2));
3214
+ console.log(`
3215
+ Total credits spent: ${result.total_credits ?? 0} cr`);
3216
+ console.log(`Latency: ${result.latency_ms ?? 0} ms`);
3217
+ }
3218
+ if (result.errors && result.errors.length > 0) {
3219
+ console.log("\nErrors:");
3220
+ for (const err of result.errors) {
3221
+ console.log(` - ${err}`);
3222
+ }
3223
+ }
3224
+ });
4048
3225
  await program.parseAsync(process.argv);