agentbnb 3.0.0 → 3.1.1

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,41 +1,30 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- getActivityFeed,
4
- getCard,
5
- getRequestLog,
6
- getSkillRequestCount,
7
- insertCard,
8
- insertRequestLog,
9
- listCards,
10
- openDatabase,
11
- updateCard,
12
- updateReputation,
13
- updateSkillAvailability,
14
- updateSkillIdleRate
15
- } from "../chunk-PJSYSVKN.js";
3
+ executeCapabilityRequest,
4
+ generateKeyPair,
5
+ loadKeyPair,
6
+ releaseRequesterEscrow,
7
+ saveKeyPair,
8
+ settleRequesterEscrow,
9
+ signEscrowReceipt
10
+ } from "../chunk-MGHI67GR.js";
11
+ import {
12
+ RelayMessageSchema
13
+ } from "../chunk-3Y36WQDV.js";
16
14
  import {
17
15
  AutoRequestor,
18
16
  BudgetManager,
19
17
  DEFAULT_AUTONOMY_CONFIG,
20
18
  DEFAULT_BUDGET_CONFIG,
21
- bootstrapAgent,
22
- confirmEscrowDebit,
23
19
  filterCards,
24
20
  getAutonomyTier,
25
- getBalance,
26
- getTransactions,
27
- holdEscrow,
28
21
  insertAuditEvent,
29
22
  interpolateObject,
30
23
  listPendingRequests,
31
- openCreditDb,
32
- recordEarning,
33
- releaseEscrow,
34
24
  requestCapability,
35
25
  resolvePendingRequest,
36
- searchCards,
37
- settleEscrow
38
- } from "../chunk-V7M6GIJZ.js";
26
+ searchCards
27
+ } from "../chunk-2ETVQXP7.js";
39
28
  import {
40
29
  findPeer,
41
30
  getConfigDir,
@@ -45,85 +34,134 @@ import {
45
34
  saveConfig,
46
35
  savePeer
47
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-QAY6XTT7.js";
50
+ import {
51
+ bootstrapAgent,
52
+ getBalance,
53
+ getTransactions,
54
+ holdEscrow,
55
+ openCreditDb,
56
+ releaseEscrow
57
+ } from "../chunk-MZCNJ5PY.js";
48
58
  import {
49
59
  AgentBnBError,
50
- CapabilityCardSchema,
60
+ AnyCardSchema,
51
61
  CapabilityCardV2Schema
52
- } from "../chunk-TQMI73LL.js";
62
+ } from "../chunk-7RU5INZI.js";
53
63
 
54
64
  // src/cli/index.ts
55
65
  import { Command } from "commander";
56
- import { readFileSync as readFileSync4 } from "fs";
66
+ import { readFileSync as readFileSync5 } from "fs";
57
67
  import { createRequire } from "module";
58
68
  import { randomBytes } from "crypto";
59
- import { join as join3 } from "path";
69
+ import { join as join4 } from "path";
60
70
  import { networkInterfaces, homedir } from "os";
61
- import { createInterface } from "readline";
71
+ import { createInterface as createInterface2 } from "readline";
62
72
 
63
- // src/credit/signing.ts
64
- import { generateKeyPairSync, sign, verify, createPublicKey, createPrivateKey } from "crypto";
65
- import { writeFileSync, readFileSync, existsSync, chmodSync } from "fs";
73
+ // src/identity/identity.ts
74
+ import { z } from "zod";
75
+ import { createHash } from "crypto";
76
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
66
77
  import { join } from "path";
67
- function generateKeyPair() {
68
- const { publicKey, privateKey } = generateKeyPairSync("ed25519", {
69
- publicKeyEncoding: { type: "spki", format: "der" },
70
- privateKeyEncoding: { type: "pkcs8", format: "der" }
71
- });
72
- return {
73
- publicKey: Buffer.from(publicKey),
74
- privateKey: Buffer.from(privateKey)
75
- };
76
- }
77
- function saveKeyPair(configDir, keys) {
78
- const privatePath = join(configDir, "private.key");
79
- const publicPath = join(configDir, "public.key");
80
- writeFileSync(privatePath, keys.privateKey);
81
- chmodSync(privatePath, 384);
82
- writeFileSync(publicPath, keys.publicKey);
78
+ var AgentIdentitySchema = z.object({
79
+ /** Deterministic ID derived from public key: sha256(hex).slice(0, 16). */
80
+ agent_id: z.string().min(1),
81
+ /** Human-readable owner name (from config or init). */
82
+ owner: z.string().min(1),
83
+ /** Hex-encoded Ed25519 public key. */
84
+ public_key: z.string().min(1),
85
+ /** ISO 8601 timestamp of identity creation. */
86
+ created_at: z.string().datetime(),
87
+ /** Optional guarantor info if linked to a human. */
88
+ guarantor: z.object({
89
+ github_login: z.string().min(1),
90
+ verified_at: z.string().datetime()
91
+ }).optional()
92
+ });
93
+ var AgentCertificateSchema = z.object({
94
+ identity: AgentIdentitySchema,
95
+ /** ISO 8601 timestamp of certificate issuance. */
96
+ issued_at: z.string().datetime(),
97
+ /** ISO 8601 timestamp of certificate expiry. */
98
+ expires_at: z.string().datetime(),
99
+ /** Hex-encoded public key of the issuer (same as identity for self-signed). */
100
+ issuer_public_key: z.string().min(1),
101
+ /** Base64url Ed25519 signature over { identity, issued_at, expires_at, issuer_public_key }. */
102
+ signature: z.string().min(1)
103
+ });
104
+ var IDENTITY_FILENAME = "identity.json";
105
+ function deriveAgentId(publicKeyHex) {
106
+ return createHash("sha256").update(publicKeyHex, "hex").digest("hex").slice(0, 16);
83
107
  }
84
- function loadKeyPair(configDir) {
85
- const privatePath = join(configDir, "private.key");
86
- const publicPath = join(configDir, "public.key");
87
- if (!existsSync(privatePath) || !existsSync(publicPath)) {
88
- throw new AgentBnBError("Keypair not found. Run `agentbnb init` to generate one.", "KEYPAIR_NOT_FOUND");
108
+ function createIdentity(configDir, owner) {
109
+ if (!existsSync(configDir)) {
110
+ mkdirSync(configDir, { recursive: true });
89
111
  }
90
- return {
91
- publicKey: readFileSync(publicPath),
92
- privateKey: readFileSync(privatePath)
112
+ let keys;
113
+ try {
114
+ keys = loadKeyPair(configDir);
115
+ } catch {
116
+ keys = generateKeyPair();
117
+ saveKeyPair(configDir, keys);
118
+ }
119
+ const publicKeyHex = keys.publicKey.toString("hex");
120
+ const agentId = deriveAgentId(publicKeyHex);
121
+ const identity = {
122
+ agent_id: agentId,
123
+ owner,
124
+ public_key: publicKeyHex,
125
+ created_at: (/* @__PURE__ */ new Date()).toISOString()
93
126
  };
127
+ saveIdentity(configDir, identity);
128
+ return identity;
94
129
  }
95
- function canonicalJson(data) {
96
- return JSON.stringify(data, Object.keys(data).sort());
97
- }
98
- function signEscrowReceipt(data, privateKey) {
99
- const message = Buffer.from(canonicalJson(data), "utf-8");
100
- const keyObject = createPrivateKey({ key: privateKey, format: "der", type: "pkcs8" });
101
- const signature = sign(null, message, keyObject);
102
- return signature.toString("base64url");
103
- }
104
- function verifyEscrowReceipt(data, signature, publicKey) {
130
+ function loadIdentity(configDir) {
131
+ const filePath = join(configDir, IDENTITY_FILENAME);
132
+ if (!existsSync(filePath)) return null;
105
133
  try {
106
- const message = Buffer.from(canonicalJson(data), "utf-8");
107
- const keyObject = createPublicKey({ key: publicKey, format: "der", type: "spki" });
108
- const sigBuffer = Buffer.from(signature, "base64url");
109
- return verify(null, message, keyObject, sigBuffer);
134
+ const raw = readFileSync(filePath, "utf-8");
135
+ return AgentIdentitySchema.parse(JSON.parse(raw));
110
136
  } catch {
111
- return false;
137
+ return null;
138
+ }
139
+ }
140
+ function saveIdentity(configDir, identity) {
141
+ if (!existsSync(configDir)) {
142
+ mkdirSync(configDir, { recursive: true });
112
143
  }
144
+ const filePath = join(configDir, IDENTITY_FILENAME);
145
+ writeFileSync(filePath, JSON.stringify(identity, null, 2), "utf-8");
146
+ }
147
+ function ensureIdentity(configDir, owner) {
148
+ const existing = loadIdentity(configDir);
149
+ if (existing) return existing;
150
+ return createIdentity(configDir, owner);
113
151
  }
114
152
 
115
153
  // src/credit/escrow-receipt.ts
116
- import { z } from "zod";
154
+ import { z as z2 } from "zod";
117
155
  import { randomUUID } from "crypto";
118
- var EscrowReceiptSchema = z.object({
119
- requester_owner: z.string().min(1),
120
- requester_public_key: z.string().min(1),
121
- amount: z.number().positive(),
122
- card_id: z.string().min(1),
123
- skill_id: z.string().optional(),
124
- timestamp: z.string(),
125
- nonce: z.string().uuid(),
126
- signature: z.string().min(1)
156
+ var EscrowReceiptSchema = z2.object({
157
+ requester_owner: z2.string().min(1),
158
+ requester_public_key: z2.string().min(1),
159
+ amount: z2.number().positive(),
160
+ card_id: z2.string().min(1),
161
+ skill_id: z2.string().optional(),
162
+ timestamp: z2.string(),
163
+ nonce: z2.string().uuid(),
164
+ signature: z2.string().min(1)
127
165
  });
128
166
  function createSignedEscrowReceipt(db, privateKey, publicKey, opts) {
129
167
  const escrowId = holdEscrow(db, opts.owner, opts.amount, opts.cardId);
@@ -144,24 +182,6 @@ function createSignedEscrowReceipt(db, privateKey, publicKey, opts) {
144
182
  return { escrowId, receipt };
145
183
  }
146
184
 
147
- // src/credit/settlement.ts
148
- function settleProviderEarning(providerDb, providerOwner, receipt) {
149
- recordEarning(
150
- providerDb,
151
- providerOwner,
152
- receipt.amount,
153
- receipt.card_id,
154
- receipt.nonce
155
- );
156
- return { settled: true };
157
- }
158
- function settleRequesterEscrow(requesterDb, escrowId) {
159
- confirmEscrowDebit(requesterDb, escrowId);
160
- }
161
- function releaseRequesterEscrow(requesterDb, escrowId) {
162
- releaseEscrow(requesterDb, escrowId);
163
- }
164
-
165
185
  // src/autonomy/idle-monitor.ts
166
186
  import { Cron } from "croner";
167
187
  var IdleMonitor = class {
@@ -507,8 +527,183 @@ function buildDraftCard(apiKey, owner) {
507
527
  };
508
528
  }
509
529
 
530
+ // src/onboarding/index.ts
531
+ import { randomUUID as randomUUID3 } from "crypto";
532
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
533
+ import { join as join2 } from "path";
534
+
535
+ // src/onboarding/capability-templates.ts
536
+ var API_PATTERNS = [
537
+ {
538
+ pattern: /openai|gpt-4|gpt-3|chatgpt|dall-e/i,
539
+ capability: { key: "openai", name: "OpenAI Text Generation", category: "Text Gen", credits_per_call: 3, tags: ["llm", "text", "generation"] }
540
+ },
541
+ {
542
+ pattern: /elevenlabs|eleven.?labs/i,
543
+ capability: { key: "elevenlabs", name: "ElevenLabs TTS", category: "TTS", credits_per_call: 5, tags: ["tts", "audio", "voice"] }
544
+ },
545
+ {
546
+ pattern: /anthropic|claude/i,
547
+ capability: { key: "anthropic", name: "Anthropic Claude", category: "Text Gen", credits_per_call: 3, tags: ["llm", "text", "generation"] }
548
+ },
549
+ {
550
+ pattern: /recraft/i,
551
+ capability: { key: "recraft", name: "Recraft V4 Image Gen", category: "Image Gen", credits_per_call: 8, tags: ["image", "generation", "design"] }
552
+ },
553
+ {
554
+ pattern: /kling/i,
555
+ capability: { key: "kling", name: "Kling AI Video Gen", category: "Video Gen", credits_per_call: 10, tags: ["video", "generation"] }
556
+ },
557
+ {
558
+ pattern: /stable.?diffusion|sdxl|comfyui/i,
559
+ capability: { key: "stable-diffusion", name: "Stable Diffusion Image Gen", category: "Image Gen", credits_per_call: 6, tags: ["image", "generation", "diffusion"] }
560
+ },
561
+ {
562
+ pattern: /whisper|speech.?to.?text|stt/i,
563
+ capability: { key: "whisper", name: "Whisper Speech-to-Text", category: "STT", credits_per_call: 3, tags: ["stt", "audio", "transcription"] }
564
+ },
565
+ {
566
+ pattern: /puppeteer|playwright|selenium/i,
567
+ capability: { key: "puppeteer", name: "Web Scraping & Automation", category: "Web Scraping", credits_per_call: 2, tags: ["scraping", "automation", "browser"] }
568
+ },
569
+ {
570
+ pattern: /ffmpeg/i,
571
+ capability: { key: "ffmpeg", name: "FFmpeg Media Processing", category: "Media Processing", credits_per_call: 3, tags: ["media", "audio", "video", "processing"] }
572
+ },
573
+ {
574
+ pattern: /tesseract|ocr/i,
575
+ capability: { key: "tesseract", name: "OCR Text Extraction", category: "OCR", credits_per_call: 4, tags: ["ocr", "text", "extraction"] }
576
+ }
577
+ ];
578
+ var INTERACTIVE_TEMPLATES = [
579
+ { key: "openai", name: "Text Generation (GPT-4o / Claude / Gemini)", category: "Text Gen", credits_per_call: 3, tags: ["llm", "text", "generation"] },
580
+ { key: "image-gen", name: "Image Generation (DALL-E / Recraft / Stable Diffusion)", category: "Image Gen", credits_per_call: 8, tags: ["image", "generation"] },
581
+ { key: "tts", name: "TTS / Voice (ElevenLabs / Google TTS)", category: "TTS", credits_per_call: 5, tags: ["tts", "audio", "voice"] },
582
+ { key: "video-gen", name: "Video Generation (Kling / Runway)", category: "Video Gen", credits_per_call: 10, tags: ["video", "generation"] },
583
+ { key: "code-review", name: "Code Review / Analysis", category: "Code", credits_per_call: 3, tags: ["code", "review", "analysis"] },
584
+ { key: "scraping", name: "Web Scraping / Data Extraction", category: "Web Scraping", credits_per_call: 2, tags: ["scraping", "data", "extraction"] },
585
+ { key: "translation", name: "Translation", category: "Translation", credits_per_call: 3, tags: ["translation", "language", "text"] },
586
+ { key: "custom", name: "Custom (describe it)", category: "Custom", credits_per_call: 5, tags: ["custom"] }
587
+ ];
588
+
589
+ // src/onboarding/detect-from-docs.ts
590
+ function detectFromDocs(content) {
591
+ if (!content || content.trim().length === 0) {
592
+ return [];
593
+ }
594
+ const seen = /* @__PURE__ */ new Set();
595
+ const results = [];
596
+ for (const entry of API_PATTERNS) {
597
+ if (entry.pattern.test(content) && !seen.has(entry.capability.key)) {
598
+ seen.add(entry.capability.key);
599
+ results.push({ ...entry.capability });
600
+ }
601
+ }
602
+ return results;
603
+ }
604
+
605
+ // src/onboarding/interactive.ts
606
+ import { createInterface } from "readline";
607
+ async function interactiveTemplateMenu() {
608
+ console.log("\nNo capabilities auto-detected.\n");
609
+ console.log("What can your agent do? Pick from templates:\n");
610
+ for (let i = 0; i < INTERACTIVE_TEMPLATES.length; i++) {
611
+ console.log(` ${i + 1}. ${INTERACTIVE_TEMPLATES[i].name}`);
612
+ }
613
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
614
+ const answer = await new Promise((resolve) => {
615
+ rl.question("\nSelect [1-8, comma-separated]: ", (ans) => {
616
+ rl.close();
617
+ resolve(ans);
618
+ });
619
+ });
620
+ return parseSelection(answer);
621
+ }
622
+ function parseSelection(input) {
623
+ if (!input || input.trim().length === 0) {
624
+ return [];
625
+ }
626
+ const selected = [];
627
+ const seen = /* @__PURE__ */ new Set();
628
+ const parts = input.split(",").map((s) => s.trim());
629
+ for (const part of parts) {
630
+ const num = parseInt(part, 10);
631
+ if (isNaN(num) || num < 1 || num > INTERACTIVE_TEMPLATES.length) {
632
+ continue;
633
+ }
634
+ const template = INTERACTIVE_TEMPLATES[num - 1];
635
+ if (!seen.has(template.key)) {
636
+ seen.add(template.key);
637
+ selected.push({ ...template });
638
+ }
639
+ }
640
+ return selected;
641
+ }
642
+
643
+ // src/onboarding/index.ts
644
+ var DOC_FILES = ["SOUL.md", "CLAUDE.md", "AGENTS.md", "README.md"];
645
+ function detectCapabilities(opts = {}) {
646
+ const cwd = opts.cwd ?? process.cwd();
647
+ if (opts.fromFile) {
648
+ const filePath = opts.fromFile.startsWith("/") ? opts.fromFile : join2(cwd, opts.fromFile);
649
+ if (existsSync2(filePath)) {
650
+ const content = readFileSync2(filePath, "utf-8");
651
+ const capabilities = detectFromDocs(content);
652
+ if (capabilities.length > 0) {
653
+ return { source: "docs", capabilities, sourceFile: filePath };
654
+ }
655
+ }
656
+ return { source: "none", capabilities: [] };
657
+ }
658
+ for (const fileName of DOC_FILES) {
659
+ const filePath = join2(cwd, fileName);
660
+ if (!existsSync2(filePath)) continue;
661
+ const content = readFileSync2(filePath, "utf-8");
662
+ if (fileName === "SOUL.md") {
663
+ return { source: "soul", capabilities: [], soulContent: content, sourceFile: filePath };
664
+ }
665
+ const capabilities = detectFromDocs(content);
666
+ if (capabilities.length > 0) {
667
+ return { source: "docs", capabilities, sourceFile: filePath };
668
+ }
669
+ }
670
+ const envKeys = detectApiKeys(KNOWN_API_KEYS);
671
+ if (envKeys.length > 0) {
672
+ return { source: "env", capabilities: [], envKeys };
673
+ }
674
+ return { source: "none", capabilities: [] };
675
+ }
676
+ function capabilitiesToV2Card(capabilities, owner, agentName) {
677
+ const now = (/* @__PURE__ */ new Date()).toISOString();
678
+ const skills = capabilities.map((cap) => ({
679
+ id: cap.key,
680
+ name: cap.name,
681
+ description: `${cap.name} capability \u2014 ${cap.category}`,
682
+ level: 1,
683
+ category: cap.category.toLowerCase().replace(/\s+/g, "_"),
684
+ inputs: [{ name: "input", type: "text", required: true }],
685
+ outputs: [{ name: "output", type: "text", required: true }],
686
+ pricing: { credits_per_call: cap.credits_per_call },
687
+ availability: { online: true },
688
+ metadata: {
689
+ tags: cap.tags
690
+ }
691
+ }));
692
+ const card = {
693
+ spec_version: "2.0",
694
+ id: randomUUID3(),
695
+ owner,
696
+ agent_name: agentName ?? owner,
697
+ skills,
698
+ availability: { online: true },
699
+ created_at: now,
700
+ updated_at: now
701
+ };
702
+ return CapabilityCardV2Schema.parse(card);
703
+ }
704
+
510
705
  // src/runtime/agent-runtime.ts
511
- import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
706
+ import { readFileSync as readFileSync3, existsSync as existsSync3 } from "fs";
512
707
 
513
708
  // src/skills/executor.ts
514
709
  var SkillExecutor = class {
@@ -591,98 +786,98 @@ function createSkillExecutor(configs, modes) {
591
786
  }
592
787
 
593
788
  // src/skills/skill-config.ts
594
- import { z as z2 } from "zod";
789
+ import { z as z3 } from "zod";
595
790
  import yaml from "js-yaml";
596
- var PricingSchema = z2.object({
597
- credits_per_call: z2.number().nonnegative(),
598
- credits_per_minute: z2.number().nonnegative().optional(),
599
- free_tier: z2.number().nonnegative().optional()
791
+ var PricingSchema = z3.object({
792
+ credits_per_call: z3.number().nonnegative(),
793
+ credits_per_minute: z3.number().nonnegative().optional(),
794
+ free_tier: z3.number().nonnegative().optional()
600
795
  });
601
- var ApiAuthSchema = z2.discriminatedUnion("type", [
602
- z2.object({
603
- type: z2.literal("bearer"),
604
- token: z2.string()
796
+ var ApiAuthSchema = z3.discriminatedUnion("type", [
797
+ z3.object({
798
+ type: z3.literal("bearer"),
799
+ token: z3.string()
605
800
  }),
606
- z2.object({
607
- type: z2.literal("apikey"),
608
- header: z2.string().default("X-API-Key"),
609
- key: z2.string()
801
+ z3.object({
802
+ type: z3.literal("apikey"),
803
+ header: z3.string().default("X-API-Key"),
804
+ key: z3.string()
610
805
  }),
611
- z2.object({
612
- type: z2.literal("basic"),
613
- username: z2.string(),
614
- password: z2.string()
806
+ z3.object({
807
+ type: z3.literal("basic"),
808
+ username: z3.string(),
809
+ password: z3.string()
615
810
  })
616
811
  ]);
617
- var ApiSkillConfigSchema = z2.object({
618
- id: z2.string().min(1),
619
- type: z2.literal("api"),
620
- name: z2.string().min(1),
621
- endpoint: z2.string().min(1),
622
- method: z2.enum(["GET", "POST", "PUT", "DELETE"]),
812
+ var ApiSkillConfigSchema = z3.object({
813
+ id: z3.string().min(1),
814
+ type: z3.literal("api"),
815
+ name: z3.string().min(1),
816
+ endpoint: z3.string().min(1),
817
+ method: z3.enum(["GET", "POST", "PUT", "DELETE"]),
623
818
  auth: ApiAuthSchema.optional(),
624
- input_mapping: z2.record(z2.string()).default({}),
625
- output_mapping: z2.record(z2.string()).default({}),
819
+ input_mapping: z3.record(z3.string()).default({}),
820
+ output_mapping: z3.record(z3.string()).default({}),
626
821
  pricing: PricingSchema,
627
- timeout_ms: z2.number().positive().default(3e4),
628
- retries: z2.number().nonnegative().int().default(0),
629
- provider: z2.string().optional()
822
+ timeout_ms: z3.number().positive().default(3e4),
823
+ retries: z3.number().nonnegative().int().default(0),
824
+ provider: z3.string().optional()
630
825
  });
631
- var PipelineStepSchema = z2.union([
632
- z2.object({
633
- skill_id: z2.string().min(1),
634
- input_mapping: z2.record(z2.string()).default({})
826
+ var PipelineStepSchema = z3.union([
827
+ z3.object({
828
+ skill_id: z3.string().min(1),
829
+ input_mapping: z3.record(z3.string()).default({})
635
830
  }),
636
- z2.object({
637
- command: z2.string().min(1),
638
- input_mapping: z2.record(z2.string()).default({})
831
+ z3.object({
832
+ command: z3.string().min(1),
833
+ input_mapping: z3.record(z3.string()).default({})
639
834
  })
640
835
  ]);
641
- var PipelineSkillConfigSchema = z2.object({
642
- id: z2.string().min(1),
643
- type: z2.literal("pipeline"),
644
- name: z2.string().min(1),
645
- steps: z2.array(PipelineStepSchema).min(1),
836
+ var PipelineSkillConfigSchema = z3.object({
837
+ id: z3.string().min(1),
838
+ type: z3.literal("pipeline"),
839
+ name: z3.string().min(1),
840
+ steps: z3.array(PipelineStepSchema).min(1),
646
841
  pricing: PricingSchema,
647
- timeout_ms: z2.number().positive().optional()
842
+ timeout_ms: z3.number().positive().optional()
648
843
  });
649
- var OpenClawSkillConfigSchema = z2.object({
650
- id: z2.string().min(1),
651
- type: z2.literal("openclaw"),
652
- name: z2.string().min(1),
653
- agent_name: z2.string().min(1),
654
- channel: z2.enum(["telegram", "webhook", "process"]),
844
+ var OpenClawSkillConfigSchema = z3.object({
845
+ id: z3.string().min(1),
846
+ type: z3.literal("openclaw"),
847
+ name: z3.string().min(1),
848
+ agent_name: z3.string().min(1),
849
+ channel: z3.enum(["telegram", "webhook", "process"]),
655
850
  pricing: PricingSchema,
656
- timeout_ms: z2.number().positive().optional()
851
+ timeout_ms: z3.number().positive().optional()
657
852
  });
658
- var CommandSkillConfigSchema = z2.object({
659
- id: z2.string().min(1),
660
- type: z2.literal("command"),
661
- name: z2.string().min(1),
662
- command: z2.string().min(1),
663
- output_type: z2.enum(["json", "text", "file"]),
664
- allowed_commands: z2.array(z2.string()).optional(),
665
- working_dir: z2.string().optional(),
666
- timeout_ms: z2.number().positive().default(3e4),
853
+ var CommandSkillConfigSchema = z3.object({
854
+ id: z3.string().min(1),
855
+ type: z3.literal("command"),
856
+ name: z3.string().min(1),
857
+ command: z3.string().min(1),
858
+ output_type: z3.enum(["json", "text", "file"]),
859
+ allowed_commands: z3.array(z3.string()).optional(),
860
+ working_dir: z3.string().optional(),
861
+ timeout_ms: z3.number().positive().default(3e4),
667
862
  pricing: PricingSchema
668
863
  });
669
- var ConductorSkillConfigSchema = z2.object({
670
- id: z2.string().min(1),
671
- type: z2.literal("conductor"),
672
- name: z2.string().min(1),
673
- conductor_skill: z2.enum(["orchestrate", "plan"]),
864
+ var ConductorSkillConfigSchema = z3.object({
865
+ id: z3.string().min(1),
866
+ type: z3.literal("conductor"),
867
+ name: z3.string().min(1),
868
+ conductor_skill: z3.enum(["orchestrate", "plan"]),
674
869
  pricing: PricingSchema,
675
- timeout_ms: z2.number().positive().optional()
870
+ timeout_ms: z3.number().positive().optional()
676
871
  });
677
- var SkillConfigSchema = z2.discriminatedUnion("type", [
872
+ var SkillConfigSchema = z3.discriminatedUnion("type", [
678
873
  ApiSkillConfigSchema,
679
874
  PipelineSkillConfigSchema,
680
875
  OpenClawSkillConfigSchema,
681
876
  CommandSkillConfigSchema,
682
877
  ConductorSkillConfigSchema
683
878
  ]);
684
- var SkillsFileSchema = z2.object({
685
- skills: z2.array(SkillConfigSchema)
879
+ var SkillsFileSchema = z3.object({
880
+ skills: z3.array(SkillConfigSchema)
686
881
  });
687
882
  function expandEnvVars(value) {
688
883
  return value.replace(/\$\{([^}]+)\}/g, (_match, varName) => {
@@ -1319,19 +1514,19 @@ var AgentRuntime = class {
1319
1514
  * 3. Populate the Map with all 4 modes — SkillExecutor sees them via reference.
1320
1515
  */
1321
1516
  async initSkillExecutor() {
1322
- const hasSkillsYaml = this.skillsYamlPath && existsSync2(this.skillsYamlPath);
1517
+ const hasSkillsYaml = this.skillsYamlPath && existsSync3(this.skillsYamlPath);
1323
1518
  if (!hasSkillsYaml && !this.conductorEnabled) {
1324
1519
  return;
1325
1520
  }
1326
1521
  let configs = [];
1327
1522
  if (hasSkillsYaml) {
1328
- const yamlContent = readFileSync2(this.skillsYamlPath, "utf8");
1523
+ const yamlContent = readFileSync3(this.skillsYamlPath, "utf8");
1329
1524
  configs = parseSkillsFile(yamlContent);
1330
1525
  }
1331
1526
  const modes = /* @__PURE__ */ new Map();
1332
1527
  if (this.conductorEnabled) {
1333
- const { ConductorMode } = await import("../conductor-mode-DJ3RIJ5T.js");
1334
- const { registerConductorCard, CONDUCTOR_OWNER } = await import("../card-P5C36VBD.js");
1528
+ const { ConductorMode } = await import("../conductor-mode-GPLAM2XO.js");
1529
+ const { registerConductorCard, CONDUCTOR_OWNER } = await import("../card-EWIXC377.js");
1335
1530
  const { loadPeers: loadPeers2 } = await import("../peers-G36URZYB.js");
1336
1531
  registerConductorCard(this.registryDb);
1337
1532
  const resolveAgentUrl = (owner) => {
@@ -1437,7 +1632,6 @@ var AgentRuntime = class {
1437
1632
 
1438
1633
  // src/gateway/server.ts
1439
1634
  import Fastify from "fastify";
1440
- import { randomUUID as randomUUID3 } from "crypto";
1441
1635
  var VERSION = "0.0.1";
1442
1636
  function createGatewayServer(opts) {
1443
1637
  const {
@@ -1501,270 +1695,402 @@ function createGatewayServer(opts) {
1501
1695
  error: { code: -32602, message: "Invalid params: card_id required" }
1502
1696
  });
1503
1697
  }
1504
- const card = getCard(registryDb, cardId);
1505
- if (!card) {
1506
- return reply.send({
1507
- jsonrpc: "2.0",
1508
- id,
1509
- error: { code: -32602, message: `Card not found: ${cardId}` }
1510
- });
1511
- }
1512
1698
  const requester = params.requester ?? "unknown";
1513
- let creditsNeeded;
1514
- let cardName;
1515
- let resolvedSkillId;
1516
- const rawCard = card;
1517
- if (Array.isArray(rawCard["skills"])) {
1518
- const v2card = card;
1519
- const skill = skillId ? v2card.skills.find((s) => s.id === skillId) : v2card.skills[0];
1520
- if (!skill) {
1521
- return reply.send({
1522
- jsonrpc: "2.0",
1523
- id,
1524
- error: { code: -32602, message: `Skill not found: ${skillId}` }
1525
- });
1526
- }
1527
- creditsNeeded = skill.pricing.credits_per_call;
1528
- cardName = skill.name;
1529
- resolvedSkillId = skill.id;
1530
- } else {
1531
- creditsNeeded = card.pricing.credits_per_call;
1532
- cardName = card.name;
1533
- }
1534
1699
  const receipt = params.escrow_receipt;
1535
- let escrowId = null;
1536
- let isRemoteEscrow = false;
1537
- if (receipt) {
1538
- const { signature, ...receiptData } = receipt;
1539
- const publicKeyBuf = Buffer.from(receipt.requester_public_key, "hex");
1540
- const valid = verifyEscrowReceipt(receiptData, signature, publicKeyBuf);
1541
- if (!valid) {
1542
- return reply.send({
1543
- jsonrpc: "2.0",
1544
- id,
1545
- error: { code: -32603, message: "Invalid escrow receipt signature" }
1546
- });
1547
- }
1548
- if (receipt.amount < creditsNeeded) {
1549
- return reply.send({
1550
- jsonrpc: "2.0",
1551
- id,
1552
- error: { code: -32603, message: "Insufficient escrow amount" }
1553
- });
1554
- }
1555
- const receiptAge = Date.now() - new Date(receipt.timestamp).getTime();
1556
- if (receiptAge > 5 * 60 * 1e3) {
1557
- return reply.send({
1558
- jsonrpc: "2.0",
1559
- id,
1560
- error: { code: -32603, message: "Escrow receipt expired" }
1561
- });
1562
- }
1563
- isRemoteEscrow = true;
1700
+ const result = await executeCapabilityRequest({
1701
+ registryDb,
1702
+ creditDb,
1703
+ cardId,
1704
+ skillId,
1705
+ params,
1706
+ requester,
1707
+ escrowReceipt: receipt,
1708
+ skillExecutor,
1709
+ handlerUrl,
1710
+ timeoutMs
1711
+ });
1712
+ if (result.success) {
1713
+ return reply.send({ jsonrpc: "2.0", id, result: result.result });
1564
1714
  } else {
1565
- try {
1566
- const balance = getBalance(creditDb, requester);
1567
- if (balance < creditsNeeded) {
1568
- return reply.send({
1569
- jsonrpc: "2.0",
1570
- id,
1571
- error: { code: -32603, message: "Insufficient credits" }
1572
- });
1573
- }
1574
- escrowId = holdEscrow(creditDb, requester, creditsNeeded, cardId);
1575
- } catch (err) {
1576
- const msg = err instanceof AgentBnBError ? err.message : "Failed to hold escrow";
1577
- return reply.send({
1578
- jsonrpc: "2.0",
1579
- id,
1580
- error: { code: -32603, message: msg }
1581
- });
1582
- }
1715
+ return reply.send({ jsonrpc: "2.0", id, error: result.error });
1583
1716
  }
1584
- const startMs = Date.now();
1585
- if (skillExecutor) {
1586
- const targetSkillId = resolvedSkillId ?? skillId ?? cardId;
1587
- let execResult;
1588
- try {
1589
- execResult = await skillExecutor.execute(targetSkillId, params);
1590
- } catch (err) {
1591
- if (!isRemoteEscrow && escrowId) releaseEscrow(creditDb, escrowId);
1592
- updateReputation(registryDb, cardId, false, Date.now() - startMs);
1717
+ });
1718
+ return fastify;
1719
+ }
1720
+
1721
+ // src/registry/server.ts
1722
+ import Fastify2 from "fastify";
1723
+ import cors from "@fastify/cors";
1724
+ import fastifyStatic from "@fastify/static";
1725
+ import fastifyWebsocket from "@fastify/websocket";
1726
+ import { join as join3, dirname } from "path";
1727
+ import { fileURLToPath } from "url";
1728
+ import { existsSync as existsSync4 } from "fs";
1729
+
1730
+ // src/relay/websocket-relay.ts
1731
+ import { randomUUID as randomUUID4 } from "crypto";
1732
+ var RATE_LIMIT_MAX = 60;
1733
+ var RATE_LIMIT_WINDOW_MS = 6e4;
1734
+ var RELAY_TIMEOUT_MS = 3e4;
1735
+ function registerWebSocketRelay(server, db) {
1736
+ const connections = /* @__PURE__ */ new Map();
1737
+ const pendingRequests = /* @__PURE__ */ new Map();
1738
+ const rateLimits = /* @__PURE__ */ new Map();
1739
+ function checkRateLimit(owner) {
1740
+ const now = Date.now();
1741
+ const entry = rateLimits.get(owner);
1742
+ if (!entry || now >= entry.resetTime) {
1743
+ rateLimits.set(owner, { count: 1, resetTime: now + RATE_LIMIT_WINDOW_MS });
1744
+ return true;
1745
+ }
1746
+ if (entry.count >= RATE_LIMIT_MAX) {
1747
+ return false;
1748
+ }
1749
+ entry.count++;
1750
+ return true;
1751
+ }
1752
+ function markOwnerOffline(owner) {
1753
+ try {
1754
+ const stmt = db.prepare("SELECT id, data FROM capability_cards WHERE owner = ?");
1755
+ const rows = stmt.all(owner);
1756
+ for (const row of rows) {
1593
1757
  try {
1594
- insertRequestLog(registryDb, {
1595
- id: randomUUID3(),
1596
- card_id: cardId,
1597
- card_name: cardName,
1598
- skill_id: resolvedSkillId,
1599
- requester,
1600
- status: "failure",
1601
- latency_ms: Date.now() - startMs,
1602
- credits_charged: 0,
1603
- created_at: (/* @__PURE__ */ new Date()).toISOString()
1604
- });
1758
+ const card = JSON.parse(row.data);
1759
+ if (card.availability?.online) {
1760
+ card.availability.online = false;
1761
+ card.updated_at = (/* @__PURE__ */ new Date()).toISOString();
1762
+ const updateStmt = db.prepare("UPDATE capability_cards SET data = ?, updated_at = ? WHERE id = ?");
1763
+ updateStmt.run(JSON.stringify(card), card.updated_at, row.id);
1764
+ }
1605
1765
  } catch {
1606
1766
  }
1607
- const message = err instanceof Error ? err.message : "Execution error";
1608
- return reply.send({
1609
- jsonrpc: "2.0",
1610
- id,
1611
- error: {
1612
- code: -32603,
1613
- message,
1614
- ...isRemoteEscrow ? { data: { receipt_released: true } } : {}
1615
- }
1616
- });
1617
1767
  }
1618
- if (!execResult.success) {
1619
- if (!isRemoteEscrow && escrowId) releaseEscrow(creditDb, escrowId);
1620
- updateReputation(registryDb, cardId, false, execResult.latency_ms);
1768
+ } catch {
1769
+ }
1770
+ }
1771
+ function markOwnerOnline(owner) {
1772
+ try {
1773
+ const stmt = db.prepare("SELECT id, data FROM capability_cards WHERE owner = ?");
1774
+ const rows = stmt.all(owner);
1775
+ for (const row of rows) {
1621
1776
  try {
1622
- insertRequestLog(registryDb, {
1623
- id: randomUUID3(),
1624
- card_id: cardId,
1625
- card_name: cardName,
1626
- skill_id: resolvedSkillId,
1627
- requester,
1628
- status: "failure",
1629
- latency_ms: execResult.latency_ms,
1630
- credits_charged: 0,
1631
- created_at: (/* @__PURE__ */ new Date()).toISOString()
1632
- });
1777
+ const card = JSON.parse(row.data);
1778
+ card.availability = { ...card.availability, online: true };
1779
+ card.updated_at = (/* @__PURE__ */ new Date()).toISOString();
1780
+ const updateStmt = db.prepare("UPDATE capability_cards SET data = ?, updated_at = ? WHERE id = ?");
1781
+ updateStmt.run(JSON.stringify(card), card.updated_at, row.id);
1633
1782
  } catch {
1634
1783
  }
1635
- return reply.send({
1636
- jsonrpc: "2.0",
1637
- id,
1638
- error: {
1639
- code: -32603,
1640
- message: execResult.error ?? "Execution failed",
1641
- ...isRemoteEscrow ? { data: { receipt_released: true } } : {}
1642
- }
1643
- });
1644
1784
  }
1645
- if (isRemoteEscrow && receipt) {
1646
- settleProviderEarning(creditDb, card.owner, receipt);
1647
- } else if (escrowId) {
1648
- settleEscrow(creditDb, escrowId, card.owner);
1649
- }
1650
- updateReputation(registryDb, cardId, true, execResult.latency_ms);
1785
+ } catch {
1786
+ }
1787
+ }
1788
+ function upsertCard(cardData, owner) {
1789
+ const cardId = cardData.id;
1790
+ const existing = getCard(db, cardId);
1791
+ if (existing) {
1792
+ const updates = { ...cardData, availability: { ...cardData.availability ?? {}, online: true } };
1793
+ updateCard(db, cardId, owner, updates);
1794
+ } else {
1795
+ const card = { ...cardData, availability: { ...cardData.availability ?? {}, online: true } };
1796
+ insertCard(db, card);
1797
+ }
1798
+ return cardId;
1799
+ }
1800
+ function logAgentJoined(owner, cardName, cardId) {
1801
+ try {
1802
+ insertRequestLog(db, {
1803
+ id: randomUUID4(),
1804
+ card_id: cardId,
1805
+ card_name: cardName,
1806
+ requester: owner,
1807
+ status: "success",
1808
+ latency_ms: 0,
1809
+ credits_charged: 0,
1810
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
1811
+ action_type: "agent_joined"
1812
+ });
1813
+ } catch {
1814
+ }
1815
+ }
1816
+ function sendMessage(ws, msg) {
1817
+ if (ws.readyState === 1) {
1818
+ ws.send(JSON.stringify(msg));
1819
+ }
1820
+ }
1821
+ function handleRegister(ws, msg) {
1822
+ const { owner, card } = msg;
1823
+ const existing = connections.get(owner);
1824
+ if (existing && existing !== ws) {
1651
1825
  try {
1652
- insertRequestLog(registryDb, {
1653
- id: randomUUID3(),
1654
- card_id: cardId,
1655
- card_name: cardName,
1656
- skill_id: resolvedSkillId,
1657
- requester,
1658
- status: "success",
1659
- latency_ms: execResult.latency_ms,
1660
- credits_charged: creditsNeeded,
1661
- created_at: (/* @__PURE__ */ new Date()).toISOString()
1662
- });
1826
+ existing.close(1e3, "Replaced by new connection");
1663
1827
  } catch {
1664
1828
  }
1665
- const successResult = isRemoteEscrow ? { ...typeof execResult.result === "object" && execResult.result !== null ? execResult.result : { data: execResult.result }, receipt_settled: true, receipt_nonce: receipt.nonce } : execResult.result;
1666
- return reply.send({ jsonrpc: "2.0", id, result: successResult });
1667
1829
  }
1668
- const controller = new AbortController();
1669
- const timer = setTimeout(() => controller.abort(), timeoutMs);
1670
- try {
1671
- const response = await fetch(handlerUrl, {
1672
- method: "POST",
1673
- headers: { "Content-Type": "application/json" },
1674
- body: JSON.stringify({ card_id: cardId, skill_id: resolvedSkillId, params }),
1675
- signal: controller.signal
1830
+ connections.set(owner, ws);
1831
+ const cardId = upsertCard(card, owner);
1832
+ const cardName = card.name ?? card.agent_name ?? owner;
1833
+ logAgentJoined(owner, cardName, cardId);
1834
+ markOwnerOnline(owner);
1835
+ sendMessage(ws, { type: "registered", agent_id: cardId });
1836
+ }
1837
+ function handleRelayRequest(ws, msg, fromOwner) {
1838
+ if (!checkRateLimit(fromOwner)) {
1839
+ sendMessage(ws, {
1840
+ type: "error",
1841
+ code: "rate_limited",
1842
+ message: `Rate limit exceeded: max ${RATE_LIMIT_MAX} requests per minute`,
1843
+ request_id: msg.id
1676
1844
  });
1677
- clearTimeout(timer);
1678
- if (!response.ok) {
1679
- if (!isRemoteEscrow && escrowId) releaseEscrow(creditDb, escrowId);
1680
- updateReputation(registryDb, cardId, false, Date.now() - startMs);
1681
- try {
1682
- insertRequestLog(registryDb, {
1683
- id: randomUUID3(),
1684
- card_id: cardId,
1685
- card_name: cardName,
1686
- skill_id: resolvedSkillId,
1687
- requester,
1688
- status: "failure",
1689
- latency_ms: Date.now() - startMs,
1690
- credits_charged: 0,
1691
- created_at: (/* @__PURE__ */ new Date()).toISOString()
1692
- });
1693
- } catch {
1694
- }
1695
- return reply.send({
1696
- jsonrpc: "2.0",
1697
- id,
1698
- error: {
1699
- code: -32603,
1700
- message: `Handler returned ${response.status}`,
1701
- ...isRemoteEscrow ? { data: { receipt_released: true } } : {}
1702
- }
1703
- });
1704
- }
1705
- const result = await response.json();
1706
- if (isRemoteEscrow && receipt) {
1707
- settleProviderEarning(creditDb, card.owner, receipt);
1708
- } else if (escrowId) {
1709
- settleEscrow(creditDb, escrowId, card.owner);
1845
+ return;
1846
+ }
1847
+ const targetWs = connections.get(msg.target_owner);
1848
+ if (!targetWs || targetWs.readyState !== 1) {
1849
+ sendMessage(ws, {
1850
+ type: "response",
1851
+ id: msg.id,
1852
+ error: { code: -32603, message: `Agent offline: ${msg.target_owner}` }
1853
+ });
1854
+ return;
1855
+ }
1856
+ const timeout = setTimeout(() => {
1857
+ pendingRequests.delete(msg.id);
1858
+ sendMessage(ws, {
1859
+ type: "response",
1860
+ id: msg.id,
1861
+ error: { code: -32603, message: "Relay request timeout" }
1862
+ });
1863
+ }, RELAY_TIMEOUT_MS);
1864
+ pendingRequests.set(msg.id, { originOwner: fromOwner, timeout });
1865
+ sendMessage(targetWs, {
1866
+ type: "incoming_request",
1867
+ id: msg.id,
1868
+ from_owner: fromOwner,
1869
+ card_id: msg.card_id,
1870
+ skill_id: msg.skill_id,
1871
+ params: msg.params,
1872
+ requester: msg.requester ?? fromOwner,
1873
+ escrow_receipt: msg.escrow_receipt
1874
+ });
1875
+ }
1876
+ function handleRelayResponse(msg) {
1877
+ const pending = pendingRequests.get(msg.id);
1878
+ if (!pending) return;
1879
+ clearTimeout(pending.timeout);
1880
+ pendingRequests.delete(msg.id);
1881
+ const originWs = connections.get(pending.originOwner);
1882
+ if (originWs && originWs.readyState === 1) {
1883
+ sendMessage(originWs, {
1884
+ type: "response",
1885
+ id: msg.id,
1886
+ result: msg.result,
1887
+ error: msg.error
1888
+ });
1889
+ }
1890
+ }
1891
+ function handleDisconnect(owner) {
1892
+ if (!owner) return;
1893
+ connections.delete(owner);
1894
+ rateLimits.delete(owner);
1895
+ markOwnerOffline(owner);
1896
+ for (const [reqId, pending] of pendingRequests) {
1897
+ if (pending.originOwner === owner) {
1898
+ clearTimeout(pending.timeout);
1899
+ pendingRequests.delete(reqId);
1710
1900
  }
1711
- updateReputation(registryDb, cardId, true, Date.now() - startMs);
1901
+ }
1902
+ }
1903
+ server.get("/ws", { websocket: true }, (rawSocket, _request) => {
1904
+ const socket = rawSocket;
1905
+ let registeredOwner;
1906
+ socket.on("message", (raw) => {
1907
+ let data;
1712
1908
  try {
1713
- insertRequestLog(registryDb, {
1714
- id: randomUUID3(),
1715
- card_id: cardId,
1716
- card_name: cardName,
1717
- skill_id: resolvedSkillId,
1718
- requester,
1719
- status: "success",
1720
- latency_ms: Date.now() - startMs,
1721
- credits_charged: creditsNeeded,
1722
- created_at: (/* @__PURE__ */ new Date()).toISOString()
1723
- });
1909
+ data = JSON.parse(typeof raw === "string" ? raw : raw.toString("utf-8"));
1724
1910
  } catch {
1911
+ sendMessage(socket, { type: "error", code: "invalid_json", message: "Invalid JSON" });
1912
+ return;
1725
1913
  }
1726
- const successResult = isRemoteEscrow ? { ...typeof result === "object" && result !== null ? result : { data: result }, receipt_settled: true, receipt_nonce: receipt.nonce } : result;
1727
- return reply.send({ jsonrpc: "2.0", id, result: successResult });
1728
- } catch (err) {
1729
- clearTimeout(timer);
1730
- if (!isRemoteEscrow && escrowId) releaseEscrow(creditDb, escrowId);
1731
- updateReputation(registryDb, cardId, false, Date.now() - startMs);
1732
- const isTimeout = err instanceof Error && err.name === "AbortError";
1733
- try {
1734
- insertRequestLog(registryDb, {
1735
- id: randomUUID3(),
1736
- card_id: cardId,
1737
- card_name: cardName,
1738
- skill_id: resolvedSkillId,
1739
- requester,
1740
- status: isTimeout ? "timeout" : "failure",
1741
- latency_ms: Date.now() - startMs,
1742
- credits_charged: 0,
1743
- created_at: (/* @__PURE__ */ new Date()).toISOString()
1914
+ const parsed = RelayMessageSchema.safeParse(data);
1915
+ if (!parsed.success) {
1916
+ sendMessage(socket, {
1917
+ type: "error",
1918
+ code: "invalid_message",
1919
+ message: `Invalid message: ${parsed.error.issues[0]?.message ?? "unknown error"}`
1744
1920
  });
1745
- } catch {
1921
+ return;
1746
1922
  }
1747
- return reply.send({
1748
- jsonrpc: "2.0",
1749
- id,
1750
- error: {
1751
- code: -32603,
1752
- message: isTimeout ? "Execution timeout" : "Handler error",
1753
- ...isRemoteEscrow ? { data: { receipt_released: true } } : {}
1923
+ const msg = parsed.data;
1924
+ switch (msg.type) {
1925
+ case "register":
1926
+ registeredOwner = msg.owner;
1927
+ handleRegister(socket, msg);
1928
+ break;
1929
+ case "relay_request":
1930
+ if (!registeredOwner) {
1931
+ sendMessage(socket, {
1932
+ type: "error",
1933
+ code: "not_registered",
1934
+ message: "Must send register message before relay requests"
1935
+ });
1936
+ return;
1937
+ }
1938
+ handleRelayRequest(socket, msg, registeredOwner);
1939
+ break;
1940
+ case "relay_response":
1941
+ handleRelayResponse(msg);
1942
+ break;
1943
+ default:
1944
+ break;
1945
+ }
1946
+ });
1947
+ socket.on("close", () => {
1948
+ handleDisconnect(registeredOwner);
1949
+ });
1950
+ socket.on("error", () => {
1951
+ handleDisconnect(registeredOwner);
1952
+ });
1953
+ });
1954
+ return {
1955
+ getOnlineCount: () => connections.size,
1956
+ getOnlineOwners: () => Array.from(connections.keys()),
1957
+ shutdown: () => {
1958
+ for (const [, ws] of connections) {
1959
+ try {
1960
+ ws.close(1001, "Server shutting down");
1961
+ } catch {
1754
1962
  }
1755
- });
1963
+ }
1964
+ connections.clear();
1965
+ for (const [, pending] of pendingRequests) {
1966
+ clearTimeout(pending.timeout);
1967
+ }
1968
+ pendingRequests.clear();
1969
+ rateLimits.clear();
1756
1970
  }
1757
- });
1758
- return fastify;
1971
+ };
1972
+ }
1973
+
1974
+ // src/identity/guarantor.ts
1975
+ import { z as z4 } from "zod";
1976
+ import { randomUUID as randomUUID5 } from "crypto";
1977
+ var MAX_AGENTS_PER_GUARANTOR = 10;
1978
+ var GUARANTOR_CREDIT_POOL = 50;
1979
+ var GuarantorRecordSchema = z4.object({
1980
+ id: z4.string().uuid(),
1981
+ github_login: z4.string().min(1),
1982
+ agent_count: z4.number().int().nonnegative(),
1983
+ credit_pool: z4.number().int().nonnegative(),
1984
+ created_at: z4.string().datetime()
1985
+ });
1986
+ var GUARANTOR_SCHEMA = `
1987
+ CREATE TABLE IF NOT EXISTS guarantors (
1988
+ id TEXT PRIMARY KEY,
1989
+ github_login TEXT UNIQUE NOT NULL,
1990
+ agent_count INTEGER NOT NULL DEFAULT 0,
1991
+ credit_pool INTEGER NOT NULL DEFAULT ${GUARANTOR_CREDIT_POOL},
1992
+ created_at TEXT NOT NULL
1993
+ );
1994
+
1995
+ CREATE TABLE IF NOT EXISTS agent_guarantors (
1996
+ agent_id TEXT PRIMARY KEY,
1997
+ guarantor_id TEXT NOT NULL,
1998
+ linked_at TEXT NOT NULL,
1999
+ FOREIGN KEY (guarantor_id) REFERENCES guarantors(id)
2000
+ );
2001
+ `;
2002
+ function ensureGuarantorTables(db) {
2003
+ db.exec(GUARANTOR_SCHEMA);
2004
+ }
2005
+ function registerGuarantor(db, githubLogin) {
2006
+ ensureGuarantorTables(db);
2007
+ const existing = db.prepare("SELECT * FROM guarantors WHERE github_login = ?").get(githubLogin);
2008
+ if (existing) {
2009
+ throw new AgentBnBError(
2010
+ `Guarantor already registered: ${githubLogin}`,
2011
+ "GUARANTOR_EXISTS"
2012
+ );
2013
+ }
2014
+ const record = {
2015
+ id: randomUUID5(),
2016
+ github_login: githubLogin,
2017
+ agent_count: 0,
2018
+ credit_pool: GUARANTOR_CREDIT_POOL,
2019
+ created_at: (/* @__PURE__ */ new Date()).toISOString()
2020
+ };
2021
+ db.prepare(
2022
+ "INSERT INTO guarantors (id, github_login, agent_count, credit_pool, created_at) VALUES (?, ?, ?, ?, ?)"
2023
+ ).run(record.id, record.github_login, record.agent_count, record.credit_pool, record.created_at);
2024
+ return record;
2025
+ }
2026
+ function linkAgentToGuarantor(db, agentId, githubLogin) {
2027
+ ensureGuarantorTables(db);
2028
+ const guarantor = db.prepare("SELECT * FROM guarantors WHERE github_login = ?").get(githubLogin);
2029
+ if (!guarantor) {
2030
+ throw new AgentBnBError(
2031
+ `Guarantor not found: ${githubLogin}`,
2032
+ "GUARANTOR_NOT_FOUND"
2033
+ );
2034
+ }
2035
+ if (guarantor["agent_count"] >= MAX_AGENTS_PER_GUARANTOR) {
2036
+ throw new AgentBnBError(
2037
+ `Maximum agents per guarantor reached (${MAX_AGENTS_PER_GUARANTOR})`,
2038
+ "MAX_AGENTS_EXCEEDED"
2039
+ );
2040
+ }
2041
+ const existingLink = db.prepare("SELECT * FROM agent_guarantors WHERE agent_id = ?").get(agentId);
2042
+ if (existingLink) {
2043
+ throw new AgentBnBError(
2044
+ `Agent ${agentId} is already linked to a guarantor`,
2045
+ "AGENT_ALREADY_LINKED"
2046
+ );
2047
+ }
2048
+ db.transaction(() => {
2049
+ db.prepare("INSERT INTO agent_guarantors (agent_id, guarantor_id, linked_at) VALUES (?, ?, ?)").run(
2050
+ agentId,
2051
+ guarantor["id"],
2052
+ (/* @__PURE__ */ new Date()).toISOString()
2053
+ );
2054
+ db.prepare("UPDATE guarantors SET agent_count = agent_count + 1 WHERE id = ?").run(guarantor["id"]);
2055
+ })();
2056
+ return getGuarantor(db, githubLogin);
2057
+ }
2058
+ function getGuarantor(db, githubLogin) {
2059
+ ensureGuarantorTables(db);
2060
+ const row = db.prepare("SELECT * FROM guarantors WHERE github_login = ?").get(githubLogin);
2061
+ if (!row) return null;
2062
+ return {
2063
+ id: row["id"],
2064
+ github_login: row["github_login"],
2065
+ agent_count: row["agent_count"],
2066
+ credit_pool: row["credit_pool"],
2067
+ created_at: row["created_at"]
2068
+ };
2069
+ }
2070
+ function getAgentGuarantor(db, agentId) {
2071
+ ensureGuarantorTables(db);
2072
+ const link = db.prepare(
2073
+ `SELECT g.* FROM guarantors g
2074
+ JOIN agent_guarantors ag ON ag.guarantor_id = g.id
2075
+ WHERE ag.agent_id = ?`
2076
+ ).get(agentId);
2077
+ if (!link) return null;
2078
+ return {
2079
+ id: link["id"],
2080
+ github_login: link["github_login"],
2081
+ agent_count: link["agent_count"],
2082
+ credit_pool: link["credit_pool"],
2083
+ created_at: link["created_at"]
2084
+ };
2085
+ }
2086
+ function initiateGithubAuth() {
2087
+ return {
2088
+ auth_url: "https://github.com/login/oauth/authorize?client_id=PLACEHOLDER&scope=read:user",
2089
+ state: randomUUID5()
2090
+ };
1759
2091
  }
1760
2092
 
1761
2093
  // src/registry/server.ts
1762
- import Fastify2 from "fastify";
1763
- import cors from "@fastify/cors";
1764
- import fastifyStatic from "@fastify/static";
1765
- import { join as join2, dirname } from "path";
1766
- import { fileURLToPath } from "url";
1767
- import { existsSync as existsSync3 } from "fs";
1768
2094
  function stripInternal(card) {
1769
2095
  const { _internal: _, ...publicCard } = card;
1770
2096
  return publicCard;
@@ -1777,20 +2103,28 @@ function createRegistryServer(opts) {
1777
2103
  methods: ["GET", "POST", "PATCH", "OPTIONS"],
1778
2104
  allowedHeaders: ["Content-Type", "Authorization"]
1779
2105
  });
2106
+ void server.register(fastifyWebsocket);
2107
+ let relayState = null;
2108
+ if (opts.creditDb) {
2109
+ relayState = registerWebSocketRelay(server, db);
2110
+ }
1780
2111
  const __filename = fileURLToPath(import.meta.url);
1781
2112
  const __dirname = dirname(__filename);
1782
2113
  const hubDistCandidates = [
1783
- join2(__dirname, "../../hub/dist"),
2114
+ join3(__dirname, "../../hub/dist"),
1784
2115
  // When running from dist/registry/server.js
1785
- join2(__dirname, "../../../hub/dist")
2116
+ join3(__dirname, "../../../hub/dist")
1786
2117
  // Fallback for alternative layouts
1787
2118
  ];
1788
- const hubDistDir = hubDistCandidates.find((p) => existsSync3(p));
2119
+ const hubDistDir = hubDistCandidates.find((p) => existsSync4(p));
1789
2120
  if (hubDistDir) {
1790
2121
  void server.register(fastifyStatic, {
1791
2122
  root: hubDistDir,
1792
2123
  prefix: "/hub/"
1793
2124
  });
2125
+ server.get("/", async (_request, reply) => {
2126
+ return reply.redirect("/hub/");
2127
+ });
1794
2128
  server.get("/hub", async (_request, reply) => {
1795
2129
  return reply.redirect("/hub/");
1796
2130
  });
@@ -1838,12 +2172,47 @@ function createRegistryServer(opts) {
1838
2172
  (c) => (c.metadata?.avg_latency_ms ?? Infinity) <= maxLatencyMs
1839
2173
  );
1840
2174
  }
1841
- if (sort === "success_rate") {
2175
+ const usesStmt = db.prepare(`
2176
+ SELECT card_id, skill_id, COUNT(*) as cnt
2177
+ FROM request_log
2178
+ WHERE status = 'success'
2179
+ AND created_at > datetime('now', '-7 days')
2180
+ AND (action_type IS NULL OR action_type = 'auto_share')
2181
+ GROUP BY card_id, skill_id
2182
+ `);
2183
+ const usesRows = usesStmt.all();
2184
+ const usesMap = /* @__PURE__ */ new Map();
2185
+ for (const row of usesRows) {
2186
+ usesMap.set(row.card_id, (usesMap.get(row.card_id) ?? 0) + row.cnt);
2187
+ if (row.skill_id) {
2188
+ usesMap.set(row.skill_id, (usesMap.get(row.skill_id) ?? 0) + row.cnt);
2189
+ }
2190
+ }
2191
+ if (sort === "popular") {
2192
+ cards = [...cards].sort((a, b) => {
2193
+ const aUses = usesMap.get(a.id) ?? 0;
2194
+ const bUses = usesMap.get(b.id) ?? 0;
2195
+ return bUses - aUses;
2196
+ });
2197
+ } else if (sort === "rated" || sort === "success_rate") {
1842
2198
  cards = [...cards].sort((a, b) => {
1843
2199
  const aRate = a.metadata?.success_rate ?? -1;
1844
2200
  const bRate = b.metadata?.success_rate ?? -1;
1845
2201
  return bRate - aRate;
1846
2202
  });
2203
+ } else if (sort === "cheapest") {
2204
+ cards = [...cards].sort((a, b) => {
2205
+ return a.pricing.credits_per_call - b.pricing.credits_per_call;
2206
+ });
2207
+ } else if (sort === "newest") {
2208
+ const createdStmt = db.prepare("SELECT id, created_at FROM capability_cards");
2209
+ const createdRows = createdStmt.all();
2210
+ const createdMap = new Map(createdRows.map((r) => [r.id, r.created_at]));
2211
+ cards = [...cards].sort((a, b) => {
2212
+ const aDate = createdMap.get(a.id) ?? "";
2213
+ const bDate = createdMap.get(b.id) ?? "";
2214
+ return bDate.localeCompare(aDate);
2215
+ });
1847
2216
  } else if (sort === "latency") {
1848
2217
  cards = [...cards].sort((a, b) => {
1849
2218
  const aLatency = a.metadata?.avg_latency_ms ?? Infinity;
@@ -1853,9 +2222,32 @@ function createRegistryServer(opts) {
1853
2222
  }
1854
2223
  const total = cards.length;
1855
2224
  const items = cards.slice(offset, offset + limit).map(stripInternal);
1856
- const result = { total, limit, offset, items };
2225
+ const usesThisWeek = {};
2226
+ for (const [key, count] of usesMap) {
2227
+ if (count > 0) usesThisWeek[key] = count;
2228
+ }
2229
+ const result = { total, limit, offset, items, uses_this_week: usesThisWeek };
1857
2230
  return reply.send(result);
1858
2231
  });
2232
+ server.get("/api/cards/trending", async (_request, reply) => {
2233
+ const trendingStmt = db.prepare(`
2234
+ SELECT rl.card_id, COUNT(*) as recent_requests
2235
+ FROM request_log rl
2236
+ WHERE rl.status = 'success'
2237
+ AND rl.created_at > datetime('now', '-7 days')
2238
+ AND (rl.action_type IS NULL OR rl.action_type = 'auto_share')
2239
+ GROUP BY rl.card_id
2240
+ ORDER BY recent_requests DESC
2241
+ LIMIT 10
2242
+ `);
2243
+ const trendingRows = trendingStmt.all();
2244
+ const items = trendingRows.map((row) => {
2245
+ const card = getCard(db, row.card_id);
2246
+ if (!card) return null;
2247
+ return { ...stripInternal(card), uses_this_week: row.recent_requests };
2248
+ }).filter((item) => item !== null);
2249
+ return reply.send({ items });
2250
+ });
1859
2251
  server.get("/cards/:id", async (request, reply) => {
1860
2252
  const { id } = request.params;
1861
2253
  const card = getCard(db, id);
@@ -1864,6 +2256,57 @@ function createRegistryServer(opts) {
1864
2256
  }
1865
2257
  return reply.send(stripInternal(card));
1866
2258
  });
2259
+ server.post("/cards", async (request, reply) => {
2260
+ const body = request.body;
2261
+ if (!body.spec_version) {
2262
+ body.spec_version = "1.0";
2263
+ }
2264
+ const result = AnyCardSchema.safeParse(body);
2265
+ if (!result.success) {
2266
+ return reply.code(400).send({
2267
+ error: "Card validation failed",
2268
+ issues: result.error.issues
2269
+ });
2270
+ }
2271
+ const card = result.data;
2272
+ const now = (/* @__PURE__ */ new Date()).toISOString();
2273
+ if (card.spec_version === "2.0") {
2274
+ const cardWithTimestamps = {
2275
+ ...card,
2276
+ created_at: card.created_at ?? now,
2277
+ updated_at: now
2278
+ };
2279
+ db.prepare(
2280
+ `INSERT OR REPLACE INTO capability_cards (id, owner, data, created_at, updated_at)
2281
+ VALUES (?, ?, ?, ?, ?)`
2282
+ ).run(
2283
+ cardWithTimestamps.id,
2284
+ cardWithTimestamps.owner,
2285
+ JSON.stringify(cardWithTimestamps),
2286
+ cardWithTimestamps.created_at,
2287
+ cardWithTimestamps.updated_at
2288
+ );
2289
+ } else {
2290
+ try {
2291
+ insertCard(db, card);
2292
+ } catch (err) {
2293
+ if (err instanceof AgentBnBError && err.code === "VALIDATION_ERROR") {
2294
+ return reply.code(400).send({ error: err.message });
2295
+ }
2296
+ throw err;
2297
+ }
2298
+ }
2299
+ return reply.code(201).send({ ok: true, id: card.id });
2300
+ });
2301
+ server.delete("/cards/:id", async (request, reply) => {
2302
+ const { id } = request.params;
2303
+ const card = getCard(db, id);
2304
+ if (!card) {
2305
+ return reply.code(404).send({ error: "Not found" });
2306
+ }
2307
+ db.prepare("DELETE FROM capability_cards WHERE id = ?").run(id);
2308
+ return reply.send({ ok: true, id });
2309
+ });
1867
2310
  server.get("/api/agents", async (_request, reply) => {
1868
2311
  const allCards = listCards(db);
1869
2312
  const ownerMap = /* @__PURE__ */ new Map();
@@ -1955,6 +2398,86 @@ function createRegistryServer(opts) {
1955
2398
  const items = getActivityFeed(db, limit, since);
1956
2399
  return reply.send({ items, total: items.length, limit });
1957
2400
  });
2401
+ server.get("/api/stats", async (_request, reply) => {
2402
+ const allCards = listCards(db);
2403
+ const onlineOwners = /* @__PURE__ */ new Set();
2404
+ if (relayState) {
2405
+ for (const owner of relayState.getOnlineOwners()) {
2406
+ onlineOwners.add(owner);
2407
+ }
2408
+ }
2409
+ for (const card of allCards) {
2410
+ if (card.availability.online) {
2411
+ onlineOwners.add(card.owner);
2412
+ }
2413
+ }
2414
+ const exchangeStmt = db.prepare(
2415
+ "SELECT COUNT(*) as count FROM request_log WHERE status = 'success' AND (action_type IS NULL OR action_type = 'auto_share')"
2416
+ );
2417
+ const exchangeRow = exchangeStmt.get();
2418
+ return reply.send({
2419
+ agents_online: onlineOwners.size,
2420
+ total_capabilities: allCards.reduce((sum, card) => {
2421
+ const v2 = card;
2422
+ return sum + (v2.skills?.length ?? 1);
2423
+ }, 0),
2424
+ total_exchanges: exchangeRow.count
2425
+ });
2426
+ });
2427
+ server.post("/api/identity/register", async (request, reply) => {
2428
+ if (!opts.creditDb) {
2429
+ return reply.code(503).send({ error: "Credit database not configured" });
2430
+ }
2431
+ const body = request.body;
2432
+ const githubLogin = typeof body.github_login === "string" ? body.github_login.trim() : "";
2433
+ if (!githubLogin) {
2434
+ return reply.code(400).send({ error: "github_login is required" });
2435
+ }
2436
+ try {
2437
+ const record = registerGuarantor(opts.creditDb, githubLogin);
2438
+ const auth = initiateGithubAuth();
2439
+ return reply.code(201).send({ guarantor: record, oauth: auth });
2440
+ } catch (err) {
2441
+ if (err instanceof AgentBnBError && err.code === "GUARANTOR_EXISTS") {
2442
+ return reply.code(409).send({ error: err.message });
2443
+ }
2444
+ throw err;
2445
+ }
2446
+ });
2447
+ server.post("/api/identity/link", async (request, reply) => {
2448
+ if (!opts.creditDb) {
2449
+ return reply.code(503).send({ error: "Credit database not configured" });
2450
+ }
2451
+ const body = request.body;
2452
+ const agentId = typeof body.agent_id === "string" ? body.agent_id.trim() : "";
2453
+ const githubLogin = typeof body.github_login === "string" ? body.github_login.trim() : "";
2454
+ if (!agentId || !githubLogin) {
2455
+ return reply.code(400).send({ error: "agent_id and github_login are required" });
2456
+ }
2457
+ try {
2458
+ const record = linkAgentToGuarantor(opts.creditDb, agentId, githubLogin);
2459
+ return reply.send({ guarantor: record });
2460
+ } catch (err) {
2461
+ if (err instanceof AgentBnBError) {
2462
+ const statusMap = {
2463
+ GUARANTOR_NOT_FOUND: 404,
2464
+ MAX_AGENTS_EXCEEDED: 409,
2465
+ AGENT_ALREADY_LINKED: 409
2466
+ };
2467
+ const status = statusMap[err.code] ?? 400;
2468
+ return reply.code(status).send({ error: err.message });
2469
+ }
2470
+ throw err;
2471
+ }
2472
+ });
2473
+ server.get("/api/identity/:agent_id", async (request, reply) => {
2474
+ if (!opts.creditDb) {
2475
+ return reply.code(503).send({ error: "Credit database not configured" });
2476
+ }
2477
+ const { agent_id } = request.params;
2478
+ const guarantor = getAgentGuarantor(opts.creditDb, agent_id);
2479
+ return reply.send({ agent_id, guarantor });
2480
+ });
1958
2481
  if (opts.ownerApiKey && opts.ownerName) {
1959
2482
  const ownerApiKey = opts.ownerApiKey;
1960
2483
  const ownerName = opts.ownerName;
@@ -2061,7 +2584,7 @@ function createRegistryServer(opts) {
2061
2584
  });
2062
2585
  });
2063
2586
  }
2064
- return server;
2587
+ return { server, relayState };
2065
2588
  }
2066
2589
 
2067
2590
  // src/discovery/mdns.ts
@@ -2135,10 +2658,10 @@ async function stopAnnouncement() {
2135
2658
  }
2136
2659
 
2137
2660
  // src/openclaw/soul-sync.ts
2138
- import { randomUUID as randomUUID5 } from "crypto";
2661
+ import { randomUUID as randomUUID7 } from "crypto";
2139
2662
 
2140
2663
  // src/skills/publish-capability.ts
2141
- import { randomUUID as randomUUID4 } from "crypto";
2664
+ import { randomUUID as randomUUID6 } from "crypto";
2142
2665
  function parseSoulMd(content) {
2143
2666
  const lines = content.split("\n");
2144
2667
  let name = "";
@@ -2209,7 +2732,7 @@ function parseSoulMdV2(content) {
2209
2732
  const parsed = parseSoulMd(content);
2210
2733
  const skills = parsed.capabilities.map((cap) => {
2211
2734
  const sanitizedId = cap.name.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
2212
- const id = sanitizedId.length > 0 ? sanitizedId : randomUUID5();
2735
+ const id = sanitizedId.length > 0 ? sanitizedId : randomUUID7();
2213
2736
  return {
2214
2737
  id,
2215
2738
  name: cap.name,
@@ -2251,7 +2774,7 @@ function publishFromSoulV2(db, soulContent, owner) {
2251
2774
  (c) => c.spec_version === "2.0"
2252
2775
  );
2253
2776
  const now = (/* @__PURE__ */ new Date()).toISOString();
2254
- const cardId = existingV2?.id ?? randomUUID5();
2777
+ const cardId = existingV2?.id ?? randomUUID7();
2255
2778
  const card = {
2256
2779
  spec_version: "2.0",
2257
2780
  id: cardId,
@@ -2276,7 +2799,7 @@ function publishFromSoulV2(db, soulContent, owner) {
2276
2799
  }
2277
2800
 
2278
2801
  // src/openclaw/heartbeat-writer.ts
2279
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync4 } from "fs";
2802
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, existsSync as existsSync5 } from "fs";
2280
2803
  var HEARTBEAT_MARKER_START = "<!-- agentbnb:start -->";
2281
2804
  var HEARTBEAT_MARKER_END = "<!-- agentbnb:end -->";
2282
2805
  function generateHeartbeatSection(autonomy, budget) {
@@ -2312,11 +2835,11 @@ function generateHeartbeatSection(autonomy, budget) {
2312
2835
  ].join("\n");
2313
2836
  }
2314
2837
  function injectHeartbeatSection(heartbeatPath, section) {
2315
- if (!existsSync4(heartbeatPath)) {
2838
+ if (!existsSync5(heartbeatPath)) {
2316
2839
  writeFileSync2(heartbeatPath, section + "\n", "utf-8");
2317
2840
  return;
2318
2841
  }
2319
- let content = readFileSync3(heartbeatPath, "utf-8");
2842
+ let content = readFileSync4(heartbeatPath, "utf-8");
2320
2843
  const startIdx = content.indexOf(HEARTBEAT_MARKER_START);
2321
2844
  const endIdx = content.indexOf(HEARTBEAT_MARKER_END);
2322
2845
  if (startIdx !== -1 && endIdx !== -1) {
@@ -2365,7 +2888,7 @@ function getOpenClawStatus(config, db, creditDb) {
2365
2888
  var require2 = createRequire(import.meta.url);
2366
2889
  var pkg = require2("../../package.json");
2367
2890
  async function confirm(question) {
2368
- const rl = createInterface({ input: process.stdin, output: process.stdout });
2891
+ const rl = createInterface2({ input: process.stdin, output: process.stdout });
2369
2892
  try {
2370
2893
  return await new Promise((resolve) => {
2371
2894
  rl.question(question, (answer) => {
@@ -2387,12 +2910,12 @@ function getLanIp() {
2387
2910
  }
2388
2911
  var program = new Command();
2389
2912
  program.name("agentbnb").description("P2P Agent Capability Sharing Protocol \u2014 Airbnb for AI agent pipelines").version(pkg.version);
2390
- program.command("init").description("Initialize AgentBnB config and create agent identity").option("--owner <name>", "Agent owner name").option("--port <port>", "Gateway port", "7700").option("--host <ip>", "Override gateway host IP (default: auto-detected LAN IP)").option("--yes", "Auto-confirm all draft cards (non-interactive)").option("--no-detect", "Skip API key detection").option("--json", "Output as JSON").action(async (opts) => {
2913
+ program.command("init").description("Initialize AgentBnB config and create agent identity").option("--owner <name>", "Agent owner name").option("--port <port>", "Gateway port", "7700").option("--host <ip>", "Override gateway host IP (default: auto-detected LAN IP)").option("--yes", "Auto-confirm all draft cards (non-interactive)").option("--no-detect", "Skip API key detection").option("--from <file>", "Parse a specific file for capability detection").option("--json", "Output as JSON").action(async (opts) => {
2391
2914
  const owner = opts.owner ?? `agent-${randomBytes(4).toString("hex")}`;
2392
2915
  const token = randomBytes(32).toString("hex");
2393
2916
  const configDir = getConfigDir();
2394
- const dbPath = join3(configDir, "registry.db");
2395
- const creditDbPath = join3(configDir, "credit.db");
2917
+ const dbPath = join4(configDir, "registry.db");
2918
+ const creditDbPath = join4(configDir, "credit.db");
2396
2919
  const port = parseInt(opts.port, 10);
2397
2920
  const ip = opts.host ?? getLanIp();
2398
2921
  const existingConfig = loadConfig();
@@ -2415,23 +2938,86 @@ program.command("init").description("Initialize AgentBnB config and create agent
2415
2938
  saveKeyPair(configDir, keys);
2416
2939
  keypairStatus = "generated";
2417
2940
  }
2941
+ const identity = ensureIdentity(configDir, owner);
2418
2942
  const creditDb = openCreditDb(creditDbPath);
2419
2943
  bootstrapAgent(creditDb, owner, 100);
2420
2944
  creditDb.close();
2421
2945
  const skipDetect = opts.detect === false;
2422
- let detectedKeys = [];
2423
- let detectedPorts = [];
2424
2946
  const publishedCards = [];
2947
+ let detectedSource = "none";
2425
2948
  if (!skipDetect) {
2426
- detectedKeys = detectApiKeys(KNOWN_API_KEYS);
2427
- detectedPorts = await detectOpenPorts([7700, 7701, 8080, 3e3, 8e3, 11434]);
2428
- if (detectedKeys.length > 0) {
2949
+ if (!opts.json) {
2950
+ console.log("\nDetecting capabilities...");
2951
+ }
2952
+ const result = detectCapabilities({ fromFile: opts.from, cwd: process.cwd() });
2953
+ detectedSource = result.source;
2954
+ if (result.source === "soul") {
2955
+ if (!opts.json) {
2956
+ console.log(` Found SOUL.md \u2014 extracting capabilities...`);
2957
+ }
2958
+ const db = openDatabase(dbPath);
2959
+ try {
2960
+ const card = publishFromSoulV2(db, result.soulContent, owner);
2961
+ publishedCards.push({ id: card.id, name: card.agent_name });
2962
+ if (!opts.json) {
2963
+ console.log(` Published v2.0 card: ${card.agent_name} (${card.skills.length} skills)`);
2964
+ }
2965
+ } finally {
2966
+ db.close();
2967
+ }
2968
+ } else if (result.source === "docs") {
2429
2969
  if (!opts.json) {
2430
- console.log(`
2431
- Detected ${detectedKeys.length} API key${detectedKeys.length > 1 ? "s" : ""}: ${detectedKeys.join(", ")}`);
2970
+ console.log(` Found ${result.sourceFile ?? "docs"} \u2014 detected ${result.capabilities.length} capabilities:`);
2971
+ for (const cap of result.capabilities) {
2972
+ console.log(` ${cap.name} (${cap.category}, cr ${cap.credits_per_call}/call)`);
2973
+ }
2432
2974
  }
2975
+ const card = capabilitiesToV2Card(result.capabilities, owner);
2976
+ if (opts.yes) {
2977
+ const db = openDatabase(dbPath);
2978
+ try {
2979
+ db.prepare(
2980
+ `INSERT OR REPLACE INTO capability_cards (id, owner, data, created_at, updated_at)
2981
+ VALUES (?, ?, ?, ?, ?)`
2982
+ ).run(card.id, card.owner, JSON.stringify(card), card.created_at, card.updated_at);
2983
+ publishedCards.push({ id: card.id, name: card.agent_name });
2984
+ if (!opts.json) {
2985
+ console.log(` Published v2.0 card: ${card.agent_name} (${card.skills.length} skills)`);
2986
+ }
2987
+ } finally {
2988
+ db.close();
2989
+ }
2990
+ } else if (process.stdout.isTTY) {
2991
+ const yes = await confirm(`
2992
+ Publish these ${card.skills.length} capabilities? [y/N] `);
2993
+ if (yes) {
2994
+ const db = openDatabase(dbPath);
2995
+ try {
2996
+ db.prepare(
2997
+ `INSERT OR REPLACE INTO capability_cards (id, owner, data, created_at, updated_at)
2998
+ VALUES (?, ?, ?, ?, ?)`
2999
+ ).run(card.id, card.owner, JSON.stringify(card), card.created_at, card.updated_at);
3000
+ publishedCards.push({ id: card.id, name: card.agent_name });
3001
+ console.log(` Published v2.0 card: ${card.agent_name} (${card.skills.length} skills)`);
3002
+ } finally {
3003
+ db.close();
3004
+ }
3005
+ } else {
3006
+ console.log(" Skipped publishing.");
3007
+ }
3008
+ } else {
3009
+ if (!opts.json) {
3010
+ console.log(" Non-interactive environment. Re-run with --yes to auto-publish.");
3011
+ }
3012
+ }
3013
+ } else if (result.source === "env") {
3014
+ const detectedKeys = result.envKeys ?? [];
3015
+ if (!opts.json) {
3016
+ console.log(` Detected ${detectedKeys.length} API key${detectedKeys.length > 1 ? "s" : ""}: ${detectedKeys.join(", ")}`);
3017
+ }
3018
+ const detectedPorts = await detectOpenPorts([7700, 7701, 8080, 3e3, 8e3, 11434]);
2433
3019
  if (detectedPorts.length > 0 && !opts.json) {
2434
- console.log(`Found services on ports: ${detectedPorts.join(", ")}`);
3020
+ console.log(` Found services on ports: ${detectedPorts.join(", ")}`);
2435
3021
  }
2436
3022
  const drafts = detectedKeys.map((key) => buildDraftCard(key, owner)).filter((card) => card !== null);
2437
3023
  if (opts.yes) {
@@ -2441,7 +3027,7 @@ Detected ${detectedKeys.length} API key${detectedKeys.length > 1 ? "s" : ""}: ${
2441
3027
  insertCard(db, card);
2442
3028
  publishedCards.push({ id: card.id, name: card.name });
2443
3029
  if (!opts.json) {
2444
- console.log(`Published: ${card.name} (${card.id})`);
3030
+ console.log(` Published: ${card.name} (${card.id})`);
2445
3031
  }
2446
3032
  }
2447
3033
  } finally {
@@ -2455,9 +3041,9 @@ Detected ${detectedKeys.length} API key${detectedKeys.length > 1 ? "s" : ""}: ${
2455
3041
  if (yes) {
2456
3042
  insertCard(db, card);
2457
3043
  publishedCards.push({ id: card.id, name: card.name });
2458
- console.log(`Published: ${card.name} (${card.id})`);
3044
+ console.log(` Published: ${card.name} (${card.id})`);
2459
3045
  } else {
2460
- console.log(`Skipped: ${card.name}`);
3046
+ console.log(` Skipped: ${card.name}`);
2461
3047
  }
2462
3048
  }
2463
3049
  } finally {
@@ -2465,12 +3051,29 @@ Detected ${detectedKeys.length} API key${detectedKeys.length > 1 ? "s" : ""}: ${
2465
3051
  }
2466
3052
  } else {
2467
3053
  if (!opts.json) {
2468
- console.log("Non-interactive environment detected. Re-run with --yes to auto-publish draft cards.");
3054
+ console.log(" Non-interactive environment. Re-run with --yes to auto-publish.");
2469
3055
  }
2470
3056
  }
2471
3057
  } else {
2472
- if (!opts.json) {
2473
- console.log("\nNo API keys detected. You can manually publish cards with `agentbnb publish`.");
3058
+ if (process.stdout.isTTY && !opts.yes && !opts.json) {
3059
+ const selected = await interactiveTemplateMenu();
3060
+ if (selected.length > 0) {
3061
+ const card = capabilitiesToV2Card(selected, owner);
3062
+ const db = openDatabase(dbPath);
3063
+ try {
3064
+ db.prepare(
3065
+ `INSERT OR REPLACE INTO capability_cards (id, owner, data, created_at, updated_at)
3066
+ VALUES (?, ?, ?, ?, ?)`
3067
+ ).run(card.id, card.owner, JSON.stringify(card), card.created_at, card.updated_at);
3068
+ publishedCards.push({ id: card.id, name: card.agent_name });
3069
+ console.log(`
3070
+ Published v2.0 card: ${card.agent_name} (${card.skills.length} skills)`);
3071
+ } finally {
3072
+ db.close();
3073
+ }
3074
+ }
3075
+ } else if (!opts.json) {
3076
+ console.log(" No capabilities detected. You can manually publish with `agentbnb publish`.");
2474
3077
  }
2475
3078
  }
2476
3079
  }
@@ -2481,10 +3084,11 @@ Detected ${detectedKeys.length} API key${detectedKeys.length > 1 ? "s" : ""}: ${
2481
3084
  config_dir: configDir,
2482
3085
  token,
2483
3086
  gateway_url: config.gateway_url,
2484
- keypair: keypairStatus
3087
+ keypair: keypairStatus,
3088
+ agent_id: identity.agent_id
2485
3089
  };
2486
3090
  if (!skipDetect) {
2487
- jsonOutput.detected_keys = detectedKeys;
3091
+ jsonOutput.detected_source = detectedSource;
2488
3092
  jsonOutput.published_cards = publishedCards;
2489
3093
  }
2490
3094
  console.log(JSON.stringify(jsonOutput, null, 2));
@@ -2495,10 +3099,11 @@ Detected ${detectedKeys.length} API key${detectedKeys.length > 1 ? "s" : ""}: ${
2495
3099
  console.log(` Config: ${configDir}/config.json`);
2496
3100
  console.log(` Credits: 100 (starter grant)`);
2497
3101
  console.log(` Keypair: ${keypairStatus === "generated" ? "generated (Ed25519)" : "preserved (existing)"}`);
3102
+ console.log(` Agent ID: ${identity.agent_id}`);
2498
3103
  console.log(` Gateway: http://${ip}:${port}`);
2499
3104
  }
2500
3105
  });
2501
- program.command("publish <card.json>").description("Publish a Capability Card to the registry").option("--json", "Output as JSON").action(async (cardPath, opts) => {
3106
+ program.command("publish <card.json>").description("Publish a Capability Card to the registry (v1.0 or v2.0)").option("--json", "Output as JSON").option("--registry <url>", "POST card to a remote registry URL instead of local DB").action(async (cardPath, opts) => {
2502
3107
  const config = loadConfig();
2503
3108
  if (!config) {
2504
3109
  console.error("Error: not initialized. Run `agentbnb init` first.");
@@ -2506,7 +3111,7 @@ program.command("publish <card.json>").description("Publish a Capability Card to
2506
3111
  }
2507
3112
  let raw;
2508
3113
  try {
2509
- raw = readFileSync4(cardPath, "utf-8");
3114
+ raw = readFileSync5(cardPath, "utf-8");
2510
3115
  } catch {
2511
3116
  console.error(`Error: cannot read file: ${cardPath}`);
2512
3117
  process.exit(1);
@@ -2518,7 +3123,10 @@ program.command("publish <card.json>").description("Publish a Capability Card to
2518
3123
  console.error("Error: invalid JSON in card file.");
2519
3124
  process.exit(1);
2520
3125
  }
2521
- const result = CapabilityCardSchema.safeParse(parsed);
3126
+ if (typeof parsed === "object" && parsed !== null && !("spec_version" in parsed)) {
3127
+ parsed.spec_version = "1.0";
3128
+ }
3129
+ const result = AnyCardSchema.safeParse(parsed);
2522
3130
  if (!result.success) {
2523
3131
  if (opts.json) {
2524
3132
  console.log(JSON.stringify({ success: false, errors: result.error.issues }, null, 2));
@@ -2530,16 +3138,56 @@ program.command("publish <card.json>").description("Publish a Capability Card to
2530
3138
  }
2531
3139
  process.exit(1);
2532
3140
  }
3141
+ const card = result.data;
3142
+ const cardName = card.spec_version === "2.0" ? card.agent_name : card.name;
2533
3143
  const db = openDatabase(config.db_path);
2534
3144
  try {
2535
- insertCard(db, result.data);
3145
+ if (card.spec_version === "2.0") {
3146
+ const now = (/* @__PURE__ */ new Date()).toISOString();
3147
+ const cardWithTimestamps = { ...card, created_at: card.created_at ?? now, updated_at: now };
3148
+ db.prepare(
3149
+ "INSERT OR REPLACE INTO capability_cards (id, owner, data, created_at, updated_at) VALUES (?, ?, ?, ?, ?)"
3150
+ ).run(cardWithTimestamps.id, cardWithTimestamps.owner, JSON.stringify(cardWithTimestamps), cardWithTimestamps.created_at, cardWithTimestamps.updated_at);
3151
+ } else {
3152
+ insertCard(db, card);
3153
+ }
2536
3154
  } finally {
2537
3155
  db.close();
2538
3156
  }
3157
+ if (!opts.json) {
3158
+ console.log(`Published locally: ${cardName} (${card.id})`);
3159
+ }
3160
+ const registryUrl = opts.registry ?? config.registry;
3161
+ let remoteSuccess = false;
3162
+ if (registryUrl) {
3163
+ const url = `${registryUrl.replace(/\/$/, "")}/cards`;
3164
+ try {
3165
+ const response = await fetch(url, {
3166
+ method: "POST",
3167
+ headers: { "Content-Type": "application/json" },
3168
+ body: JSON.stringify(card)
3169
+ });
3170
+ if (!response.ok) {
3171
+ const body = await response.text();
3172
+ console.error(`Warning: remote registry returned ${response.status}: ${body}`);
3173
+ } else {
3174
+ remoteSuccess = true;
3175
+ if (!opts.json) {
3176
+ console.log(`Published to registry: ${url}`);
3177
+ }
3178
+ }
3179
+ } catch (err) {
3180
+ console.error(`Warning: cannot reach registry at ${url}: ${err.message}`);
3181
+ }
3182
+ }
2539
3183
  if (opts.json) {
2540
- console.log(JSON.stringify({ success: true, id: result.data.id, name: result.data.name }, null, 2));
2541
- } else {
2542
- console.log(`Published: ${result.data.name} (${result.data.id})`);
3184
+ console.log(JSON.stringify({
3185
+ success: true,
3186
+ id: card.id,
3187
+ name: cardName,
3188
+ ...registryUrl ? { registry: registryUrl, remote_published: remoteSuccess } : {}
3189
+ }, null, 2));
3190
+ } else if (!registryUrl) {
2543
3191
  }
2544
3192
  });
2545
3193
  program.command("discover [query]").description("Search available capabilities in the registry").option("--level <level>", "Filter by level (1, 2, or 3)").option("--online", "Only show online capabilities").option("--local", "Browse for agents on the local network via mDNS").option("--registry <url>", "Remote registry URL to query (e.g., http://host:7701)").option("--tag <tag>", "Filter by metadata tag").option("--json", "Output as JSON").action(async (query, opts) => {
@@ -2558,7 +3206,7 @@ program.command("discover [query]").description("Search available capabilities i
2558
3206
  console.log("No agents found on local network.");
2559
3207
  return;
2560
3208
  }
2561
- const col2 = (s, w) => s.slice(0, w).padEnd(w);
3209
+ const col2 = (s, w) => (s ?? "").slice(0, w).padEnd(w);
2562
3210
  console.log(col2("Name", 24) + " " + col2("URL", 32) + " " + col2("Owner", 20));
2563
3211
  console.log("-".repeat(80));
2564
3212
  for (const agent of discovered) {
@@ -2628,7 +3276,7 @@ ${discovered.length} agent(s) found on local network`);
2628
3276
  console.log("No capabilities found.");
2629
3277
  return;
2630
3278
  }
2631
- const col = (s, w) => s.slice(0, w).padEnd(w);
3279
+ const col = (s, w) => (s ?? "").slice(0, w).padEnd(w);
2632
3280
  if (hasRemote) {
2633
3281
  console.log(
2634
3282
  col("ID", 16) + " " + col("Name", 28) + " " + col("Lvl", 3) + " " + col("Credits", 7) + " " + col("Online", 6) + " " + col("Source", 8)
@@ -2636,10 +3284,11 @@ ${discovered.length} agent(s) found on local network`);
2636
3284
  console.log("-".repeat(80));
2637
3285
  for (const card of outputCards) {
2638
3286
  const shortId = card.id.slice(0, 8) + "...";
3287
+ const displayName = card.name ?? card.agent_name ?? "";
2639
3288
  const source = "source" in card ? card.source : "local";
2640
3289
  const sourceTag = source === "remote" ? "[remote]" : "[local]";
2641
3290
  console.log(
2642
- col(shortId, 16) + " " + col(card.name, 28) + " " + col(String(card.level), 3) + " " + col(String(card.pricing.credits_per_call), 7) + " " + col(card.availability.online ? "yes" : "no", 6) + " " + col(sourceTag, 8)
3291
+ col(shortId, 16) + " " + col(displayName, 28) + " " + col(String(card.level ?? ""), 3) + " " + col(String(card.pricing?.credits_per_call ?? ""), 7) + " " + col(card.availability?.online ? "yes" : "no", 6) + " " + col(sourceTag, 8)
2643
3292
  );
2644
3293
  }
2645
3294
  } else {
@@ -2649,8 +3298,9 @@ ${discovered.length} agent(s) found on local network`);
2649
3298
  console.log("-".repeat(72));
2650
3299
  for (const card of outputCards) {
2651
3300
  const shortId = card.id.slice(0, 8) + "...";
3301
+ const displayName = card.name ?? card.agent_name ?? "";
2652
3302
  console.log(
2653
- col(shortId, 16) + " " + col(card.name, 32) + " " + col(String(card.level), 3) + " " + col(String(card.pricing.credits_per_call), 7) + " " + col(card.availability.online ? "yes" : "no", 6)
3303
+ col(shortId, 16) + " " + col(displayName, 32) + " " + col(String(card.level ?? ""), 3) + " " + col(String(card.pricing?.credits_per_call ?? ""), 7) + " " + col(card.availability?.online ? "yes" : "no", 6)
2654
3304
  );
2655
3305
  }
2656
3306
  }
@@ -2673,8 +3323,8 @@ program.command("request [card-id]").description("Request a capability from anot
2673
3323
  process.exit(1);
2674
3324
  }
2675
3325
  }
2676
- const registryDb = openDatabase(join3(getConfigDir(), "registry.db"));
2677
- const creditDb = openCreditDb(join3(getConfigDir(), "credit.db"));
3326
+ const registryDb = openDatabase(join4(getConfigDir(), "registry.db"));
3327
+ const creditDb = openCreditDb(join4(getConfigDir(), "credit.db"));
2678
3328
  registryDb.pragma("busy_timeout = 5000");
2679
3329
  creditDb.pragma("busy_timeout = 5000");
2680
3330
  try {
@@ -2733,7 +3383,7 @@ program.command("request [card-id]").description("Request a capability from anot
2733
3383
  let escrowReceipt;
2734
3384
  if (useReceipt) {
2735
3385
  const configDir = getConfigDir();
2736
- const creditDb = openCreditDb(join3(configDir, "credit.db"));
3386
+ const creditDb = openCreditDb(join4(configDir, "credit.db"));
2737
3387
  creditDb.pragma("busy_timeout = 5000");
2738
3388
  try {
2739
3389
  const keys = loadKeyPair(configDir);
@@ -2774,7 +3424,7 @@ program.command("request [card-id]").description("Request a capability from anot
2774
3424
  });
2775
3425
  if (useReceipt && escrowId) {
2776
3426
  const configDir = getConfigDir();
2777
- const creditDb = openCreditDb(join3(configDir, "credit.db"));
3427
+ const creditDb = openCreditDb(join4(configDir, "credit.db"));
2778
3428
  creditDb.pragma("busy_timeout = 5000");
2779
3429
  try {
2780
3430
  settleRequesterEscrow(creditDb, escrowId);
@@ -2794,7 +3444,7 @@ program.command("request [card-id]").description("Request a capability from anot
2794
3444
  } catch (err) {
2795
3445
  if (useReceipt && escrowId) {
2796
3446
  const configDir = getConfigDir();
2797
- const creditDb = openCreditDb(join3(configDir, "credit.db"));
3447
+ const creditDb = openCreditDb(join4(configDir, "credit.db"));
2798
3448
  creditDb.pragma("busy_timeout = 5000");
2799
3449
  try {
2800
3450
  releaseRequesterEscrow(creditDb, escrowId);
@@ -2849,12 +3499,12 @@ Active Escrows (${heldEscrows.length}):`);
2849
3499
  if (transactions.length > 0) {
2850
3500
  console.log("\nRecent Transactions:");
2851
3501
  for (const tx of transactions) {
2852
- const sign2 = tx.amount > 0 ? "+" : "";
2853
- console.log(` ${tx.created_at.slice(0, 19)} ${sign2}${tx.amount} ${tx.reason}`);
3502
+ const sign = tx.amount > 0 ? "+" : "";
3503
+ console.log(` ${tx.created_at.slice(0, 19)} ${sign}${tx.amount} ${tx.reason}`);
2854
3504
  }
2855
3505
  }
2856
3506
  });
2857
- program.command("serve").description("Start the AgentBnB gateway server").option("--port <port>", "Port to listen on (overrides config)").option("--handler-url <url>", "Local capability handler URL", "http://localhost:8080").option("--skills-yaml <path>", "Path to skills.yaml (default: ~/.agentbnb/skills.yaml)").option("--registry-port <port>", "Public registry API port (0 to disable)", "7701").option("--conductor", "Enable Conductor orchestration mode").option("--announce", "Announce this gateway on the local network via mDNS").action(async (opts) => {
3507
+ 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) => {
2858
3508
  const config = loadConfig();
2859
3509
  if (!config) {
2860
3510
  console.error("Error: not initialized. Run `agentbnb init` first.");
@@ -2862,7 +3512,7 @@ program.command("serve").description("Start the AgentBnB gateway server").option
2862
3512
  }
2863
3513
  const port = opts.port ? parseInt(opts.port, 10) : config.gateway_port;
2864
3514
  const registryPort = parseInt(opts.registryPort, 10);
2865
- const skillsYamlPath = opts.skillsYaml ?? join3(homedir(), ".agentbnb", "skills.yaml");
3515
+ const skillsYamlPath = opts.skillsYaml ?? join4(homedir(), ".agentbnb", "skills.yaml");
2866
3516
  const runtime = new AgentRuntime({
2867
3517
  registryDbPath: config.db_path,
2868
3518
  creditDbPath: config.credit_db_path,
@@ -2895,14 +3545,18 @@ program.command("serve").description("Start the AgentBnB gateway server").option
2895
3545
  handlerUrl: opts.handlerUrl,
2896
3546
  skillExecutor: runtime.skillExecutor
2897
3547
  });
2898
- let registryServer = null;
3548
+ let registryFastify = null;
3549
+ let relayClient = null;
2899
3550
  const gracefulShutdown = async () => {
2900
3551
  console.log("\nShutting down...");
3552
+ if (relayClient) {
3553
+ relayClient.disconnect();
3554
+ }
2901
3555
  if (opts.announce) {
2902
3556
  await stopAnnouncement();
2903
3557
  }
2904
- if (registryServer) {
2905
- await registryServer.close();
3558
+ if (registryFastify) {
3559
+ await registryFastify.close();
2906
3560
  }
2907
3561
  await server.close();
2908
3562
  await runtime.shutdown();
@@ -2921,15 +3575,66 @@ program.command("serve").description("Start the AgentBnB gateway server").option
2921
3575
  if (!config.api_key) {
2922
3576
  console.warn("No API key found. Run `agentbnb init` to enable dashboard features.");
2923
3577
  }
2924
- registryServer = createRegistryServer({
3578
+ const { server: regServer, relayState } = createRegistryServer({
2925
3579
  registryDb: runtime.registryDb,
2926
3580
  silent: false,
2927
3581
  ownerName: config.owner,
2928
3582
  ownerApiKey: config.api_key,
2929
3583
  creditDb: runtime.creditDb
2930
3584
  });
2931
- await registryServer.listen({ port: registryPort, host: "0.0.0.0" });
3585
+ registryFastify = regServer;
3586
+ await registryFastify.listen({ port: registryPort, host: "0.0.0.0" });
2932
3587
  console.log(`Registry API: http://0.0.0.0:${registryPort}/cards`);
3588
+ if (relayState) {
3589
+ console.log(`WebSocket relay active on /ws`);
3590
+ }
3591
+ }
3592
+ if (opts.registry) {
3593
+ const { RelayClient } = await import("../websocket-client-5TIQDYQ4.js");
3594
+ const { executeCapabilityRequest: executeCapabilityRequest2 } = await import("../execute-NZXTSSVV.js");
3595
+ const cards = listCards(runtime.registryDb, config.owner);
3596
+ const card = cards[0] ?? {
3597
+ id: config.owner,
3598
+ owner: config.owner,
3599
+ name: config.owner,
3600
+ description: "Agent registered via CLI",
3601
+ spec_version: "1.0",
3602
+ level: 1,
3603
+ inputs: [],
3604
+ outputs: [],
3605
+ pricing: { credits_per_call: 0 },
3606
+ availability: { online: true }
3607
+ };
3608
+ relayClient = new RelayClient({
3609
+ registryUrl: opts.registry,
3610
+ owner: config.owner,
3611
+ token: config.token,
3612
+ card,
3613
+ onRequest: async (req) => {
3614
+ const result = await executeCapabilityRequest2({
3615
+ registryDb: runtime.registryDb,
3616
+ creditDb: runtime.creditDb,
3617
+ cardId: req.card_id,
3618
+ skillId: req.skill_id,
3619
+ params: req.params,
3620
+ requester: req.requester ?? req.from_owner,
3621
+ escrowReceipt: req.escrow_receipt,
3622
+ skillExecutor: runtime.skillExecutor,
3623
+ handlerUrl: opts.handlerUrl
3624
+ });
3625
+ if (result.success) {
3626
+ return { result: result.result };
3627
+ }
3628
+ return { error: { code: result.error.code, message: result.error.message } };
3629
+ }
3630
+ });
3631
+ try {
3632
+ await relayClient.connect();
3633
+ console.log(`Connected to registry: ${opts.registry}`);
3634
+ } catch (err) {
3635
+ console.warn(`Warning: could not connect to registry ${opts.registry}: ${err instanceof Error ? err.message : err}`);
3636
+ console.warn("Will auto-reconnect in background...");
3637
+ }
2933
3638
  }
2934
3639
  if (opts.announce) {
2935
3640
  announceGateway(config.owner, port);
@@ -2937,8 +3642,9 @@ program.command("serve").description("Start the AgentBnB gateway server").option
2937
3642
  }
2938
3643
  } catch (err) {
2939
3644
  console.error("Failed to start:", err);
2940
- if (registryServer) {
2941
- await registryServer.close().catch(() => {
3645
+ if (relayClient) relayClient.disconnect();
3646
+ if (registryFastify) {
3647
+ await registryFastify.close().catch(() => {
2942
3648
  });
2943
3649
  }
2944
3650
  await runtime.shutdown();
@@ -2969,7 +3675,7 @@ peersCommand.option("--json", "Output as JSON").action(async (opts) => {
2969
3675
  console.log("No peers registered. Use `agentbnb connect` to add one.");
2970
3676
  return;
2971
3677
  }
2972
- const col = (s, w) => s.slice(0, w).padEnd(w);
3678
+ const col = (s, w) => (s ?? "").slice(0, w).padEnd(w);
2973
3679
  console.log(col("Name", 20) + " " + col("URL", 36) + " " + col("Added", 20));
2974
3680
  console.log("-".repeat(80));
2975
3681
  for (const peer of peers) {
@@ -3093,7 +3799,7 @@ openclaw.command("sync").description("Read SOUL.md and publish/update a v2.0 cap
3093
3799
  }
3094
3800
  let content;
3095
3801
  try {
3096
- content = readFileSync4(opts.soulPath, "utf-8");
3802
+ content = readFileSync5(opts.soulPath, "utf-8");
3097
3803
  } catch {
3098
3804
  console.error(`Error: cannot read SOUL.md at ${opts.soulPath}`);
3099
3805
  process.exit(1);
@@ -3154,7 +3860,7 @@ openclaw.command("rules").description("Print HEARTBEAT.md rules block (or inject
3154
3860
  }
3155
3861
  });
3156
3862
  program.command("conduct <task>").description("Orchestrate a complex task across the AgentBnB network").option("--plan-only", "Show execution plan without executing").option("--max-budget <credits>", "Maximum credits to spend", "100").option("--json", "Output as JSON").action(async (task, opts) => {
3157
- const { conductAction } = await import("../conduct-JZJS2ZHA.js");
3863
+ const { conductAction } = await import("../conduct-5T3LGXMF.js");
3158
3864
  const result = await conductAction(task, opts);
3159
3865
  if (opts.json) {
3160
3866
  console.log(JSON.stringify(result, null, 2));