agentbnb 4.0.1 → 4.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/README.md +33 -2
  2. package/dist/{card-4XH4AOTE.js → card-RNEWSAQ6.js} +1 -1
  3. package/dist/card-RSGDCHCV.js +88 -0
  4. package/dist/{chunk-MQKYGY5I.js → chunk-4P3EMGL4.js} +3 -3
  5. package/dist/{chunk-DVAS2443.js → chunk-5KFI5X7B.js} +1 -1
  6. package/dist/{chunk-Q7HRI666.js → chunk-5QGXARLJ.js} +8 -6
  7. package/dist/{chunk-3UKAVIMC.js → chunk-BH6WGYFB.js} +4 -4
  8. package/dist/{chunk-XQHN6ITI.js → chunk-DNWT5FZQ.js} +22 -2
  9. package/dist/chunk-EVBX22YU.js +68 -0
  10. package/dist/{chunk-QJEOCKVF.js → chunk-FF226TIV.js} +1 -1
  11. package/dist/{chunk-6K5WUVF3.js → chunk-GGYC5U2Z.js} +4 -4
  12. package/dist/{chunk-ODBGCCEH.js → chunk-HH24WMFN.js} +18 -3
  13. package/dist/chunk-JXEOE7HX.js +295 -0
  14. package/dist/{chunk-M3G5NR2Z.js → chunk-QITOPASZ.js} +8 -2
  15. package/dist/{chunk-TLU7ALCZ.js → chunk-T7NS2J2B.js} +1 -1
  16. package/dist/chunk-UB2NPFC7.js +165 -0
  17. package/dist/{chunk-FNKBHBYK.js → chunk-WGZ5AGOX.js} +37 -3
  18. package/dist/{chunk-KJG2UJV5.js → chunk-XND2DWTZ.js} +3 -2
  19. package/dist/cli/index.d.ts +1 -0
  20. package/dist/cli/index.js +406 -135
  21. package/dist/{client-BTPIFY7E.js → client-T5MTY3CS.js} +3 -3
  22. package/dist/conduct-GZQNFTRP.js +19 -0
  23. package/dist/{conduct-CW62HBPT.js → conduct-N52JX7RT.js} +9 -9
  24. package/dist/conductor-mode-ESGFZ6T5.js +739 -0
  25. package/dist/{conductor-mode-3JS4VWCR.js → conductor-mode-XUWGR4ZE.js} +7 -7
  26. package/dist/execute-QH6F54D7.js +10 -0
  27. package/dist/index.d.ts +151 -2
  28. package/dist/index.js +135 -67
  29. package/dist/peers-E4MKNNDN.js +12 -0
  30. package/dist/{request-CNZ3XIVX.js → request-4GQSSM4B.js} +8 -8
  31. package/dist/{serve-skill-SUOGUM7N.js → serve-skill-Q6NHX2RA.js} +5 -5
  32. package/dist/{server-2LWHL24P.js → server-B5E566CI.js} +10 -10
  33. package/dist/skills/agentbnb/bootstrap.js +2001 -0
  34. package/openclaw.plugin.json +54 -0
  35. package/package.json +9 -6
  36. package/skills/agentbnb/HEARTBEAT.rules.md +47 -0
  37. package/skills/agentbnb/SKILL.md +166 -0
  38. package/skills/agentbnb/auto-request.ts +14 -0
  39. package/skills/agentbnb/auto-share.ts +10 -0
  40. package/skills/agentbnb/bootstrap.test.ts +323 -0
  41. package/skills/agentbnb/bootstrap.ts +126 -0
  42. package/skills/agentbnb/credit-mgr.ts +11 -0
  43. package/skills/agentbnb/gateway.ts +12 -0
  44. package/skills/agentbnb/install.sh +210 -0
  45. package/dist/conduct-FXLVGKD5.js +0 -19
  46. package/dist/execute-EXOITLHN.js +0 -10
  47. package/dist/types-FGBUZ3QV.js +0 -18
@@ -0,0 +1,2001 @@
1
+ import {
2
+ DEFAULT_AUTONOMY_CONFIG,
3
+ generateKeyPair,
4
+ getAutonomyTier,
5
+ getBalance,
6
+ holdEscrow,
7
+ insertAuditEvent,
8
+ interpolateObject,
9
+ loadKeyPair,
10
+ openCreditDb,
11
+ recordEarning,
12
+ releaseEscrow,
13
+ saveKeyPair,
14
+ settleEscrow,
15
+ verifyEscrowReceipt
16
+ } from "../../chunk-JXEOE7HX.js";
17
+ import {
18
+ AgentBnBError,
19
+ CapabilityCardV2Schema
20
+ } from "../../chunk-UB2NPFC7.js";
21
+
22
+ // skills/agentbnb/bootstrap.ts
23
+ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
24
+ import { randomUUID as randomUUID4 } from "crypto";
25
+ import { homedir } from "os";
26
+ import { join as join2 } from "path";
27
+
28
+ // src/runtime/agent-runtime.ts
29
+ import { readFileSync, existsSync } from "fs";
30
+
31
+ // src/registry/store.ts
32
+ import Database from "better-sqlite3";
33
+
34
+ // src/registry/request-log.ts
35
+ function createRequestLogTable(db) {
36
+ db.exec(`
37
+ CREATE TABLE IF NOT EXISTS request_log (
38
+ id TEXT PRIMARY KEY,
39
+ card_id TEXT NOT NULL,
40
+ card_name TEXT NOT NULL,
41
+ requester TEXT NOT NULL,
42
+ status TEXT NOT NULL CHECK(status IN ('success', 'failure', 'timeout')),
43
+ latency_ms INTEGER NOT NULL,
44
+ credits_charged INTEGER NOT NULL,
45
+ created_at TEXT NOT NULL
46
+ );
47
+
48
+ CREATE INDEX IF NOT EXISTS request_log_created_at_idx
49
+ ON request_log (created_at DESC);
50
+ `);
51
+ try {
52
+ db.exec("ALTER TABLE request_log ADD COLUMN skill_id TEXT");
53
+ } catch {
54
+ }
55
+ try {
56
+ db.exec("ALTER TABLE request_log ADD COLUMN action_type TEXT");
57
+ } catch {
58
+ }
59
+ try {
60
+ db.exec("ALTER TABLE request_log ADD COLUMN tier_invoked INTEGER");
61
+ } catch {
62
+ }
63
+ }
64
+ function insertRequestLog(db, entry) {
65
+ const stmt = db.prepare(`
66
+ INSERT INTO request_log (id, card_id, card_name, requester, status, latency_ms, credits_charged, created_at, skill_id, action_type, tier_invoked)
67
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
68
+ `);
69
+ stmt.run(
70
+ entry.id,
71
+ entry.card_id,
72
+ entry.card_name,
73
+ entry.requester,
74
+ entry.status,
75
+ entry.latency_ms,
76
+ entry.credits_charged,
77
+ entry.created_at,
78
+ entry.skill_id ?? null,
79
+ entry.action_type ?? null,
80
+ entry.tier_invoked ?? null
81
+ );
82
+ }
83
+ function getSkillRequestCount(db, skillId, windowMs) {
84
+ const cutoff = new Date(Date.now() - windowMs).toISOString();
85
+ const stmt = db.prepare(
86
+ `SELECT COUNT(*) as cnt FROM request_log
87
+ WHERE skill_id = ? AND created_at >= ? AND status = 'success' AND action_type IS NULL`
88
+ );
89
+ const row = stmt.get(skillId, cutoff);
90
+ return row.cnt;
91
+ }
92
+
93
+ // src/registry/store.ts
94
+ var V2_FTS_TRIGGERS = `
95
+ DROP TRIGGER IF EXISTS cards_ai;
96
+ DROP TRIGGER IF EXISTS cards_au;
97
+ DROP TRIGGER IF EXISTS cards_ad;
98
+
99
+ CREATE TRIGGER cards_ai AFTER INSERT ON capability_cards BEGIN
100
+ INSERT INTO cards_fts(rowid, id, owner, name, description, tags)
101
+ VALUES (
102
+ new.rowid,
103
+ new.id,
104
+ new.owner,
105
+ COALESCE(
106
+ (SELECT group_concat(json_extract(value, '$.name'), ' ')
107
+ FROM json_each(json_extract(new.data, '$.skills'))),
108
+ json_extract(new.data, '$.name'),
109
+ ''
110
+ ),
111
+ COALESCE(
112
+ (SELECT group_concat(json_extract(value, '$.description'), ' ')
113
+ FROM json_each(json_extract(new.data, '$.skills'))),
114
+ json_extract(new.data, '$.description'),
115
+ ''
116
+ ),
117
+ COALESCE(
118
+ (SELECT group_concat(json_extract(value, '$.metadata.tags'), ' ')
119
+ FROM json_each(json_extract(new.data, '$.skills'))),
120
+ (SELECT group_concat(value, ' ')
121
+ FROM json_each(json_extract(new.data, '$.metadata.tags'))),
122
+ ''
123
+ )
124
+ );
125
+ END;
126
+
127
+ CREATE TRIGGER cards_au AFTER UPDATE ON capability_cards BEGIN
128
+ INSERT INTO cards_fts(cards_fts, rowid, id, owner, name, description, tags)
129
+ VALUES (
130
+ 'delete',
131
+ old.rowid,
132
+ old.id,
133
+ old.owner,
134
+ COALESCE(
135
+ (SELECT group_concat(json_extract(value, '$.name'), ' ')
136
+ FROM json_each(json_extract(old.data, '$.skills'))),
137
+ json_extract(old.data, '$.name'),
138
+ ''
139
+ ),
140
+ COALESCE(
141
+ (SELECT group_concat(json_extract(value, '$.description'), ' ')
142
+ FROM json_each(json_extract(old.data, '$.skills'))),
143
+ json_extract(old.data, '$.description'),
144
+ ''
145
+ ),
146
+ COALESCE(
147
+ (SELECT group_concat(json_extract(value, '$.metadata.tags'), ' ')
148
+ FROM json_each(json_extract(old.data, '$.skills'))),
149
+ (SELECT group_concat(value, ' ')
150
+ FROM json_each(json_extract(old.data, '$.metadata.tags'))),
151
+ ''
152
+ )
153
+ );
154
+ INSERT INTO cards_fts(rowid, id, owner, name, description, tags)
155
+ VALUES (
156
+ new.rowid,
157
+ new.id,
158
+ new.owner,
159
+ COALESCE(
160
+ (SELECT group_concat(json_extract(value, '$.name'), ' ')
161
+ FROM json_each(json_extract(new.data, '$.skills'))),
162
+ json_extract(new.data, '$.name'),
163
+ ''
164
+ ),
165
+ COALESCE(
166
+ (SELECT group_concat(json_extract(value, '$.description'), ' ')
167
+ FROM json_each(json_extract(new.data, '$.skills'))),
168
+ json_extract(new.data, '$.description'),
169
+ ''
170
+ ),
171
+ COALESCE(
172
+ (SELECT group_concat(json_extract(value, '$.metadata.tags'), ' ')
173
+ FROM json_each(json_extract(new.data, '$.skills'))),
174
+ (SELECT group_concat(value, ' ')
175
+ FROM json_each(json_extract(new.data, '$.metadata.tags'))),
176
+ ''
177
+ )
178
+ );
179
+ END;
180
+
181
+ CREATE TRIGGER cards_ad AFTER DELETE ON capability_cards BEGIN
182
+ INSERT INTO cards_fts(cards_fts, rowid, id, owner, name, description, tags)
183
+ VALUES (
184
+ 'delete',
185
+ old.rowid,
186
+ old.id,
187
+ old.owner,
188
+ COALESCE(
189
+ (SELECT group_concat(json_extract(value, '$.name'), ' ')
190
+ FROM json_each(json_extract(old.data, '$.skills'))),
191
+ json_extract(old.data, '$.name'),
192
+ ''
193
+ ),
194
+ COALESCE(
195
+ (SELECT group_concat(json_extract(value, '$.description'), ' ')
196
+ FROM json_each(json_extract(old.data, '$.skills'))),
197
+ json_extract(old.data, '$.description'),
198
+ ''
199
+ ),
200
+ COALESCE(
201
+ (SELECT group_concat(json_extract(value, '$.metadata.tags'), ' ')
202
+ FROM json_each(json_extract(old.data, '$.skills'))),
203
+ (SELECT group_concat(value, ' ')
204
+ FROM json_each(json_extract(old.data, '$.metadata.tags'))),
205
+ ''
206
+ )
207
+ );
208
+ END;
209
+ `;
210
+ function openDatabase(path = ":memory:") {
211
+ const db = new Database(path);
212
+ db.pragma("journal_mode = WAL");
213
+ db.pragma("foreign_keys = ON");
214
+ db.exec(`
215
+ CREATE TABLE IF NOT EXISTS capability_cards (
216
+ id TEXT PRIMARY KEY,
217
+ owner TEXT NOT NULL,
218
+ data TEXT NOT NULL,
219
+ created_at TEXT NOT NULL,
220
+ updated_at TEXT NOT NULL
221
+ );
222
+
223
+ CREATE TABLE IF NOT EXISTS pending_requests (
224
+ id TEXT PRIMARY KEY,
225
+ skill_query TEXT NOT NULL,
226
+ max_cost_credits REAL NOT NULL,
227
+ selected_peer TEXT,
228
+ selected_card_id TEXT,
229
+ selected_skill_id TEXT,
230
+ credits REAL NOT NULL,
231
+ status TEXT NOT NULL DEFAULT 'pending',
232
+ params TEXT,
233
+ created_at TEXT NOT NULL,
234
+ resolved_at TEXT
235
+ );
236
+
237
+ CREATE VIRTUAL TABLE IF NOT EXISTS cards_fts USING fts5(
238
+ id UNINDEXED,
239
+ owner,
240
+ name,
241
+ description,
242
+ tags,
243
+ content=""
244
+ );
245
+ `);
246
+ createRequestLogTable(db);
247
+ runMigrations(db);
248
+ return db;
249
+ }
250
+ function runMigrations(db) {
251
+ const version = db.pragma("user_version")[0]?.user_version ?? 0;
252
+ if (version < 2) {
253
+ migrateV1toV2(db);
254
+ }
255
+ }
256
+ function migrateV1toV2(db) {
257
+ const migrate = db.transaction(() => {
258
+ const rows = db.prepare("SELECT rowid, id, data FROM capability_cards").all();
259
+ const now = (/* @__PURE__ */ new Date()).toISOString();
260
+ for (const row of rows) {
261
+ const parsed = JSON.parse(row.data);
262
+ if (parsed["spec_version"] === "2.0") continue;
263
+ const v1 = parsed;
264
+ const v2 = {
265
+ spec_version: "2.0",
266
+ id: v1.id,
267
+ owner: v1.owner,
268
+ agent_name: v1.name,
269
+ skills: [
270
+ {
271
+ id: `skill-${v1.id}`,
272
+ name: v1.name,
273
+ description: v1.description,
274
+ level: v1.level,
275
+ inputs: v1.inputs,
276
+ outputs: v1.outputs,
277
+ pricing: v1.pricing,
278
+ availability: { online: v1.availability.online },
279
+ powered_by: v1.powered_by,
280
+ metadata: v1.metadata,
281
+ _internal: v1._internal
282
+ }
283
+ ],
284
+ availability: v1.availability,
285
+ created_at: v1.created_at,
286
+ updated_at: now
287
+ };
288
+ db.prepare("UPDATE capability_cards SET data = ?, updated_at = ? WHERE id = ?").run(
289
+ JSON.stringify(v2),
290
+ now,
291
+ v2.id
292
+ );
293
+ }
294
+ db.exec(V2_FTS_TRIGGERS);
295
+ db.exec(`INSERT INTO cards_fts(cards_fts) VALUES('delete-all')`);
296
+ const allRows = db.prepare("SELECT rowid, id, owner, data FROM capability_cards").all();
297
+ const ftsInsert = db.prepare(
298
+ "INSERT INTO cards_fts(rowid, id, owner, name, description, tags) VALUES (?, ?, ?, ?, ?, ?)"
299
+ );
300
+ for (const row of allRows) {
301
+ const data = JSON.parse(row.data);
302
+ const skills = data["skills"] ?? [];
303
+ let name;
304
+ let description;
305
+ let tags;
306
+ if (skills.length > 0) {
307
+ name = skills.map((s) => String(s["name"] ?? "")).join(" ");
308
+ description = skills.map((s) => String(s["description"] ?? "")).join(" ");
309
+ tags = skills.flatMap((s) => {
310
+ const meta = s["metadata"];
311
+ return meta?.["tags"] ?? [];
312
+ }).join(" ");
313
+ } else {
314
+ name = String(data["name"] ?? "");
315
+ description = String(data["description"] ?? "");
316
+ const meta = data["metadata"];
317
+ const rawTags = meta?.["tags"] ?? [];
318
+ tags = rawTags.join(" ");
319
+ }
320
+ ftsInsert.run(row.rowid, row.id, row.owner, name, description, tags);
321
+ }
322
+ db.pragma("user_version = 2");
323
+ });
324
+ migrate();
325
+ }
326
+ function getCard(db, id) {
327
+ const stmt = db.prepare("SELECT data FROM capability_cards WHERE id = ?");
328
+ const row = stmt.get(id);
329
+ if (!row) return null;
330
+ return JSON.parse(row.data);
331
+ }
332
+ function updateReputation(db, cardId, success, latencyMs) {
333
+ const existing = getCard(db, cardId);
334
+ if (!existing) return;
335
+ const ALPHA = 0.1;
336
+ const observed = success ? 1 : 0;
337
+ const prevSuccessRate = existing.metadata?.success_rate;
338
+ const prevLatency = existing.metadata?.avg_latency_ms;
339
+ const newSuccessRate = prevSuccessRate === void 0 ? observed : ALPHA * observed + (1 - ALPHA) * prevSuccessRate;
340
+ const newLatency = prevLatency === void 0 ? latencyMs : ALPHA * latencyMs + (1 - ALPHA) * prevLatency;
341
+ const now = (/* @__PURE__ */ new Date()).toISOString();
342
+ const updatedMetadata = {
343
+ ...existing.metadata,
344
+ success_rate: Math.round(newSuccessRate * 1e3) / 1e3,
345
+ avg_latency_ms: Math.round(newLatency)
346
+ };
347
+ const updatedCard = { ...existing, metadata: updatedMetadata, updated_at: now };
348
+ const stmt = db.prepare(`
349
+ UPDATE capability_cards
350
+ SET data = ?, updated_at = ?
351
+ WHERE id = ?
352
+ `);
353
+ stmt.run(JSON.stringify(updatedCard), now, cardId);
354
+ }
355
+ function updateSkillAvailability(db, cardId, skillId, online) {
356
+ const row = db.prepare("SELECT data FROM capability_cards WHERE id = ?").get(cardId);
357
+ if (!row) return;
358
+ const card = JSON.parse(row.data);
359
+ const skills = card["skills"];
360
+ if (!skills) return;
361
+ const skill = skills.find((s) => s["id"] === skillId);
362
+ if (!skill) return;
363
+ const existing = skill["availability"] ?? {};
364
+ skill["availability"] = { ...existing, online };
365
+ const now = (/* @__PURE__ */ new Date()).toISOString();
366
+ db.prepare("UPDATE capability_cards SET data = ?, updated_at = ? WHERE id = ?").run(
367
+ JSON.stringify(card),
368
+ now,
369
+ cardId
370
+ );
371
+ }
372
+ function updateSkillIdleRate(db, cardId, skillId, idleRate) {
373
+ const row = db.prepare("SELECT data FROM capability_cards WHERE id = ?").get(cardId);
374
+ if (!row) return;
375
+ const card = JSON.parse(row.data);
376
+ const skills = card["skills"];
377
+ if (!skills) return;
378
+ const skill = skills.find((s) => s["id"] === skillId);
379
+ if (!skill) return;
380
+ const existing = skill["_internal"] ?? {};
381
+ skill["_internal"] = {
382
+ ...existing,
383
+ idle_rate: idleRate,
384
+ idle_rate_computed_at: (/* @__PURE__ */ new Date()).toISOString()
385
+ };
386
+ const now = (/* @__PURE__ */ new Date()).toISOString();
387
+ db.prepare("UPDATE capability_cards SET data = ?, updated_at = ? WHERE id = ?").run(
388
+ JSON.stringify(card),
389
+ now,
390
+ cardId
391
+ );
392
+ }
393
+ function listCards(db, owner) {
394
+ let stmt;
395
+ let rows;
396
+ if (owner !== void 0) {
397
+ stmt = db.prepare("SELECT data FROM capability_cards WHERE owner = ?");
398
+ rows = stmt.all(owner);
399
+ } else {
400
+ stmt = db.prepare("SELECT data FROM capability_cards");
401
+ rows = stmt.all();
402
+ }
403
+ return rows.map((row) => JSON.parse(row.data));
404
+ }
405
+
406
+ // src/skills/executor.ts
407
+ var SkillExecutor = class {
408
+ skillMap;
409
+ modeMap;
410
+ /**
411
+ * @param configs - Parsed SkillConfig array (from parseSkillsFile).
412
+ * @param modes - Map from skill type string to its executor implementation.
413
+ */
414
+ constructor(configs, modes) {
415
+ this.skillMap = new Map(configs.map((c) => [c.id, c]));
416
+ this.modeMap = modes;
417
+ }
418
+ /**
419
+ * Execute a skill by ID with the given input parameters.
420
+ *
421
+ * Dispatch order:
422
+ * 1. Look up skill config by skillId.
423
+ * 2. Find executor mode by config.type.
424
+ * 3. Invoke mode.execute(), wrap with latency timing.
425
+ * 4. Catch any thrown errors and return as ExecutionResult with success:false.
426
+ *
427
+ * @param skillId - The ID of the skill to execute.
428
+ * @param params - Input parameters for the skill.
429
+ * @returns ExecutionResult including success, result/error, and latency_ms.
430
+ */
431
+ async execute(skillId, params, onProgress) {
432
+ const startTime = Date.now();
433
+ const config = this.skillMap.get(skillId);
434
+ if (!config) {
435
+ return {
436
+ success: false,
437
+ error: `Skill not found: "${skillId}"`,
438
+ latency_ms: Date.now() - startTime
439
+ };
440
+ }
441
+ const mode = this.modeMap.get(config.type);
442
+ if (!mode) {
443
+ return {
444
+ success: false,
445
+ error: `No executor registered for skill type "${config.type}" (skill: "${skillId}")`,
446
+ latency_ms: Date.now() - startTime
447
+ };
448
+ }
449
+ try {
450
+ const modeResult = await mode.execute(config, params, onProgress);
451
+ return {
452
+ ...modeResult,
453
+ latency_ms: Date.now() - startTime
454
+ };
455
+ } catch (err) {
456
+ const message = err instanceof Error ? err.message : String(err);
457
+ return {
458
+ success: false,
459
+ error: message,
460
+ latency_ms: Date.now() - startTime
461
+ };
462
+ }
463
+ }
464
+ /**
465
+ * Returns the IDs of all registered skills.
466
+ *
467
+ * @returns Array of skill ID strings.
468
+ */
469
+ listSkills() {
470
+ return Array.from(this.skillMap.keys());
471
+ }
472
+ /**
473
+ * Returns the SkillConfig for a given skill ID, or undefined if not found.
474
+ *
475
+ * @param skillId - The skill ID to look up.
476
+ * @returns The SkillConfig or undefined.
477
+ */
478
+ getSkillConfig(skillId) {
479
+ return this.skillMap.get(skillId);
480
+ }
481
+ };
482
+ function createSkillExecutor(configs, modes) {
483
+ return new SkillExecutor(configs, modes);
484
+ }
485
+
486
+ // src/skills/skill-config.ts
487
+ import { z } from "zod";
488
+ import yaml from "js-yaml";
489
+ var PricingSchema = z.object({
490
+ credits_per_call: z.number().nonnegative(),
491
+ credits_per_minute: z.number().nonnegative().optional(),
492
+ free_tier: z.number().nonnegative().optional()
493
+ });
494
+ var ApiAuthSchema = z.discriminatedUnion("type", [
495
+ z.object({
496
+ type: z.literal("bearer"),
497
+ token: z.string()
498
+ }),
499
+ z.object({
500
+ type: z.literal("apikey"),
501
+ header: z.string().default("X-API-Key"),
502
+ key: z.string()
503
+ }),
504
+ z.object({
505
+ type: z.literal("basic"),
506
+ username: z.string(),
507
+ password: z.string()
508
+ })
509
+ ]);
510
+ var ApiSkillConfigSchema = z.object({
511
+ id: z.string().min(1),
512
+ type: z.literal("api"),
513
+ name: z.string().min(1),
514
+ endpoint: z.string().min(1),
515
+ method: z.enum(["GET", "POST", "PUT", "DELETE"]),
516
+ auth: ApiAuthSchema.optional(),
517
+ input_mapping: z.record(z.string()).default({}),
518
+ output_mapping: z.record(z.string()).default({}),
519
+ pricing: PricingSchema,
520
+ timeout_ms: z.number().positive().default(3e4),
521
+ retries: z.number().nonnegative().int().default(0),
522
+ provider: z.string().optional()
523
+ });
524
+ var PipelineStepSchema = z.union([
525
+ z.object({
526
+ skill_id: z.string().min(1),
527
+ input_mapping: z.record(z.string()).default({})
528
+ }),
529
+ z.object({
530
+ command: z.string().min(1),
531
+ input_mapping: z.record(z.string()).default({})
532
+ })
533
+ ]);
534
+ var PipelineSkillConfigSchema = z.object({
535
+ id: z.string().min(1),
536
+ type: z.literal("pipeline"),
537
+ name: z.string().min(1),
538
+ steps: z.array(PipelineStepSchema).min(1),
539
+ pricing: PricingSchema,
540
+ timeout_ms: z.number().positive().optional()
541
+ });
542
+ var OpenClawSkillConfigSchema = z.object({
543
+ id: z.string().min(1),
544
+ type: z.literal("openclaw"),
545
+ name: z.string().min(1),
546
+ agent_name: z.string().min(1),
547
+ channel: z.enum(["telegram", "webhook", "process"]),
548
+ pricing: PricingSchema,
549
+ timeout_ms: z.number().positive().optional()
550
+ });
551
+ var CommandSkillConfigSchema = z.object({
552
+ id: z.string().min(1),
553
+ type: z.literal("command"),
554
+ name: z.string().min(1),
555
+ command: z.string().min(1),
556
+ output_type: z.enum(["json", "text", "file"]),
557
+ allowed_commands: z.array(z.string()).optional(),
558
+ working_dir: z.string().optional(),
559
+ timeout_ms: z.number().positive().default(3e4),
560
+ pricing: PricingSchema
561
+ });
562
+ var ConductorSkillConfigSchema = z.object({
563
+ id: z.string().min(1),
564
+ type: z.literal("conductor"),
565
+ name: z.string().min(1),
566
+ conductor_skill: z.enum(["orchestrate", "plan"]),
567
+ pricing: PricingSchema,
568
+ timeout_ms: z.number().positive().optional()
569
+ });
570
+ var SkillConfigSchema = z.discriminatedUnion("type", [
571
+ ApiSkillConfigSchema,
572
+ PipelineSkillConfigSchema,
573
+ OpenClawSkillConfigSchema,
574
+ CommandSkillConfigSchema,
575
+ ConductorSkillConfigSchema
576
+ ]);
577
+ var SkillsFileSchema = z.object({
578
+ skills: z.array(SkillConfigSchema)
579
+ });
580
+ function expandEnvVars(value) {
581
+ return value.replace(/\$\{([^}]+)\}/g, (_match, varName) => {
582
+ if (/[.a-z]/.test(varName)) {
583
+ return _match;
584
+ }
585
+ const envValue = process.env[varName];
586
+ if (envValue === void 0) {
587
+ throw new Error(`Environment variable "${varName}" is not defined`);
588
+ }
589
+ return envValue;
590
+ });
591
+ }
592
+ function expandEnvVarsDeep(value) {
593
+ if (typeof value === "string") {
594
+ return expandEnvVars(value);
595
+ }
596
+ if (Array.isArray(value)) {
597
+ return value.map(expandEnvVarsDeep);
598
+ }
599
+ if (value !== null && typeof value === "object") {
600
+ const result = {};
601
+ for (const [k, v] of Object.entries(value)) {
602
+ result[k] = expandEnvVarsDeep(v);
603
+ }
604
+ return result;
605
+ }
606
+ return value;
607
+ }
608
+ function parseSkillsFile(yamlContent) {
609
+ const raw = yaml.load(yamlContent);
610
+ const expanded = expandEnvVarsDeep(raw);
611
+ const result = SkillsFileSchema.parse(expanded);
612
+ return result.skills;
613
+ }
614
+
615
+ // src/skills/api-executor.ts
616
+ function parseMappingTarget(mapping) {
617
+ const dotIndex = mapping.indexOf(".");
618
+ if (dotIndex < 0) {
619
+ throw new Error(`Invalid input_mapping format: "${mapping}" (expected "target.key")`);
620
+ }
621
+ const target = mapping.slice(0, dotIndex);
622
+ const key = mapping.slice(dotIndex + 1);
623
+ if (!["body", "query", "path", "header"].includes(target)) {
624
+ throw new Error(
625
+ `Invalid mapping target "${target}" in "${mapping}" (must be body|query|path|header)`
626
+ );
627
+ }
628
+ return { target, key };
629
+ }
630
+ function extractByPath(obj, dotPath) {
631
+ const normalizedPath = dotPath.startsWith("response.") ? dotPath.slice("response.".length) : dotPath;
632
+ const parts = normalizedPath.split(".");
633
+ let current = obj;
634
+ for (const part of parts) {
635
+ if (current === null || typeof current !== "object") {
636
+ return void 0;
637
+ }
638
+ current = current[part];
639
+ }
640
+ return current;
641
+ }
642
+ function buildAuthHeaders(auth) {
643
+ if (!auth) return {};
644
+ switch (auth.type) {
645
+ case "bearer":
646
+ return { Authorization: `Bearer ${auth.token}` };
647
+ case "apikey":
648
+ return { [auth.header]: auth.key };
649
+ case "basic": {
650
+ const encoded = Buffer.from(`${auth.username}:${auth.password}`).toString("base64");
651
+ return { Authorization: `Basic ${encoded}` };
652
+ }
653
+ }
654
+ }
655
+ function applyInputMapping(params, mapping) {
656
+ const body = {};
657
+ const query = {};
658
+ const pathParams = {};
659
+ const headers = {};
660
+ for (const [paramName, mappingValue] of Object.entries(mapping)) {
661
+ const value = params[paramName];
662
+ if (value === void 0) continue;
663
+ const { target, key } = parseMappingTarget(mappingValue);
664
+ switch (target) {
665
+ case "body":
666
+ body[key] = value;
667
+ break;
668
+ case "query":
669
+ query[key] = String(value);
670
+ break;
671
+ case "path":
672
+ pathParams[key] = String(value);
673
+ break;
674
+ case "header":
675
+ headers[key] = String(value).replace(/[\r\n]/g, "");
676
+ break;
677
+ }
678
+ }
679
+ return { body, query, pathParams, headers };
680
+ }
681
+ function sleep(ms) {
682
+ return new Promise((resolve) => setTimeout(resolve, ms));
683
+ }
684
+ var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([429, 500, 503]);
685
+ var ApiExecutor = class {
686
+ /**
687
+ * Execute an API call described by the given skill config.
688
+ *
689
+ * @param config - The validated SkillConfig (must be ApiSkillConfig).
690
+ * @param params - Input parameters to map to the HTTP request.
691
+ * @returns Partial ExecutionResult (without latency_ms — added by SkillExecutor).
692
+ */
693
+ async execute(config, params) {
694
+ const apiConfig = config;
695
+ const { body, query, pathParams, headers: mappedHeaders } = applyInputMapping(
696
+ params,
697
+ apiConfig.input_mapping
698
+ );
699
+ let url = apiConfig.endpoint;
700
+ for (const [key, value] of Object.entries(pathParams)) {
701
+ url = url.replace(`{${key}}`, encodeURIComponent(value));
702
+ }
703
+ if (Object.keys(query).length > 0) {
704
+ const qs = new URLSearchParams(query).toString();
705
+ url = `${url}?${qs}`;
706
+ }
707
+ const authHeaders = buildAuthHeaders(apiConfig.auth);
708
+ const requestHeaders = {
709
+ ...authHeaders,
710
+ ...mappedHeaders
711
+ };
712
+ const hasBody = ["POST", "PUT"].includes(apiConfig.method);
713
+ if (hasBody) {
714
+ requestHeaders["Content-Type"] = "application/json";
715
+ }
716
+ const maxAttempts = (apiConfig.retries ?? 0) + 1;
717
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
718
+ if (attempt > 0) {
719
+ await sleep(100 * Math.pow(2, attempt - 1));
720
+ }
721
+ const controller = new AbortController();
722
+ const timeoutId = setTimeout(
723
+ () => controller.abort(),
724
+ apiConfig.timeout_ms ?? 3e4
725
+ );
726
+ let response;
727
+ try {
728
+ response = await fetch(url, {
729
+ method: apiConfig.method,
730
+ headers: requestHeaders,
731
+ body: hasBody ? JSON.stringify(body) : void 0,
732
+ signal: controller.signal
733
+ });
734
+ } catch (err) {
735
+ clearTimeout(timeoutId);
736
+ const message = err instanceof Error ? err.message : String(err);
737
+ const isAbort = err instanceof Error && err.name === "AbortError" || message.toLowerCase().includes("abort");
738
+ if (isAbort) {
739
+ return { success: false, error: `Request timeout after ${apiConfig.timeout_ms}ms` };
740
+ }
741
+ return { success: false, error: message };
742
+ } finally {
743
+ clearTimeout(timeoutId);
744
+ }
745
+ if (!response.ok && RETRYABLE_STATUSES.has(response.status) && attempt < maxAttempts - 1) {
746
+ continue;
747
+ }
748
+ if (!response.ok) {
749
+ return {
750
+ success: false,
751
+ error: `HTTP ${response.status} from ${apiConfig.endpoint}`
752
+ };
753
+ }
754
+ const responseBody = await response.json();
755
+ const outputMapping = apiConfig.output_mapping;
756
+ if (Object.keys(outputMapping).length === 0) {
757
+ return { success: true, result: responseBody };
758
+ }
759
+ const mappedOutput = {};
760
+ for (const [outputKey, path] of Object.entries(outputMapping)) {
761
+ mappedOutput[outputKey] = extractByPath(responseBody, path);
762
+ }
763
+ return { success: true, result: mappedOutput };
764
+ }
765
+ return { success: false, error: "Unexpected: retry loop exhausted" };
766
+ }
767
+ };
768
+
769
+ // src/skills/pipeline-executor.ts
770
+ import { execFile } from "child_process";
771
+ import { promisify } from "util";
772
+ var execFileAsync = promisify(execFile);
773
+ function shellEscape(value) {
774
+ return "'" + value.replace(/'/g, "'\\''") + "'";
775
+ }
776
+ function safeInterpolateCommand(template, context) {
777
+ return template.replace(/\$\{([^}]+)\}/g, (_match, expr) => {
778
+ const parts = expr.split(".");
779
+ let current = context;
780
+ for (const part of parts) {
781
+ if (current === null || typeof current !== "object") return "";
782
+ const bracketMatch = part.match(/^(\w+)\[(\d+)\]$/);
783
+ if (bracketMatch) {
784
+ current = current[bracketMatch[1]];
785
+ if (Array.isArray(current)) {
786
+ current = current[parseInt(bracketMatch[2], 10)];
787
+ } else {
788
+ return "";
789
+ }
790
+ } else {
791
+ current = current[part];
792
+ }
793
+ }
794
+ if (current === void 0 || current === null) return "";
795
+ return shellEscape(String(current));
796
+ });
797
+ }
798
+ var PipelineExecutor = class {
799
+ /**
800
+ * @param skillExecutor - The parent SkillExecutor used to dispatch sub-skill calls.
801
+ */
802
+ constructor(skillExecutor) {
803
+ this.skillExecutor = skillExecutor;
804
+ }
805
+ /**
806
+ * Execute a pipeline skill config sequentially.
807
+ *
808
+ * Algorithm:
809
+ * 1. Initialise context: { params, steps: [], prev: { result: null } }
810
+ * 2. For each step:
811
+ * a. Resolve input_mapping keys against current context via interpolateObject.
812
+ * b. If step has `skill_id`: dispatch via skillExecutor.execute(). On failure → stop.
813
+ * c. If step has `command`: interpolate command string, run via exec(). On non-zero exit → stop.
814
+ * d. Store step result in context.steps[i] and context.prev.
815
+ * 3. Return success with final step result (or null for empty pipeline).
816
+ *
817
+ * @param config - The PipelineSkillConfig for this skill.
818
+ * @param params - Input parameters from the caller.
819
+ * @returns Partial ExecutionResult (without latency_ms — added by SkillExecutor wrapper).
820
+ */
821
+ async execute(config, params, onProgress) {
822
+ const pipelineConfig = config;
823
+ const steps = pipelineConfig.steps ?? [];
824
+ if (steps.length === 0) {
825
+ return { success: true, result: null };
826
+ }
827
+ const context = {
828
+ params,
829
+ steps: [],
830
+ prev: { result: null }
831
+ };
832
+ for (let i = 0; i < steps.length; i++) {
833
+ const step = steps[i];
834
+ if (step === void 0) {
835
+ return {
836
+ success: false,
837
+ error: `Step ${i} failed: step definition is undefined`
838
+ };
839
+ }
840
+ const resolvedInputs = interpolateObject(
841
+ step.input_mapping,
842
+ context
843
+ );
844
+ let stepResult;
845
+ if ("skill_id" in step && step.skill_id) {
846
+ const subResult = await this.skillExecutor.execute(
847
+ step.skill_id,
848
+ resolvedInputs
849
+ );
850
+ if (!subResult.success) {
851
+ return {
852
+ success: false,
853
+ error: `Step ${i} failed: ${subResult.error ?? "unknown error"}`
854
+ };
855
+ }
856
+ stepResult = subResult.result;
857
+ } else if ("command" in step && step.command) {
858
+ const interpolatedCommand = safeInterpolateCommand(
859
+ step.command,
860
+ context
861
+ );
862
+ try {
863
+ const { stdout } = await execFileAsync("/bin/sh", ["-c", interpolatedCommand], { timeout: 3e4 });
864
+ stepResult = stdout.trim();
865
+ } catch (err) {
866
+ const message = err instanceof Error ? err.message : String(err);
867
+ return {
868
+ success: false,
869
+ error: `Step ${i} failed: ${message}`
870
+ };
871
+ }
872
+ } else {
873
+ return {
874
+ success: false,
875
+ error: `Step ${i} failed: step must have either "skill_id" or "command"`
876
+ };
877
+ }
878
+ context.steps.push({ result: stepResult });
879
+ context.prev = { result: stepResult };
880
+ if (onProgress && i < steps.length - 1) {
881
+ onProgress({
882
+ step: i + 1,
883
+ total: steps.length,
884
+ message: `Completed step ${i + 1}/${steps.length}`
885
+ });
886
+ }
887
+ }
888
+ const lastStep = context.steps[context.steps.length - 1];
889
+ return {
890
+ success: true,
891
+ result: lastStep !== void 0 ? lastStep.result : null
892
+ };
893
+ }
894
+ };
895
+
896
+ // src/skills/openclaw-bridge.ts
897
+ import { execFileSync } from "child_process";
898
+ var DEFAULT_BASE_URL = "http://localhost:3000";
899
+ var DEFAULT_TIMEOUT_MS = 6e4;
900
+ function buildPayload(config, params) {
901
+ return {
902
+ task: config.name,
903
+ params,
904
+ source: "agentbnb",
905
+ skill_id: config.id
906
+ };
907
+ }
908
+ async function executeWebhook(config, payload) {
909
+ const baseUrl = process.env["OPENCLAW_BASE_URL"] ?? DEFAULT_BASE_URL;
910
+ const url = `${baseUrl}/openclaw/${config.agent_name}/task`;
911
+ const timeoutMs = config.timeout_ms ?? DEFAULT_TIMEOUT_MS;
912
+ const controller = new AbortController();
913
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
914
+ try {
915
+ const response = await fetch(url, {
916
+ method: "POST",
917
+ headers: { "Content-Type": "application/json" },
918
+ body: JSON.stringify(payload),
919
+ signal: controller.signal
920
+ });
921
+ if (!response.ok) {
922
+ return {
923
+ success: false,
924
+ error: `Webhook returned HTTP ${response.status}: ${response.statusText}`
925
+ };
926
+ }
927
+ const result = await response.json();
928
+ return { success: true, result };
929
+ } catch (err) {
930
+ if (err instanceof Error && err.name === "AbortError") {
931
+ return {
932
+ success: false,
933
+ error: `OpenClaw webhook timed out after ${timeoutMs}ms`
934
+ };
935
+ }
936
+ const message = err instanceof Error ? err.message : String(err);
937
+ return { success: false, error: message };
938
+ } finally {
939
+ clearTimeout(timer);
940
+ }
941
+ }
942
+ function validateAgentName(name) {
943
+ return /^[a-zA-Z0-9._-]+$/.test(name);
944
+ }
945
+ function executeProcess(config, payload) {
946
+ const timeoutMs = config.timeout_ms ?? DEFAULT_TIMEOUT_MS;
947
+ if (!validateAgentName(config.agent_name)) {
948
+ return {
949
+ success: false,
950
+ error: `Invalid agent name: "${config.agent_name}" (only alphanumeric, hyphens, underscores, dots allowed)`
951
+ };
952
+ }
953
+ const inputJson = JSON.stringify(payload);
954
+ try {
955
+ const stdout = execFileSync("openclaw", ["run", config.agent_name, "--input", inputJson], {
956
+ timeout: timeoutMs
957
+ });
958
+ const text = stdout.toString().trim();
959
+ const result = JSON.parse(text);
960
+ return { success: true, result };
961
+ } catch (err) {
962
+ const message = err instanceof Error ? err.message : String(err);
963
+ return { success: false, error: message };
964
+ }
965
+ }
966
+ async function executeTelegram(config, payload) {
967
+ const token = process.env["TELEGRAM_BOT_TOKEN"];
968
+ if (!token) {
969
+ return {
970
+ success: false,
971
+ error: "TELEGRAM_BOT_TOKEN environment variable is not set"
972
+ };
973
+ }
974
+ const chatId = process.env["TELEGRAM_CHAT_ID"];
975
+ if (!chatId) {
976
+ return {
977
+ success: false,
978
+ error: "TELEGRAM_CHAT_ID environment variable is not set"
979
+ };
980
+ }
981
+ const text = `[AgentBnB] Skill: ${config.name} (${config.id})
982
+ Agent: ${config.agent_name}
983
+ Params: ${JSON.stringify(payload.params ?? {})}`;
984
+ const url = `https://api.telegram.org/bot${token}/sendMessage`;
985
+ try {
986
+ await fetch(url, {
987
+ method: "POST",
988
+ headers: { "Content-Type": "application/json" },
989
+ body: JSON.stringify({ chat_id: chatId, text })
990
+ });
991
+ return {
992
+ success: true,
993
+ result: { sent: true, channel: "telegram" }
994
+ };
995
+ } catch (err) {
996
+ const message = err instanceof Error ? err.message : String(err);
997
+ return { success: false, error: message };
998
+ }
999
+ }
1000
+ var OpenClawBridge = class {
1001
+ /**
1002
+ * Execute a skill with the given config and input parameters.
1003
+ *
1004
+ * @param config - The SkillConfig for this skill (must be type 'openclaw').
1005
+ * @param params - Input parameters passed by the caller.
1006
+ * @returns Partial ExecutionResult without latency_ms.
1007
+ */
1008
+ async execute(config, params) {
1009
+ const ocConfig = config;
1010
+ const payload = buildPayload(ocConfig, params);
1011
+ switch (ocConfig.channel) {
1012
+ case "webhook":
1013
+ return executeWebhook(ocConfig, payload);
1014
+ case "process":
1015
+ return executeProcess(ocConfig, payload);
1016
+ case "telegram":
1017
+ return executeTelegram(ocConfig, payload);
1018
+ default: {
1019
+ const unknownChannel = ocConfig.channel;
1020
+ return {
1021
+ success: false,
1022
+ error: `Unknown OpenClaw channel: "${String(unknownChannel)}"`
1023
+ };
1024
+ }
1025
+ }
1026
+ }
1027
+ };
1028
+
1029
+ // src/skills/command-executor.ts
1030
+ import { execFile as execFile2 } from "child_process";
1031
+ function shellEscape2(value) {
1032
+ return "'" + value.replace(/'/g, "'\\''") + "'";
1033
+ }
1034
+ function safeInterpolateCommand2(template, context) {
1035
+ return template.replace(/\$\{([^}]+)\}/g, (_match, expr) => {
1036
+ const parts = expr.split(".");
1037
+ let current = context;
1038
+ for (const part of parts) {
1039
+ if (current === null || typeof current !== "object") return "";
1040
+ const bracketMatch = part.match(/^(\w+)\[(\d+)\]$/);
1041
+ if (bracketMatch) {
1042
+ current = current[bracketMatch[1]];
1043
+ if (Array.isArray(current)) {
1044
+ current = current[parseInt(bracketMatch[2], 10)];
1045
+ } else {
1046
+ return "";
1047
+ }
1048
+ } else {
1049
+ current = current[part];
1050
+ }
1051
+ }
1052
+ if (current === void 0 || current === null) return "";
1053
+ return shellEscape2(String(current));
1054
+ });
1055
+ }
1056
+ function execFileAsync2(file, args, options) {
1057
+ return new Promise((resolve, reject) => {
1058
+ execFile2(file, args, options, (error, stdout, stderr) => {
1059
+ const stdoutStr = typeof stdout === "string" ? stdout : stdout.toString();
1060
+ const stderrStr = typeof stderr === "string" ? stderr : stderr.toString();
1061
+ if (error) {
1062
+ const enriched = Object.assign(error, { stderr: stderrStr });
1063
+ reject(enriched);
1064
+ } else {
1065
+ resolve({ stdout: stdoutStr, stderr: stderrStr });
1066
+ }
1067
+ });
1068
+ });
1069
+ }
1070
+ var CommandExecutor = class {
1071
+ /**
1072
+ * Execute a command skill with the provided parameters.
1073
+ *
1074
+ * Steps:
1075
+ * 1. Security check: base command must be in `allowed_commands` if set.
1076
+ * 2. Interpolate `config.command` using `{ params }` context.
1077
+ * 3. Run via `child_process.exec` with timeout and cwd.
1078
+ * 4. Parse stdout based on `output_type`: text | json | file.
1079
+ *
1080
+ * @param config - Validated CommandSkillConfig.
1081
+ * @param params - Input parameters passed by the caller.
1082
+ * @returns Partial ExecutionResult (without latency_ms).
1083
+ */
1084
+ async execute(config, params) {
1085
+ const cmdConfig = config;
1086
+ const baseCommand = cmdConfig.command.trim().split(/\s+/)[0] ?? "";
1087
+ if (cmdConfig.allowed_commands && cmdConfig.allowed_commands.length > 0) {
1088
+ if (!cmdConfig.allowed_commands.includes(baseCommand)) {
1089
+ return {
1090
+ success: false,
1091
+ error: `Command not allowed: "${baseCommand}". Allowed: ${cmdConfig.allowed_commands.join(", ")}`
1092
+ };
1093
+ }
1094
+ }
1095
+ const interpolatedCommand = safeInterpolateCommand2(cmdConfig.command, { params });
1096
+ const timeout = cmdConfig.timeout_ms ?? 3e4;
1097
+ const cwd = cmdConfig.working_dir ?? process.cwd();
1098
+ let stdout;
1099
+ try {
1100
+ const result = await execFileAsync2("/bin/sh", ["-c", interpolatedCommand], {
1101
+ timeout,
1102
+ cwd,
1103
+ maxBuffer: 10 * 1024 * 1024
1104
+ // 10 MB
1105
+ });
1106
+ stdout = result.stdout;
1107
+ } catch (err) {
1108
+ if (err instanceof Error) {
1109
+ const message = err.message;
1110
+ const stderrContent = err.stderr ?? "";
1111
+ if (message.includes("timed out") || message.includes("ETIMEDOUT") || err.code === "ETIMEDOUT") {
1112
+ return {
1113
+ success: false,
1114
+ error: `Command timed out after ${timeout}ms`
1115
+ };
1116
+ }
1117
+ return {
1118
+ success: false,
1119
+ error: stderrContent.trim() || message
1120
+ };
1121
+ }
1122
+ return {
1123
+ success: false,
1124
+ error: String(err)
1125
+ };
1126
+ }
1127
+ const rawOutput = stdout.trim();
1128
+ switch (cmdConfig.output_type) {
1129
+ case "text":
1130
+ return { success: true, result: rawOutput };
1131
+ case "json": {
1132
+ try {
1133
+ const parsed = JSON.parse(rawOutput);
1134
+ return { success: true, result: parsed };
1135
+ } catch {
1136
+ return {
1137
+ success: false,
1138
+ error: `Failed to parse JSON output: ${rawOutput.slice(0, 100)}`
1139
+ };
1140
+ }
1141
+ }
1142
+ case "file":
1143
+ return { success: true, result: { file_path: rawOutput } };
1144
+ default:
1145
+ return {
1146
+ success: false,
1147
+ error: `Unknown output_type: ${String(cmdConfig.output_type)}`
1148
+ };
1149
+ }
1150
+ }
1151
+ };
1152
+
1153
+ // src/runtime/agent-runtime.ts
1154
+ var AgentRuntime = class {
1155
+ /** The registry SQLite database instance */
1156
+ registryDb;
1157
+ /** The credit SQLite database instance */
1158
+ creditDb;
1159
+ /** The agent owner identifier */
1160
+ owner;
1161
+ /** Registered background Cron jobs */
1162
+ jobs = [];
1163
+ /**
1164
+ * The SkillExecutor instance, populated by start() if skillsYamlPath is set.
1165
+ * Undefined if no skills.yaml was provided or the file does not exist.
1166
+ */
1167
+ skillExecutor;
1168
+ draining = false;
1169
+ orphanedEscrowAgeMinutes;
1170
+ skillsYamlPath;
1171
+ conductorEnabled;
1172
+ conductorToken;
1173
+ /**
1174
+ * Creates a new AgentRuntime instance.
1175
+ * Opens both databases with WAL mode, foreign_keys=ON, and busy_timeout=5000.
1176
+ * Schema migrations are applied via openDatabase() and openCreditDb().
1177
+ *
1178
+ * @param options - Runtime configuration options.
1179
+ */
1180
+ constructor(options) {
1181
+ this.owner = options.owner;
1182
+ this.orphanedEscrowAgeMinutes = options.orphanedEscrowAgeMinutes ?? 10;
1183
+ this.skillsYamlPath = options.skillsYamlPath;
1184
+ this.conductorEnabled = options.conductorEnabled ?? false;
1185
+ this.conductorToken = options.conductorToken ?? "";
1186
+ this.registryDb = openDatabase(options.registryDbPath);
1187
+ this.creditDb = openCreditDb(options.creditDbPath);
1188
+ this.registryDb.pragma("busy_timeout = 5000");
1189
+ this.creditDb.pragma("busy_timeout = 5000");
1190
+ }
1191
+ /**
1192
+ * Registers a Cron job to be managed by this runtime.
1193
+ * Registered jobs will be stopped automatically on shutdown().
1194
+ *
1195
+ * @param job - The Cron job instance to register.
1196
+ */
1197
+ registerJob(job) {
1198
+ this.jobs.push(job);
1199
+ }
1200
+ /**
1201
+ * Starts the runtime.
1202
+ * Recovers orphaned escrows (held escrows older than orphanedEscrowAgeMinutes).
1203
+ * If skillsYamlPath is set and the file exists, initializes SkillExecutor with
1204
+ * all four executor modes (api, pipeline, openclaw, command).
1205
+ *
1206
+ * Call this after creating the runtime and before accepting requests.
1207
+ */
1208
+ async start() {
1209
+ await this.recoverOrphanedEscrows();
1210
+ await this.initSkillExecutor();
1211
+ }
1212
+ /**
1213
+ * Initializes SkillExecutor from skills.yaml if skillsYamlPath is configured
1214
+ * and the file exists on disk.
1215
+ *
1216
+ * Uses a mutable Map to handle the PipelineExecutor circular dependency:
1217
+ * 1. Create an empty modes Map and a SkillExecutor (holds Map reference).
1218
+ * 2. Create PipelineExecutor passing the SkillExecutor (for sub-skill dispatch).
1219
+ * 3. Populate the Map with all 4 modes — SkillExecutor sees them via reference.
1220
+ */
1221
+ async initSkillExecutor() {
1222
+ const hasSkillsYaml = this.skillsYamlPath && existsSync(this.skillsYamlPath);
1223
+ if (!hasSkillsYaml && !this.conductorEnabled) {
1224
+ return;
1225
+ }
1226
+ let configs = [];
1227
+ if (hasSkillsYaml) {
1228
+ const yamlContent = readFileSync(this.skillsYamlPath, "utf8");
1229
+ configs = parseSkillsFile(yamlContent);
1230
+ }
1231
+ const modes = /* @__PURE__ */ new Map();
1232
+ if (this.conductorEnabled) {
1233
+ const { ConductorMode } = await import("../../conductor-mode-ESGFZ6T5.js");
1234
+ const { registerConductorCard, CONDUCTOR_OWNER } = await import("../../card-RNEWSAQ6.js");
1235
+ const { loadPeers } = await import("../../peers-E4MKNNDN.js");
1236
+ registerConductorCard(this.registryDb);
1237
+ const resolveAgentUrl = (owner) => {
1238
+ const peers = loadPeers();
1239
+ const peer = peers.find((p) => p.name.toLowerCase() === owner.toLowerCase());
1240
+ if (!peer) {
1241
+ throw new Error(
1242
+ `No peer found for agent owner "${owner}". Add with: agentbnb peers add ${owner} <url> <token>`
1243
+ );
1244
+ }
1245
+ const stmt = this.registryDb.prepare(
1246
+ "SELECT id FROM capability_cards WHERE owner = ? LIMIT 1"
1247
+ );
1248
+ const row = stmt.get(owner);
1249
+ const cardId = row?.id ?? owner;
1250
+ return { url: peer.url, cardId };
1251
+ };
1252
+ const conductorMode = new ConductorMode({
1253
+ db: this.registryDb,
1254
+ creditDb: this.creditDb,
1255
+ conductorOwner: CONDUCTOR_OWNER,
1256
+ gatewayToken: this.conductorToken,
1257
+ resolveAgentUrl,
1258
+ maxBudget: 100
1259
+ });
1260
+ modes.set("conductor", conductorMode);
1261
+ configs.push(
1262
+ {
1263
+ id: "orchestrate",
1264
+ type: "conductor",
1265
+ name: "Orchestrate",
1266
+ conductor_skill: "orchestrate",
1267
+ pricing: { credits_per_call: 5 }
1268
+ },
1269
+ {
1270
+ id: "plan",
1271
+ type: "conductor",
1272
+ name: "Plan",
1273
+ conductor_skill: "plan",
1274
+ pricing: { credits_per_call: 1 }
1275
+ }
1276
+ );
1277
+ }
1278
+ const executor = createSkillExecutor(configs, modes);
1279
+ if (hasSkillsYaml) {
1280
+ const pipelineExecutor = new PipelineExecutor(executor);
1281
+ modes.set("api", new ApiExecutor());
1282
+ modes.set("pipeline", pipelineExecutor);
1283
+ modes.set("openclaw", new OpenClawBridge());
1284
+ modes.set("command", new CommandExecutor());
1285
+ }
1286
+ this.skillExecutor = executor;
1287
+ }
1288
+ /**
1289
+ * Recovers orphaned escrows by releasing them.
1290
+ * Orphaned escrows are 'held' escrows older than the configured age threshold.
1291
+ * Errors during individual release are swallowed (escrow may have settled between query and release).
1292
+ */
1293
+ async recoverOrphanedEscrows() {
1294
+ const cutoff = new Date(
1295
+ Date.now() - this.orphanedEscrowAgeMinutes * 60 * 1e3
1296
+ ).toISOString();
1297
+ const orphaned = this.creditDb.prepare(
1298
+ "SELECT id FROM credit_escrow WHERE status = 'held' AND created_at < ?"
1299
+ ).all(cutoff);
1300
+ for (const row of orphaned) {
1301
+ try {
1302
+ releaseEscrow(this.creditDb, row.id);
1303
+ } catch {
1304
+ }
1305
+ }
1306
+ }
1307
+ /**
1308
+ * Shuts down the runtime gracefully.
1309
+ * Sets draining flag, stops all registered Cron jobs, and closes both databases.
1310
+ * Idempotent — safe to call multiple times.
1311
+ */
1312
+ async shutdown() {
1313
+ if (this.draining) {
1314
+ return;
1315
+ }
1316
+ this.draining = true;
1317
+ for (const job of this.jobs) {
1318
+ job.stop();
1319
+ }
1320
+ try {
1321
+ this.registryDb.close();
1322
+ } catch {
1323
+ }
1324
+ try {
1325
+ this.creditDb.close();
1326
+ } catch {
1327
+ }
1328
+ }
1329
+ /**
1330
+ * Returns true if the runtime is shutting down or has shut down.
1331
+ * Background handlers should check this before processing new requests.
1332
+ */
1333
+ get isDraining() {
1334
+ return this.draining;
1335
+ }
1336
+ };
1337
+
1338
+ // src/openclaw/soul-sync.ts
1339
+ import { randomUUID as randomUUID2 } from "crypto";
1340
+
1341
+ // src/skills/publish-capability.ts
1342
+ import { randomUUID } from "crypto";
1343
+ function parseSoulMd(content) {
1344
+ const lines = content.split("\n");
1345
+ let name = "";
1346
+ let description = "";
1347
+ const capabilities = [];
1348
+ const unknownSections = [];
1349
+ let currentSection = null;
1350
+ let currentCapabilityName = "";
1351
+ let currentCapabilityLines = [];
1352
+ let currentCapabilityPricing = void 0;
1353
+ let descriptionLines = [];
1354
+ let pastFirstH1 = false;
1355
+ let pastFirstH2 = false;
1356
+ const flushCapability = () => {
1357
+ if (currentCapabilityName) {
1358
+ const cap = {
1359
+ name: currentCapabilityName,
1360
+ description: currentCapabilityLines.join(" ").trim()
1361
+ };
1362
+ if (currentCapabilityPricing !== void 0) {
1363
+ cap.pricing = currentCapabilityPricing;
1364
+ }
1365
+ capabilities.push(cap);
1366
+ currentCapabilityName = "";
1367
+ currentCapabilityLines = [];
1368
+ currentCapabilityPricing = void 0;
1369
+ }
1370
+ };
1371
+ for (const line of lines) {
1372
+ const trimmed = line.trim();
1373
+ if (/^# /.test(trimmed) && !pastFirstH1) {
1374
+ name = trimmed.slice(2).trim();
1375
+ pastFirstH1 = true;
1376
+ currentSection = "preamble";
1377
+ continue;
1378
+ }
1379
+ if (/^## /.test(trimmed)) {
1380
+ flushCapability();
1381
+ const capName = trimmed.slice(3).trim();
1382
+ currentCapabilityName = capName;
1383
+ currentSection = "capability";
1384
+ pastFirstH2 = true;
1385
+ continue;
1386
+ }
1387
+ if (/^#{3,} /.test(trimmed)) {
1388
+ const sectionName = trimmed.replace(/^#+\s*/, "");
1389
+ if (!unknownSections.includes(sectionName)) {
1390
+ unknownSections.push(sectionName);
1391
+ }
1392
+ continue;
1393
+ }
1394
+ if (trimmed === "") continue;
1395
+ if (currentSection === "preamble" && !pastFirstH2) {
1396
+ descriptionLines.push(trimmed);
1397
+ } else if (currentSection === "capability") {
1398
+ const pricingMatch = trimmed.match(/^pricing:\s*(\d+(?:\.\d+)?)$/i);
1399
+ if (pricingMatch) {
1400
+ const val = parseFloat(pricingMatch[1]);
1401
+ if (!isNaN(val) && val >= 0) {
1402
+ currentCapabilityPricing = val;
1403
+ }
1404
+ } else {
1405
+ currentCapabilityLines.push(trimmed);
1406
+ }
1407
+ }
1408
+ }
1409
+ flushCapability();
1410
+ if (descriptionLines.length > 0) {
1411
+ description = descriptionLines[0] ?? "";
1412
+ }
1413
+ return {
1414
+ name,
1415
+ description,
1416
+ level: 2,
1417
+ capabilities,
1418
+ unknownSections
1419
+ };
1420
+ }
1421
+
1422
+ // src/openclaw/soul-sync.ts
1423
+ function parseSoulMdV2(content) {
1424
+ const parsed = parseSoulMd(content);
1425
+ const skills = parsed.capabilities.map((cap) => {
1426
+ const sanitizedId = cap.name.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
1427
+ const id = sanitizedId.length > 0 ? sanitizedId : randomUUID2();
1428
+ return {
1429
+ id,
1430
+ name: cap.name,
1431
+ description: (cap.description.slice(0, 500) || cap.name).slice(0, 500),
1432
+ level: 2,
1433
+ inputs: [
1434
+ {
1435
+ name: "input",
1436
+ type: "text",
1437
+ description: "Input for the skill",
1438
+ required: true
1439
+ }
1440
+ ],
1441
+ outputs: [
1442
+ {
1443
+ name: "output",
1444
+ type: "text",
1445
+ description: "Output from the skill",
1446
+ required: true
1447
+ }
1448
+ ],
1449
+ pricing: { credits_per_call: cap.pricing !== void 0 ? cap.pricing : 10 },
1450
+ availability: { online: true }
1451
+ };
1452
+ });
1453
+ return {
1454
+ agentName: parsed.name || "Unknown Agent",
1455
+ description: parsed.description,
1456
+ skills
1457
+ };
1458
+ }
1459
+ function publishFromSoulV2(db, soulContent, owner) {
1460
+ const { agentName, skills } = parseSoulMdV2(soulContent);
1461
+ if (skills.length === 0) {
1462
+ throw new AgentBnBError("SOUL.md has no H2 sections", "VALIDATION_ERROR");
1463
+ }
1464
+ const existingCards = listCards(db, owner);
1465
+ const existingV2 = existingCards.find(
1466
+ (c) => c.spec_version === "2.0"
1467
+ );
1468
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1469
+ const cardId = existingV2?.id ?? randomUUID2();
1470
+ const card = {
1471
+ spec_version: "2.0",
1472
+ id: cardId,
1473
+ owner,
1474
+ agent_name: agentName,
1475
+ skills,
1476
+ availability: { online: true },
1477
+ created_at: existingV2?.created_at ?? now,
1478
+ updated_at: now
1479
+ };
1480
+ CapabilityCardV2Schema.parse(card);
1481
+ if (existingV2) {
1482
+ db.prepare(
1483
+ "UPDATE capability_cards SET data = ?, updated_at = ? WHERE id = ?"
1484
+ ).run(JSON.stringify(card), now, cardId);
1485
+ } else {
1486
+ db.prepare(
1487
+ "INSERT INTO capability_cards (id, owner, data, created_at, updated_at) VALUES (?, ?, ?, ?, ?)"
1488
+ ).run(cardId, owner, JSON.stringify(card), now, now);
1489
+ }
1490
+ return card;
1491
+ }
1492
+
1493
+ // src/gateway/server.ts
1494
+ import Fastify from "fastify";
1495
+
1496
+ // src/gateway/execute.ts
1497
+ import { randomUUID as randomUUID3 } from "crypto";
1498
+
1499
+ // src/credit/settlement.ts
1500
+ function settleProviderEarning(providerDb, providerOwner, receipt) {
1501
+ recordEarning(
1502
+ providerDb,
1503
+ providerOwner,
1504
+ receipt.amount,
1505
+ receipt.card_id,
1506
+ receipt.nonce
1507
+ );
1508
+ return { settled: true };
1509
+ }
1510
+
1511
+ // src/gateway/execute.ts
1512
+ async function executeCapabilityRequest(opts) {
1513
+ const {
1514
+ registryDb,
1515
+ creditDb,
1516
+ cardId,
1517
+ skillId,
1518
+ params,
1519
+ requester,
1520
+ escrowReceipt: receipt,
1521
+ skillExecutor,
1522
+ handlerUrl,
1523
+ timeoutMs = 3e5,
1524
+ onProgress,
1525
+ relayAuthorized = false
1526
+ } = opts;
1527
+ const card = getCard(registryDb, cardId);
1528
+ if (!card) {
1529
+ return { success: false, error: { code: -32602, message: `Card not found: ${cardId}` } };
1530
+ }
1531
+ let creditsNeeded;
1532
+ let cardName;
1533
+ let resolvedSkillId;
1534
+ const rawCard = card;
1535
+ if (Array.isArray(rawCard["skills"])) {
1536
+ const v2card = card;
1537
+ const skill = skillId ? v2card.skills.find((s) => s.id === skillId) : v2card.skills[0];
1538
+ if (!skill) {
1539
+ return { success: false, error: { code: -32602, message: `Skill not found: ${skillId}` } };
1540
+ }
1541
+ creditsNeeded = skill.pricing.credits_per_call;
1542
+ cardName = skill.name;
1543
+ resolvedSkillId = skill.id;
1544
+ } else {
1545
+ creditsNeeded = card.pricing.credits_per_call;
1546
+ cardName = card.name;
1547
+ }
1548
+ let escrowId = null;
1549
+ let isRemoteEscrow = false;
1550
+ if (relayAuthorized) {
1551
+ } else if (receipt) {
1552
+ const { signature, ...receiptData2 } = receipt;
1553
+ const publicKeyBuf = Buffer.from(receipt.requester_public_key, "hex");
1554
+ const valid = verifyEscrowReceipt(receiptData2, signature, publicKeyBuf);
1555
+ if (!valid) {
1556
+ return { success: false, error: { code: -32603, message: "Invalid escrow receipt signature" } };
1557
+ }
1558
+ if (receipt.amount < creditsNeeded) {
1559
+ return { success: false, error: { code: -32603, message: "Insufficient escrow amount" } };
1560
+ }
1561
+ const receiptAge = Date.now() - new Date(receipt.timestamp).getTime();
1562
+ if (receiptAge > 5 * 60 * 1e3) {
1563
+ return { success: false, error: { code: -32603, message: "Escrow receipt expired" } };
1564
+ }
1565
+ isRemoteEscrow = true;
1566
+ } else {
1567
+ try {
1568
+ const balance = getBalance(creditDb, requester);
1569
+ if (balance < creditsNeeded) {
1570
+ return { success: false, error: { code: -32603, message: "Insufficient credits" } };
1571
+ }
1572
+ escrowId = holdEscrow(creditDb, requester, creditsNeeded, cardId);
1573
+ } catch (err) {
1574
+ const msg = err instanceof AgentBnBError ? err.message : "Failed to hold escrow";
1575
+ return { success: false, error: { code: -32603, message: msg } };
1576
+ }
1577
+ }
1578
+ const startMs = Date.now();
1579
+ const receiptData = isRemoteEscrow ? { receipt_released: true } : void 0;
1580
+ const handleFailure = (status, latencyMs, message) => {
1581
+ if (!isRemoteEscrow && escrowId) releaseEscrow(creditDb, escrowId);
1582
+ updateReputation(registryDb, cardId, false, latencyMs);
1583
+ try {
1584
+ insertRequestLog(registryDb, {
1585
+ id: randomUUID3(),
1586
+ card_id: cardId,
1587
+ card_name: cardName,
1588
+ skill_id: resolvedSkillId,
1589
+ requester,
1590
+ status,
1591
+ latency_ms: latencyMs,
1592
+ credits_charged: 0,
1593
+ created_at: (/* @__PURE__ */ new Date()).toISOString()
1594
+ });
1595
+ } catch {
1596
+ }
1597
+ return {
1598
+ success: false,
1599
+ error: { code: -32603, message, ...receiptData ? { data: receiptData } : {} }
1600
+ };
1601
+ };
1602
+ const handleSuccess = (result, latencyMs) => {
1603
+ if (isRemoteEscrow && receipt) {
1604
+ settleProviderEarning(creditDb, card.owner, receipt);
1605
+ } else if (escrowId) {
1606
+ settleEscrow(creditDb, escrowId, card.owner);
1607
+ }
1608
+ updateReputation(registryDb, cardId, true, latencyMs);
1609
+ try {
1610
+ insertRequestLog(registryDb, {
1611
+ id: randomUUID3(),
1612
+ card_id: cardId,
1613
+ card_name: cardName,
1614
+ skill_id: resolvedSkillId,
1615
+ requester,
1616
+ status: "success",
1617
+ latency_ms: latencyMs,
1618
+ credits_charged: creditsNeeded,
1619
+ created_at: (/* @__PURE__ */ new Date()).toISOString()
1620
+ });
1621
+ } catch {
1622
+ }
1623
+ const successResult = isRemoteEscrow ? {
1624
+ ...typeof result === "object" && result !== null ? result : { data: result },
1625
+ receipt_settled: true,
1626
+ receipt_nonce: receipt.nonce
1627
+ } : result;
1628
+ return { success: true, result: successResult };
1629
+ };
1630
+ if (skillExecutor) {
1631
+ const targetSkillId = resolvedSkillId ?? skillId ?? cardId;
1632
+ try {
1633
+ const execResult = await skillExecutor.execute(targetSkillId, params, onProgress);
1634
+ if (!execResult.success) {
1635
+ return handleFailure("failure", execResult.latency_ms, execResult.error ?? "Execution failed");
1636
+ }
1637
+ return handleSuccess(execResult.result, execResult.latency_ms);
1638
+ } catch (err) {
1639
+ const message = err instanceof Error ? err.message : "Execution error";
1640
+ return handleFailure("failure", Date.now() - startMs, message);
1641
+ }
1642
+ }
1643
+ if (!handlerUrl) {
1644
+ return handleFailure("failure", Date.now() - startMs, "No skill executor or handler URL configured");
1645
+ }
1646
+ const controller = new AbortController();
1647
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
1648
+ try {
1649
+ const response = await fetch(handlerUrl, {
1650
+ method: "POST",
1651
+ headers: { "Content-Type": "application/json" },
1652
+ body: JSON.stringify({ card_id: cardId, skill_id: resolvedSkillId, params }),
1653
+ signal: controller.signal
1654
+ });
1655
+ clearTimeout(timer);
1656
+ if (!response.ok) {
1657
+ return handleFailure("failure", Date.now() - startMs, `Handler returned ${response.status}`);
1658
+ }
1659
+ const result = await response.json();
1660
+ return handleSuccess(result, Date.now() - startMs);
1661
+ } catch (err) {
1662
+ clearTimeout(timer);
1663
+ const isTimeout = err instanceof Error && err.name === "AbortError";
1664
+ return handleFailure(
1665
+ isTimeout ? "timeout" : "failure",
1666
+ Date.now() - startMs,
1667
+ isTimeout ? "Execution timeout" : "Handler error"
1668
+ );
1669
+ }
1670
+ }
1671
+
1672
+ // src/gateway/server.ts
1673
+ var VERSION = "0.0.1";
1674
+ function createGatewayServer(opts) {
1675
+ const {
1676
+ registryDb,
1677
+ creditDb,
1678
+ tokens,
1679
+ handlerUrl,
1680
+ timeoutMs = 3e5,
1681
+ silent = false,
1682
+ skillExecutor
1683
+ } = opts;
1684
+ const fastify = Fastify({ logger: !silent });
1685
+ const tokenSet = new Set(tokens);
1686
+ fastify.addHook("onRequest", async (request) => {
1687
+ if (request.method === "GET" && request.url === "/health") return;
1688
+ const auth = request.headers.authorization;
1689
+ if (auth && auth.startsWith("Bearer ")) {
1690
+ const token = auth.slice("Bearer ".length).trim();
1691
+ if (tokenSet.has(token)) {
1692
+ request._authenticated = true;
1693
+ }
1694
+ }
1695
+ });
1696
+ fastify.addHook("preHandler", async (request, reply) => {
1697
+ if (request._authenticated) return;
1698
+ if (request.method === "GET" && request.url === "/health") return;
1699
+ const agentId = request.headers["x-agent-id"];
1700
+ const publicKeyHex = request.headers["x-agent-public-key"];
1701
+ const signature = request.headers["x-agent-signature"];
1702
+ if (agentId && publicKeyHex && signature) {
1703
+ try {
1704
+ const publicKeyBuf = Buffer.from(publicKeyHex, "hex");
1705
+ const body = request.body;
1706
+ if (body && typeof body === "object") {
1707
+ const valid = verifyEscrowReceipt(body, signature, publicKeyBuf);
1708
+ if (valid) return;
1709
+ }
1710
+ } catch {
1711
+ }
1712
+ }
1713
+ await reply.status(401).send({
1714
+ jsonrpc: "2.0",
1715
+ id: null,
1716
+ error: { code: -32e3, message: "Unauthorized: provide Bearer token or X-Agent-Id/Signature headers" }
1717
+ });
1718
+ });
1719
+ fastify.get("/health", async () => {
1720
+ return { status: "ok", version: VERSION, uptime: process.uptime() };
1721
+ });
1722
+ fastify.post("/rpc", async (request, reply) => {
1723
+ const body = request.body;
1724
+ if (body.jsonrpc !== "2.0" || !body.method) {
1725
+ return reply.status(400).send({
1726
+ jsonrpc: "2.0",
1727
+ id: body.id ?? null,
1728
+ error: { code: -32600, message: "Invalid Request" }
1729
+ });
1730
+ }
1731
+ const id = body.id ?? null;
1732
+ if (body.method !== "capability.execute") {
1733
+ return reply.send({
1734
+ jsonrpc: "2.0",
1735
+ id,
1736
+ error: { code: -32601, message: "Method not found" }
1737
+ });
1738
+ }
1739
+ const params = body.params ?? {};
1740
+ const cardId = params.card_id;
1741
+ const skillId = params.skill_id;
1742
+ if (!cardId) {
1743
+ return reply.send({
1744
+ jsonrpc: "2.0",
1745
+ id,
1746
+ error: { code: -32602, message: "Invalid params: card_id required" }
1747
+ });
1748
+ }
1749
+ const requester = params.requester ?? "unknown";
1750
+ const receipt = params.escrow_receipt;
1751
+ const result = await executeCapabilityRequest({
1752
+ registryDb,
1753
+ creditDb,
1754
+ cardId,
1755
+ skillId,
1756
+ params,
1757
+ requester,
1758
+ escrowReceipt: receipt,
1759
+ skillExecutor,
1760
+ handlerUrl,
1761
+ timeoutMs
1762
+ });
1763
+ if (result.success) {
1764
+ return reply.send({ jsonrpc: "2.0", id, result: result.result });
1765
+ } else {
1766
+ return reply.send({ jsonrpc: "2.0", id, error: result.error });
1767
+ }
1768
+ });
1769
+ return fastify;
1770
+ }
1771
+
1772
+ // src/autonomy/idle-monitor.ts
1773
+ import { Cron } from "croner";
1774
+ var IdleMonitor = class {
1775
+ job;
1776
+ owner;
1777
+ db;
1778
+ idleThreshold;
1779
+ autonomyConfig;
1780
+ /**
1781
+ * Creates a new IdleMonitor instance. The Cron job is constructed paused.
1782
+ * Call `start()` to activate polling.
1783
+ *
1784
+ * @param opts - IdleMonitor configuration options.
1785
+ */
1786
+ constructor(opts) {
1787
+ this.owner = opts.owner;
1788
+ this.db = opts.db;
1789
+ this.idleThreshold = opts.idleThreshold ?? 0.7;
1790
+ this.autonomyConfig = opts.autonomyConfig ?? DEFAULT_AUTONOMY_CONFIG;
1791
+ this.job = new Cron("0 * * * * *", { paused: true }, () => {
1792
+ void this.poll();
1793
+ });
1794
+ }
1795
+ /**
1796
+ * Starts the Cron polling loop by resuming the paused job.
1797
+ *
1798
+ * @returns The Cron job instance, for registration with AgentRuntime.registerJob().
1799
+ */
1800
+ start() {
1801
+ this.job.resume();
1802
+ return this.job;
1803
+ }
1804
+ /**
1805
+ * Returns the underlying Cron job instance.
1806
+ * Used for testing or for registering with AgentRuntime.registerJob() without starting.
1807
+ *
1808
+ * @returns The Cron job instance.
1809
+ */
1810
+ getJob() {
1811
+ return this.job;
1812
+ }
1813
+ /**
1814
+ * Polls the registry for all v2.0 cards owned by this agent, computes per-skill
1815
+ * idle rates from the past hour of request_log data, and triggers auto-share if eligible.
1816
+ *
1817
+ * Called automatically by the Cron job every 60 seconds.
1818
+ * Can be called directly in tests without needing to wait for the timer.
1819
+ */
1820
+ async poll() {
1821
+ const cards = listCards(this.db, this.owner);
1822
+ for (const card of cards) {
1823
+ const maybeV2 = card;
1824
+ if (!Array.isArray(maybeV2.skills)) {
1825
+ continue;
1826
+ }
1827
+ for (const skill of maybeV2.skills) {
1828
+ const capacity = skill.metadata?.capacity?.calls_per_hour ?? 60;
1829
+ const count = getSkillRequestCount(this.db, skill.id, 60 * 60 * 1e3);
1830
+ const idleRate = Math.max(0, 1 - count / capacity);
1831
+ updateSkillIdleRate(this.db, card.id, skill.id, idleRate);
1832
+ const isOnline = skill.availability?.online ?? false;
1833
+ if (idleRate >= this.idleThreshold && !isOnline) {
1834
+ const tier = getAutonomyTier(0, this.autonomyConfig);
1835
+ if (tier === 1) {
1836
+ updateSkillAvailability(this.db, card.id, skill.id, true);
1837
+ insertAuditEvent(this.db, {
1838
+ type: "auto_share",
1839
+ skill_id: skill.id,
1840
+ tier_invoked: 1,
1841
+ idle_rate: idleRate
1842
+ });
1843
+ } else if (tier === 2) {
1844
+ updateSkillAvailability(this.db, card.id, skill.id, true);
1845
+ insertAuditEvent(this.db, {
1846
+ type: "auto_share_notify",
1847
+ skill_id: skill.id,
1848
+ tier_invoked: 2,
1849
+ idle_rate: idleRate
1850
+ });
1851
+ } else {
1852
+ insertAuditEvent(this.db, {
1853
+ type: "auto_share_pending",
1854
+ skill_id: skill.id,
1855
+ tier_invoked: 3,
1856
+ idle_rate: idleRate
1857
+ });
1858
+ }
1859
+ }
1860
+ }
1861
+ }
1862
+ }
1863
+ };
1864
+
1865
+ // src/identity/identity.ts
1866
+ import { z as z2 } from "zod";
1867
+ import { createHash } from "crypto";
1868
+ import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync2, mkdirSync } from "fs";
1869
+ import { join } from "path";
1870
+ var AgentIdentitySchema = z2.object({
1871
+ /** Deterministic ID derived from public key: sha256(hex).slice(0, 16). */
1872
+ agent_id: z2.string().min(1),
1873
+ /** Human-readable owner name (from config or init). */
1874
+ owner: z2.string().min(1),
1875
+ /** Hex-encoded Ed25519 public key. */
1876
+ public_key: z2.string().min(1),
1877
+ /** ISO 8601 timestamp of identity creation. */
1878
+ created_at: z2.string().datetime(),
1879
+ /** Optional guarantor info if linked to a human. */
1880
+ guarantor: z2.object({
1881
+ github_login: z2.string().min(1),
1882
+ verified_at: z2.string().datetime()
1883
+ }).optional()
1884
+ });
1885
+ var AgentCertificateSchema = z2.object({
1886
+ identity: AgentIdentitySchema,
1887
+ /** ISO 8601 timestamp of certificate issuance. */
1888
+ issued_at: z2.string().datetime(),
1889
+ /** ISO 8601 timestamp of certificate expiry. */
1890
+ expires_at: z2.string().datetime(),
1891
+ /** Hex-encoded public key of the issuer (same as identity for self-signed). */
1892
+ issuer_public_key: z2.string().min(1),
1893
+ /** Base64url Ed25519 signature over { identity, issued_at, expires_at, issuer_public_key }. */
1894
+ signature: z2.string().min(1)
1895
+ });
1896
+ var IDENTITY_FILENAME = "identity.json";
1897
+ function deriveAgentId(publicKeyHex) {
1898
+ return createHash("sha256").update(publicKeyHex, "hex").digest("hex").slice(0, 16);
1899
+ }
1900
+ function createIdentity(configDir, owner) {
1901
+ if (!existsSync2(configDir)) {
1902
+ mkdirSync(configDir, { recursive: true });
1903
+ }
1904
+ let keys;
1905
+ try {
1906
+ keys = loadKeyPair(configDir);
1907
+ } catch {
1908
+ keys = generateKeyPair();
1909
+ saveKeyPair(configDir, keys);
1910
+ }
1911
+ const publicKeyHex = keys.publicKey.toString("hex");
1912
+ const agentId = deriveAgentId(publicKeyHex);
1913
+ const identity = {
1914
+ agent_id: agentId,
1915
+ owner,
1916
+ public_key: publicKeyHex,
1917
+ created_at: (/* @__PURE__ */ new Date()).toISOString()
1918
+ };
1919
+ saveIdentity(configDir, identity);
1920
+ return identity;
1921
+ }
1922
+ function loadIdentity(configDir) {
1923
+ const filePath = join(configDir, IDENTITY_FILENAME);
1924
+ if (!existsSync2(filePath)) return null;
1925
+ try {
1926
+ const raw = readFileSync2(filePath, "utf-8");
1927
+ return AgentIdentitySchema.parse(JSON.parse(raw));
1928
+ } catch {
1929
+ return null;
1930
+ }
1931
+ }
1932
+ function saveIdentity(configDir, identity) {
1933
+ if (!existsSync2(configDir)) {
1934
+ mkdirSync(configDir, { recursive: true });
1935
+ }
1936
+ const filePath = join(configDir, IDENTITY_FILENAME);
1937
+ writeFileSync(filePath, JSON.stringify(identity, null, 2), "utf-8");
1938
+ }
1939
+ function ensureIdentity(configDir, owner) {
1940
+ const existing = loadIdentity(configDir);
1941
+ if (existing) {
1942
+ if (existing.owner !== owner) {
1943
+ existing.owner = owner;
1944
+ saveIdentity(configDir, existing);
1945
+ }
1946
+ return existing;
1947
+ }
1948
+ return createIdentity(configDir, owner);
1949
+ }
1950
+
1951
+ // skills/agentbnb/bootstrap.ts
1952
+ async function activate(config) {
1953
+ const {
1954
+ owner,
1955
+ soulMdPath,
1956
+ registryDbPath = join2(homedir(), ".agentbnb", "registry.db"),
1957
+ creditDbPath = join2(homedir(), ".agentbnb", "credit.db"),
1958
+ gatewayPort = 7700,
1959
+ gatewayToken = randomUUID4(),
1960
+ autonomyConfig = DEFAULT_AUTONOMY_CONFIG,
1961
+ silent = false
1962
+ } = config;
1963
+ const identityRequired = config.identityRequired ?? false;
1964
+ const handlerUrl = config.handlerUrl ?? `http://localhost:${gatewayPort}`;
1965
+ if (!existsSync3(soulMdPath)) {
1966
+ throw new AgentBnBError(`SOUL.md not found at path: ${soulMdPath}`, "FILE_NOT_FOUND");
1967
+ }
1968
+ const soulContent = readFileSync3(soulMdPath, "utf8");
1969
+ const runtime = new AgentRuntime({ registryDbPath, creditDbPath, owner });
1970
+ await runtime.start();
1971
+ let identity = null;
1972
+ if (identityRequired) {
1973
+ const configDir = join2(homedir(), ".agentbnb");
1974
+ identity = ensureIdentity(configDir, owner);
1975
+ }
1976
+ const card = publishFromSoulV2(runtime.registryDb, soulContent, owner);
1977
+ const gateway = createGatewayServer({
1978
+ port: gatewayPort,
1979
+ registryDb: runtime.registryDb,
1980
+ creditDb: runtime.creditDb,
1981
+ tokens: [gatewayToken],
1982
+ handlerUrl,
1983
+ silent
1984
+ });
1985
+ await gateway.listen({ port: gatewayPort, host: "0.0.0.0" });
1986
+ const idleMonitor = new IdleMonitor({ owner, db: runtime.registryDb, autonomyConfig });
1987
+ const idleJob = idleMonitor.start();
1988
+ runtime.registerJob(idleJob);
1989
+ return { runtime, gateway, idleMonitor, card, identity };
1990
+ }
1991
+ async function deactivate(ctx) {
1992
+ try {
1993
+ await ctx.gateway.close();
1994
+ await ctx.runtime.shutdown();
1995
+ } catch {
1996
+ }
1997
+ }
1998
+ export {
1999
+ activate,
2000
+ deactivate
2001
+ };