agentbnb 3.1.6 → 4.0.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.
Files changed (37) hide show
  1. package/README.md +117 -86
  2. package/dist/{card-IE5UV5QX.js → card-4XH4AOTE.js} +11 -4
  3. package/dist/chunk-3MJT4PZG.js +50 -0
  4. package/dist/{conduct-IEQ567ET.js → chunk-3UKAVIMC.js} +70 -31
  5. package/dist/chunk-5AH3CMOX.js +62 -0
  6. package/dist/{chunk-IZZ4FP45.js → chunk-6K5WUVF3.js} +33 -166
  7. package/dist/chunk-75OC6E4F.js +33 -0
  8. package/dist/chunk-DVAS2443.js +63 -0
  9. package/dist/{chunk-XA63SD4T.js → chunk-FNKBHBYK.js} +3 -0
  10. package/dist/{websocket-client-5TIQDYQ4.js → chunk-JOY533UH.js} +38 -4
  11. package/dist/chunk-KJG2UJV5.js +83 -0
  12. package/dist/chunk-M3G5NR2Z.js +90 -0
  13. package/dist/{chunk-7OACGAFD.js → chunk-MQKYGY5I.js} +63 -24
  14. package/dist/chunk-ODBGCCEH.js +358 -0
  15. package/dist/{chunk-QSPWE5AE.js → chunk-Q7HRI666.js} +9 -6
  16. package/dist/chunk-QJEOCKVF.js +148 -0
  17. package/dist/{chunk-3Y36WQDV.js → chunk-QT7TEVNV.js} +14 -2
  18. package/dist/{chunk-UOGDK2S2.js → chunk-TLU7ALCZ.js} +1 -1
  19. package/dist/{chunk-QHQPXO67.js → chunk-XQHN6ITI.js} +1 -58
  20. package/dist/cli/index.js +2734 -850
  21. package/dist/client-BTPIFY7E.js +10 -0
  22. package/dist/conduct-CW62HBPT.js +52 -0
  23. package/dist/conduct-FXLVGKD5.js +19 -0
  24. package/dist/{conductor-mode-IO45PWMI.js → conductor-mode-3JS4VWCR.js} +16 -7
  25. package/dist/execute-EXOITLHN.js +10 -0
  26. package/dist/index.d.ts +1005 -916
  27. package/dist/index.js +516 -120
  28. package/dist/{peers-G36URZYB.js → peers-K7FSHPN3.js} +2 -1
  29. package/dist/request-CNZ3XIVX.js +196 -0
  30. package/dist/serve-skill-SUOGUM7N.js +104 -0
  31. package/dist/server-2LWHL24P.js +295 -0
  32. package/dist/types-FGBUZ3QV.js +18 -0
  33. package/dist/websocket-client-6IIDGXKB.js +7 -0
  34. package/package.json +4 -1
  35. package/dist/chunk-BEI5MTNZ.js +0 -91
  36. package/dist/cli/index.d.ts +0 -1
  37. package/dist/execute-SWWEHV2K.js +0 -9
package/dist/cli/index.js CHANGED
@@ -1,35 +1,50 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- executeCapabilityRequest,
4
- releaseRequesterEscrow,
5
- settleRequesterEscrow
6
- } from "../chunk-QSPWE5AE.js";
3
+ deriveAgentId,
4
+ ensureIdentity
5
+ } from "../chunk-M3G5NR2Z.js";
7
6
  import {
8
- RelayMessageSchema
9
- } from "../chunk-3Y36WQDV.js";
7
+ createLedger,
8
+ identityAuthPlugin
9
+ } from "../chunk-ODBGCCEH.js";
10
+ import {
11
+ interpolateObject
12
+ } from "../chunk-3MJT4PZG.js";
10
13
  import {
11
14
  AutoRequestor,
12
15
  BudgetManager,
13
16
  DEFAULT_AUTONOMY_CONFIG,
14
17
  DEFAULT_BUDGET_CONFIG,
15
- filterCards,
16
18
  getAutonomyTier,
17
19
  insertAuditEvent,
18
- interpolateObject,
19
20
  listPendingRequests,
20
- requestCapability,
21
- resolvePendingRequest,
21
+ resolvePendingRequest
22
+ } from "../chunk-6K5WUVF3.js";
23
+ import {
24
+ fetchRemoteCards,
25
+ filterCards,
26
+ mergeResults,
22
27
  searchCards
23
- } from "../chunk-IZZ4FP45.js";
28
+ } from "../chunk-QJEOCKVF.js";
29
+ import {
30
+ requestCapability
31
+ } from "../chunk-KJG2UJV5.js";
24
32
  import {
25
33
  findPeer,
26
- getConfigDir,
27
- loadConfig,
28
34
  loadPeers,
29
35
  removePeer,
30
- saveConfig,
31
36
  savePeer
32
- } from "../chunk-BEI5MTNZ.js";
37
+ } from "../chunk-5AH3CMOX.js";
38
+ import {
39
+ getConfigDir,
40
+ loadConfig,
41
+ saveConfig
42
+ } from "../chunk-75OC6E4F.js";
43
+ import {
44
+ executeCapabilityRequest,
45
+ releaseRequesterEscrow,
46
+ settleRequesterEscrow
47
+ } from "../chunk-Q7HRI666.js";
33
48
  import {
34
49
  getActivityFeed,
35
50
  getCard,
@@ -42,127 +57,53 @@ import {
42
57
  updateCard,
43
58
  updateSkillAvailability,
44
59
  updateSkillIdleRate
45
- } from "../chunk-UOGDK2S2.js";
60
+ } from "../chunk-TLU7ALCZ.js";
46
61
  import {
47
62
  bootstrapAgent,
48
- generateKeyPair,
49
63
  getBalance,
50
64
  getTransactions,
51
65
  holdEscrow,
52
- loadKeyPair,
53
66
  openCreditDb,
54
67
  releaseEscrow,
68
+ settleEscrow
69
+ } from "../chunk-XQHN6ITI.js";
70
+ import {
71
+ generateKeyPair,
72
+ loadKeyPair,
55
73
  saveKeyPair,
56
74
  signEscrowReceipt,
57
75
  verifyEscrowReceipt
58
- } from "../chunk-QHQPXO67.js";
76
+ } from "../chunk-DVAS2443.js";
59
77
  import {
60
78
  AgentBnBError,
61
79
  AnyCardSchema,
62
80
  CapabilityCardV2Schema
63
- } from "../chunk-XA63SD4T.js";
81
+ } from "../chunk-FNKBHBYK.js";
82
+ import {
83
+ RelayMessageSchema
84
+ } from "../chunk-QT7TEVNV.js";
64
85
 
65
86
  // src/cli/index.ts
66
87
  import { Command } from "commander";
67
- import { readFileSync as readFileSync5 } from "fs";
88
+ import { readFileSync as readFileSync4 } from "fs";
68
89
  import { createRequire } from "module";
69
- import { randomBytes } from "crypto";
70
- import { join as join4 } from "path";
90
+ import { randomBytes as randomBytes2 } from "crypto";
91
+ import { join as join3 } from "path";
71
92
  import { networkInterfaces, homedir } from "os";
72
93
  import { createInterface as createInterface2 } from "readline";
73
94
 
74
- // src/identity/identity.ts
75
- import { z } from "zod";
76
- import { createHash } from "crypto";
77
- import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
78
- import { join } from "path";
79
- var AgentIdentitySchema = z.object({
80
- /** Deterministic ID derived from public key: sha256(hex).slice(0, 16). */
81
- agent_id: z.string().min(1),
82
- /** Human-readable owner name (from config or init). */
83
- owner: z.string().min(1),
84
- /** Hex-encoded Ed25519 public key. */
85
- public_key: z.string().min(1),
86
- /** ISO 8601 timestamp of identity creation. */
87
- created_at: z.string().datetime(),
88
- /** Optional guarantor info if linked to a human. */
89
- guarantor: z.object({
90
- github_login: z.string().min(1),
91
- verified_at: z.string().datetime()
92
- }).optional()
93
- });
94
- var AgentCertificateSchema = z.object({
95
- identity: AgentIdentitySchema,
96
- /** ISO 8601 timestamp of certificate issuance. */
97
- issued_at: z.string().datetime(),
98
- /** ISO 8601 timestamp of certificate expiry. */
99
- expires_at: z.string().datetime(),
100
- /** Hex-encoded public key of the issuer (same as identity for self-signed). */
101
- issuer_public_key: z.string().min(1),
102
- /** Base64url Ed25519 signature over { identity, issued_at, expires_at, issuer_public_key }. */
103
- signature: z.string().min(1)
104
- });
105
- var IDENTITY_FILENAME = "identity.json";
106
- function deriveAgentId(publicKeyHex) {
107
- return createHash("sha256").update(publicKeyHex, "hex").digest("hex").slice(0, 16);
108
- }
109
- function createIdentity(configDir, owner) {
110
- if (!existsSync(configDir)) {
111
- mkdirSync(configDir, { recursive: true });
112
- }
113
- let keys;
114
- try {
115
- keys = loadKeyPair(configDir);
116
- } catch {
117
- keys = generateKeyPair();
118
- saveKeyPair(configDir, keys);
119
- }
120
- const publicKeyHex = keys.publicKey.toString("hex");
121
- const agentId = deriveAgentId(publicKeyHex);
122
- const identity = {
123
- agent_id: agentId,
124
- owner,
125
- public_key: publicKeyHex,
126
- created_at: (/* @__PURE__ */ new Date()).toISOString()
127
- };
128
- saveIdentity(configDir, identity);
129
- return identity;
130
- }
131
- function loadIdentity(configDir) {
132
- const filePath = join(configDir, IDENTITY_FILENAME);
133
- if (!existsSync(filePath)) return null;
134
- try {
135
- const raw = readFileSync(filePath, "utf-8");
136
- return AgentIdentitySchema.parse(JSON.parse(raw));
137
- } catch {
138
- return null;
139
- }
140
- }
141
- function saveIdentity(configDir, identity) {
142
- if (!existsSync(configDir)) {
143
- mkdirSync(configDir, { recursive: true });
144
- }
145
- const filePath = join(configDir, IDENTITY_FILENAME);
146
- writeFileSync(filePath, JSON.stringify(identity, null, 2), "utf-8");
147
- }
148
- function ensureIdentity(configDir, owner) {
149
- const existing = loadIdentity(configDir);
150
- if (existing) return existing;
151
- return createIdentity(configDir, owner);
152
- }
153
-
154
95
  // src/credit/escrow-receipt.ts
155
- import { z as z2 } from "zod";
96
+ import { z } from "zod";
156
97
  import { randomUUID } from "crypto";
157
- var EscrowReceiptSchema = z2.object({
158
- requester_owner: z2.string().min(1),
159
- requester_public_key: z2.string().min(1),
160
- amount: z2.number().positive(),
161
- card_id: z2.string().min(1),
162
- skill_id: z2.string().optional(),
163
- timestamp: z2.string(),
164
- nonce: z2.string().uuid(),
165
- signature: z2.string().min(1)
98
+ var EscrowReceiptSchema = z.object({
99
+ requester_owner: z.string().min(1),
100
+ requester_public_key: z.string().min(1),
101
+ amount: z.number().positive(),
102
+ card_id: z.string().min(1),
103
+ skill_id: z.string().optional(),
104
+ timestamp: z.string(),
105
+ nonce: z.string().uuid(),
106
+ signature: z.string().min(1)
166
107
  });
167
108
  function createSignedEscrowReceipt(db, privateKey, publicKey, opts) {
168
109
  const escrowId = holdEscrow(db, opts.owner, opts.amount, opts.cardId);
@@ -276,89 +217,6 @@ var IdleMonitor = class {
276
217
  }
277
218
  };
278
219
 
279
- // src/cli/remote-registry.ts
280
- var RegistryTimeoutError = class extends AgentBnBError {
281
- constructor(url) {
282
- super(
283
- `Registry at ${url} did not respond within 5s. Showing local results only.`,
284
- "REGISTRY_TIMEOUT"
285
- );
286
- this.name = "RegistryTimeoutError";
287
- }
288
- };
289
- var RegistryConnectionError = class extends AgentBnBError {
290
- constructor(url) {
291
- super(
292
- `Cannot reach ${url}. Is the registry running? Showing local results only.`,
293
- "REGISTRY_CONNECTION"
294
- );
295
- this.name = "RegistryConnectionError";
296
- }
297
- };
298
- var RegistryAuthError = class extends AgentBnBError {
299
- constructor(url) {
300
- super(
301
- `Authentication failed for ${url}. Run \`agentbnb config set token <your-token>\`.`,
302
- "REGISTRY_AUTH"
303
- );
304
- this.name = "RegistryAuthError";
305
- }
306
- };
307
- async function fetchRemoteCards(registryUrl, params, timeoutMs = 5e3) {
308
- let cardsUrl;
309
- try {
310
- cardsUrl = new URL("/cards", registryUrl);
311
- } catch {
312
- throw new AgentBnBError(`Invalid registry URL: ${registryUrl}`, "INVALID_REGISTRY_URL");
313
- }
314
- const searchParams = new URLSearchParams();
315
- if (params.q !== void 0) searchParams.set("q", params.q);
316
- if (params.level !== void 0) searchParams.set("level", String(params.level));
317
- if (params.online !== void 0) searchParams.set("online", String(params.online));
318
- if (params.tag !== void 0) searchParams.set("tag", params.tag);
319
- searchParams.set("limit", "100");
320
- cardsUrl.search = searchParams.toString();
321
- const controller = new AbortController();
322
- const timer = setTimeout(() => controller.abort(), timeoutMs);
323
- let response;
324
- try {
325
- response = await fetch(cardsUrl.toString(), { signal: controller.signal });
326
- } catch (err) {
327
- clearTimeout(timer);
328
- const isTimeout = err instanceof Error && err.name === "AbortError";
329
- if (isTimeout) {
330
- throw new RegistryTimeoutError(registryUrl);
331
- }
332
- throw new RegistryConnectionError(registryUrl);
333
- } finally {
334
- clearTimeout(timer);
335
- }
336
- if (response.status === 401 || response.status === 403) {
337
- throw new RegistryAuthError(registryUrl);
338
- }
339
- if (!response.ok) {
340
- throw new RegistryConnectionError(registryUrl);
341
- }
342
- const body = await response.json();
343
- return body.items;
344
- }
345
- function mergeResults(localCards, remoteCards, hasQuery) {
346
- const taggedLocal = localCards.map((c) => ({ ...c, source: "local" }));
347
- const taggedRemote = remoteCards.map((c) => ({ ...c, source: "remote" }));
348
- const localIds = new Set(localCards.map((c) => c.id));
349
- const dedupedRemote = taggedRemote.filter((c) => !localIds.has(c.id));
350
- if (!hasQuery) {
351
- return [...taggedLocal, ...dedupedRemote];
352
- }
353
- const result = [];
354
- const maxLen = Math.max(taggedLocal.length, dedupedRemote.length);
355
- for (let i = 0; i < maxLen; i++) {
356
- if (i < taggedLocal.length) result.push(taggedLocal[i]);
357
- if (i < dedupedRemote.length) result.push(dedupedRemote[i]);
358
- }
359
- return result;
360
- }
361
-
362
220
  // src/cli/onboarding.ts
363
221
  import { randomUUID as randomUUID2 } from "crypto";
364
222
  import { createConnection } from "net";
@@ -530,8 +388,8 @@ function buildDraftCard(apiKey, owner) {
530
388
 
531
389
  // src/onboarding/index.ts
532
390
  import { randomUUID as randomUUID3 } from "crypto";
533
- import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
534
- import { join as join2 } from "path";
391
+ import { existsSync, readFileSync } from "fs";
392
+ import { join } from "path";
535
393
 
536
394
  // src/onboarding/capability-templates.ts
537
395
  var API_PATTERNS = [
@@ -646,9 +504,9 @@ var DOC_FILES = ["SOUL.md", "CLAUDE.md", "AGENTS.md", "README.md"];
646
504
  function detectCapabilities(opts = {}) {
647
505
  const cwd = opts.cwd ?? process.cwd();
648
506
  if (opts.fromFile) {
649
- const filePath = opts.fromFile.startsWith("/") ? opts.fromFile : join2(cwd, opts.fromFile);
650
- if (existsSync2(filePath)) {
651
- const content = readFileSync2(filePath, "utf-8");
507
+ const filePath = opts.fromFile.startsWith("/") ? opts.fromFile : join(cwd, opts.fromFile);
508
+ if (existsSync(filePath)) {
509
+ const content = readFileSync(filePath, "utf-8");
652
510
  const capabilities = detectFromDocs(content);
653
511
  if (capabilities.length > 0) {
654
512
  return { source: "docs", capabilities, sourceFile: filePath };
@@ -657,9 +515,9 @@ function detectCapabilities(opts = {}) {
657
515
  return { source: "none", capabilities: [] };
658
516
  }
659
517
  for (const fileName of DOC_FILES) {
660
- const filePath = join2(cwd, fileName);
661
- if (!existsSync2(filePath)) continue;
662
- const content = readFileSync2(filePath, "utf-8");
518
+ const filePath = join(cwd, fileName);
519
+ if (!existsSync(filePath)) continue;
520
+ const content = readFileSync(filePath, "utf-8");
663
521
  if (fileName === "SOUL.md") {
664
522
  return { source: "soul", capabilities: [], soulContent: content, sourceFile: filePath };
665
523
  }
@@ -703,8 +561,49 @@ function capabilitiesToV2Card(capabilities, owner, agentName) {
703
561
  return CapabilityCardV2Schema.parse(card);
704
562
  }
705
563
 
564
+ // src/registry/pricing.ts
565
+ function getPricingStats(db, query) {
566
+ const cards = searchCards(db, query);
567
+ const prices = [];
568
+ const queryLower = query.toLowerCase();
569
+ const queryWords = queryLower.split(/\s+/).filter((w) => w.length > 0);
570
+ for (const card of cards) {
571
+ const v2 = card;
572
+ if (v2.skills && v2.skills.length > 0) {
573
+ for (const skill of v2.skills) {
574
+ const nameMatch = skillMatchesQuery(skill, queryWords);
575
+ if (nameMatch) {
576
+ prices.push(skill.pricing.credits_per_call);
577
+ }
578
+ }
579
+ } else {
580
+ prices.push(card.pricing.credits_per_call);
581
+ }
582
+ }
583
+ if (prices.length === 0) {
584
+ return { min: 0, max: 0, median: 0, mean: 0, count: 0 };
585
+ }
586
+ prices.sort((a, b) => a - b);
587
+ const min = prices[0];
588
+ const max = prices[prices.length - 1];
589
+ const mean = prices.reduce((sum, p) => sum + p, 0) / prices.length;
590
+ const median = computeMedian(prices);
591
+ return { min, max, median, mean, count: prices.length };
592
+ }
593
+ function skillMatchesQuery(skill, queryWords) {
594
+ const text = `${skill.name} ${skill.description}`.toLowerCase();
595
+ return queryWords.some((word) => text.includes(word));
596
+ }
597
+ function computeMedian(sorted) {
598
+ const mid = Math.floor(sorted.length / 2);
599
+ if (sorted.length % 2 === 1) {
600
+ return sorted[mid];
601
+ }
602
+ return (sorted[mid - 1] + sorted[mid]) / 2;
603
+ }
604
+
706
605
  // src/runtime/agent-runtime.ts
707
- import { readFileSync as readFileSync3, existsSync as existsSync3 } from "fs";
606
+ import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
708
607
 
709
608
  // src/skills/executor.ts
710
609
  var SkillExecutor = class {
@@ -731,7 +630,7 @@ var SkillExecutor = class {
731
630
  * @param params - Input parameters for the skill.
732
631
  * @returns ExecutionResult including success, result/error, and latency_ms.
733
632
  */
734
- async execute(skillId, params) {
633
+ async execute(skillId, params, onProgress) {
735
634
  const startTime = Date.now();
736
635
  const config = this.skillMap.get(skillId);
737
636
  if (!config) {
@@ -750,7 +649,7 @@ var SkillExecutor = class {
750
649
  };
751
650
  }
752
651
  try {
753
- const modeResult = await mode.execute(config, params);
652
+ const modeResult = await mode.execute(config, params, onProgress);
754
653
  return {
755
654
  ...modeResult,
756
655
  latency_ms: Date.now() - startTime
@@ -787,98 +686,98 @@ function createSkillExecutor(configs, modes) {
787
686
  }
788
687
 
789
688
  // src/skills/skill-config.ts
790
- import { z as z3 } from "zod";
689
+ import { z as z2 } from "zod";
791
690
  import yaml from "js-yaml";
792
- var PricingSchema = z3.object({
793
- credits_per_call: z3.number().nonnegative(),
794
- credits_per_minute: z3.number().nonnegative().optional(),
795
- free_tier: z3.number().nonnegative().optional()
691
+ var PricingSchema = z2.object({
692
+ credits_per_call: z2.number().nonnegative(),
693
+ credits_per_minute: z2.number().nonnegative().optional(),
694
+ free_tier: z2.number().nonnegative().optional()
796
695
  });
797
- var ApiAuthSchema = z3.discriminatedUnion("type", [
798
- z3.object({
799
- type: z3.literal("bearer"),
800
- token: z3.string()
696
+ var ApiAuthSchema = z2.discriminatedUnion("type", [
697
+ z2.object({
698
+ type: z2.literal("bearer"),
699
+ token: z2.string()
801
700
  }),
802
- z3.object({
803
- type: z3.literal("apikey"),
804
- header: z3.string().default("X-API-Key"),
805
- key: z3.string()
701
+ z2.object({
702
+ type: z2.literal("apikey"),
703
+ header: z2.string().default("X-API-Key"),
704
+ key: z2.string()
806
705
  }),
807
- z3.object({
808
- type: z3.literal("basic"),
809
- username: z3.string(),
810
- password: z3.string()
706
+ z2.object({
707
+ type: z2.literal("basic"),
708
+ username: z2.string(),
709
+ password: z2.string()
811
710
  })
812
711
  ]);
813
- var ApiSkillConfigSchema = z3.object({
814
- id: z3.string().min(1),
815
- type: z3.literal("api"),
816
- name: z3.string().min(1),
817
- endpoint: z3.string().min(1),
818
- method: z3.enum(["GET", "POST", "PUT", "DELETE"]),
712
+ var ApiSkillConfigSchema = z2.object({
713
+ id: z2.string().min(1),
714
+ type: z2.literal("api"),
715
+ name: z2.string().min(1),
716
+ endpoint: z2.string().min(1),
717
+ method: z2.enum(["GET", "POST", "PUT", "DELETE"]),
819
718
  auth: ApiAuthSchema.optional(),
820
- input_mapping: z3.record(z3.string()).default({}),
821
- output_mapping: z3.record(z3.string()).default({}),
719
+ input_mapping: z2.record(z2.string()).default({}),
720
+ output_mapping: z2.record(z2.string()).default({}),
822
721
  pricing: PricingSchema,
823
- timeout_ms: z3.number().positive().default(3e4),
824
- retries: z3.number().nonnegative().int().default(0),
825
- provider: z3.string().optional()
722
+ timeout_ms: z2.number().positive().default(3e4),
723
+ retries: z2.number().nonnegative().int().default(0),
724
+ provider: z2.string().optional()
826
725
  });
827
- var PipelineStepSchema = z3.union([
828
- z3.object({
829
- skill_id: z3.string().min(1),
830
- input_mapping: z3.record(z3.string()).default({})
726
+ var PipelineStepSchema = z2.union([
727
+ z2.object({
728
+ skill_id: z2.string().min(1),
729
+ input_mapping: z2.record(z2.string()).default({})
831
730
  }),
832
- z3.object({
833
- command: z3.string().min(1),
834
- input_mapping: z3.record(z3.string()).default({})
731
+ z2.object({
732
+ command: z2.string().min(1),
733
+ input_mapping: z2.record(z2.string()).default({})
835
734
  })
836
735
  ]);
837
- var PipelineSkillConfigSchema = z3.object({
838
- id: z3.string().min(1),
839
- type: z3.literal("pipeline"),
840
- name: z3.string().min(1),
841
- steps: z3.array(PipelineStepSchema).min(1),
736
+ var PipelineSkillConfigSchema = z2.object({
737
+ id: z2.string().min(1),
738
+ type: z2.literal("pipeline"),
739
+ name: z2.string().min(1),
740
+ steps: z2.array(PipelineStepSchema).min(1),
842
741
  pricing: PricingSchema,
843
- timeout_ms: z3.number().positive().optional()
742
+ timeout_ms: z2.number().positive().optional()
844
743
  });
845
- var OpenClawSkillConfigSchema = z3.object({
846
- id: z3.string().min(1),
847
- type: z3.literal("openclaw"),
848
- name: z3.string().min(1),
849
- agent_name: z3.string().min(1),
850
- channel: z3.enum(["telegram", "webhook", "process"]),
744
+ var OpenClawSkillConfigSchema = z2.object({
745
+ id: z2.string().min(1),
746
+ type: z2.literal("openclaw"),
747
+ name: z2.string().min(1),
748
+ agent_name: z2.string().min(1),
749
+ channel: z2.enum(["telegram", "webhook", "process"]),
851
750
  pricing: PricingSchema,
852
- timeout_ms: z3.number().positive().optional()
751
+ timeout_ms: z2.number().positive().optional()
853
752
  });
854
- var CommandSkillConfigSchema = z3.object({
855
- id: z3.string().min(1),
856
- type: z3.literal("command"),
857
- name: z3.string().min(1),
858
- command: z3.string().min(1),
859
- output_type: z3.enum(["json", "text", "file"]),
860
- allowed_commands: z3.array(z3.string()).optional(),
861
- working_dir: z3.string().optional(),
862
- timeout_ms: z3.number().positive().default(3e4),
753
+ var CommandSkillConfigSchema = z2.object({
754
+ id: z2.string().min(1),
755
+ type: z2.literal("command"),
756
+ name: z2.string().min(1),
757
+ command: z2.string().min(1),
758
+ output_type: z2.enum(["json", "text", "file"]),
759
+ allowed_commands: z2.array(z2.string()).optional(),
760
+ working_dir: z2.string().optional(),
761
+ timeout_ms: z2.number().positive().default(3e4),
863
762
  pricing: PricingSchema
864
763
  });
865
- var ConductorSkillConfigSchema = z3.object({
866
- id: z3.string().min(1),
867
- type: z3.literal("conductor"),
868
- name: z3.string().min(1),
869
- conductor_skill: z3.enum(["orchestrate", "plan"]),
764
+ var ConductorSkillConfigSchema = z2.object({
765
+ id: z2.string().min(1),
766
+ type: z2.literal("conductor"),
767
+ name: z2.string().min(1),
768
+ conductor_skill: z2.enum(["orchestrate", "plan"]),
870
769
  pricing: PricingSchema,
871
- timeout_ms: z3.number().positive().optional()
770
+ timeout_ms: z2.number().positive().optional()
872
771
  });
873
- var SkillConfigSchema = z3.discriminatedUnion("type", [
772
+ var SkillConfigSchema = z2.discriminatedUnion("type", [
874
773
  ApiSkillConfigSchema,
875
774
  PipelineSkillConfigSchema,
876
775
  OpenClawSkillConfigSchema,
877
776
  CommandSkillConfigSchema,
878
777
  ConductorSkillConfigSchema
879
778
  ]);
880
- var SkillsFileSchema = z3.object({
881
- skills: z3.array(SkillConfigSchema)
779
+ var SkillsFileSchema = z2.object({
780
+ skills: z2.array(SkillConfigSchema)
882
781
  });
883
782
  function expandEnvVars(value) {
884
783
  return value.replace(/\$\{([^}]+)\}/g, (_match, varName) => {
@@ -1121,7 +1020,7 @@ var PipelineExecutor = class {
1121
1020
  * @param params - Input parameters from the caller.
1122
1021
  * @returns Partial ExecutionResult (without latency_ms — added by SkillExecutor wrapper).
1123
1022
  */
1124
- async execute(config, params) {
1023
+ async execute(config, params, onProgress) {
1125
1024
  const pipelineConfig = config;
1126
1025
  const steps = pipelineConfig.steps ?? [];
1127
1026
  if (steps.length === 0) {
@@ -1180,6 +1079,13 @@ var PipelineExecutor = class {
1180
1079
  }
1181
1080
  context.steps.push({ result: stepResult });
1182
1081
  context.prev = { result: stepResult };
1082
+ if (onProgress && i < steps.length - 1) {
1083
+ onProgress({
1084
+ step: i + 1,
1085
+ total: steps.length,
1086
+ message: `Completed step ${i + 1}/${steps.length}`
1087
+ });
1088
+ }
1183
1089
  }
1184
1090
  const lastStep = context.steps[context.steps.length - 1];
1185
1091
  return {
@@ -1515,20 +1421,20 @@ var AgentRuntime = class {
1515
1421
  * 3. Populate the Map with all 4 modes — SkillExecutor sees them via reference.
1516
1422
  */
1517
1423
  async initSkillExecutor() {
1518
- const hasSkillsYaml = this.skillsYamlPath && existsSync3(this.skillsYamlPath);
1424
+ const hasSkillsYaml = this.skillsYamlPath && existsSync2(this.skillsYamlPath);
1519
1425
  if (!hasSkillsYaml && !this.conductorEnabled) {
1520
1426
  return;
1521
1427
  }
1522
1428
  let configs = [];
1523
1429
  if (hasSkillsYaml) {
1524
- const yamlContent = readFileSync3(this.skillsYamlPath, "utf8");
1430
+ const yamlContent = readFileSync2(this.skillsYamlPath, "utf8");
1525
1431
  configs = parseSkillsFile(yamlContent);
1526
1432
  }
1527
1433
  const modes = /* @__PURE__ */ new Map();
1528
1434
  if (this.conductorEnabled) {
1529
- const { ConductorMode } = await import("../conductor-mode-IO45PWMI.js");
1530
- const { registerConductorCard, CONDUCTOR_OWNER } = await import("../card-IE5UV5QX.js");
1531
- const { loadPeers: loadPeers2 } = await import("../peers-G36URZYB.js");
1435
+ const { ConductorMode } = await import("../conductor-mode-3JS4VWCR.js");
1436
+ const { registerConductorCard, CONDUCTOR_OWNER } = await import("../card-4XH4AOTE.js");
1437
+ const { loadPeers: loadPeers2 } = await import("../peers-K7FSHPN3.js");
1532
1438
  registerConductorCard(this.registryDb);
1533
1439
  const resolveAgentUrl = (owner) => {
1534
1440
  const peers = loadPeers2();
@@ -1640,7 +1546,7 @@ function createGatewayServer(opts) {
1640
1546
  creditDb,
1641
1547
  tokens,
1642
1548
  handlerUrl,
1643
- timeoutMs = 3e4,
1549
+ timeoutMs = 3e5,
1644
1550
  silent = false,
1645
1551
  skillExecutor
1646
1552
  } = opts;
@@ -1735,21 +1641,365 @@ function createGatewayServer(opts) {
1735
1641
  // src/registry/server.ts
1736
1642
  import Fastify2 from "fastify";
1737
1643
  import cors from "@fastify/cors";
1644
+ import swagger from "@fastify/swagger";
1645
+ import swaggerUi from "@fastify/swagger-ui";
1738
1646
  import fastifyStatic from "@fastify/static";
1739
1647
  import fastifyWebsocket from "@fastify/websocket";
1740
- import { join as join3, dirname } from "path";
1648
+ import { join as join2, dirname } from "path";
1741
1649
  import { fileURLToPath } from "url";
1742
- import { existsSync as existsSync4 } from "fs";
1650
+ import { existsSync as existsSync3 } from "fs";
1743
1651
 
1744
1652
  // src/relay/websocket-relay.ts
1653
+ import { randomUUID as randomUUID6 } from "crypto";
1654
+
1655
+ // src/relay/relay-credit.ts
1656
+ function lookupCardPrice(registryDb, cardId, skillId) {
1657
+ const row = registryDb.prepare("SELECT data FROM capability_cards WHERE id = ?").get(cardId);
1658
+ if (!row) return null;
1659
+ let card;
1660
+ try {
1661
+ card = JSON.parse(row.data);
1662
+ } catch {
1663
+ return null;
1664
+ }
1665
+ if (skillId && Array.isArray(card.skills)) {
1666
+ const skills = card.skills;
1667
+ const skill = skills.find((s) => s.id === skillId);
1668
+ if (skill) {
1669
+ const skillPricing = skill.pricing;
1670
+ if (skillPricing && typeof skillPricing.credits_per_call === "number") {
1671
+ return skillPricing.credits_per_call;
1672
+ }
1673
+ }
1674
+ }
1675
+ const pricing = card.pricing;
1676
+ if (!pricing || typeof pricing.credits_per_call !== "number") {
1677
+ return null;
1678
+ }
1679
+ return pricing.credits_per_call;
1680
+ }
1681
+ function holdForRelay(creditDb, owner, amount, cardId) {
1682
+ return holdEscrow(creditDb, owner, amount, cardId);
1683
+ }
1684
+ function settleForRelay(creditDb, escrowId, recipientOwner) {
1685
+ settleEscrow(creditDb, escrowId, recipientOwner);
1686
+ }
1687
+ function calculateConductorFee(totalSubTaskCost) {
1688
+ if (totalSubTaskCost <= 0) return 0;
1689
+ const fee = Math.ceil(totalSubTaskCost * 0.1);
1690
+ return Math.max(1, Math.min(20, fee));
1691
+ }
1692
+ function releaseForRelay(creditDb, escrowId) {
1693
+ if (escrowId === void 0) return;
1694
+ releaseEscrow(creditDb, escrowId);
1695
+ }
1696
+
1697
+ // src/hub-agent/relay-bridge.ts
1698
+ import { randomUUID as randomUUID5 } from "crypto";
1699
+
1700
+ // src/hub-agent/job-queue.ts
1745
1701
  import { randomUUID as randomUUID4 } from "crypto";
1702
+ function initJobQueue(db) {
1703
+ db.exec(`
1704
+ CREATE TABLE IF NOT EXISTS hub_agent_jobs (
1705
+ id TEXT PRIMARY KEY,
1706
+ hub_agent_id TEXT NOT NULL,
1707
+ skill_id TEXT NOT NULL,
1708
+ requester_owner TEXT NOT NULL,
1709
+ params TEXT,
1710
+ status TEXT NOT NULL DEFAULT 'queued',
1711
+ result TEXT,
1712
+ escrow_id TEXT,
1713
+ relay_owner TEXT,
1714
+ created_at TEXT NOT NULL,
1715
+ updated_at TEXT NOT NULL
1716
+ )
1717
+ `);
1718
+ }
1719
+ function insertJob(db, input) {
1720
+ const id = randomUUID4();
1721
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1722
+ const paramsJson = JSON.stringify(input.params);
1723
+ db.prepare(`
1724
+ INSERT INTO hub_agent_jobs (id, hub_agent_id, skill_id, requester_owner, params, status, escrow_id, relay_owner, created_at, updated_at)
1725
+ VALUES (?, ?, ?, ?, ?, 'queued', ?, ?, ?, ?)
1726
+ `).run(
1727
+ id,
1728
+ input.hub_agent_id,
1729
+ input.skill_id,
1730
+ input.requester_owner,
1731
+ paramsJson,
1732
+ input.escrow_id ?? null,
1733
+ input.relay_owner ?? null,
1734
+ now,
1735
+ now
1736
+ );
1737
+ return {
1738
+ id,
1739
+ hub_agent_id: input.hub_agent_id,
1740
+ skill_id: input.skill_id,
1741
+ requester_owner: input.requester_owner,
1742
+ params: paramsJson,
1743
+ status: "queued",
1744
+ result: null,
1745
+ escrow_id: input.escrow_id ?? null,
1746
+ relay_owner: input.relay_owner ?? null,
1747
+ created_at: now,
1748
+ updated_at: now
1749
+ };
1750
+ }
1751
+ function getJob(db, jobId) {
1752
+ const row = db.prepare("SELECT * FROM hub_agent_jobs WHERE id = ?").get(jobId);
1753
+ return row ?? null;
1754
+ }
1755
+ function listJobs(db, hubAgentId, status) {
1756
+ if (status) {
1757
+ return db.prepare(
1758
+ "SELECT * FROM hub_agent_jobs WHERE hub_agent_id = ? AND status = ? ORDER BY created_at DESC"
1759
+ ).all(hubAgentId, status);
1760
+ }
1761
+ return db.prepare(
1762
+ "SELECT * FROM hub_agent_jobs WHERE hub_agent_id = ? ORDER BY created_at DESC"
1763
+ ).all(hubAgentId);
1764
+ }
1765
+ function updateJobStatus(db, jobId, status, result) {
1766
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1767
+ if (result !== void 0) {
1768
+ db.prepare("UPDATE hub_agent_jobs SET status = ?, result = ?, updated_at = ? WHERE id = ?").run(status, result, now, jobId);
1769
+ } else {
1770
+ db.prepare("UPDATE hub_agent_jobs SET status = ?, updated_at = ? WHERE id = ?").run(status, now, jobId);
1771
+ }
1772
+ }
1773
+ function getJobsByRelayOwner(db, relayOwner) {
1774
+ return db.prepare(
1775
+ "SELECT * FROM hub_agent_jobs WHERE relay_owner = ? AND status = ? ORDER BY created_at ASC"
1776
+ ).all(relayOwner, "queued");
1777
+ }
1778
+
1779
+ // src/hub-agent/crypto.ts
1780
+ import { createCipheriv, createDecipheriv, randomBytes } from "crypto";
1781
+ function getMasterKey() {
1782
+ const hex = process.env.HUB_MASTER_KEY;
1783
+ if (!hex || hex.length !== 64) {
1784
+ throw new AgentBnBError(
1785
+ "HUB_MASTER_KEY must be a 64-character hex string (32 bytes)",
1786
+ "MISSING_MASTER_KEY"
1787
+ );
1788
+ }
1789
+ return Buffer.from(hex, "hex");
1790
+ }
1791
+ function encrypt(plaintext, masterKey) {
1792
+ const iv = randomBytes(12);
1793
+ const cipher = createCipheriv("aes-256-gcm", masterKey, iv);
1794
+ const encrypted = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
1795
+ const authTag = cipher.getAuthTag();
1796
+ return `${iv.toString("hex")}:${authTag.toString("hex")}:${encrypted.toString("hex")}`;
1797
+ }
1798
+ function decrypt(encrypted, masterKey) {
1799
+ const [ivHex, authTagHex, ciphertextHex] = encrypted.split(":");
1800
+ const iv = Buffer.from(ivHex, "hex");
1801
+ const authTag = Buffer.from(authTagHex, "hex");
1802
+ const ciphertext = Buffer.from(ciphertextHex, "hex");
1803
+ const decipher = createDecipheriv("aes-256-gcm", masterKey, iv);
1804
+ decipher.setAuthTag(authTag);
1805
+ const decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
1806
+ return decrypted.toString("utf8");
1807
+ }
1808
+
1809
+ // src/hub-agent/store.ts
1810
+ function initHubAgentTable(db) {
1811
+ db.exec(`
1812
+ CREATE TABLE IF NOT EXISTS hub_agents (
1813
+ agent_id TEXT PRIMARY KEY,
1814
+ name TEXT NOT NULL,
1815
+ owner_public_key TEXT NOT NULL,
1816
+ public_key TEXT NOT NULL,
1817
+ private_key_enc TEXT NOT NULL,
1818
+ secrets_enc TEXT,
1819
+ skill_routes TEXT NOT NULL DEFAULT '[]',
1820
+ status TEXT NOT NULL DEFAULT 'active',
1821
+ created_at TEXT NOT NULL,
1822
+ updated_at TEXT NOT NULL
1823
+ )
1824
+ `);
1825
+ }
1826
+ function createHubAgent(db, req, ownerPublicKey) {
1827
+ const masterKey = getMasterKey();
1828
+ const keys = generateKeyPair();
1829
+ const publicKeyHex = keys.publicKey.toString("hex");
1830
+ const agentId = deriveAgentId(publicKeyHex);
1831
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1832
+ const privateKeyEnc = encrypt(keys.privateKey.toString("hex"), masterKey);
1833
+ const secretsEnc = req.secrets ? encrypt(JSON.stringify(req.secrets), masterKey) : null;
1834
+ db.prepare(`
1835
+ INSERT INTO hub_agents (agent_id, name, owner_public_key, public_key, private_key_enc, secrets_enc, skill_routes, status, created_at, updated_at)
1836
+ VALUES (?, ?, ?, ?, ?, ?, ?, 'active', ?, ?)
1837
+ `).run(
1838
+ agentId,
1839
+ req.name,
1840
+ ownerPublicKey,
1841
+ publicKeyHex,
1842
+ privateKeyEnc,
1843
+ secretsEnc,
1844
+ JSON.stringify(req.skill_routes),
1845
+ now,
1846
+ now
1847
+ );
1848
+ return {
1849
+ agent_id: agentId,
1850
+ name: req.name,
1851
+ owner_public_key: ownerPublicKey,
1852
+ public_key: publicKeyHex,
1853
+ skill_routes: req.skill_routes,
1854
+ status: "active",
1855
+ created_at: now,
1856
+ updated_at: now
1857
+ };
1858
+ }
1859
+ function getHubAgent(db, agentId) {
1860
+ const row = db.prepare("SELECT * FROM hub_agents WHERE agent_id = ?").get(agentId);
1861
+ if (!row) return null;
1862
+ const masterKey = getMasterKey();
1863
+ const secrets = row.secrets_enc ? JSON.parse(decrypt(row.secrets_enc, masterKey)) : void 0;
1864
+ return {
1865
+ agent_id: row.agent_id,
1866
+ name: row.name,
1867
+ owner_public_key: row.owner_public_key,
1868
+ public_key: row.public_key,
1869
+ skill_routes: JSON.parse(row.skill_routes),
1870
+ status: row.status,
1871
+ created_at: row.created_at,
1872
+ updated_at: row.updated_at,
1873
+ ...secrets ? { secrets } : {}
1874
+ };
1875
+ }
1876
+ function listHubAgents(db) {
1877
+ const rows = db.prepare("SELECT agent_id, name, owner_public_key, public_key, skill_routes, status, created_at, updated_at FROM hub_agents ORDER BY created_at DESC").all();
1878
+ return rows.map((row) => ({
1879
+ agent_id: row.agent_id,
1880
+ name: row.name,
1881
+ owner_public_key: row.owner_public_key,
1882
+ public_key: row.public_key,
1883
+ skill_routes: JSON.parse(row.skill_routes),
1884
+ status: row.status,
1885
+ created_at: row.created_at,
1886
+ updated_at: row.updated_at
1887
+ }));
1888
+ }
1889
+ function updateHubAgent(db, agentId, updates) {
1890
+ const existing = db.prepare("SELECT * FROM hub_agents WHERE agent_id = ?").get(agentId);
1891
+ if (!existing) return null;
1892
+ const masterKey = getMasterKey();
1893
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1894
+ const newName = updates.name ?? existing.name;
1895
+ const newSkillRoutes = updates.skill_routes ? JSON.stringify(updates.skill_routes) : existing.skill_routes;
1896
+ const newSecretsEnc = updates.secrets !== void 0 ? encrypt(JSON.stringify(updates.secrets), masterKey) : existing.secrets_enc;
1897
+ db.prepare(`
1898
+ UPDATE hub_agents SET name = ?, skill_routes = ?, secrets_enc = ?, updated_at = ?
1899
+ WHERE agent_id = ?
1900
+ `).run(newName, newSkillRoutes, newSecretsEnc, now, agentId);
1901
+ const secrets = newSecretsEnc ? JSON.parse(decrypt(newSecretsEnc, masterKey)) : void 0;
1902
+ return {
1903
+ agent_id: existing.agent_id,
1904
+ name: newName,
1905
+ owner_public_key: existing.owner_public_key,
1906
+ public_key: existing.public_key,
1907
+ skill_routes: JSON.parse(newSkillRoutes),
1908
+ status: existing.status,
1909
+ created_at: existing.created_at,
1910
+ updated_at: now,
1911
+ ...secrets ? { secrets } : {}
1912
+ };
1913
+ }
1914
+ function deleteHubAgent(db, agentId) {
1915
+ const result = db.prepare("DELETE FROM hub_agents WHERE agent_id = ?").run(agentId);
1916
+ return result.changes > 0;
1917
+ }
1918
+
1919
+ // src/hub-agent/relay-bridge.ts
1920
+ var JOB_DISPATCH_TIMEOUT_MS = 3e5;
1921
+ function createRelayBridge(opts) {
1922
+ const { registryDb, creditDb, sendMessage, pendingRequests, connections } = opts;
1923
+ function onAgentOnline(owner) {
1924
+ const jobs = getJobsByRelayOwner(registryDb, owner);
1925
+ if (jobs.length === 0) return;
1926
+ const targetWs = connections.get(owner);
1927
+ if (!targetWs) return;
1928
+ for (const job of jobs) {
1929
+ updateJobStatus(registryDb, job.id, "dispatched");
1930
+ const agent = getHubAgent(registryDb, job.hub_agent_id);
1931
+ const cardId = agent ? agent.agent_id.padEnd(32, "0").replace(/^(.{8})(.{4})(.{4})(.{4})(.{12}).*$/, "$1-$2-$3-$4-$5") : job.hub_agent_id;
1932
+ const requestId = randomUUID5();
1933
+ let params = {};
1934
+ try {
1935
+ params = JSON.parse(job.params);
1936
+ } catch {
1937
+ }
1938
+ const timeout = setTimeout(() => {
1939
+ const pending = pendingRequests.get(requestId);
1940
+ if (pending) {
1941
+ pendingRequests.delete(requestId);
1942
+ updateJobStatus(registryDb, job.id, "failed", JSON.stringify({ error: "dispatch timeout" }));
1943
+ if (job.escrow_id) {
1944
+ try {
1945
+ releaseForRelay(creditDb, job.escrow_id);
1946
+ } catch (e) {
1947
+ console.error("[relay-bridge] escrow release on timeout failed:", e);
1948
+ }
1949
+ }
1950
+ }
1951
+ }, JOB_DISPATCH_TIMEOUT_MS);
1952
+ pendingRequests.set(requestId, {
1953
+ originOwner: job.requester_owner,
1954
+ timeout,
1955
+ escrowId: job.escrow_id ?? void 0,
1956
+ targetOwner: owner,
1957
+ jobId: job.id
1958
+ });
1959
+ sendMessage(targetWs, {
1960
+ type: "incoming_request",
1961
+ id: requestId,
1962
+ from_owner: job.requester_owner,
1963
+ card_id: cardId,
1964
+ skill_id: job.skill_id,
1965
+ params
1966
+ });
1967
+ }
1968
+ }
1969
+ return { onAgentOnline };
1970
+ }
1971
+ function handleJobRelayResponse(opts) {
1972
+ const { registryDb, creditDb, jobId, escrowId, relayOwner, result, error } = opts;
1973
+ if (error) {
1974
+ updateJobStatus(registryDb, jobId, "failed", JSON.stringify(error));
1975
+ if (escrowId) {
1976
+ try {
1977
+ releaseForRelay(creditDb, escrowId);
1978
+ } catch (e) {
1979
+ console.error("[relay-bridge] escrow release on error failed:", e);
1980
+ }
1981
+ }
1982
+ } else {
1983
+ updateJobStatus(registryDb, jobId, "completed", JSON.stringify(result));
1984
+ if (escrowId) {
1985
+ try {
1986
+ settleForRelay(creditDb, escrowId, relayOwner);
1987
+ } catch (e) {
1988
+ console.error("[relay-bridge] escrow settle failed:", e);
1989
+ }
1990
+ }
1991
+ }
1992
+ }
1993
+
1994
+ // src/relay/websocket-relay.ts
1746
1995
  var RATE_LIMIT_MAX = 60;
1747
1996
  var RATE_LIMIT_WINDOW_MS = 6e4;
1748
- var RELAY_TIMEOUT_MS = 3e4;
1749
- function registerWebSocketRelay(server, db) {
1997
+ var RELAY_TIMEOUT_MS = 3e5;
1998
+ function registerWebSocketRelay(server, db, creditDb) {
1750
1999
  const connections = /* @__PURE__ */ new Map();
1751
2000
  const pendingRequests = /* @__PURE__ */ new Map();
1752
2001
  const rateLimits = /* @__PURE__ */ new Map();
2002
+ let onAgentOnlineCallback;
1753
2003
  function checkRateLimit(owner) {
1754
2004
  const now = Date.now();
1755
2005
  const entry = rateLimits.get(owner);
@@ -1800,21 +2050,32 @@ function registerWebSocketRelay(server, db) {
1800
2050
  }
1801
2051
  }
1802
2052
  function upsertCard(cardData, owner) {
1803
- const cardId = cardData.id;
1804
- const existing = getCard(db, cardId);
2053
+ const parsed = AnyCardSchema.safeParse(cardData);
2054
+ if (!parsed.success) {
2055
+ throw new AgentBnBError(
2056
+ `Card validation failed: ${parsed.error.message}`,
2057
+ "VALIDATION_ERROR"
2058
+ );
2059
+ }
2060
+ const card = { ...parsed.data, availability: { ...parsed.data.availability, online: true } };
2061
+ const cardId = card.id;
2062
+ const now = (/* @__PURE__ */ new Date()).toISOString();
2063
+ const existing = db.prepare("SELECT id FROM capability_cards WHERE id = ?").get(cardId);
1805
2064
  if (existing) {
1806
- const updates = { ...cardData, availability: { ...cardData.availability ?? {}, online: true } };
1807
- updateCard(db, cardId, owner, updates);
2065
+ db.prepare(
2066
+ "UPDATE capability_cards SET data = ?, updated_at = ? WHERE id = ?"
2067
+ ).run(JSON.stringify(card), now, cardId);
1808
2068
  } else {
1809
- const card = { ...cardData, availability: { ...cardData.availability ?? {}, online: true } };
1810
- insertCard(db, card);
2069
+ db.prepare(
2070
+ "INSERT INTO capability_cards (id, owner, data, created_at, updated_at) VALUES (?, ?, ?, ?, ?)"
2071
+ ).run(cardId, owner, JSON.stringify(card), now, now);
1811
2072
  }
1812
2073
  return cardId;
1813
2074
  }
1814
2075
  function logAgentJoined(owner, cardName, cardId) {
1815
2076
  try {
1816
2077
  insertRequestLog(db, {
1817
- id: randomUUID4(),
2078
+ id: randomUUID6(),
1818
2079
  card_id: cardId,
1819
2080
  card_name: cardName,
1820
2081
  requester: owner,
@@ -1845,10 +2106,25 @@ function registerWebSocketRelay(server, db) {
1845
2106
  const cardId = upsertCard(card, owner);
1846
2107
  const cardName = card.name ?? card.agent_name ?? owner;
1847
2108
  logAgentJoined(owner, cardName, cardId);
2109
+ if (msg.cards && msg.cards.length > 0) {
2110
+ for (const extraCard of msg.cards) {
2111
+ try {
2112
+ upsertCard(extraCard, owner);
2113
+ } catch {
2114
+ }
2115
+ }
2116
+ }
1848
2117
  markOwnerOnline(owner);
2118
+ if (onAgentOnlineCallback) {
2119
+ try {
2120
+ onAgentOnlineCallback(owner);
2121
+ } catch (e) {
2122
+ console.error("[relay] onAgentOnline callback error:", e);
2123
+ }
2124
+ }
1849
2125
  sendMessage(ws, { type: "registered", agent_id: cardId });
1850
2126
  }
1851
- function handleRelayRequest(ws, msg, fromOwner) {
2127
+ async function handleRelayRequest(ws, msg, fromOwner) {
1852
2128
  if (!checkRateLimit(fromOwner)) {
1853
2129
  sendMessage(ws, {
1854
2130
  type: "error",
@@ -1867,15 +2143,42 @@ function registerWebSocketRelay(server, db) {
1867
2143
  });
1868
2144
  return;
1869
2145
  }
2146
+ let escrowId;
2147
+ if (creditDb) {
2148
+ try {
2149
+ const price = lookupCardPrice(db, msg.card_id, msg.skill_id);
2150
+ if (price !== null && price > 0) {
2151
+ escrowId = holdForRelay(creditDb, fromOwner, price, msg.card_id);
2152
+ }
2153
+ } catch (err) {
2154
+ if (err instanceof AgentBnBError && err.code === "INSUFFICIENT_CREDITS") {
2155
+ sendMessage(ws, {
2156
+ type: "response",
2157
+ id: msg.id,
2158
+ error: { code: -32603, message: "Insufficient credits" }
2159
+ });
2160
+ return;
2161
+ }
2162
+ console.error("[relay] credit hold error (non-fatal):", err);
2163
+ }
2164
+ }
1870
2165
  const timeout = setTimeout(() => {
2166
+ const pending = pendingRequests.get(msg.id);
1871
2167
  pendingRequests.delete(msg.id);
2168
+ if (pending?.escrowId && creditDb) {
2169
+ try {
2170
+ releaseForRelay(creditDb, pending.escrowId);
2171
+ } catch (e) {
2172
+ console.error("[relay] escrow release on timeout failed:", e);
2173
+ }
2174
+ }
1872
2175
  sendMessage(ws, {
1873
2176
  type: "response",
1874
2177
  id: msg.id,
1875
2178
  error: { code: -32603, message: "Relay request timeout" }
1876
2179
  });
1877
2180
  }, RELAY_TIMEOUT_MS);
1878
- pendingRequests.set(msg.id, { originOwner: fromOwner, timeout });
2181
+ pendingRequests.set(msg.id, { originOwner: fromOwner, timeout, escrowId, targetOwner: msg.target_owner });
1879
2182
  sendMessage(targetWs, {
1880
2183
  type: "incoming_request",
1881
2184
  id: msg.id,
@@ -1887,18 +2190,98 @@ function registerWebSocketRelay(server, db) {
1887
2190
  escrow_receipt: msg.escrow_receipt
1888
2191
  });
1889
2192
  }
2193
+ function handleRelayProgress(msg) {
2194
+ const pending = pendingRequests.get(msg.id);
2195
+ if (!pending) return;
2196
+ clearTimeout(pending.timeout);
2197
+ const newTimeout = setTimeout(() => {
2198
+ const p = pendingRequests.get(msg.id);
2199
+ pendingRequests.delete(msg.id);
2200
+ if (p?.escrowId && creditDb) {
2201
+ try {
2202
+ releaseForRelay(creditDb, p.escrowId);
2203
+ } catch (e) {
2204
+ console.error("[relay] escrow release on progress timeout failed:", e);
2205
+ }
2206
+ }
2207
+ const originWs2 = connections.get(pending.originOwner);
2208
+ if (originWs2 && originWs2.readyState === 1) {
2209
+ sendMessage(originWs2, {
2210
+ type: "response",
2211
+ id: msg.id,
2212
+ error: { code: -32603, message: "Relay request timeout" }
2213
+ });
2214
+ }
2215
+ }, RELAY_TIMEOUT_MS);
2216
+ pending.timeout = newTimeout;
2217
+ const originWs = connections.get(pending.originOwner);
2218
+ if (originWs && originWs.readyState === 1) {
2219
+ sendMessage(originWs, {
2220
+ type: "relay_progress",
2221
+ id: msg.id,
2222
+ progress: msg.progress,
2223
+ message: msg.message
2224
+ });
2225
+ }
2226
+ }
1890
2227
  function handleRelayResponse(msg) {
1891
2228
  const pending = pendingRequests.get(msg.id);
1892
2229
  if (!pending) return;
1893
2230
  clearTimeout(pending.timeout);
1894
2231
  pendingRequests.delete(msg.id);
2232
+ if (pending.jobId && creditDb) {
2233
+ try {
2234
+ handleJobRelayResponse({
2235
+ registryDb: db,
2236
+ creditDb,
2237
+ jobId: pending.jobId,
2238
+ escrowId: pending.escrowId,
2239
+ relayOwner: pending.targetOwner ?? "",
2240
+ result: msg.error === void 0 ? msg.result : void 0,
2241
+ error: msg.error
2242
+ });
2243
+ } catch (e) {
2244
+ console.error("[relay] job relay response handling failed:", e);
2245
+ }
2246
+ const originWs2 = connections.get(pending.originOwner);
2247
+ if (originWs2 && originWs2.readyState === 1) {
2248
+ sendMessage(originWs2, { type: "response", id: msg.id, result: msg.result, error: msg.error });
2249
+ }
2250
+ return;
2251
+ }
2252
+ if (pending.escrowId && creditDb) {
2253
+ try {
2254
+ if (msg.error === void 0) {
2255
+ settleForRelay(creditDb, pending.escrowId, pending.targetOwner);
2256
+ } else {
2257
+ releaseForRelay(creditDb, pending.escrowId);
2258
+ }
2259
+ } catch (e) {
2260
+ console.error("[relay] escrow settle/release on response failed:", e);
2261
+ }
2262
+ }
2263
+ let conductorFee = 0;
2264
+ if (creditDb && msg.error === void 0 && typeof msg.result === "object" && msg.result !== null && "total_credits" in msg.result && typeof msg.result.total_credits === "number") {
2265
+ const totalCredits = msg.result.total_credits;
2266
+ conductorFee = calculateConductorFee(totalCredits);
2267
+ if (conductorFee > 0) {
2268
+ try {
2269
+ const feeEscrowId = holdForRelay(creditDb, pending.originOwner, conductorFee, msg.id);
2270
+ settleForRelay(creditDb, feeEscrowId, pending.targetOwner);
2271
+ } catch (e) {
2272
+ console.error("[relay] conductor fee settlement failed (non-fatal):", e);
2273
+ conductorFee = 0;
2274
+ }
2275
+ }
2276
+ }
1895
2277
  const originWs = connections.get(pending.originOwner);
1896
2278
  if (originWs && originWs.readyState === 1) {
1897
2279
  sendMessage(originWs, {
1898
2280
  type: "response",
1899
2281
  id: msg.id,
1900
2282
  result: msg.result,
1901
- error: msg.error
2283
+ error: msg.error,
2284
+ ...conductorFee > 0 ? { conductor_fee: conductorFee } : {}
1902
2285
  });
1903
2286
  }
1904
2287
  }
@@ -1908,55 +2291,85 @@ function registerWebSocketRelay(server, db) {
1908
2291
  rateLimits.delete(owner);
1909
2292
  markOwnerOffline(owner);
1910
2293
  for (const [reqId, pending] of pendingRequests) {
1911
- if (pending.originOwner === owner) {
2294
+ if (pending.targetOwner === owner) {
1912
2295
  clearTimeout(pending.timeout);
1913
2296
  pendingRequests.delete(reqId);
1914
- }
1915
- }
1916
- }
1917
- server.get("/ws", { websocket: true }, (rawSocket, _request) => {
1918
- const socket = rawSocket;
1919
- let registeredOwner;
1920
- socket.on("message", (raw) => {
1921
- let data;
1922
- try {
1923
- data = JSON.parse(typeof raw === "string" ? raw : raw.toString("utf-8"));
1924
- } catch {
1925
- sendMessage(socket, { type: "error", code: "invalid_json", message: "Invalid JSON" });
1926
- return;
1927
- }
1928
- const parsed = RelayMessageSchema.safeParse(data);
1929
- if (!parsed.success) {
1930
- sendMessage(socket, {
1931
- type: "error",
1932
- code: "invalid_message",
1933
- message: `Invalid message: ${parsed.error.issues[0]?.message ?? "unknown error"}`
1934
- });
1935
- return;
1936
- }
1937
- const msg = parsed.data;
1938
- switch (msg.type) {
1939
- case "register":
1940
- registeredOwner = msg.owner;
1941
- handleRegister(socket, msg);
1942
- break;
1943
- case "relay_request":
1944
- if (!registeredOwner) {
1945
- sendMessage(socket, {
1946
- type: "error",
1947
- code: "not_registered",
1948
- message: "Must send register message before relay requests"
1949
- });
1950
- return;
2297
+ if (pending.escrowId && creditDb) {
2298
+ try {
2299
+ releaseForRelay(creditDb, pending.escrowId);
2300
+ } catch (e) {
2301
+ console.error("[relay] escrow release on disconnect failed:", e);
1951
2302
  }
1952
- handleRelayRequest(socket, msg, registeredOwner);
1953
- break;
1954
- case "relay_response":
1955
- handleRelayResponse(msg);
1956
- break;
1957
- default:
1958
- break;
2303
+ }
2304
+ const originWs = connections.get(pending.originOwner);
2305
+ if (originWs && originWs.readyState === 1) {
2306
+ sendMessage(originWs, {
2307
+ type: "response",
2308
+ id: reqId,
2309
+ error: { code: -32603, message: "Provider disconnected" }
2310
+ });
2311
+ }
2312
+ } else if (pending.originOwner === owner) {
2313
+ clearTimeout(pending.timeout);
2314
+ pendingRequests.delete(reqId);
2315
+ if (pending.escrowId && creditDb) {
2316
+ try {
2317
+ releaseForRelay(creditDb, pending.escrowId);
2318
+ } catch (e) {
2319
+ console.error("[relay] escrow release on requester disconnect failed:", e);
2320
+ }
2321
+ }
1959
2322
  }
2323
+ }
2324
+ }
2325
+ server.get("/ws", { websocket: true }, (rawSocket, _request) => {
2326
+ const socket = rawSocket;
2327
+ let registeredOwner;
2328
+ socket.on("message", (raw) => {
2329
+ void (async () => {
2330
+ let data;
2331
+ try {
2332
+ data = JSON.parse(typeof raw === "string" ? raw : raw.toString("utf-8"));
2333
+ } catch {
2334
+ sendMessage(socket, { type: "error", code: "invalid_json", message: "Invalid JSON" });
2335
+ return;
2336
+ }
2337
+ const parsed = RelayMessageSchema.safeParse(data);
2338
+ if (!parsed.success) {
2339
+ sendMessage(socket, {
2340
+ type: "error",
2341
+ code: "invalid_message",
2342
+ message: `Invalid message: ${parsed.error.issues[0]?.message ?? "unknown error"}`
2343
+ });
2344
+ return;
2345
+ }
2346
+ const msg = parsed.data;
2347
+ switch (msg.type) {
2348
+ case "register":
2349
+ registeredOwner = msg.owner;
2350
+ handleRegister(socket, msg);
2351
+ break;
2352
+ case "relay_request":
2353
+ if (!registeredOwner) {
2354
+ sendMessage(socket, {
2355
+ type: "error",
2356
+ code: "not_registered",
2357
+ message: "Must send register message before relay requests"
2358
+ });
2359
+ return;
2360
+ }
2361
+ await handleRelayRequest(socket, msg, registeredOwner);
2362
+ break;
2363
+ case "relay_response":
2364
+ handleRelayResponse(msg);
2365
+ break;
2366
+ case "relay_progress":
2367
+ handleRelayProgress(msg);
2368
+ break;
2369
+ default:
2370
+ break;
2371
+ }
2372
+ })();
1960
2373
  });
1961
2374
  socket.on("close", () => {
1962
2375
  handleDisconnect(registeredOwner);
@@ -1981,21 +2394,29 @@ function registerWebSocketRelay(server, db) {
1981
2394
  }
1982
2395
  pendingRequests.clear();
1983
2396
  rateLimits.clear();
2397
+ },
2398
+ setOnAgentOnline: (cb) => {
2399
+ onAgentOnlineCallback = cb;
2400
+ },
2401
+ getConnections: () => connections,
2402
+ getPendingRequests: () => pendingRequests,
2403
+ sendMessage: (ws, msg) => {
2404
+ sendMessage(ws, msg);
1984
2405
  }
1985
2406
  };
1986
2407
  }
1987
2408
 
1988
2409
  // src/identity/guarantor.ts
1989
- import { z as z4 } from "zod";
1990
- import { randomUUID as randomUUID5 } from "crypto";
2410
+ import { z as z3 } from "zod";
2411
+ import { randomUUID as randomUUID7 } from "crypto";
1991
2412
  var MAX_AGENTS_PER_GUARANTOR = 10;
1992
2413
  var GUARANTOR_CREDIT_POOL = 50;
1993
- var GuarantorRecordSchema = z4.object({
1994
- id: z4.string().uuid(),
1995
- github_login: z4.string().min(1),
1996
- agent_count: z4.number().int().nonnegative(),
1997
- credit_pool: z4.number().int().nonnegative(),
1998
- created_at: z4.string().datetime()
2414
+ var GuarantorRecordSchema = z3.object({
2415
+ id: z3.string().uuid(),
2416
+ github_login: z3.string().min(1),
2417
+ agent_count: z3.number().int().nonnegative(),
2418
+ credit_pool: z3.number().int().nonnegative(),
2419
+ created_at: z3.string().datetime()
1999
2420
  });
2000
2421
  var GUARANTOR_SCHEMA = `
2001
2422
  CREATE TABLE IF NOT EXISTS guarantors (
@@ -2026,7 +2447,7 @@ function registerGuarantor(db, githubLogin) {
2026
2447
  );
2027
2448
  }
2028
2449
  const record = {
2029
- id: randomUUID5(),
2450
+ id: randomUUID7(),
2030
2451
  github_login: githubLogin,
2031
2452
  agent_count: 0,
2032
2453
  credit_pool: GUARANTOR_CREDIT_POOL,
@@ -2100,10 +2521,849 @@ function getAgentGuarantor(db, agentId) {
2100
2521
  function initiateGithubAuth() {
2101
2522
  return {
2102
2523
  auth_url: "https://github.com/login/oauth/authorize?client_id=PLACEHOLDER&scope=read:user",
2103
- state: randomUUID5()
2524
+ state: randomUUID7()
2104
2525
  };
2105
2526
  }
2106
2527
 
2528
+ // src/registry/free-tier.ts
2529
+ function initFreeTierTable(db) {
2530
+ db.exec(`
2531
+ CREATE TABLE IF NOT EXISTS credit_free_tier_usage (
2532
+ agent_public_key TEXT NOT NULL,
2533
+ skill_id TEXT NOT NULL,
2534
+ usage_count INTEGER NOT NULL DEFAULT 0,
2535
+ last_used_at TEXT NOT NULL,
2536
+ PRIMARY KEY (agent_public_key, skill_id)
2537
+ )
2538
+ `);
2539
+ }
2540
+
2541
+ // src/registry/credit-routes.ts
2542
+ async function creditRoutesPlugin(fastify, options) {
2543
+ const { creditDb } = options;
2544
+ creditDb.exec(`
2545
+ CREATE TABLE IF NOT EXISTS credit_grants (
2546
+ public_key TEXT PRIMARY KEY,
2547
+ granted_at TEXT NOT NULL
2548
+ )
2549
+ `);
2550
+ initFreeTierTable(creditDb);
2551
+ await fastify.register(async (scope) => {
2552
+ identityAuthPlugin(scope);
2553
+ scope.post("/api/credits/hold", {
2554
+ schema: {
2555
+ tags: ["credits"],
2556
+ summary: "Hold credits in escrow during capability execution",
2557
+ security: [{ ed25519Auth: [] }],
2558
+ body: {
2559
+ type: "object",
2560
+ properties: {
2561
+ owner: { type: "string" },
2562
+ amount: { type: "number" },
2563
+ cardId: { type: "string" }
2564
+ },
2565
+ required: ["owner", "amount", "cardId"]
2566
+ },
2567
+ response: {
2568
+ 200: { type: "object", properties: { escrowId: { type: "string" } } },
2569
+ 400: { type: "object", properties: { error: { type: "string" }, code: { type: "string" } } }
2570
+ }
2571
+ }
2572
+ }, async (request, reply) => {
2573
+ const body = request.body;
2574
+ const owner = typeof body.owner === "string" ? body.owner.trim() : "";
2575
+ const amount = typeof body.amount === "number" ? body.amount : NaN;
2576
+ const cardId = typeof body.cardId === "string" ? body.cardId.trim() : "";
2577
+ if (!owner || isNaN(amount) || amount <= 0 || !cardId) {
2578
+ return reply.code(400).send({ error: "Missing or invalid required fields: owner, amount (>0), cardId" });
2579
+ }
2580
+ try {
2581
+ const escrowId = holdEscrow(creditDb, owner, amount, cardId);
2582
+ return reply.send({ escrowId });
2583
+ } catch (err) {
2584
+ if (err instanceof AgentBnBError && err.code === "INSUFFICIENT_CREDITS") {
2585
+ return reply.code(400).send({ error: err.message, code: "INSUFFICIENT_CREDITS" });
2586
+ }
2587
+ throw err;
2588
+ }
2589
+ });
2590
+ scope.post("/api/credits/settle", {
2591
+ schema: {
2592
+ tags: ["credits"],
2593
+ summary: "Transfer held credits to provider on success",
2594
+ security: [{ ed25519Auth: [] }],
2595
+ body: {
2596
+ type: "object",
2597
+ properties: {
2598
+ escrowId: { type: "string" },
2599
+ recipientOwner: { type: "string" }
2600
+ },
2601
+ required: ["escrowId", "recipientOwner"]
2602
+ },
2603
+ response: {
2604
+ 200: { type: "object", properties: { ok: { type: "boolean" } } },
2605
+ 400: { type: "object", properties: { error: { type: "string" }, code: { type: "string" } } }
2606
+ }
2607
+ }
2608
+ }, async (request, reply) => {
2609
+ const body = request.body;
2610
+ const escrowId = typeof body.escrowId === "string" ? body.escrowId.trim() : "";
2611
+ const recipientOwner = typeof body.recipientOwner === "string" ? body.recipientOwner.trim() : "";
2612
+ if (!escrowId || !recipientOwner) {
2613
+ return reply.code(400).send({ error: "escrowId and recipientOwner are required" });
2614
+ }
2615
+ try {
2616
+ settleEscrow(creditDb, escrowId, recipientOwner);
2617
+ return reply.send({ ok: true });
2618
+ } catch (err) {
2619
+ if (err instanceof AgentBnBError) {
2620
+ return reply.code(400).send({ error: err.message, code: err.code });
2621
+ }
2622
+ throw err;
2623
+ }
2624
+ });
2625
+ scope.post("/api/credits/release", {
2626
+ schema: {
2627
+ tags: ["credits"],
2628
+ summary: "Refund held credits to requester on failure",
2629
+ security: [{ ed25519Auth: [] }],
2630
+ body: {
2631
+ type: "object",
2632
+ properties: { escrowId: { type: "string" } },
2633
+ required: ["escrowId"]
2634
+ },
2635
+ response: {
2636
+ 200: { type: "object", properties: { ok: { type: "boolean" } } },
2637
+ 400: { type: "object", properties: { error: { type: "string" }, code: { type: "string" } } }
2638
+ }
2639
+ }
2640
+ }, async (request, reply) => {
2641
+ const body = request.body;
2642
+ const escrowId = typeof body.escrowId === "string" ? body.escrowId.trim() : "";
2643
+ if (!escrowId) {
2644
+ return reply.code(400).send({ error: "escrowId is required" });
2645
+ }
2646
+ try {
2647
+ releaseEscrow(creditDb, escrowId);
2648
+ return reply.send({ ok: true });
2649
+ } catch (err) {
2650
+ if (err instanceof AgentBnBError) {
2651
+ return reply.code(400).send({ error: err.message, code: err.code });
2652
+ }
2653
+ throw err;
2654
+ }
2655
+ });
2656
+ scope.post("/api/credits/grant", {
2657
+ schema: {
2658
+ tags: ["credits"],
2659
+ summary: "Bootstrap grant of 50 credits (once per identity)",
2660
+ security: [{ ed25519Auth: [] }],
2661
+ body: {
2662
+ type: "object",
2663
+ properties: {
2664
+ owner: { type: "string" },
2665
+ amount: { type: "number" }
2666
+ },
2667
+ required: ["owner"]
2668
+ },
2669
+ response: {
2670
+ 200: {
2671
+ type: "object",
2672
+ properties: {
2673
+ ok: { type: "boolean" },
2674
+ granted: { type: "number" },
2675
+ reason: { type: "string" }
2676
+ }
2677
+ },
2678
+ 400: { type: "object", properties: { error: { type: "string" } } }
2679
+ }
2680
+ }
2681
+ }, async (request, reply) => {
2682
+ const body = request.body;
2683
+ const owner = typeof body.owner === "string" ? body.owner.trim() : "";
2684
+ const amount = typeof body.amount === "number" ? body.amount : 50;
2685
+ const publicKey = request.agentPublicKey;
2686
+ if (!owner) {
2687
+ return reply.code(400).send({ error: "owner is required" });
2688
+ }
2689
+ const existing = creditDb.prepare("SELECT public_key FROM credit_grants WHERE public_key = ?").get(publicKey);
2690
+ if (existing) {
2691
+ return reply.send({ ok: true, granted: 0, reason: "already_granted" });
2692
+ }
2693
+ const now = (/* @__PURE__ */ new Date()).toISOString();
2694
+ bootstrapAgent(creditDb, owner, amount);
2695
+ creditDb.prepare("INSERT INTO credit_grants (public_key, granted_at) VALUES (?, ?)").run(publicKey, now);
2696
+ return reply.send({ ok: true, granted: amount });
2697
+ });
2698
+ scope.get("/api/credits/:owner", {
2699
+ schema: {
2700
+ tags: ["credits"],
2701
+ summary: "Get current credit balance for an agent",
2702
+ security: [{ ed25519Auth: [] }],
2703
+ params: { type: "object", properties: { owner: { type: "string" } }, required: ["owner"] },
2704
+ response: { 200: { type: "object", properties: { balance: { type: "number" } } } }
2705
+ }
2706
+ }, async (request, reply) => {
2707
+ const { owner } = request.params;
2708
+ const balance = getBalance(creditDb, owner);
2709
+ return reply.send({ balance });
2710
+ });
2711
+ scope.get("/api/credits/:owner/history", {
2712
+ schema: {
2713
+ tags: ["credits"],
2714
+ summary: "Get paginated transaction history",
2715
+ security: [{ ed25519Auth: [] }],
2716
+ params: { type: "object", properties: { owner: { type: "string" } }, required: ["owner"] },
2717
+ querystring: {
2718
+ type: "object",
2719
+ properties: { limit: { type: "integer", description: "Max entries (default 20, max 100)" } }
2720
+ },
2721
+ response: {
2722
+ 200: {
2723
+ type: "object",
2724
+ properties: { transactions: { type: "array" }, limit: { type: "integer" } }
2725
+ }
2726
+ }
2727
+ }
2728
+ }, async (request, reply) => {
2729
+ const { owner } = request.params;
2730
+ const query = request.query;
2731
+ const rawLimit = query.limit !== void 0 ? parseInt(query.limit, 10) : 20;
2732
+ const limit = Math.min(isNaN(rawLimit) || rawLimit < 1 ? 20 : rawLimit, 100);
2733
+ const transactions = getTransactions(creditDb, owner, limit);
2734
+ return reply.send({ transactions, limit });
2735
+ });
2736
+ });
2737
+ }
2738
+
2739
+ // src/hub-agent/executor.ts
2740
+ var HubAgentExecutor = class {
2741
+ constructor(registryDb, creditDb) {
2742
+ this.registryDb = registryDb;
2743
+ this.creditDb = creditDb;
2744
+ initJobQueue(this.registryDb);
2745
+ }
2746
+ /**
2747
+ * Execute a skill on a Hub Agent.
2748
+ *
2749
+ * @param agentId - The Hub Agent ID.
2750
+ * @param skillId - The skill_id to execute from the agent's routing table.
2751
+ * @param params - Input parameters for the skill.
2752
+ * @param requesterOwner - Optional requester identifier for credit escrow.
2753
+ * @returns ExecutionResult with success status, result/error, and latency_ms.
2754
+ */
2755
+ async execute(agentId, skillId, params, requesterOwner) {
2756
+ const startTime = Date.now();
2757
+ const agent = getHubAgent(this.registryDb, agentId);
2758
+ if (!agent) {
2759
+ return { success: false, error: "Hub Agent not found", latency_ms: Date.now() - startTime };
2760
+ }
2761
+ if (agent.status === "paused") {
2762
+ return { success: false, error: "Hub Agent is paused", latency_ms: Date.now() - startTime };
2763
+ }
2764
+ const route = agent.skill_routes.find((r) => r.skill_id === skillId);
2765
+ if (!route) {
2766
+ return { success: false, error: "Skill not found in routing table", latency_ms: Date.now() - startTime };
2767
+ }
2768
+ switch (route.mode) {
2769
+ case "relay":
2770
+ return this.executeRelay(route, agent, params, requesterOwner, startTime);
2771
+ case "queue":
2772
+ return this.executeQueue(route, agent, params, requesterOwner, startTime);
2773
+ case "direct_api":
2774
+ return this.executeDirectApi(route, agent, params, requesterOwner, startTime);
2775
+ }
2776
+ }
2777
+ /**
2778
+ * Relay mode: If the target relay agent is offline, queue the job.
2779
+ * If online, still queue (actual dispatch happens via relay bridge).
2780
+ */
2781
+ async executeRelay(route, agent, params, requesterOwner, startTime) {
2782
+ const relayOwner = route.config.relay_owner;
2783
+ if (this.isRelayOwnerOnline(relayOwner)) {
2784
+ return { success: false, error: "relay mode requires connected session agent", latency_ms: 0 };
2785
+ }
2786
+ return this.queueJob(agent, route.skill_id, params, requesterOwner, relayOwner, startTime);
2787
+ }
2788
+ /**
2789
+ * Queue mode: Always queue the job for later dispatch.
2790
+ */
2791
+ async executeQueue(route, agent, params, requesterOwner, startTime) {
2792
+ const relayOwner = route.config.relay_owner;
2793
+ return this.queueJob(agent, route.skill_id, params, requesterOwner, relayOwner, startTime);
2794
+ }
2795
+ /**
2796
+ * Queue a job with optional credit escrow.
2797
+ */
2798
+ queueJob(agent, skillId, params, requesterOwner, relayOwner, startTime) {
2799
+ const cardId = agent.agent_id.padEnd(32, "0").replace(/^(.{8})(.{4})(.{4})(.{4})(.{12}).*$/, "$1-$2-$3-$4-$5");
2800
+ let escrowId;
2801
+ if (requesterOwner) {
2802
+ const price = lookupCardPrice(this.registryDb, cardId, skillId);
2803
+ if (price !== null && price > 0) {
2804
+ escrowId = holdEscrow(this.creditDb, requesterOwner, price, cardId);
2805
+ }
2806
+ }
2807
+ const job = insertJob(this.registryDb, {
2808
+ hub_agent_id: agent.agent_id,
2809
+ skill_id: skillId,
2810
+ requester_owner: requesterOwner ?? "anonymous",
2811
+ params,
2812
+ escrow_id: escrowId,
2813
+ relay_owner: relayOwner
2814
+ });
2815
+ return {
2816
+ success: true,
2817
+ result: { queued: true, job_id: job.id },
2818
+ latency_ms: Date.now() - startTime
2819
+ };
2820
+ }
2821
+ /**
2822
+ * Check if a relay owner has any online cards in the registry.
2823
+ *
2824
+ * @param owner - The relay owner identifier.
2825
+ * @returns true if any card for this owner is online.
2826
+ */
2827
+ isRelayOwnerOnline(owner) {
2828
+ const rows = this.registryDb.prepare(
2829
+ "SELECT data FROM capability_cards WHERE owner = ?"
2830
+ ).all(owner);
2831
+ for (const row of rows) {
2832
+ try {
2833
+ const card = JSON.parse(row.data);
2834
+ const availability = card.availability;
2835
+ if (availability?.online === true) {
2836
+ return true;
2837
+ }
2838
+ } catch {
2839
+ }
2840
+ }
2841
+ return false;
2842
+ }
2843
+ /**
2844
+ * Execute a direct_api skill route via ApiExecutor.
2845
+ * Handles secret injection and credit escrow.
2846
+ */
2847
+ async executeDirectApi(route, agent, params, requesterOwner, startTime) {
2848
+ const config = this.injectSecrets(route.config, agent.secrets);
2849
+ const pricing = route.config.pricing;
2850
+ const creditsPerCall = pricing?.credits_per_call ?? 0;
2851
+ let escrowId;
2852
+ if (requesterOwner && creditsPerCall > 0) {
2853
+ const cardId = agent.agent_id.padEnd(32, "0").replace(/^(.{8})(.{4})(.{4})(.{4})(.{12}).*$/, "$1-$2-$3-$4-$5");
2854
+ escrowId = holdEscrow(this.creditDb, requesterOwner, creditsPerCall, cardId);
2855
+ }
2856
+ try {
2857
+ const apiExecutor = new ApiExecutor();
2858
+ const modeResult = await apiExecutor.execute(config, params);
2859
+ const result = {
2860
+ ...modeResult,
2861
+ latency_ms: Date.now() - startTime
2862
+ };
2863
+ if (escrowId) {
2864
+ if (result.success) {
2865
+ settleEscrow(this.creditDb, escrowId, agent.agent_id);
2866
+ } else {
2867
+ releaseEscrow(this.creditDb, escrowId);
2868
+ }
2869
+ }
2870
+ return result;
2871
+ } catch (err) {
2872
+ if (escrowId) {
2873
+ releaseEscrow(this.creditDb, escrowId);
2874
+ }
2875
+ const message = err instanceof Error ? err.message : String(err);
2876
+ return {
2877
+ success: false,
2878
+ error: message,
2879
+ latency_ms: Date.now() - startTime
2880
+ };
2881
+ }
2882
+ }
2883
+ /**
2884
+ * Injects decrypted secrets into the API skill config's auth field.
2885
+ * If secrets contain 'api_key' and auth type is 'bearer', replaces the token.
2886
+ * If secrets contain 'api_key' and auth type is 'apikey', replaces the key.
2887
+ */
2888
+ injectSecrets(config, secrets) {
2889
+ if (!secrets || Object.keys(secrets).length === 0) {
2890
+ return config;
2891
+ }
2892
+ const injected = JSON.parse(JSON.stringify(config));
2893
+ const apiKey = secrets.api_key ?? secrets.API_KEY;
2894
+ if (apiKey && injected.auth) {
2895
+ switch (injected.auth.type) {
2896
+ case "bearer":
2897
+ injected.auth.token = apiKey;
2898
+ break;
2899
+ case "apikey":
2900
+ injected.auth.key = apiKey;
2901
+ break;
2902
+ }
2903
+ }
2904
+ return injected;
2905
+ }
2906
+ };
2907
+
2908
+ // src/hub-agent/types.ts
2909
+ import { z as z4 } from "zod";
2910
+ var SkillRouteSchema = z4.discriminatedUnion("mode", [
2911
+ z4.object({
2912
+ skill_id: z4.string().min(1),
2913
+ mode: z4.literal("direct_api"),
2914
+ config: ApiSkillConfigSchema
2915
+ }),
2916
+ z4.object({
2917
+ skill_id: z4.string().min(1),
2918
+ mode: z4.literal("relay"),
2919
+ config: z4.object({ relay_owner: z4.string().min(1) })
2920
+ }),
2921
+ z4.object({
2922
+ skill_id: z4.string().min(1),
2923
+ mode: z4.literal("queue"),
2924
+ config: z4.object({ relay_owner: z4.string().min(1) }).passthrough()
2925
+ })
2926
+ ]);
2927
+ var HubAgentSchema = z4.object({
2928
+ agent_id: z4.string().min(1),
2929
+ name: z4.string().min(1),
2930
+ owner_public_key: z4.string().min(1),
2931
+ public_key: z4.string().min(1),
2932
+ skill_routes: z4.array(SkillRouteSchema),
2933
+ status: z4.enum(["active", "paused"]),
2934
+ created_at: z4.string(),
2935
+ updated_at: z4.string()
2936
+ });
2937
+ var CreateAgentRequestSchema = z4.object({
2938
+ name: z4.string().min(1),
2939
+ skill_routes: z4.array(SkillRouteSchema),
2940
+ secrets: z4.record(z4.string()).optional()
2941
+ });
2942
+
2943
+ // src/hub-agent/routes.ts
2944
+ function buildCapabilityCard(agentId, name, publicKey, skillRoutes) {
2945
+ const now = (/* @__PURE__ */ new Date()).toISOString();
2946
+ const skills = skillRoutes.map((route) => ({
2947
+ id: route.skill_id,
2948
+ name: route.mode === "direct_api" ? route.config.name : route.skill_id,
2949
+ description: route.mode === "direct_api" ? `API skill: ${route.config.name}` : `${route.mode} skill: ${route.skill_id}`,
2950
+ level: 1,
2951
+ inputs: [],
2952
+ outputs: [],
2953
+ pricing: route.mode === "direct_api" ? route.config.pricing : { credits_per_call: 10 }
2954
+ }));
2955
+ if (skills.length === 0) {
2956
+ skills.push({
2957
+ id: "default",
2958
+ name,
2959
+ description: `Hub Agent: ${name}`,
2960
+ level: 1,
2961
+ inputs: [],
2962
+ outputs: [],
2963
+ pricing: { credits_per_call: 10 }
2964
+ });
2965
+ }
2966
+ return {
2967
+ spec_version: "2.0",
2968
+ id: agentId.padEnd(32, "0").replace(/^(.{8})(.{4})(.{4})(.{4})(.{12}).*$/, "$1-$2-$3-$4-$5"),
2969
+ owner: publicKey.slice(0, 16),
2970
+ agent_name: name,
2971
+ skills,
2972
+ availability: { online: true },
2973
+ created_at: now,
2974
+ updated_at: now
2975
+ };
2976
+ }
2977
+ function upsertCardRaw(db, cardData, owner) {
2978
+ const parsed = AnyCardSchema.safeParse(cardData);
2979
+ if (!parsed.success) {
2980
+ throw new AgentBnBError(
2981
+ `Card validation failed: ${parsed.error.message}`,
2982
+ "VALIDATION_ERROR"
2983
+ );
2984
+ }
2985
+ const card = parsed.data;
2986
+ const cardId = card.id;
2987
+ const now = (/* @__PURE__ */ new Date()).toISOString();
2988
+ const existing = db.prepare("SELECT id FROM capability_cards WHERE id = ?").get(cardId);
2989
+ if (existing) {
2990
+ db.prepare("UPDATE capability_cards SET data = ?, updated_at = ? WHERE id = ?").run(JSON.stringify(card), now, cardId);
2991
+ } else {
2992
+ db.prepare("INSERT INTO capability_cards (id, owner, data, created_at, updated_at) VALUES (?, ?, ?, ?, ?)").run(cardId, owner, JSON.stringify(card), now, now);
2993
+ }
2994
+ return cardId;
2995
+ }
2996
+ function sanitizeAgent(agent) {
2997
+ const { secrets, private_key_enc, ...safe } = agent;
2998
+ if (secrets && typeof secrets === "object") {
2999
+ return { ...safe, secret_keys: Object.keys(secrets) };
3000
+ }
3001
+ return safe;
3002
+ }
3003
+ async function hubAgentRoutesPlugin(fastify, options) {
3004
+ const { registryDb, creditDb } = options;
3005
+ initHubAgentTable(registryDb);
3006
+ initJobQueue(registryDb);
3007
+ fastify.post("/api/hub-agents", {
3008
+ schema: {
3009
+ tags: ["hub-agents"],
3010
+ summary: "Create a new Hub Agent with Ed25519 identity",
3011
+ body: {
3012
+ type: "object",
3013
+ required: ["name"],
3014
+ properties: {
3015
+ name: { type: "string", minLength: 1 },
3016
+ skill_routes: { type: "array" },
3017
+ secrets: { type: "object" }
3018
+ }
3019
+ },
3020
+ response: {
3021
+ 201: {
3022
+ type: "object",
3023
+ properties: {
3024
+ agent_id: { type: "string" },
3025
+ name: { type: "string" },
3026
+ public_key: { type: "string" },
3027
+ skill_routes: { type: "array" },
3028
+ status: { type: "string" },
3029
+ created_at: { type: "string" },
3030
+ updated_at: { type: "string" }
3031
+ }
3032
+ },
3033
+ 400: {
3034
+ type: "object",
3035
+ properties: { error: { type: "string" } }
3036
+ }
3037
+ }
3038
+ }
3039
+ }, async (request, reply) => {
3040
+ const parseResult = CreateAgentRequestSchema.safeParse(request.body);
3041
+ if (!parseResult.success) {
3042
+ return reply.code(400).send({ error: parseResult.error.message });
3043
+ }
3044
+ const req = parseResult.data;
3045
+ const ownerPublicKey = "hub-server";
3046
+ try {
3047
+ const agent = createHubAgent(registryDb, req, ownerPublicKey);
3048
+ bootstrapAgent(creditDb, agent.agent_id, 50);
3049
+ const cardData = buildCapabilityCard(agent.agent_id, agent.name, agent.public_key, agent.skill_routes);
3050
+ try {
3051
+ upsertCardRaw(registryDb, cardData, agent.agent_id);
3052
+ } catch {
3053
+ }
3054
+ return reply.code(201).send(sanitizeAgent(agent));
3055
+ } catch (err) {
3056
+ if (err instanceof AgentBnBError) {
3057
+ return reply.code(400).send({ error: err.message });
3058
+ }
3059
+ throw err;
3060
+ }
3061
+ });
3062
+ fastify.get("/api/hub-agents", {
3063
+ schema: {
3064
+ tags: ["hub-agents"],
3065
+ summary: "List all Hub Agents",
3066
+ response: {
3067
+ 200: {
3068
+ type: "object",
3069
+ properties: {
3070
+ agents: { type: "array" }
3071
+ }
3072
+ }
3073
+ }
3074
+ }
3075
+ }, async (_request, reply) => {
3076
+ const agents = listHubAgents(registryDb);
3077
+ return reply.send({ agents });
3078
+ });
3079
+ fastify.get("/api/hub-agents/:id", {
3080
+ schema: {
3081
+ tags: ["hub-agents"],
3082
+ summary: "Get a single Hub Agent by ID",
3083
+ params: {
3084
+ type: "object",
3085
+ properties: { id: { type: "string" } },
3086
+ required: ["id"]
3087
+ },
3088
+ response: {
3089
+ 200: {
3090
+ type: "object",
3091
+ properties: {
3092
+ agent_id: { type: "string" },
3093
+ name: { type: "string" },
3094
+ public_key: { type: "string" },
3095
+ skill_routes: { type: "array" },
3096
+ status: { type: "string" },
3097
+ secret_keys: { type: "array", items: { type: "string" } }
3098
+ }
3099
+ },
3100
+ 404: {
3101
+ type: "object",
3102
+ properties: { error: { type: "string" } }
3103
+ }
3104
+ }
3105
+ }
3106
+ }, async (request, reply) => {
3107
+ const { id } = request.params;
3108
+ const agent = getHubAgent(registryDb, id);
3109
+ if (!agent) {
3110
+ return reply.code(404).send({ error: "Hub Agent not found" });
3111
+ }
3112
+ return reply.send(sanitizeAgent(agent));
3113
+ });
3114
+ fastify.put("/api/hub-agents/:id", {
3115
+ schema: {
3116
+ tags: ["hub-agents"],
3117
+ summary: "Update a Hub Agent",
3118
+ params: {
3119
+ type: "object",
3120
+ properties: { id: { type: "string" } },
3121
+ required: ["id"]
3122
+ },
3123
+ body: {
3124
+ type: "object",
3125
+ properties: {
3126
+ name: { type: "string" },
3127
+ skill_routes: { type: "array" },
3128
+ secrets: { type: "object" }
3129
+ }
3130
+ },
3131
+ response: {
3132
+ 200: {
3133
+ type: "object",
3134
+ properties: {
3135
+ agent_id: { type: "string" },
3136
+ name: { type: "string" },
3137
+ skill_routes: { type: "array" },
3138
+ status: { type: "string" }
3139
+ }
3140
+ },
3141
+ 404: {
3142
+ type: "object",
3143
+ properties: { error: { type: "string" } }
3144
+ }
3145
+ }
3146
+ }
3147
+ }, async (request, reply) => {
3148
+ const { id } = request.params;
3149
+ const body = request.body;
3150
+ const updates = {};
3151
+ if (typeof body.name === "string") updates.name = body.name;
3152
+ if (Array.isArray(body.skill_routes)) updates.skill_routes = body.skill_routes;
3153
+ if (body.secrets && typeof body.secrets === "object") updates.secrets = body.secrets;
3154
+ const agent = updateHubAgent(registryDb, id, updates);
3155
+ if (!agent) {
3156
+ return reply.code(404).send({ error: "Hub Agent not found" });
3157
+ }
3158
+ if (updates.skill_routes) {
3159
+ const cardData = buildCapabilityCard(agent.agent_id, agent.name, agent.public_key, agent.skill_routes);
3160
+ try {
3161
+ upsertCardRaw(registryDb, cardData, agent.agent_id);
3162
+ } catch {
3163
+ }
3164
+ }
3165
+ return reply.send(sanitizeAgent(agent));
3166
+ });
3167
+ fastify.delete("/api/hub-agents/:id", {
3168
+ schema: {
3169
+ tags: ["hub-agents"],
3170
+ summary: "Delete a Hub Agent",
3171
+ params: {
3172
+ type: "object",
3173
+ properties: { id: { type: "string" } },
3174
+ required: ["id"]
3175
+ },
3176
+ response: {
3177
+ 200: {
3178
+ type: "object",
3179
+ properties: { ok: { type: "boolean" } }
3180
+ },
3181
+ 404: {
3182
+ type: "object",
3183
+ properties: { error: { type: "string" } }
3184
+ }
3185
+ }
3186
+ }
3187
+ }, async (request, reply) => {
3188
+ const { id } = request.params;
3189
+ const agent = getHubAgent(registryDb, id);
3190
+ if (!agent) {
3191
+ return reply.code(404).send({ error: "Hub Agent not found" });
3192
+ }
3193
+ deleteHubAgent(registryDb, id);
3194
+ const cardId = id.padEnd(32, "0").replace(/^(.{8})(.{4})(.{4})(.{4})(.{12}).*$/, "$1-$2-$3-$4-$5");
3195
+ try {
3196
+ registryDb.prepare("DELETE FROM capability_cards WHERE id = ?").run(cardId);
3197
+ } catch {
3198
+ }
3199
+ return reply.send({ ok: true });
3200
+ });
3201
+ fastify.post("/api/hub-agents/:id/execute", {
3202
+ schema: {
3203
+ tags: ["hub-agents"],
3204
+ summary: "Execute a skill on a Hub Agent",
3205
+ params: {
3206
+ type: "object",
3207
+ properties: { id: { type: "string" } },
3208
+ required: ["id"]
3209
+ },
3210
+ body: {
3211
+ type: "object",
3212
+ required: ["skill_id"],
3213
+ properties: {
3214
+ skill_id: { type: "string" },
3215
+ params: { type: "object" },
3216
+ requester_owner: { type: "string" }
3217
+ }
3218
+ }
3219
+ }
3220
+ }, async (request, reply) => {
3221
+ const { id } = request.params;
3222
+ const body = request.body;
3223
+ const executor = new HubAgentExecutor(registryDb, creditDb);
3224
+ const result = await executor.execute(
3225
+ id,
3226
+ body.skill_id,
3227
+ body.params ?? {},
3228
+ body.requester_owner
3229
+ );
3230
+ if (!result.success && result.error === "Hub Agent not found") {
3231
+ return reply.code(404).send(result);
3232
+ }
3233
+ if (!result.success) {
3234
+ return reply.code(400).send(result);
3235
+ }
3236
+ return reply.send(result);
3237
+ });
3238
+ fastify.get("/api/hub-agents/:id/jobs", {
3239
+ schema: {
3240
+ tags: ["hub-agents"],
3241
+ summary: "List jobs for a Hub Agent",
3242
+ params: {
3243
+ type: "object",
3244
+ properties: { id: { type: "string" } },
3245
+ required: ["id"]
3246
+ },
3247
+ querystring: {
3248
+ type: "object",
3249
+ properties: {
3250
+ status: { type: "string", enum: ["queued", "dispatched", "completed", "failed"] }
3251
+ }
3252
+ },
3253
+ response: {
3254
+ 200: {
3255
+ type: "object",
3256
+ properties: {
3257
+ jobs: { type: "array" }
3258
+ }
3259
+ }
3260
+ }
3261
+ }
3262
+ }, async (request, reply) => {
3263
+ const { id } = request.params;
3264
+ const { status } = request.query ?? {};
3265
+ const jobs = listJobs(registryDb, id, status);
3266
+ return reply.send({ jobs });
3267
+ });
3268
+ fastify.get("/api/hub-agents/:id/jobs/:jobId", {
3269
+ schema: {
3270
+ tags: ["hub-agents"],
3271
+ summary: "Get a single job by ID",
3272
+ params: {
3273
+ type: "object",
3274
+ properties: {
3275
+ id: { type: "string" },
3276
+ jobId: { type: "string" }
3277
+ },
3278
+ required: ["id", "jobId"]
3279
+ },
3280
+ response: {
3281
+ 200: {
3282
+ type: "object",
3283
+ properties: {
3284
+ id: { type: "string" },
3285
+ hub_agent_id: { type: "string" },
3286
+ skill_id: { type: "string" },
3287
+ status: { type: "string" }
3288
+ }
3289
+ },
3290
+ 404: {
3291
+ type: "object",
3292
+ properties: { error: { type: "string" } }
3293
+ }
3294
+ }
3295
+ }
3296
+ }, async (request, reply) => {
3297
+ const { jobId } = request.params;
3298
+ const job = getJob(registryDb, jobId);
3299
+ if (!job) {
3300
+ return reply.code(404).send({ error: "Job not found" });
3301
+ }
3302
+ return reply.send(job);
3303
+ });
3304
+ }
3305
+
3306
+ // src/registry/openapi-gpt-actions.ts
3307
+ function convertToGptActions(openapiSpec, serverUrl) {
3308
+ const spec = JSON.parse(JSON.stringify(openapiSpec));
3309
+ spec.servers = [{ url: serverUrl }];
3310
+ const paths = spec.paths;
3311
+ if (paths) {
3312
+ const filteredPaths = {};
3313
+ for (const [path, methods] of Object.entries(paths)) {
3314
+ if (path.startsWith("/me") || path.startsWith("/draft") || path.startsWith("/docs") || path.startsWith("/ws") || path.startsWith("/api/credits")) {
3315
+ continue;
3316
+ }
3317
+ const filteredMethods = {};
3318
+ for (const [method, operation] of Object.entries(methods)) {
3319
+ if (method === "get" || method === "post") {
3320
+ const op = operation;
3321
+ if (!op.operationId) {
3322
+ op.operationId = deriveOperationId(method, path);
3323
+ }
3324
+ delete op.security;
3325
+ filteredMethods[method] = op;
3326
+ }
3327
+ }
3328
+ if (Object.keys(filteredMethods).length > 0) {
3329
+ filteredPaths[path] = filteredMethods;
3330
+ }
3331
+ }
3332
+ spec.paths = filteredPaths;
3333
+ }
3334
+ const components = spec.components;
3335
+ if (components) {
3336
+ delete components.securitySchemes;
3337
+ }
3338
+ const usedTags = /* @__PURE__ */ new Set();
3339
+ if (spec.paths) {
3340
+ for (const methods of Object.values(spec.paths)) {
3341
+ for (const op of Object.values(methods)) {
3342
+ const operation = op;
3343
+ if (Array.isArray(operation.tags)) {
3344
+ for (const tag of operation.tags) {
3345
+ usedTags.add(tag);
3346
+ }
3347
+ }
3348
+ }
3349
+ }
3350
+ }
3351
+ if (Array.isArray(spec.tags)) {
3352
+ spec.tags = spec.tags.filter((t) => usedTags.has(t.name));
3353
+ }
3354
+ return spec;
3355
+ }
3356
+ function deriveOperationId(method, path) {
3357
+ const segments = path.split("/").filter((s) => s.length > 0).map((s) => {
3358
+ if (s.startsWith("{") || s.startsWith(":")) {
3359
+ const paramName = s.replace(/[{}:]/g, "");
3360
+ return "By" + paramName.charAt(0).toUpperCase() + paramName.slice(1);
3361
+ }
3362
+ return s.split("-").map((part, i) => i === 0 ? part.charAt(0).toUpperCase() + part.slice(1) : part.charAt(0).toUpperCase() + part.slice(1)).join("");
3363
+ });
3364
+ return method + segments.join("");
3365
+ }
3366
+
2107
3367
  // src/registry/server.ts
2108
3368
  function stripInternal(card) {
2109
3369
  const { _internal: _, ...publicCard } = card;
@@ -2112,25 +3372,76 @@ function stripInternal(card) {
2112
3372
  function createRegistryServer(opts) {
2113
3373
  const { registryDb: db, silent = false } = opts;
2114
3374
  const server = Fastify2({ logger: !silent });
3375
+ void server.register(swagger, {
3376
+ openapi: {
3377
+ openapi: "3.0.3",
3378
+ info: {
3379
+ title: "AgentBnB Registry API",
3380
+ description: "P2P Agent Capability Sharing Protocol \u2014 discover, publish, and exchange agent capabilities",
3381
+ version: "3.1.6"
3382
+ },
3383
+ servers: [{ url: "/", description: "Registry server" }],
3384
+ tags: [
3385
+ { name: "cards", description: "Capability card CRUD" },
3386
+ { name: "credits", description: "Credit hold/settle/release (Ed25519 auth required)" },
3387
+ { name: "agents", description: "Agent profiles and reputation" },
3388
+ { name: "identity", description: "Agent identity and guarantor registration" },
3389
+ { name: "owner", description: "Owner-only endpoints (Bearer auth required)" },
3390
+ { name: "system", description: "Health and stats" },
3391
+ { name: "pricing", description: "Market pricing statistics" }
3392
+ ],
3393
+ components: {
3394
+ securitySchemes: {
3395
+ bearerAuth: { type: "http", scheme: "bearer" },
3396
+ ed25519Auth: {
3397
+ type: "apiKey",
3398
+ in: "header",
3399
+ name: "X-Agent-PublicKey",
3400
+ description: "Ed25519 public key (hex). Also requires X-Agent-Signature and X-Agent-Timestamp headers."
3401
+ }
3402
+ }
3403
+ }
3404
+ }
3405
+ });
3406
+ void server.register(swaggerUi, {
3407
+ routePrefix: "/docs",
3408
+ uiConfig: { docExpansion: "list", deepLinking: true }
3409
+ });
2115
3410
  void server.register(cors, {
2116
3411
  origin: true,
2117
3412
  methods: ["GET", "POST", "PATCH", "OPTIONS"],
2118
- allowedHeaders: ["Content-Type", "Authorization"]
3413
+ allowedHeaders: ["Content-Type", "Authorization", "X-Agent-PublicKey", "X-Agent-Signature", "X-Agent-Timestamp"]
2119
3414
  });
2120
3415
  void server.register(fastifyWebsocket);
2121
3416
  let relayState = null;
2122
3417
  if (opts.creditDb) {
2123
- relayState = registerWebSocketRelay(server, db);
3418
+ relayState = registerWebSocketRelay(server, db, opts.creditDb);
3419
+ }
3420
+ if (opts.creditDb) {
3421
+ void server.register(creditRoutesPlugin, { creditDb: opts.creditDb });
3422
+ }
3423
+ if (opts.creditDb) {
3424
+ void server.register(hubAgentRoutesPlugin, { registryDb: db, creditDb: opts.creditDb });
3425
+ if (relayState?.setOnAgentOnline && relayState.getConnections && relayState.getPendingRequests && relayState.sendMessage) {
3426
+ const bridge = createRelayBridge({
3427
+ registryDb: db,
3428
+ creditDb: opts.creditDb,
3429
+ sendMessage: relayState.sendMessage,
3430
+ pendingRequests: relayState.getPendingRequests(),
3431
+ connections: relayState.getConnections()
3432
+ });
3433
+ relayState.setOnAgentOnline(bridge.onAgentOnline);
3434
+ }
2124
3435
  }
2125
3436
  const __filename = fileURLToPath(import.meta.url);
2126
3437
  const __dirname = dirname(__filename);
2127
3438
  const hubDistCandidates = [
2128
- join3(__dirname, "../../hub/dist"),
3439
+ join2(__dirname, "../../hub/dist"),
2129
3440
  // When running from dist/registry/server.js
2130
- join3(__dirname, "../../../hub/dist")
3441
+ join2(__dirname, "../../../hub/dist")
2131
3442
  // Fallback for alternative layouts
2132
3443
  ];
2133
- const hubDistDir = hubDistCandidates.find((p) => existsSync4(p));
3444
+ const hubDistDir = hubDistCandidates.find((p) => existsSync3(p));
2134
3445
  if (hubDistDir) {
2135
3446
  void server.register(fastifyStatic, {
2136
3447
  root: hubDistDir,
@@ -2149,44 +3460,82 @@ function createRegistryServer(opts) {
2149
3460
  return reply.code(404).send({ error: "Not found" });
2150
3461
  });
2151
3462
  }
2152
- server.get("/health", async (_request, reply) => {
2153
- return reply.send({ status: "ok" });
2154
- });
2155
- server.get("/cards", async (request, reply) => {
2156
- const query = request.query;
2157
- const q = query.q?.trim() ?? "";
2158
- const levelRaw = query.level !== void 0 ? parseInt(query.level, 10) : void 0;
2159
- const level = levelRaw === 1 || levelRaw === 2 || levelRaw === 3 ? levelRaw : void 0;
2160
- const onlineRaw = query.online;
2161
- const online = onlineRaw === "true" ? true : onlineRaw === "false" ? false : void 0;
2162
- const tag = query.tag?.trim();
2163
- const minSuccessRate = query.min_success_rate !== void 0 ? parseFloat(query.min_success_rate) : void 0;
2164
- const maxLatencyMs = query.max_latency_ms !== void 0 ? parseFloat(query.max_latency_ms) : void 0;
2165
- const sort = query.sort;
2166
- const rawLimit = query.limit !== void 0 ? parseInt(query.limit, 10) : 20;
2167
- const limit = Math.min(isNaN(rawLimit) || rawLimit < 1 ? 20 : rawLimit, 100);
2168
- const rawOffset = query.offset !== void 0 ? parseInt(query.offset, 10) : 0;
2169
- const offset = isNaN(rawOffset) || rawOffset < 0 ? 0 : rawOffset;
2170
- let cards;
2171
- if (q.length > 0) {
2172
- cards = searchCards(db, q, { level, online });
2173
- } else {
2174
- cards = filterCards(db, { level, online });
2175
- }
2176
- if (tag !== void 0 && tag.length > 0) {
2177
- cards = cards.filter((c) => c.metadata?.tags?.includes(tag));
2178
- }
2179
- if (minSuccessRate !== void 0 && !isNaN(minSuccessRate)) {
2180
- cards = cards.filter(
2181
- (c) => (c.metadata?.success_rate ?? -1) >= minSuccessRate
2182
- );
2183
- }
2184
- if (maxLatencyMs !== void 0 && !isNaN(maxLatencyMs)) {
2185
- cards = cards.filter(
2186
- (c) => (c.metadata?.avg_latency_ms ?? Infinity) <= maxLatencyMs
2187
- );
2188
- }
2189
- const usesStmt = db.prepare(`
3463
+ void server.register(async (api) => {
3464
+ api.get("/health", {
3465
+ schema: {
3466
+ tags: ["system"],
3467
+ summary: "Liveness probe",
3468
+ response: { 200: { type: "object", properties: { status: { type: "string" } } } }
3469
+ }
3470
+ }, async (_request, reply) => {
3471
+ return reply.send({ status: "ok" });
3472
+ });
3473
+ api.get("/cards", {
3474
+ schema: {
3475
+ tags: ["cards"],
3476
+ summary: "List and search capability cards",
3477
+ querystring: {
3478
+ type: "object",
3479
+ properties: {
3480
+ q: { type: "string", description: "Full-text search query" },
3481
+ level: { type: "integer", enum: [1, 2, 3], description: "Capability level filter" },
3482
+ online: { type: "string", enum: ["true", "false"], description: "Availability filter" },
3483
+ tag: { type: "string", description: "Filter by metadata tag" },
3484
+ min_success_rate: { type: "number", description: "Minimum success rate (0-1)" },
3485
+ max_latency_ms: { type: "number", description: "Maximum average latency in ms" },
3486
+ sort: { type: "string", enum: ["popular", "rated", "success_rate", "cheapest", "newest", "latency"], description: "Sort order" },
3487
+ limit: { type: "integer", default: 20, description: "Max items per page (max 100)" },
3488
+ offset: { type: "integer", default: 0, description: "Pagination offset" }
3489
+ }
3490
+ },
3491
+ response: {
3492
+ 200: {
3493
+ type: "object",
3494
+ properties: {
3495
+ total: { type: "integer" },
3496
+ limit: { type: "integer" },
3497
+ offset: { type: "integer" },
3498
+ items: { type: "array" },
3499
+ uses_this_week: { type: "object", additionalProperties: { type: "number" } }
3500
+ }
3501
+ }
3502
+ }
3503
+ }
3504
+ }, async (request, reply) => {
3505
+ const query = request.query;
3506
+ const q = query.q?.trim() ?? "";
3507
+ const levelRaw = query.level !== void 0 ? parseInt(query.level, 10) : void 0;
3508
+ const level = levelRaw === 1 || levelRaw === 2 || levelRaw === 3 ? levelRaw : void 0;
3509
+ const onlineRaw = query.online;
3510
+ const online = onlineRaw === "true" ? true : onlineRaw === "false" ? false : void 0;
3511
+ const tag = query.tag?.trim();
3512
+ const minSuccessRate = query.min_success_rate !== void 0 ? parseFloat(query.min_success_rate) : void 0;
3513
+ const maxLatencyMs = query.max_latency_ms !== void 0 ? parseFloat(query.max_latency_ms) : void 0;
3514
+ const sort = query.sort;
3515
+ const rawLimit = query.limit !== void 0 ? parseInt(query.limit, 10) : 20;
3516
+ const limit = Math.min(isNaN(rawLimit) || rawLimit < 1 ? 20 : rawLimit, 100);
3517
+ const rawOffset = query.offset !== void 0 ? parseInt(query.offset, 10) : 0;
3518
+ const offset = isNaN(rawOffset) || rawOffset < 0 ? 0 : rawOffset;
3519
+ let cards;
3520
+ if (q.length > 0) {
3521
+ cards = searchCards(db, q, { level, online });
3522
+ } else {
3523
+ cards = filterCards(db, { level, online });
3524
+ }
3525
+ if (tag !== void 0 && tag.length > 0) {
3526
+ cards = cards.filter((c) => c.metadata?.tags?.includes(tag));
3527
+ }
3528
+ if (minSuccessRate !== void 0 && !isNaN(minSuccessRate)) {
3529
+ cards = cards.filter(
3530
+ (c) => (c.metadata?.success_rate ?? -1) >= minSuccessRate
3531
+ );
3532
+ }
3533
+ if (maxLatencyMs !== void 0 && !isNaN(maxLatencyMs)) {
3534
+ cards = cards.filter(
3535
+ (c) => (c.metadata?.avg_latency_ms ?? Infinity) <= maxLatencyMs
3536
+ );
3537
+ }
3538
+ const usesStmt = db.prepare(`
2190
3539
  SELECT card_id, skill_id, COUNT(*) as cnt
2191
3540
  FROM request_log
2192
3541
  WHERE status = 'success'
@@ -2194,57 +3543,63 @@ function createRegistryServer(opts) {
2194
3543
  AND (action_type IS NULL OR action_type = 'auto_share')
2195
3544
  GROUP BY card_id, skill_id
2196
3545
  `);
2197
- const usesRows = usesStmt.all();
2198
- const usesMap = /* @__PURE__ */ new Map();
2199
- for (const row of usesRows) {
2200
- usesMap.set(row.card_id, (usesMap.get(row.card_id) ?? 0) + row.cnt);
2201
- if (row.skill_id) {
2202
- usesMap.set(row.skill_id, (usesMap.get(row.skill_id) ?? 0) + row.cnt);
2203
- }
2204
- }
2205
- if (sort === "popular") {
2206
- cards = [...cards].sort((a, b) => {
2207
- const aUses = usesMap.get(a.id) ?? 0;
2208
- const bUses = usesMap.get(b.id) ?? 0;
2209
- return bUses - aUses;
2210
- });
2211
- } else if (sort === "rated" || sort === "success_rate") {
2212
- cards = [...cards].sort((a, b) => {
2213
- const aRate = a.metadata?.success_rate ?? -1;
2214
- const bRate = b.metadata?.success_rate ?? -1;
2215
- return bRate - aRate;
2216
- });
2217
- } else if (sort === "cheapest") {
2218
- cards = [...cards].sort((a, b) => {
2219
- return a.pricing.credits_per_call - b.pricing.credits_per_call;
2220
- });
2221
- } else if (sort === "newest") {
2222
- const createdStmt = db.prepare("SELECT id, created_at FROM capability_cards");
2223
- const createdRows = createdStmt.all();
2224
- const createdMap = new Map(createdRows.map((r) => [r.id, r.created_at]));
2225
- cards = [...cards].sort((a, b) => {
2226
- const aDate = createdMap.get(a.id) ?? "";
2227
- const bDate = createdMap.get(b.id) ?? "";
2228
- return bDate.localeCompare(aDate);
2229
- });
2230
- } else if (sort === "latency") {
2231
- cards = [...cards].sort((a, b) => {
2232
- const aLatency = a.metadata?.avg_latency_ms ?? Infinity;
2233
- const bLatency = b.metadata?.avg_latency_ms ?? Infinity;
2234
- return aLatency - bLatency;
2235
- });
2236
- }
2237
- const total = cards.length;
2238
- const items = cards.slice(offset, offset + limit).map(stripInternal);
2239
- const usesThisWeek = {};
2240
- for (const [key, count] of usesMap) {
2241
- if (count > 0) usesThisWeek[key] = count;
2242
- }
2243
- const result = { total, limit, offset, items, uses_this_week: usesThisWeek };
2244
- return reply.send(result);
2245
- });
2246
- server.get("/api/cards/trending", async (_request, reply) => {
2247
- const trendingStmt = db.prepare(`
3546
+ const usesRows = usesStmt.all();
3547
+ const usesMap = /* @__PURE__ */ new Map();
3548
+ for (const row of usesRows) {
3549
+ usesMap.set(row.card_id, (usesMap.get(row.card_id) ?? 0) + row.cnt);
3550
+ if (row.skill_id) {
3551
+ usesMap.set(row.skill_id, (usesMap.get(row.skill_id) ?? 0) + row.cnt);
3552
+ }
3553
+ }
3554
+ if (sort === "popular") {
3555
+ cards = [...cards].sort((a, b) => {
3556
+ const aUses = usesMap.get(a.id) ?? 0;
3557
+ const bUses = usesMap.get(b.id) ?? 0;
3558
+ return bUses - aUses;
3559
+ });
3560
+ } else if (sort === "rated" || sort === "success_rate") {
3561
+ cards = [...cards].sort((a, b) => {
3562
+ const aRate = a.metadata?.success_rate ?? -1;
3563
+ const bRate = b.metadata?.success_rate ?? -1;
3564
+ return bRate - aRate;
3565
+ });
3566
+ } else if (sort === "cheapest") {
3567
+ cards = [...cards].sort((a, b) => {
3568
+ return a.pricing.credits_per_call - b.pricing.credits_per_call;
3569
+ });
3570
+ } else if (sort === "newest") {
3571
+ const createdStmt = db.prepare("SELECT id, created_at FROM capability_cards");
3572
+ const createdRows = createdStmt.all();
3573
+ const createdMap = new Map(createdRows.map((r) => [r.id, r.created_at]));
3574
+ cards = [...cards].sort((a, b) => {
3575
+ const aDate = createdMap.get(a.id) ?? "";
3576
+ const bDate = createdMap.get(b.id) ?? "";
3577
+ return bDate.localeCompare(aDate);
3578
+ });
3579
+ } else if (sort === "latency") {
3580
+ cards = [...cards].sort((a, b) => {
3581
+ const aLatency = a.metadata?.avg_latency_ms ?? Infinity;
3582
+ const bLatency = b.metadata?.avg_latency_ms ?? Infinity;
3583
+ return aLatency - bLatency;
3584
+ });
3585
+ }
3586
+ const total = cards.length;
3587
+ const items = cards.slice(offset, offset + limit).map(stripInternal);
3588
+ const usesThisWeek = {};
3589
+ for (const [key, count] of usesMap) {
3590
+ if (count > 0) usesThisWeek[key] = count;
3591
+ }
3592
+ const result = { total, limit, offset, items, uses_this_week: usesThisWeek };
3593
+ return reply.send(result);
3594
+ });
3595
+ api.get("/api/cards/trending", {
3596
+ schema: {
3597
+ tags: ["cards"],
3598
+ summary: "Top 10 trending skills by recent usage",
3599
+ response: { 200: { type: "object", properties: { items: { type: "array" } } } }
3600
+ }
3601
+ }, async (_request, reply) => {
3602
+ const trendingStmt = db.prepare(`
2248
3603
  SELECT rl.card_id, COUNT(*) as recent_requests
2249
3604
  FROM request_log rl
2250
3605
  WHERE rl.status = 'success'
@@ -2254,135 +3609,226 @@ function createRegistryServer(opts) {
2254
3609
  ORDER BY recent_requests DESC
2255
3610
  LIMIT 10
2256
3611
  `);
2257
- const trendingRows = trendingStmt.all();
2258
- const items = trendingRows.map((row) => {
2259
- const card = getCard(db, row.card_id);
2260
- if (!card) return null;
2261
- return { ...stripInternal(card), uses_this_week: row.recent_requests };
2262
- }).filter((item) => item !== null);
2263
- return reply.send({ items });
2264
- });
2265
- server.get("/cards/:id", async (request, reply) => {
2266
- const { id } = request.params;
2267
- const card = getCard(db, id);
2268
- if (!card) {
2269
- return reply.code(404).send({ error: "Not found" });
2270
- }
2271
- return reply.send(stripInternal(card));
2272
- });
2273
- server.post("/cards", async (request, reply) => {
2274
- const body = request.body;
2275
- if (!body.spec_version) {
2276
- body.spec_version = "1.0";
2277
- }
2278
- const result = AnyCardSchema.safeParse(body);
2279
- if (!result.success) {
2280
- return reply.code(400).send({
2281
- error: "Card validation failed",
2282
- issues: result.error.issues
2283
- });
2284
- }
2285
- const card = result.data;
2286
- const now = (/* @__PURE__ */ new Date()).toISOString();
2287
- if (card.spec_version === "2.0") {
2288
- const cardWithTimestamps = {
2289
- ...card,
2290
- created_at: card.created_at ?? now,
2291
- updated_at: now
2292
- };
2293
- db.prepare(
2294
- `INSERT OR REPLACE INTO capability_cards (id, owner, data, created_at, updated_at)
3612
+ const trendingRows = trendingStmt.all();
3613
+ const items = trendingRows.map((row) => {
3614
+ const card = getCard(db, row.card_id);
3615
+ if (!card) return null;
3616
+ return { ...stripInternal(card), uses_this_week: row.recent_requests };
3617
+ }).filter((item) => item !== null);
3618
+ return reply.send({ items });
3619
+ });
3620
+ api.get("/api/pricing", {
3621
+ schema: {
3622
+ tags: ["pricing"],
3623
+ summary: "Aggregate pricing statistics for skills matching a query",
3624
+ querystring: {
3625
+ type: "object",
3626
+ properties: { q: { type: "string", description: "Search query (required)" } },
3627
+ required: ["q"]
3628
+ },
3629
+ response: {
3630
+ 200: {
3631
+ type: "object",
3632
+ properties: {
3633
+ query: { type: "string" },
3634
+ min: { type: "number" },
3635
+ max: { type: "number" },
3636
+ median: { type: "number" },
3637
+ mean: { type: "number" },
3638
+ count: { type: "integer" }
3639
+ }
3640
+ },
3641
+ 400: { type: "object", properties: { error: { type: "string" } } }
3642
+ }
3643
+ }
3644
+ }, async (request, reply) => {
3645
+ const query = request.query;
3646
+ const q = query.q?.trim();
3647
+ if (!q) {
3648
+ return reply.code(400).send({ error: "q parameter is required" });
3649
+ }
3650
+ const stats = getPricingStats(db, q);
3651
+ return reply.send({ query: q, ...stats });
3652
+ });
3653
+ api.get("/cards/:id", {
3654
+ schema: {
3655
+ tags: ["cards"],
3656
+ summary: "Get a capability card by ID",
3657
+ params: { type: "object", properties: { id: { type: "string" } }, required: ["id"] },
3658
+ response: {
3659
+ 200: { type: "object", additionalProperties: true },
3660
+ 404: { type: "object", properties: { error: { type: "string" } } }
3661
+ }
3662
+ }
3663
+ }, async (request, reply) => {
3664
+ const { id } = request.params;
3665
+ const card = getCard(db, id);
3666
+ if (!card) {
3667
+ return reply.code(404).send({ error: "Not found" });
3668
+ }
3669
+ return reply.send(stripInternal(card));
3670
+ });
3671
+ api.post("/cards", {
3672
+ schema: {
3673
+ tags: ["cards"],
3674
+ summary: "Publish a capability card",
3675
+ body: { type: "object", additionalProperties: true, description: "Capability card JSON (v1.0 or v2.0)" },
3676
+ response: {
3677
+ 201: { type: "object", properties: { ok: { type: "boolean" }, id: { type: "string" } } },
3678
+ 400: { type: "object", properties: { error: { type: "string" }, issues: { type: "array" } } }
3679
+ }
3680
+ }
3681
+ }, async (request, reply) => {
3682
+ const body = request.body;
3683
+ if (!body.spec_version) {
3684
+ body.spec_version = "1.0";
3685
+ }
3686
+ const result = AnyCardSchema.safeParse(body);
3687
+ if (!result.success) {
3688
+ return reply.code(400).send({
3689
+ error: "Card validation failed",
3690
+ issues: result.error.issues
3691
+ });
3692
+ }
3693
+ const card = result.data;
3694
+ const now = (/* @__PURE__ */ new Date()).toISOString();
3695
+ if (card.spec_version === "2.0") {
3696
+ const cardWithTimestamps = {
3697
+ ...card,
3698
+ created_at: card.created_at ?? now,
3699
+ updated_at: now
3700
+ };
3701
+ db.prepare(
3702
+ `INSERT OR REPLACE INTO capability_cards (id, owner, data, created_at, updated_at)
2295
3703
  VALUES (?, ?, ?, ?, ?)`
2296
- ).run(
2297
- cardWithTimestamps.id,
2298
- cardWithTimestamps.owner,
2299
- JSON.stringify(cardWithTimestamps),
2300
- cardWithTimestamps.created_at,
2301
- cardWithTimestamps.updated_at
2302
- );
2303
- } else {
2304
- try {
2305
- insertCard(db, card);
2306
- } catch (err) {
2307
- if (err instanceof AgentBnBError && err.code === "VALIDATION_ERROR") {
2308
- return reply.code(400).send({ error: err.message });
3704
+ ).run(
3705
+ cardWithTimestamps.id,
3706
+ cardWithTimestamps.owner,
3707
+ JSON.stringify(cardWithTimestamps),
3708
+ cardWithTimestamps.created_at,
3709
+ cardWithTimestamps.updated_at
3710
+ );
3711
+ } else {
3712
+ try {
3713
+ insertCard(db, card);
3714
+ } catch (err) {
3715
+ if (err instanceof AgentBnBError && err.code === "VALIDATION_ERROR") {
3716
+ return reply.code(400).send({ error: err.message });
3717
+ }
3718
+ throw err;
2309
3719
  }
2310
- throw err;
2311
3720
  }
2312
- }
2313
- return reply.code(201).send({ ok: true, id: card.id });
2314
- });
2315
- server.delete("/cards/:id", async (request, reply) => {
2316
- const { id } = request.params;
2317
- const card = getCard(db, id);
2318
- if (!card) {
2319
- return reply.code(404).send({ error: "Not found" });
2320
- }
2321
- db.prepare("DELETE FROM capability_cards WHERE id = ?").run(id);
2322
- return reply.send({ ok: true, id });
2323
- });
2324
- server.get("/api/agents", async (_request, reply) => {
2325
- const allCards = listCards(db);
2326
- const ownerMap = /* @__PURE__ */ new Map();
2327
- for (const card of allCards) {
2328
- const existing = ownerMap.get(card.owner) ?? [];
2329
- existing.push(card);
2330
- ownerMap.set(card.owner, existing);
2331
- }
2332
- const creditsStmt = db.prepare(`
3721
+ return reply.code(201).send({ ok: true, id: card.id });
3722
+ });
3723
+ api.delete("/cards/:id", {
3724
+ schema: {
3725
+ tags: ["cards"],
3726
+ summary: "Delete a capability card",
3727
+ params: { type: "object", properties: { id: { type: "string" } }, required: ["id"] },
3728
+ response: {
3729
+ 200: { type: "object", properties: { ok: { type: "boolean" }, id: { type: "string" } } },
3730
+ 404: { type: "object", properties: { error: { type: "string" } } }
3731
+ }
3732
+ }
3733
+ }, async (request, reply) => {
3734
+ const { id } = request.params;
3735
+ const card = getCard(db, id);
3736
+ if (!card) {
3737
+ return reply.code(404).send({ error: "Not found" });
3738
+ }
3739
+ db.prepare("DELETE FROM capability_cards WHERE id = ?").run(id);
3740
+ return reply.send({ ok: true, id });
3741
+ });
3742
+ api.get("/api/agents", {
3743
+ schema: {
3744
+ tags: ["agents"],
3745
+ summary: "List all agent profiles sorted by reputation",
3746
+ response: {
3747
+ 200: {
3748
+ type: "object",
3749
+ properties: { items: { type: "array" }, total: { type: "integer" } }
3750
+ }
3751
+ }
3752
+ }
3753
+ }, async (_request, reply) => {
3754
+ const allCards = listCards(db);
3755
+ const ownerMap = /* @__PURE__ */ new Map();
3756
+ for (const card of allCards) {
3757
+ const existing = ownerMap.get(card.owner) ?? [];
3758
+ existing.push(card);
3759
+ ownerMap.set(card.owner, existing);
3760
+ }
3761
+ const creditsStmt = db.prepare(`
2333
3762
  SELECT cc.owner,
2334
3763
  SUM(CASE WHEN rl.status = 'success' THEN rl.credits_charged ELSE 0 END) as credits_earned
2335
3764
  FROM capability_cards cc
2336
3765
  LEFT JOIN request_log rl ON rl.card_id = cc.id
2337
3766
  GROUP BY cc.owner
2338
3767
  `);
2339
- const creditsRows = creditsStmt.all();
2340
- const creditsMap = new Map(creditsRows.map((r) => [r.owner, r.credits_earned ?? 0]));
2341
- const agents = Array.from(ownerMap.entries()).map(([owner, cards]) => {
2342
- const skillCount = cards.reduce((sum, card) => sum + (card.skills?.length ?? 1), 0);
2343
- const successRates = cards.map((c) => c.metadata?.success_rate).filter((r) => r != null);
2344
- const avgSuccessRate = successRates.length > 0 ? successRates.reduce((a, b) => a + b, 0) / successRates.length : null;
2345
- const memberStmt = db.prepare(
2346
- "SELECT MIN(created_at) as earliest FROM capability_cards WHERE owner = ?"
2347
- );
2348
- const memberRow = memberStmt.get(owner);
2349
- return {
2350
- owner,
2351
- skill_count: skillCount,
2352
- success_rate: avgSuccessRate,
2353
- total_earned: creditsMap.get(owner) ?? 0,
2354
- member_since: memberRow?.earliest ?? (/* @__PURE__ */ new Date()).toISOString()
2355
- };
2356
- });
2357
- agents.sort((a, b) => {
2358
- const aRate = a.success_rate ?? -1;
2359
- const bRate = b.success_rate ?? -1;
2360
- if (bRate !== aRate) return bRate - aRate;
2361
- return b.total_earned - a.total_earned;
3768
+ const creditsRows = creditsStmt.all();
3769
+ const creditsMap = new Map(creditsRows.map((r) => [r.owner, r.credits_earned ?? 0]));
3770
+ const agents = Array.from(ownerMap.entries()).map(([owner, cards]) => {
3771
+ const skillCount = cards.reduce((sum, card) => sum + (card.skills?.length ?? 1), 0);
3772
+ const successRates = cards.map((c) => c.metadata?.success_rate).filter((r) => r != null);
3773
+ const avgSuccessRate = successRates.length > 0 ? successRates.reduce((a, b) => a + b, 0) / successRates.length : null;
3774
+ const memberStmt = db.prepare(
3775
+ "SELECT MIN(created_at) as earliest FROM capability_cards WHERE owner = ?"
3776
+ );
3777
+ const memberRow = memberStmt.get(owner);
3778
+ return {
3779
+ owner,
3780
+ skill_count: skillCount,
3781
+ success_rate: avgSuccessRate,
3782
+ total_earned: creditsMap.get(owner) ?? 0,
3783
+ member_since: memberRow?.earliest ?? (/* @__PURE__ */ new Date()).toISOString()
3784
+ };
3785
+ });
3786
+ agents.sort((a, b) => {
3787
+ const aRate = a.success_rate ?? -1;
3788
+ const bRate = b.success_rate ?? -1;
3789
+ if (bRate !== aRate) return bRate - aRate;
3790
+ return b.total_earned - a.total_earned;
3791
+ });
3792
+ return reply.send({ items: agents, total: agents.length });
2362
3793
  });
2363
- return reply.send({ items: agents, total: agents.length });
2364
- });
2365
- server.get("/api/agents/:owner", async (request, reply) => {
2366
- const { owner } = request.params;
2367
- const ownerCards = listCards(db, owner);
2368
- if (ownerCards.length === 0) {
2369
- return reply.status(404).send({ error: "Agent not found" });
2370
- }
2371
- const skillCount = ownerCards.reduce((sum, card) => sum + (card.skills?.length ?? 1), 0);
2372
- const successRates = ownerCards.map((c) => c.metadata?.success_rate).filter((r) => r != null);
2373
- const avgSuccessRate = successRates.length > 0 ? successRates.reduce((a, b) => a + b, 0) / successRates.length : null;
2374
- const creditsStmt = db.prepare(`
3794
+ api.get("/api/agents/:owner", {
3795
+ schema: {
3796
+ tags: ["agents"],
3797
+ summary: "Get agent profile, skills, and recent activity",
3798
+ params: { type: "object", properties: { owner: { type: "string" } }, required: ["owner"] },
3799
+ response: {
3800
+ 200: {
3801
+ type: "object",
3802
+ properties: {
3803
+ profile: { type: "object", additionalProperties: true },
3804
+ skills: { type: "array" },
3805
+ recent_activity: { type: "array" }
3806
+ }
3807
+ },
3808
+ 404: { type: "object", properties: { error: { type: "string" } } }
3809
+ }
3810
+ }
3811
+ }, async (request, reply) => {
3812
+ const { owner } = request.params;
3813
+ const ownerCards = listCards(db, owner);
3814
+ if (ownerCards.length === 0) {
3815
+ return reply.status(404).send({ error: "Agent not found" });
3816
+ }
3817
+ const skillCount = ownerCards.reduce((sum, card) => sum + (card.skills?.length ?? 1), 0);
3818
+ const successRates = ownerCards.map((c) => c.metadata?.success_rate).filter((r) => r != null);
3819
+ const avgSuccessRate = successRates.length > 0 ? successRates.reduce((a, b) => a + b, 0) / successRates.length : null;
3820
+ const creditsStmt = db.prepare(`
2375
3821
  SELECT SUM(CASE WHEN rl.status = 'success' THEN rl.credits_charged ELSE 0 END) as credits_earned
2376
3822
  FROM capability_cards cc
2377
3823
  LEFT JOIN request_log rl ON rl.card_id = cc.id
2378
3824
  WHERE cc.owner = ?
2379
3825
  `);
2380
- const creditsRow = creditsStmt.get(owner);
2381
- const memberStmt = db.prepare(
2382
- "SELECT MIN(created_at) as earliest FROM capability_cards WHERE owner = ?"
2383
- );
2384
- const memberRow = memberStmt.get(owner);
2385
- const activityStmt = db.prepare(`
3826
+ const creditsRow = creditsStmt.get(owner);
3827
+ const memberStmt = db.prepare(
3828
+ "SELECT MIN(created_at) as earliest FROM capability_cards WHERE owner = ?"
3829
+ );
3830
+ const memberRow = memberStmt.get(owner);
3831
+ const activityStmt = db.prepare(`
2386
3832
  SELECT rl.id, rl.card_name, rl.requester, rl.status, rl.credits_charged, rl.created_at
2387
3833
  FROM request_log rl
2388
3834
  INNER JOIN capability_cards cc ON rl.card_id = cc.id
@@ -2390,214 +3836,429 @@ function createRegistryServer(opts) {
2390
3836
  ORDER BY rl.created_at DESC
2391
3837
  LIMIT 10
2392
3838
  `);
2393
- const recentActivity = activityStmt.all(owner);
2394
- const profile = {
2395
- owner,
2396
- skill_count: skillCount,
2397
- success_rate: avgSuccessRate,
2398
- total_earned: creditsRow?.credits_earned ?? 0,
2399
- member_since: memberRow?.earliest ?? (/* @__PURE__ */ new Date()).toISOString()
2400
- };
2401
- return reply.send({
2402
- profile,
2403
- skills: ownerCards,
2404
- recent_activity: recentActivity
3839
+ const recentActivity = activityStmt.all(owner);
3840
+ const profile = {
3841
+ owner,
3842
+ skill_count: skillCount,
3843
+ success_rate: avgSuccessRate,
3844
+ total_earned: creditsRow?.credits_earned ?? 0,
3845
+ member_since: memberRow?.earliest ?? (/* @__PURE__ */ new Date()).toISOString()
3846
+ };
3847
+ return reply.send({
3848
+ profile,
3849
+ skills: ownerCards,
3850
+ recent_activity: recentActivity
3851
+ });
2405
3852
  });
2406
- });
2407
- server.get("/api/activity", async (request, reply) => {
2408
- const query = request.query;
2409
- const rawLimit = query.limit !== void 0 ? parseInt(query.limit, 10) : 20;
2410
- const limit = Math.min(isNaN(rawLimit) || rawLimit < 1 ? 20 : rawLimit, 100);
2411
- const since = query.since?.trim() || void 0;
2412
- const items = getActivityFeed(db, limit, since);
2413
- return reply.send({ items, total: items.length, limit });
2414
- });
2415
- server.get("/api/stats", async (_request, reply) => {
2416
- const allCards = listCards(db);
2417
- const onlineOwners = /* @__PURE__ */ new Set();
2418
- if (relayState) {
2419
- for (const owner of relayState.getOnlineOwners()) {
2420
- onlineOwners.add(owner);
3853
+ api.get("/api/activity", {
3854
+ schema: {
3855
+ tags: ["system"],
3856
+ summary: "Paginated public activity feed of exchange events",
3857
+ querystring: {
3858
+ type: "object",
3859
+ properties: {
3860
+ limit: { type: "integer", default: 20, description: "Max items (max 100)" },
3861
+ since: { type: "string", description: "ISO 8601 timestamp for polling" }
3862
+ }
3863
+ },
3864
+ response: {
3865
+ 200: {
3866
+ type: "object",
3867
+ properties: {
3868
+ items: { type: "array" },
3869
+ total: { type: "integer" },
3870
+ limit: { type: "integer" }
3871
+ }
3872
+ }
3873
+ }
3874
+ }
3875
+ }, async (request, reply) => {
3876
+ const query = request.query;
3877
+ const rawLimit = query.limit !== void 0 ? parseInt(query.limit, 10) : 20;
3878
+ const limit = Math.min(isNaN(rawLimit) || rawLimit < 1 ? 20 : rawLimit, 100);
3879
+ const since = query.since?.trim() || void 0;
3880
+ const items = getActivityFeed(db, limit, since);
3881
+ return reply.send({ items, total: items.length, limit });
3882
+ });
3883
+ api.get("/api/stats", {
3884
+ schema: {
3885
+ tags: ["system"],
3886
+ summary: "Aggregate network statistics",
3887
+ response: {
3888
+ 200: {
3889
+ type: "object",
3890
+ properties: {
3891
+ agents_online: { type: "integer" },
3892
+ total_capabilities: { type: "integer" },
3893
+ total_exchanges: { type: "integer" }
3894
+ }
3895
+ }
3896
+ }
3897
+ }
3898
+ }, async (_request, reply) => {
3899
+ const allCards = listCards(db);
3900
+ const onlineOwners = /* @__PURE__ */ new Set();
3901
+ if (relayState) {
3902
+ for (const owner of relayState.getOnlineOwners()) {
3903
+ onlineOwners.add(owner);
3904
+ }
3905
+ }
3906
+ for (const card of allCards) {
3907
+ if (card.availability.online) {
3908
+ onlineOwners.add(card.owner);
3909
+ }
3910
+ }
3911
+ const exchangeStmt = db.prepare(
3912
+ "SELECT COUNT(*) as count FROM request_log WHERE status = 'success' AND (action_type IS NULL OR action_type = 'auto_share')"
3913
+ );
3914
+ const exchangeRow = exchangeStmt.get();
3915
+ return reply.send({
3916
+ agents_online: onlineOwners.size,
3917
+ total_capabilities: allCards.reduce((sum, card) => {
3918
+ const v2 = card;
3919
+ return sum + (v2.skills?.length ?? 1);
3920
+ }, 0),
3921
+ total_exchanges: exchangeRow.count
3922
+ });
3923
+ });
3924
+ api.post("/api/identity/register", {
3925
+ schema: {
3926
+ tags: ["identity"],
3927
+ summary: "Register a human guarantor via GitHub login",
3928
+ body: {
3929
+ type: "object",
3930
+ properties: { github_login: { type: "string" } },
3931
+ required: ["github_login"]
3932
+ },
3933
+ response: {
3934
+ 201: { type: "object", additionalProperties: true },
3935
+ 400: { type: "object", properties: { error: { type: "string" } } },
3936
+ 409: { type: "object", properties: { error: { type: "string" } } }
3937
+ }
2421
3938
  }
2422
- }
2423
- for (const card of allCards) {
2424
- if (card.availability.online) {
2425
- onlineOwners.add(card.owner);
3939
+ }, async (request, reply) => {
3940
+ if (!opts.creditDb) {
3941
+ return reply.code(503).send({ error: "Credit database not configured" });
2426
3942
  }
2427
- }
2428
- const exchangeStmt = db.prepare(
2429
- "SELECT COUNT(*) as count FROM request_log WHERE status = 'success' AND (action_type IS NULL OR action_type = 'auto_share')"
2430
- );
2431
- const exchangeRow = exchangeStmt.get();
2432
- return reply.send({
2433
- agents_online: onlineOwners.size,
2434
- total_capabilities: allCards.reduce((sum, card) => {
2435
- const v2 = card;
2436
- return sum + (v2.skills?.length ?? 1);
2437
- }, 0),
2438
- total_exchanges: exchangeRow.count
2439
- });
2440
- });
2441
- server.post("/api/identity/register", async (request, reply) => {
2442
- if (!opts.creditDb) {
2443
- return reply.code(503).send({ error: "Credit database not configured" });
2444
- }
2445
- const body = request.body;
2446
- const githubLogin = typeof body.github_login === "string" ? body.github_login.trim() : "";
2447
- if (!githubLogin) {
2448
- return reply.code(400).send({ error: "github_login is required" });
2449
- }
2450
- try {
2451
- const record = registerGuarantor(opts.creditDb, githubLogin);
2452
- const auth = initiateGithubAuth();
2453
- return reply.code(201).send({ guarantor: record, oauth: auth });
2454
- } catch (err) {
2455
- if (err instanceof AgentBnBError && err.code === "GUARANTOR_EXISTS") {
2456
- return reply.code(409).send({ error: err.message });
3943
+ const body = request.body;
3944
+ const githubLogin = typeof body.github_login === "string" ? body.github_login.trim() : "";
3945
+ if (!githubLogin) {
3946
+ return reply.code(400).send({ error: "github_login is required" });
2457
3947
  }
2458
- throw err;
2459
- }
2460
- });
2461
- server.post("/api/identity/link", async (request, reply) => {
2462
- if (!opts.creditDb) {
2463
- return reply.code(503).send({ error: "Credit database not configured" });
2464
- }
2465
- const body = request.body;
2466
- const agentId = typeof body.agent_id === "string" ? body.agent_id.trim() : "";
2467
- const githubLogin = typeof body.github_login === "string" ? body.github_login.trim() : "";
2468
- if (!agentId || !githubLogin) {
2469
- return reply.code(400).send({ error: "agent_id and github_login are required" });
2470
- }
2471
- try {
2472
- const record = linkAgentToGuarantor(opts.creditDb, agentId, githubLogin);
2473
- return reply.send({ guarantor: record });
2474
- } catch (err) {
2475
- if (err instanceof AgentBnBError) {
2476
- const statusMap = {
2477
- GUARANTOR_NOT_FOUND: 404,
2478
- MAX_AGENTS_EXCEEDED: 409,
2479
- AGENT_ALREADY_LINKED: 409
2480
- };
2481
- const status = statusMap[err.code] ?? 400;
2482
- return reply.code(status).send({ error: err.message });
3948
+ try {
3949
+ const record = registerGuarantor(opts.creditDb, githubLogin);
3950
+ const auth = initiateGithubAuth();
3951
+ return reply.code(201).send({ guarantor: record, oauth: auth });
3952
+ } catch (err) {
3953
+ if (err instanceof AgentBnBError && err.code === "GUARANTOR_EXISTS") {
3954
+ return reply.code(409).send({ error: err.message });
3955
+ }
3956
+ throw err;
2483
3957
  }
2484
- throw err;
2485
- }
2486
- });
2487
- server.get("/api/identity/:agent_id", async (request, reply) => {
2488
- if (!opts.creditDb) {
2489
- return reply.code(503).send({ error: "Credit database not configured" });
2490
- }
2491
- const { agent_id } = request.params;
2492
- const guarantor = getAgentGuarantor(opts.creditDb, agent_id);
2493
- return reply.send({ agent_id, guarantor });
2494
- });
2495
- if (opts.ownerApiKey && opts.ownerName) {
2496
- const ownerApiKey = opts.ownerApiKey;
2497
- const ownerName = opts.ownerName;
2498
- void server.register(async (ownerRoutes) => {
2499
- ownerRoutes.addHook("onRequest", async (request, reply) => {
2500
- const auth = request.headers.authorization;
2501
- const token = auth?.startsWith("Bearer ") ? auth.slice(7).trim() : null;
2502
- if (!token || token !== ownerApiKey) {
2503
- return reply.status(401).send({ error: "Unauthorized" });
3958
+ });
3959
+ api.post("/api/identity/link", {
3960
+ schema: {
3961
+ tags: ["identity"],
3962
+ summary: "Link an agent to a human guarantor",
3963
+ body: {
3964
+ type: "object",
3965
+ properties: {
3966
+ agent_id: { type: "string" },
3967
+ github_login: { type: "string" }
3968
+ },
3969
+ required: ["agent_id", "github_login"]
3970
+ },
3971
+ response: {
3972
+ 200: { type: "object", additionalProperties: true },
3973
+ 400: { type: "object", properties: { error: { type: "string" } } },
3974
+ 404: { type: "object", properties: { error: { type: "string" } } },
3975
+ 409: { type: "object", properties: { error: { type: "string" } } }
2504
3976
  }
2505
- });
2506
- ownerRoutes.get("/me", async (_request, reply) => {
2507
- const balance = opts.creditDb ? getBalance(opts.creditDb, ownerName) : 0;
2508
- return reply.send({ owner: ownerName, balance });
2509
- });
2510
- ownerRoutes.get("/requests", async (request, reply) => {
2511
- const query = request.query;
2512
- const rawLimit = query.limit !== void 0 ? parseInt(query.limit, 10) : 10;
2513
- const limit = Math.min(isNaN(rawLimit) || rawLimit < 1 ? 10 : rawLimit, 100);
2514
- const sinceRaw = query.since;
2515
- const validSince = ["24h", "7d", "30d"];
2516
- const since = sinceRaw && validSince.includes(sinceRaw) ? sinceRaw : void 0;
2517
- const items = getRequestLog(db, limit, since);
2518
- return reply.send({ items, limit });
2519
- });
2520
- ownerRoutes.get("/draft", async (_request, reply) => {
2521
- const detectedKeys = detectApiKeys(KNOWN_API_KEYS);
2522
- const cards = detectedKeys.map((key) => buildDraftCard(key, ownerName)).filter((card) => card !== null);
2523
- return reply.send({ cards });
2524
- });
2525
- ownerRoutes.post("/cards/:id/toggle-online", async (request, reply) => {
2526
- const { id } = request.params;
2527
- const card = getCard(db, id);
2528
- if (!card) {
2529
- return reply.code(404).send({ error: "Not found" });
3977
+ }
3978
+ }, async (request, reply) => {
3979
+ if (!opts.creditDb) {
3980
+ return reply.code(503).send({ error: "Credit database not configured" });
3981
+ }
3982
+ const body = request.body;
3983
+ const agentId = typeof body.agent_id === "string" ? body.agent_id.trim() : "";
3984
+ const githubLogin = typeof body.github_login === "string" ? body.github_login.trim() : "";
3985
+ if (!agentId || !githubLogin) {
3986
+ return reply.code(400).send({ error: "agent_id and github_login are required" });
3987
+ }
3988
+ try {
3989
+ const record = linkAgentToGuarantor(opts.creditDb, agentId, githubLogin);
3990
+ return reply.send({ guarantor: record });
3991
+ } catch (err) {
3992
+ if (err instanceof AgentBnBError) {
3993
+ const statusMap = {
3994
+ GUARANTOR_NOT_FOUND: 404,
3995
+ MAX_AGENTS_EXCEEDED: 409,
3996
+ AGENT_ALREADY_LINKED: 409
3997
+ };
3998
+ const status = statusMap[err.code] ?? 400;
3999
+ return reply.code(status).send({ error: err.message });
2530
4000
  }
2531
- try {
2532
- const newOnline = !card.availability.online;
2533
- updateCard(db, id, ownerName, {
2534
- availability: { ...card.availability, online: newOnline }
2535
- });
2536
- return reply.send({ ok: true, online: newOnline });
2537
- } catch (err) {
2538
- if (err instanceof AgentBnBError && err.code === "FORBIDDEN") {
2539
- return reply.code(403).send({ error: "Forbidden" });
4001
+ throw err;
4002
+ }
4003
+ });
4004
+ api.get("/api/identity/:agent_id", {
4005
+ schema: {
4006
+ tags: ["identity"],
4007
+ summary: "Get guarantor info for an agent",
4008
+ params: { type: "object", properties: { agent_id: { type: "string" } }, required: ["agent_id"] },
4009
+ response: {
4010
+ 200: {
4011
+ type: "object",
4012
+ properties: {
4013
+ agent_id: { type: "string" },
4014
+ guarantor: { oneOf: [{ type: "object", additionalProperties: true }, { type: "null" }] }
4015
+ }
2540
4016
  }
2541
- throw err;
2542
4017
  }
2543
- });
2544
- ownerRoutes.patch("/cards/:id", async (request, reply) => {
2545
- const { id } = request.params;
2546
- const body = request.body;
2547
- const updates = {};
2548
- if (body.description !== void 0) updates.description = body.description;
2549
- if (body.pricing !== void 0) updates.pricing = body.pricing;
2550
- try {
2551
- updateCard(db, id, ownerName, updates);
2552
- return reply.send({ ok: true });
2553
- } catch (err) {
2554
- if (err instanceof AgentBnBError) {
2555
- if (err.code === "FORBIDDEN") {
4018
+ }
4019
+ }, async (request, reply) => {
4020
+ if (!opts.creditDb) {
4021
+ return reply.code(503).send({ error: "Credit database not configured" });
4022
+ }
4023
+ const { agent_id } = request.params;
4024
+ const guarantor = getAgentGuarantor(opts.creditDb, agent_id);
4025
+ return reply.send({ agent_id, guarantor });
4026
+ });
4027
+ api.get("/api/openapi/gpt-actions", {
4028
+ schema: {
4029
+ tags: ["system"],
4030
+ summary: "GPT Actions-compatible OpenAPI schema",
4031
+ description: "Returns a GPT Builder-importable OpenAPI spec with only public GET/POST endpoints",
4032
+ querystring: {
4033
+ type: "object",
4034
+ properties: {
4035
+ server_url: { type: "string", description: "Base URL for the server (required for absolute URLs in GPT Actions)" }
4036
+ }
4037
+ },
4038
+ response: { 200: { type: "object", additionalProperties: true } }
4039
+ }
4040
+ }, async (request, reply) => {
4041
+ const query = request.query;
4042
+ const serverUrl = query.server_url?.trim() || `${request.protocol}://${request.hostname}`;
4043
+ const openapiSpec = server.swagger();
4044
+ const gptActions = convertToGptActions(openapiSpec, serverUrl);
4045
+ return reply.send(gptActions);
4046
+ });
4047
+ if (opts.ownerApiKey && opts.ownerName) {
4048
+ const ownerApiKey = opts.ownerApiKey;
4049
+ const ownerName = opts.ownerName;
4050
+ void api.register(async (ownerRoutes) => {
4051
+ ownerRoutes.addHook("onRequest", async (request, reply) => {
4052
+ const auth = request.headers.authorization;
4053
+ const token = auth?.startsWith("Bearer ") ? auth.slice(7).trim() : null;
4054
+ if (!token || token !== ownerApiKey) {
4055
+ return reply.status(401).send({ error: "Unauthorized" });
4056
+ }
4057
+ });
4058
+ ownerRoutes.get("/me", {
4059
+ schema: {
4060
+ tags: ["owner"],
4061
+ summary: "Get owner identity and credit balance",
4062
+ security: [{ bearerAuth: [] }],
4063
+ response: {
4064
+ 200: { type: "object", properties: { owner: { type: "string" }, balance: { type: "number" } } }
4065
+ }
4066
+ }
4067
+ }, async (_request, reply) => {
4068
+ let balance = 0;
4069
+ if (opts.creditDb) {
4070
+ const ledger = createLedger({ db: opts.creditDb });
4071
+ balance = await ledger.getBalance(ownerName);
4072
+ }
4073
+ return reply.send({ owner: ownerName, balance });
4074
+ });
4075
+ ownerRoutes.get("/requests", {
4076
+ schema: {
4077
+ tags: ["owner"],
4078
+ summary: "Paginated request log entries",
4079
+ security: [{ bearerAuth: [] }],
4080
+ querystring: {
4081
+ type: "object",
4082
+ properties: {
4083
+ limit: { type: "integer", description: "Max entries (default 10, max 100)" },
4084
+ since: { type: "string", enum: ["24h", "7d", "30d"], description: "Time window" }
4085
+ }
4086
+ },
4087
+ response: { 200: { type: "object", properties: { items: { type: "array" }, limit: { type: "integer" } } } }
4088
+ }
4089
+ }, async (request, reply) => {
4090
+ const query = request.query;
4091
+ const rawLimit = query.limit !== void 0 ? parseInt(query.limit, 10) : 10;
4092
+ const limit = Math.min(isNaN(rawLimit) || rawLimit < 1 ? 10 : rawLimit, 100);
4093
+ const sinceRaw = query.since;
4094
+ const validSince = ["24h", "7d", "30d"];
4095
+ const since = sinceRaw && validSince.includes(sinceRaw) ? sinceRaw : void 0;
4096
+ const items = getRequestLog(db, limit, since);
4097
+ return reply.send({ items, limit });
4098
+ });
4099
+ ownerRoutes.get("/draft", {
4100
+ schema: {
4101
+ tags: ["owner"],
4102
+ summary: "Draft capability cards from auto-detected API keys",
4103
+ security: [{ bearerAuth: [] }],
4104
+ response: { 200: { type: "object", properties: { cards: { type: "array" } } } }
4105
+ }
4106
+ }, async (_request, reply) => {
4107
+ const detectedKeys = detectApiKeys(KNOWN_API_KEYS);
4108
+ const cards = detectedKeys.map((key) => buildDraftCard(key, ownerName)).filter((card) => card !== null);
4109
+ return reply.send({ cards });
4110
+ });
4111
+ ownerRoutes.post("/cards/:id/toggle-online", {
4112
+ schema: {
4113
+ tags: ["owner"],
4114
+ summary: "Toggle card online/offline status",
4115
+ security: [{ bearerAuth: [] }],
4116
+ params: { type: "object", properties: { id: { type: "string" } }, required: ["id"] },
4117
+ response: {
4118
+ 200: { type: "object", properties: { ok: { type: "boolean" }, online: { type: "boolean" } } },
4119
+ 404: { type: "object", properties: { error: { type: "string" } } }
4120
+ }
4121
+ }
4122
+ }, async (request, reply) => {
4123
+ const { id } = request.params;
4124
+ const card = getCard(db, id);
4125
+ if (!card) {
4126
+ return reply.code(404).send({ error: "Not found" });
4127
+ }
4128
+ try {
4129
+ const newOnline = !card.availability.online;
4130
+ updateCard(db, id, ownerName, {
4131
+ availability: { ...card.availability, online: newOnline }
4132
+ });
4133
+ return reply.send({ ok: true, online: newOnline });
4134
+ } catch (err) {
4135
+ if (err instanceof AgentBnBError && err.code === "FORBIDDEN") {
2556
4136
  return reply.code(403).send({ error: "Forbidden" });
2557
4137
  }
2558
- if (err.code === "NOT_FOUND") {
2559
- return reply.code(404).send({ error: "Not found" });
4138
+ throw err;
4139
+ }
4140
+ });
4141
+ ownerRoutes.patch("/cards/:id", {
4142
+ schema: {
4143
+ tags: ["owner"],
4144
+ summary: "Update card description or pricing",
4145
+ security: [{ bearerAuth: [] }],
4146
+ params: { type: "object", properties: { id: { type: "string" } }, required: ["id"] },
4147
+ body: {
4148
+ type: "object",
4149
+ properties: {
4150
+ description: { type: "string" },
4151
+ pricing: { type: "object", additionalProperties: true }
4152
+ },
4153
+ additionalProperties: true
4154
+ },
4155
+ response: {
4156
+ 200: { type: "object", properties: { ok: { type: "boolean" } } },
4157
+ 403: { type: "object", properties: { error: { type: "string" } } },
4158
+ 404: { type: "object", properties: { error: { type: "string" } } }
2560
4159
  }
2561
4160
  }
2562
- throw err;
2563
- }
2564
- });
2565
- ownerRoutes.get("/me/pending-requests", async (_request, reply) => {
2566
- const rows = listPendingRequests(db);
2567
- return reply.send(rows);
2568
- });
2569
- ownerRoutes.post("/me/pending-requests/:id/approve", async (request, reply) => {
2570
- const { id } = request.params;
2571
- try {
2572
- resolvePendingRequest(db, id, "approved");
2573
- return reply.send({ status: "approved", id });
2574
- } catch (err) {
2575
- if (err instanceof AgentBnBError) return reply.status(404).send({ error: err.message });
2576
- throw err;
2577
- }
2578
- });
2579
- ownerRoutes.post("/me/pending-requests/:id/reject", async (request, reply) => {
2580
- const { id } = request.params;
2581
- try {
2582
- resolvePendingRequest(db, id, "rejected");
2583
- return reply.send({ status: "rejected", id });
2584
- } catch (err) {
2585
- if (err instanceof AgentBnBError) return reply.status(404).send({ error: err.message });
2586
- throw err;
2587
- }
2588
- });
2589
- ownerRoutes.get("/me/transactions", async (request, reply) => {
2590
- const query = request.query;
2591
- const rawLimit = query.limit !== void 0 ? parseInt(query.limit, 10) : 20;
2592
- const limit = Math.min(isNaN(rawLimit) || rawLimit < 1 ? 20 : rawLimit, 100);
2593
- if (!opts.creditDb) {
2594
- return reply.send({ items: [], limit });
2595
- }
2596
- const items = getTransactions(opts.creditDb, ownerName, limit);
2597
- return reply.send({ items, limit });
4161
+ }, async (request, reply) => {
4162
+ const { id } = request.params;
4163
+ const body = request.body;
4164
+ const updates = {};
4165
+ if (body.description !== void 0) updates.description = body.description;
4166
+ if (body.pricing !== void 0) updates.pricing = body.pricing;
4167
+ try {
4168
+ updateCard(db, id, ownerName, updates);
4169
+ return reply.send({ ok: true });
4170
+ } catch (err) {
4171
+ if (err instanceof AgentBnBError) {
4172
+ if (err.code === "FORBIDDEN") {
4173
+ return reply.code(403).send({ error: "Forbidden" });
4174
+ }
4175
+ if (err.code === "NOT_FOUND") {
4176
+ return reply.code(404).send({ error: "Not found" });
4177
+ }
4178
+ }
4179
+ throw err;
4180
+ }
4181
+ });
4182
+ ownerRoutes.get("/me/pending-requests", {
4183
+ schema: {
4184
+ tags: ["owner"],
4185
+ summary: "List pending Tier 3 approval queue entries",
4186
+ security: [{ bearerAuth: [] }],
4187
+ response: { 200: { type: "array" } }
4188
+ }
4189
+ }, async (_request, reply) => {
4190
+ const rows = listPendingRequests(db);
4191
+ return reply.send(rows);
4192
+ });
4193
+ ownerRoutes.post("/me/pending-requests/:id/approve", {
4194
+ schema: {
4195
+ tags: ["owner"],
4196
+ summary: "Approve a pending Tier 3 request",
4197
+ security: [{ bearerAuth: [] }],
4198
+ params: { type: "object", properties: { id: { type: "string" } }, required: ["id"] },
4199
+ response: {
4200
+ 200: { type: "object", properties: { status: { type: "string" }, id: { type: "string" } } },
4201
+ 404: { type: "object", properties: { error: { type: "string" } } }
4202
+ }
4203
+ }
4204
+ }, async (request, reply) => {
4205
+ const { id } = request.params;
4206
+ try {
4207
+ resolvePendingRequest(db, id, "approved");
4208
+ return reply.send({ status: "approved", id });
4209
+ } catch (err) {
4210
+ if (err instanceof AgentBnBError) return reply.status(404).send({ error: err.message });
4211
+ throw err;
4212
+ }
4213
+ });
4214
+ ownerRoutes.post("/me/pending-requests/:id/reject", {
4215
+ schema: {
4216
+ tags: ["owner"],
4217
+ summary: "Reject a pending Tier 3 request",
4218
+ security: [{ bearerAuth: [] }],
4219
+ params: { type: "object", properties: { id: { type: "string" } }, required: ["id"] },
4220
+ response: {
4221
+ 200: { type: "object", properties: { status: { type: "string" }, id: { type: "string" } } },
4222
+ 404: { type: "object", properties: { error: { type: "string" } } }
4223
+ }
4224
+ }
4225
+ }, async (request, reply) => {
4226
+ const { id } = request.params;
4227
+ try {
4228
+ resolvePendingRequest(db, id, "rejected");
4229
+ return reply.send({ status: "rejected", id });
4230
+ } catch (err) {
4231
+ if (err instanceof AgentBnBError) return reply.status(404).send({ error: err.message });
4232
+ throw err;
4233
+ }
4234
+ });
4235
+ ownerRoutes.get("/me/transactions", {
4236
+ schema: {
4237
+ tags: ["owner"],
4238
+ summary: "Paginated credit transaction history",
4239
+ security: [{ bearerAuth: [] }],
4240
+ querystring: {
4241
+ type: "object",
4242
+ properties: { limit: { type: "integer", description: "Max entries (default 20, max 100)" } }
4243
+ },
4244
+ response: {
4245
+ 200: { type: "object", properties: { items: { type: "array" }, limit: { type: "integer" } } }
4246
+ }
4247
+ }
4248
+ }, async (request, reply) => {
4249
+ const query = request.query;
4250
+ const rawLimit = query.limit !== void 0 ? parseInt(query.limit, 10) : 20;
4251
+ const limit = Math.min(isNaN(rawLimit) || rawLimit < 1 ? 20 : rawLimit, 100);
4252
+ if (!opts.creditDb) {
4253
+ return reply.send({ items: [], limit });
4254
+ }
4255
+ const ledger = createLedger({ db: opts.creditDb });
4256
+ const items = await ledger.getHistory(ownerName, limit);
4257
+ return reply.send({ items, limit });
4258
+ });
2598
4259
  });
2599
- });
2600
- }
4260
+ }
4261
+ });
2601
4262
  return { server, relayState };
2602
4263
  }
2603
4264
 
@@ -2672,10 +4333,10 @@ async function stopAnnouncement() {
2672
4333
  }
2673
4334
 
2674
4335
  // src/openclaw/soul-sync.ts
2675
- import { randomUUID as randomUUID7 } from "crypto";
4336
+ import { randomUUID as randomUUID9 } from "crypto";
2676
4337
 
2677
4338
  // src/skills/publish-capability.ts
2678
- import { randomUUID as randomUUID6 } from "crypto";
4339
+ import { randomUUID as randomUUID8 } from "crypto";
2679
4340
  function parseSoulMd(content) {
2680
4341
  const lines = content.split("\n");
2681
4342
  let name = "";
@@ -2685,17 +4346,23 @@ function parseSoulMd(content) {
2685
4346
  let currentSection = null;
2686
4347
  let currentCapabilityName = "";
2687
4348
  let currentCapabilityLines = [];
4349
+ let currentCapabilityPricing = void 0;
2688
4350
  let descriptionLines = [];
2689
4351
  let pastFirstH1 = false;
2690
4352
  let pastFirstH2 = false;
2691
4353
  const flushCapability = () => {
2692
4354
  if (currentCapabilityName) {
2693
- capabilities.push({
4355
+ const cap = {
2694
4356
  name: currentCapabilityName,
2695
4357
  description: currentCapabilityLines.join(" ").trim()
2696
- });
4358
+ };
4359
+ if (currentCapabilityPricing !== void 0) {
4360
+ cap.pricing = currentCapabilityPricing;
4361
+ }
4362
+ capabilities.push(cap);
2697
4363
  currentCapabilityName = "";
2698
4364
  currentCapabilityLines = [];
4365
+ currentCapabilityPricing = void 0;
2699
4366
  }
2700
4367
  };
2701
4368
  for (const line of lines) {
@@ -2725,7 +4392,15 @@ function parseSoulMd(content) {
2725
4392
  if (currentSection === "preamble" && !pastFirstH2) {
2726
4393
  descriptionLines.push(trimmed);
2727
4394
  } else if (currentSection === "capability") {
2728
- currentCapabilityLines.push(trimmed);
4395
+ const pricingMatch = trimmed.match(/^pricing:\s*(\d+(?:\.\d+)?)$/i);
4396
+ if (pricingMatch) {
4397
+ const val = parseFloat(pricingMatch[1]);
4398
+ if (!isNaN(val) && val >= 0) {
4399
+ currentCapabilityPricing = val;
4400
+ }
4401
+ } else {
4402
+ currentCapabilityLines.push(trimmed);
4403
+ }
2729
4404
  }
2730
4405
  }
2731
4406
  flushCapability();
@@ -2746,7 +4421,7 @@ function parseSoulMdV2(content) {
2746
4421
  const parsed = parseSoulMd(content);
2747
4422
  const skills = parsed.capabilities.map((cap) => {
2748
4423
  const sanitizedId = cap.name.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
2749
- const id = sanitizedId.length > 0 ? sanitizedId : randomUUID7();
4424
+ const id = sanitizedId.length > 0 ? sanitizedId : randomUUID9();
2750
4425
  return {
2751
4426
  id,
2752
4427
  name: cap.name,
@@ -2768,7 +4443,7 @@ function parseSoulMdV2(content) {
2768
4443
  required: true
2769
4444
  }
2770
4445
  ],
2771
- pricing: { credits_per_call: 10 },
4446
+ pricing: { credits_per_call: cap.pricing !== void 0 ? cap.pricing : 10 },
2772
4447
  availability: { online: true }
2773
4448
  };
2774
4449
  });
@@ -2788,7 +4463,7 @@ function publishFromSoulV2(db, soulContent, owner) {
2788
4463
  (c) => c.spec_version === "2.0"
2789
4464
  );
2790
4465
  const now = (/* @__PURE__ */ new Date()).toISOString();
2791
- const cardId = existingV2?.id ?? randomUUID7();
4466
+ const cardId = existingV2?.id ?? randomUUID9();
2792
4467
  const card = {
2793
4468
  spec_version: "2.0",
2794
4469
  id: cardId,
@@ -2813,7 +4488,7 @@ function publishFromSoulV2(db, soulContent, owner) {
2813
4488
  }
2814
4489
 
2815
4490
  // src/openclaw/heartbeat-writer.ts
2816
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, existsSync as existsSync5 } from "fs";
4491
+ import { readFileSync as readFileSync3, writeFileSync, existsSync as existsSync4 } from "fs";
2817
4492
  var HEARTBEAT_MARKER_START = "<!-- agentbnb:start -->";
2818
4493
  var HEARTBEAT_MARKER_END = "<!-- agentbnb:end -->";
2819
4494
  function generateHeartbeatSection(autonomy, budget) {
@@ -2849,11 +4524,11 @@ function generateHeartbeatSection(autonomy, budget) {
2849
4524
  ].join("\n");
2850
4525
  }
2851
4526
  function injectHeartbeatSection(heartbeatPath, section) {
2852
- if (!existsSync5(heartbeatPath)) {
2853
- writeFileSync2(heartbeatPath, section + "\n", "utf-8");
4527
+ if (!existsSync4(heartbeatPath)) {
4528
+ writeFileSync(heartbeatPath, section + "\n", "utf-8");
2854
4529
  return;
2855
4530
  }
2856
- let content = readFileSync4(heartbeatPath, "utf-8");
4531
+ let content = readFileSync3(heartbeatPath, "utf-8");
2857
4532
  const startIdx = content.indexOf(HEARTBEAT_MARKER_START);
2858
4533
  const endIdx = content.indexOf(HEARTBEAT_MARKER_END);
2859
4534
  if (startIdx !== -1 && endIdx !== -1) {
@@ -2861,7 +4536,7 @@ function injectHeartbeatSection(heartbeatPath, section) {
2861
4536
  } else {
2862
4537
  content = content + "\n" + section + "\n";
2863
4538
  }
2864
- writeFileSync2(heartbeatPath, content, "utf-8");
4539
+ writeFileSync(heartbeatPath, content, "utf-8");
2865
4540
  }
2866
4541
 
2867
4542
  // src/openclaw/skill.ts
@@ -2941,11 +4616,11 @@ function getLanIp() {
2941
4616
  var program = new Command();
2942
4617
  program.name("agentbnb").description("P2P Agent Capability Sharing Protocol \u2014 Airbnb for AI agent pipelines").version(pkg.version);
2943
4618
  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) => {
2944
- const owner = opts.owner ?? `agent-${randomBytes(4).toString("hex")}`;
2945
- const token = randomBytes(32).toString("hex");
4619
+ const owner = opts.owner ?? `agent-${randomBytes2(4).toString("hex")}`;
4620
+ const token = randomBytes2(32).toString("hex");
2946
4621
  const configDir = getConfigDir();
2947
- const dbPath = join4(configDir, "registry.db");
2948
- const creditDbPath = join4(configDir, "credit.db");
4622
+ const dbPath = join3(configDir, "registry.db");
4623
+ const creditDbPath = join3(configDir, "credit.db");
2949
4624
  const port = parseInt(opts.port, 10);
2950
4625
  const ip = opts.host ?? getLanIp();
2951
4626
  const existingConfig = loadConfig();
@@ -2959,7 +4634,7 @@ program.command("init").description("Initialize AgentBnB config and create agent
2959
4634
  credit_db_path: creditDbPath,
2960
4635
  token: existingConfig?.token ?? token,
2961
4636
  // Preserve existing token
2962
- api_key: existingConfig?.api_key ?? randomBytes(32).toString("hex")
4637
+ api_key: existingConfig?.api_key ?? randomBytes2(32).toString("hex")
2963
4638
  };
2964
4639
  saveConfig(config);
2965
4640
  let keypairStatus = "existing";
@@ -2974,6 +4649,21 @@ program.command("init").description("Initialize AgentBnB config and create agent
2974
4649
  const creditDb = openCreditDb(creditDbPath);
2975
4650
  bootstrapAgent(creditDb, owner, 100);
2976
4651
  creditDb.close();
4652
+ let registryBalance;
4653
+ if (existingConfig?.registry) {
4654
+ try {
4655
+ const identityAuth = loadIdentityAuth(owner);
4656
+ const ledger = createLedger({
4657
+ registryUrl: existingConfig.registry,
4658
+ ownerPublicKey: identityAuth.publicKey,
4659
+ privateKey: identityAuth.privateKey
4660
+ });
4661
+ await ledger.grant(owner, 50);
4662
+ registryBalance = await ledger.getBalance(owner);
4663
+ } catch (err) {
4664
+ console.warn(`Warning: could not connect to Registry for credit grant: ${err.message}`);
4665
+ }
4666
+ }
2977
4667
  const skipDetect = opts.detect === false;
2978
4668
  const publishedCards = [];
2979
4669
  let detectedSource = "none";
@@ -3119,6 +4809,9 @@ Publish these ${card.skills.length} capabilities? [y/N] `);
3119
4809
  keypair: keypairStatus,
3120
4810
  agent_id: identity.agent_id
3121
4811
  };
4812
+ if (registryBalance !== void 0) {
4813
+ jsonOutput.registry_balance = registryBalance;
4814
+ }
3122
4815
  if (!skipDetect) {
3123
4816
  jsonOutput.detected_source = detectedSource;
3124
4817
  jsonOutput.published_cards = publishedCards;
@@ -3129,7 +4822,11 @@ Publish these ${card.skills.length} capabilities? [y/N] `);
3129
4822
  console.log(` Owner: ${owner}`);
3130
4823
  console.log(` Token: ${token}`);
3131
4824
  console.log(` Config: ${configDir}/config.json`);
3132
- console.log(` Credits: 100 (starter grant)`);
4825
+ if (registryBalance !== void 0) {
4826
+ console.log(` Registry balance: ${registryBalance} credits`);
4827
+ } else {
4828
+ console.log(` Credits: 100 (starter grant)`);
4829
+ }
3133
4830
  console.log(` Keypair: ${keypairStatus === "generated" ? "generated (Ed25519)" : "preserved (existing)"}`);
3134
4831
  console.log(` Agent ID: ${identity.agent_id}`);
3135
4832
  console.log(` Gateway: http://${ip}:${port}`);
@@ -3143,7 +4840,7 @@ program.command("publish <card.json>").description("Publish a Capability Card to
3143
4840
  }
3144
4841
  let raw;
3145
4842
  try {
3146
- raw = readFileSync5(cardPath, "utf-8");
4843
+ raw = readFileSync4(cardPath, "utf-8");
3147
4844
  } catch {
3148
4845
  console.error(`Error: cannot read file: ${cardPath}`);
3149
4846
  process.exit(1);
@@ -3172,6 +4869,27 @@ program.command("publish <card.json>").description("Publish a Capability Card to
3172
4869
  }
3173
4870
  const card = result.data;
3174
4871
  const cardName = card.spec_version === "2.0" ? card.agent_name : card.name;
4872
+ if (card.spec_version === "2.0") {
4873
+ const v2card = card;
4874
+ const invalidSkill = v2card.skills?.find((s) => s.pricing.credits_per_call < 1);
4875
+ if (invalidSkill) {
4876
+ if (opts.json) {
4877
+ console.log(JSON.stringify({ success: false, error: "Minimum price is 1 credit per call", skill_id: invalidSkill.id }, null, 2));
4878
+ } else {
4879
+ console.error(`Error: Minimum price is 1 credit per call (skill "${invalidSkill.id}" has credits_per_call=${invalidSkill.pricing.credits_per_call})`);
4880
+ }
4881
+ process.exit(1);
4882
+ }
4883
+ } else {
4884
+ if (card.pricing.credits_per_call < 1) {
4885
+ if (opts.json) {
4886
+ console.log(JSON.stringify({ success: false, error: "Minimum price is 1 credit per call" }, null, 2));
4887
+ } else {
4888
+ console.error(`Error: Minimum price is 1 credit per call (card has credits_per_call=${card.pricing.credits_per_call})`);
4889
+ }
4890
+ process.exit(1);
4891
+ }
4892
+ }
3175
4893
  const db = openDatabase(config.db_path);
3176
4894
  try {
3177
4895
  if (card.spec_version === "2.0") {
@@ -3427,8 +5145,8 @@ program.command("request [card-id]").description("Request a capability from anot
3427
5145
  process.exit(1);
3428
5146
  }
3429
5147
  }
3430
- const registryDb = openDatabase(join4(getConfigDir(), "registry.db"));
3431
- const creditDb = openCreditDb(join4(getConfigDir(), "credit.db"));
5148
+ const registryDb = openDatabase(join3(getConfigDir(), "registry.db"));
5149
+ const creditDb = openCreditDb(join3(getConfigDir(), "credit.db"));
3432
5150
  registryDb.pragma("busy_timeout = 5000");
3433
5151
  creditDb.pragma("busy_timeout = 5000");
3434
5152
  try {
@@ -3438,7 +5156,8 @@ program.command("request [card-id]").description("Request a capability from anot
3438
5156
  registryDb,
3439
5157
  creditDb,
3440
5158
  autonomyConfig: config.autonomy ?? DEFAULT_AUTONOMY_CONFIG,
3441
- budgetManager
5159
+ budgetManager,
5160
+ registryUrl: config.registry
3442
5161
  });
3443
5162
  const result = await requestor.requestWithAutonomy({
3444
5163
  query: opts.query,
@@ -3466,6 +5185,7 @@ program.command("request [card-id]").description("Request a capability from anot
3466
5185
  let gatewayUrl;
3467
5186
  let token;
3468
5187
  let isRemoteRequest = false;
5188
+ let targetOwner;
3469
5189
  const identityAuth = loadIdentityAuth(config.owner);
3470
5190
  if (opts.peer) {
3471
5191
  const peer = findPeer(opts.peer);
@@ -3476,6 +5196,7 @@ program.command("request [card-id]").description("Request a capability from anot
3476
5196
  gatewayUrl = peer.url;
3477
5197
  token = peer.token;
3478
5198
  isRemoteRequest = true;
5199
+ targetOwner = opts.peer;
3479
5200
  } else {
3480
5201
  const db = openDatabase(config.db_path);
3481
5202
  let localCard;
@@ -3507,101 +5228,195 @@ program.command("request [card-id]").description("Request a capability from anot
3507
5228
  console.error(`Error: cannot reach registry: ${err.message}`);
3508
5229
  process.exit(1);
3509
5230
  }
3510
- if (!remoteCard.gateway_url || typeof remoteCard.gateway_url !== "string") {
3511
- console.error("Error: remote card has no gateway_url. The provider needs to re-publish with `agentbnb sync`.");
5231
+ targetOwner = remoteCard.owner ?? remoteCard.agent_name;
5232
+ if (remoteCard.gateway_url && typeof remoteCard.gateway_url === "string") {
5233
+ gatewayUrl = remoteCard.gateway_url;
5234
+ } else if (targetOwner && config.registry) {
5235
+ gatewayUrl = "";
5236
+ } else {
5237
+ console.error("Error: remote card has no gateway_url and no relay available. The provider needs to re-publish with `agentbnb sync`.");
3512
5238
  process.exit(1);
3513
5239
  }
3514
- gatewayUrl = remoteCard.gateway_url;
3515
5240
  token = "";
3516
5241
  isRemoteRequest = true;
3517
5242
  if (!opts.json) {
3518
5243
  const displayName = remoteCard.name ?? remoteCard.agent_name ?? cardId;
3519
- console.log(`Found remote card: ${displayName} @ ${gatewayUrl}`);
5244
+ if (gatewayUrl) {
5245
+ console.log(`Found remote card: ${displayName} @ ${gatewayUrl}`);
5246
+ } else {
5247
+ console.log(`Found remote card: ${displayName} (relay-only)`);
5248
+ }
3520
5249
  }
3521
5250
  }
3522
5251
  }
3523
5252
  const useReceipt = isRemoteRequest && opts.receipt !== false;
5253
+ const useRegistryLedger = isRemoteRequest && !!config.registry && !!gatewayUrl;
3524
5254
  if (useReceipt && !opts.cost) {
3525
5255
  console.error("Error: --cost <credits> is required for remote requests. Specify the credits to commit.");
3526
5256
  process.exit(1);
3527
5257
  }
3528
5258
  let escrowId;
3529
5259
  let escrowReceipt;
5260
+ let requestLedger;
3530
5261
  if (useReceipt) {
3531
- const configDir = getConfigDir();
3532
- const creditDb = openCreditDb(join4(configDir, "credit.db"));
3533
- creditDb.pragma("busy_timeout = 5000");
3534
- try {
3535
- const keys = loadKeyPair(configDir);
3536
- const amount = Number(opts.cost);
3537
- if (isNaN(amount) || amount <= 0) {
3538
- console.error("Error: --cost must be a positive number.");
3539
- process.exit(1);
3540
- }
3541
- const receiptResult = createSignedEscrowReceipt(creditDb, keys.privateKey, keys.publicKey, {
3542
- owner: config.owner,
3543
- amount,
3544
- cardId,
3545
- skillId: opts.skill
3546
- });
3547
- escrowId = receiptResult.escrowId;
3548
- escrowReceipt = receiptResult.receipt;
3549
- if (!opts.json) {
3550
- console.log(`Escrow: ${amount} credits held (ID: ${escrowId.slice(0, 8)}...)`);
3551
- }
3552
- } catch (err) {
3553
- creditDb.close();
3554
- const msg = err instanceof Error ? err.message : String(err);
3555
- if (opts.json) {
3556
- console.log(JSON.stringify({ success: false, error: msg }, null, 2));
3557
- } else {
3558
- console.error(`Error creating escrow receipt: ${msg}`);
3559
- }
5262
+ const amount = Number(opts.cost);
5263
+ if (isNaN(amount) || amount <= 0) {
5264
+ console.error("Error: --cost must be a positive number.");
3560
5265
  process.exit(1);
3561
5266
  }
3562
- }
3563
- try {
3564
- const result = await requestCapability({
3565
- gatewayUrl,
3566
- token,
3567
- cardId,
3568
- params: { ...params, ...opts.skill ? { skill_id: opts.skill } : {} },
3569
- escrowReceipt,
3570
- identity: identityAuth
3571
- });
3572
- if (useReceipt && escrowId) {
5267
+ if (useRegistryLedger) {
5268
+ const reqIdentityAuth = loadIdentityAuth(config.owner);
5269
+ requestLedger = createLedger({
5270
+ registryUrl: config.registry,
5271
+ ownerPublicKey: reqIdentityAuth.publicKey,
5272
+ privateKey: reqIdentityAuth.privateKey
5273
+ });
5274
+ try {
5275
+ const { escrowId: heldId } = await requestLedger.hold(config.owner, amount, cardId);
5276
+ escrowId = heldId;
5277
+ if (!opts.json) {
5278
+ console.log(`Escrow: ${amount} credits held via Registry (ID: ${escrowId.slice(0, 8)}...)`);
5279
+ }
5280
+ } catch (err) {
5281
+ const msg = err instanceof Error ? err.message : String(err);
5282
+ if (opts.json) {
5283
+ console.log(JSON.stringify({ success: false, error: msg }, null, 2));
5284
+ } else {
5285
+ console.error(`Error creating escrow via Registry: ${msg}`);
5286
+ }
5287
+ process.exit(1);
5288
+ }
5289
+ } else if (gatewayUrl) {
3573
5290
  const configDir = getConfigDir();
3574
- const creditDb = openCreditDb(join4(configDir, "credit.db"));
5291
+ const creditDb = openCreditDb(join3(configDir, "credit.db"));
3575
5292
  creditDb.pragma("busy_timeout = 5000");
3576
5293
  try {
3577
- settleRequesterEscrow(creditDb, escrowId);
5294
+ const keys = loadKeyPair(configDir);
5295
+ const receiptResult = createSignedEscrowReceipt(creditDb, keys.privateKey, keys.publicKey, {
5296
+ owner: config.owner,
5297
+ amount,
5298
+ cardId,
5299
+ skillId: opts.skill
5300
+ });
5301
+ escrowId = receiptResult.escrowId;
5302
+ escrowReceipt = receiptResult.receipt;
3578
5303
  if (!opts.json) {
3579
- console.log(`Escrow settled: ${opts.cost} credits deducted.`);
5304
+ console.log(`Escrow: ${amount} credits held (ID: ${escrowId.slice(0, 8)}...)`);
3580
5305
  }
3581
- } finally {
5306
+ } catch (err) {
3582
5307
  creditDb.close();
5308
+ const msg = err instanceof Error ? err.message : String(err);
5309
+ if (opts.json) {
5310
+ console.log(JSON.stringify({ success: false, error: msg }, null, 2));
5311
+ } else {
5312
+ console.error(`Error creating escrow receipt: ${msg}`);
5313
+ }
5314
+ process.exit(1);
5315
+ }
5316
+ creditDb.close();
5317
+ }
5318
+ }
5319
+ const settleEscrow2 = async () => {
5320
+ if (useReceipt && escrowId) {
5321
+ if (requestLedger) {
5322
+ await requestLedger.settle(escrowId, targetOwner ?? config.owner);
5323
+ if (!opts.json) console.log(`Escrow settled: ${opts.cost} credits deducted.`);
5324
+ } else if (escrowReceipt) {
5325
+ const configDir = getConfigDir();
5326
+ const creditDb = openCreditDb(join3(configDir, "credit.db"));
5327
+ creditDb.pragma("busy_timeout = 5000");
5328
+ try {
5329
+ settleRequesterEscrow(creditDb, escrowId);
5330
+ if (!opts.json) console.log(`Escrow settled: ${opts.cost} credits deducted.`);
5331
+ } finally {
5332
+ creditDb.close();
5333
+ }
5334
+ }
5335
+ }
5336
+ };
5337
+ const releaseEscrow2 = async () => {
5338
+ if (useReceipt && escrowId) {
5339
+ if (requestLedger) {
5340
+ await requestLedger.release(escrowId);
5341
+ if (!opts.json) console.log("Escrow released: credits refunded.");
5342
+ } else if (escrowReceipt) {
5343
+ const configDir = getConfigDir();
5344
+ const creditDb = openCreditDb(join3(configDir, "credit.db"));
5345
+ creditDb.pragma("busy_timeout = 5000");
5346
+ try {
5347
+ releaseRequesterEscrow(creditDb, escrowId);
5348
+ if (!opts.json) console.log("Escrow released: credits refunded.");
5349
+ } finally {
5350
+ creditDb.close();
5351
+ }
3583
5352
  }
3584
5353
  }
5354
+ };
5355
+ const printResult = (result) => {
3585
5356
  if (opts.json) {
3586
5357
  console.log(JSON.stringify({ success: true, result }, null, 2));
3587
5358
  } else {
3588
5359
  console.log("Result:");
3589
5360
  console.log(typeof result === "string" ? result : JSON.stringify(result, null, 2));
3590
5361
  }
3591
- } catch (err) {
3592
- if (useReceipt && escrowId) {
3593
- const configDir = getConfigDir();
3594
- const creditDb = openCreditDb(join4(configDir, "credit.db"));
3595
- creditDb.pragma("busy_timeout = 5000");
5362
+ };
5363
+ const isNetworkError = (err) => {
5364
+ const msg = err instanceof Error ? err.message : String(err);
5365
+ return msg.includes("NETWORK_ERROR") || msg.includes("ECONNREFUSED") || msg.includes("fetch failed") || msg.includes("Network error");
5366
+ };
5367
+ const tryViaRelay = async () => {
5368
+ const { RelayClient } = await import("../websocket-client-6IIDGXKB.js");
5369
+ const { requestViaRelay } = await import("../client-BTPIFY7E.js");
5370
+ const tempRelay = new RelayClient({
5371
+ registryUrl: config.registry,
5372
+ owner: config.owner,
5373
+ token: config.token,
5374
+ card: { id: config.owner, owner: config.owner },
5375
+ onRequest: async () => ({ error: { code: -32601, message: "Not serving" } }),
5376
+ silent: true
5377
+ });
5378
+ try {
5379
+ await tempRelay.connect();
5380
+ const result = await requestViaRelay(tempRelay, {
5381
+ targetOwner,
5382
+ cardId,
5383
+ skillId: opts.skill,
5384
+ params: { ...params, ...opts.skill ? { skill_id: opts.skill } : {} },
5385
+ escrowReceipt
5386
+ });
5387
+ return result;
5388
+ } finally {
5389
+ tempRelay.disconnect();
5390
+ }
5391
+ };
5392
+ try {
5393
+ let result;
5394
+ if (!gatewayUrl && isRemoteRequest && config.registry && targetOwner) {
5395
+ if (!opts.json) console.log("No gateway URL, requesting via relay...");
5396
+ result = await tryViaRelay();
5397
+ } else {
3596
5398
  try {
3597
- releaseRequesterEscrow(creditDb, escrowId);
3598
- if (!opts.json) {
3599
- console.log("Escrow released: credits refunded.");
5399
+ result = await requestCapability({
5400
+ gatewayUrl,
5401
+ token,
5402
+ cardId,
5403
+ params: { ...params, ...opts.skill ? { skill_id: opts.skill } : {} },
5404
+ escrowReceipt,
5405
+ identity: identityAuth
5406
+ });
5407
+ } catch (directErr) {
5408
+ if (isNetworkError(directErr) && isRemoteRequest && config.registry && targetOwner) {
5409
+ if (!opts.json) console.log("Direct connection failed, trying relay...");
5410
+ result = await tryViaRelay();
5411
+ } else {
5412
+ throw directErr;
3600
5413
  }
3601
- } finally {
3602
- creditDb.close();
3603
5414
  }
3604
5415
  }
5416
+ await settleEscrow2();
5417
+ printResult(result);
5418
+ } catch (err) {
5419
+ await releaseEscrow2();
3605
5420
  const msg = err instanceof Error ? err.message : String(err);
3606
5421
  if (opts.json) {
3607
5422
  console.log(JSON.stringify({ success: false, error: msg }, null, 2));
@@ -3621,12 +5436,28 @@ program.command("status").description("Show credit balance and recent transactio
3621
5436
  let balance;
3622
5437
  let transactions;
3623
5438
  let heldEscrows;
3624
- try {
3625
- balance = getBalance(creditDb, config.owner);
3626
- transactions = getTransactions(creditDb, config.owner, 5);
3627
- heldEscrows = creditDb.prepare("SELECT id, amount, card_id, created_at FROM credit_escrow WHERE owner = ? AND status = ?").all(config.owner, "held");
3628
- } finally {
3629
- creditDb.close();
5439
+ if (config.registry) {
5440
+ const statusIdentityAuth = loadIdentityAuth(config.owner);
5441
+ const statusLedger = createLedger({
5442
+ registryUrl: config.registry,
5443
+ ownerPublicKey: statusIdentityAuth.publicKey,
5444
+ privateKey: statusIdentityAuth.privateKey
5445
+ });
5446
+ try {
5447
+ balance = await statusLedger.getBalance(config.owner);
5448
+ transactions = await statusLedger.getHistory(config.owner, 5);
5449
+ heldEscrows = creditDb.prepare("SELECT id, amount, card_id, created_at FROM credit_escrow WHERE owner = ? AND status = ?").all(config.owner, "held");
5450
+ } finally {
5451
+ creditDb.close();
5452
+ }
5453
+ } else {
5454
+ try {
5455
+ balance = getBalance(creditDb, config.owner);
5456
+ transactions = getTransactions(creditDb, config.owner, 5);
5457
+ heldEscrows = creditDb.prepare("SELECT id, amount, card_id, created_at FROM credit_escrow WHERE owner = ? AND status = ?").all(config.owner, "held");
5458
+ } finally {
5459
+ creditDb.close();
5460
+ }
3630
5461
  }
3631
5462
  if (opts.json) {
3632
5463
  console.log(JSON.stringify({ owner: config.owner, balance, held_escrows: heldEscrows, recent_transactions: transactions }, null, 2));
@@ -3651,7 +5482,7 @@ Active Escrows (${heldEscrows.length}):`);
3651
5482
  }
3652
5483
  }
3653
5484
  });
3654
- 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) => {
5485
+ 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").option("--no-relay", "Do not auto-connect to remote registry relay").action(async (opts) => {
3655
5486
  const config = loadConfig();
3656
5487
  if (!config) {
3657
5488
  console.error("Error: not initialized. Run `agentbnb init` first.");
@@ -3659,7 +5490,7 @@ program.command("serve").description("Start the AgentBnB gateway server").option
3659
5490
  }
3660
5491
  const port = opts.port ? parseInt(opts.port, 10) : config.gateway_port;
3661
5492
  const registryPort = parseInt(opts.registryPort, 10);
3662
- const skillsYamlPath = opts.skillsYaml ?? join4(homedir(), ".agentbnb", "skills.yaml");
5493
+ const skillsYamlPath = opts.skillsYaml ?? join3(homedir(), ".agentbnb", "skills.yaml");
3663
5494
  const runtime = new AgentRuntime({
3664
5495
  registryDbPath: config.db_path,
3665
5496
  creditDbPath: config.credit_db_path,
@@ -3675,6 +5506,19 @@ program.command("serve").description("Start the AgentBnB gateway server").option
3675
5506
  if (opts.conductor) {
3676
5507
  console.log("Conductor mode enabled \u2014 orchestrate/plan skills available via gateway");
3677
5508
  }
5509
+ if (opts.conductor && config.conductor?.public) {
5510
+ const { buildConductorCard } = await import("../card-4XH4AOTE.js");
5511
+ const { AnyCardSchema: AnyCard } = await import("../types-FGBUZ3QV.js");
5512
+ const conductorCard = buildConductorCard(config.owner);
5513
+ const now = (/* @__PURE__ */ new Date()).toISOString();
5514
+ const existing = runtime.registryDb.prepare("SELECT id FROM capability_cards WHERE id = ?").get(conductorCard.id);
5515
+ if (existing) {
5516
+ runtime.registryDb.prepare("UPDATE capability_cards SET data = ?, updated_at = ? WHERE id = ?").run(JSON.stringify(conductorCard), now, conductorCard.id);
5517
+ } else {
5518
+ runtime.registryDb.prepare("INSERT INTO capability_cards (id, owner, data, created_at, updated_at) VALUES (?, ?, ?, ?, ?)").run(conductorCard.id, config.owner, JSON.stringify(conductorCard), now, now);
5519
+ }
5520
+ console.log("Conductor card registered locally (conductor.public: true)");
5521
+ }
3678
5522
  const autonomyConfig = config.autonomy ?? DEFAULT_AUTONOMY_CONFIG;
3679
5523
  const idleMonitor = new IdleMonitor({
3680
5524
  owner: config.owner,
@@ -3736,9 +5580,10 @@ program.command("serve").description("Start the AgentBnB gateway server").option
3736
5580
  console.log(`WebSocket relay active on /ws`);
3737
5581
  }
3738
5582
  }
3739
- if (opts.registry) {
3740
- const { RelayClient } = await import("../websocket-client-5TIQDYQ4.js");
3741
- const { executeCapabilityRequest: executeCapabilityRequest2 } = await import("../execute-SWWEHV2K.js");
5583
+ const relayUrl = opts.registry ?? config.registry;
5584
+ if (relayUrl && opts.relay !== false) {
5585
+ const { RelayClient } = await import("../websocket-client-6IIDGXKB.js");
5586
+ const { executeCapabilityRequest: executeCapabilityRequest2 } = await import("../execute-EXOITLHN.js");
3742
5587
  const cards = listCards(runtime.registryDb, config.owner);
3743
5588
  const card = cards[0] ?? {
3744
5589
  id: config.owner,
@@ -3752,12 +5597,23 @@ program.command("serve").description("Start the AgentBnB gateway server").option
3752
5597
  pricing: { credits_per_call: 0 },
3753
5598
  availability: { online: true }
3754
5599
  };
5600
+ const additionalCards = [];
5601
+ if (config.conductor?.public) {
5602
+ const { buildConductorCard } = await import("../card-4XH4AOTE.js");
5603
+ const conductorCard = buildConductorCard(config.owner);
5604
+ additionalCards.push(conductorCard);
5605
+ console.log("Conductor card will be published to registry (conductor.public: true)");
5606
+ }
3755
5607
  relayClient = new RelayClient({
3756
- registryUrl: opts.registry,
5608
+ registryUrl: relayUrl,
3757
5609
  owner: config.owner,
3758
5610
  token: config.token,
3759
5611
  card,
5612
+ cards: additionalCards.length > 0 ? additionalCards : void 0,
3760
5613
  onRequest: async (req) => {
5614
+ const onProgress = (info) => {
5615
+ relayClient.sendProgress(req.id, info);
5616
+ };
3761
5617
  const result = await executeCapabilityRequest2({
3762
5618
  registryDb: runtime.registryDb,
3763
5619
  creditDb: runtime.creditDb,
@@ -3767,7 +5623,8 @@ program.command("serve").description("Start the AgentBnB gateway server").option
3767
5623
  requester: req.requester ?? req.from_owner,
3768
5624
  escrowReceipt: req.escrow_receipt,
3769
5625
  skillExecutor: runtime.skillExecutor,
3770
- handlerUrl: opts.handlerUrl
5626
+ handlerUrl: opts.handlerUrl,
5627
+ onProgress
3771
5628
  });
3772
5629
  if (result.success) {
3773
5630
  return { result: result.result };
@@ -3777,9 +5634,9 @@ program.command("serve").description("Start the AgentBnB gateway server").option
3777
5634
  });
3778
5635
  try {
3779
5636
  await relayClient.connect();
3780
- console.log(`Connected to registry: ${opts.registry}`);
5637
+ console.log(`Connected to registry: ${relayUrl}${opts.registry ? "" : " (auto)"}`);
3781
5638
  } catch (err) {
3782
- console.warn(`Warning: could not connect to registry ${opts.registry}: ${err instanceof Error ? err.message : err}`);
5639
+ console.warn(`Warning: could not connect to registry ${relayUrl}: ${err instanceof Error ? err.message : err}`);
3783
5640
  console.warn("Will auto-reconnect in background...");
3784
5641
  }
3785
5642
  }
@@ -3842,7 +5699,7 @@ peersCommand.command("remove <name>").description("Remove a registered peer").ac
3842
5699
  });
3843
5700
  var configCmd = program.command("config").description("Get or set AgentBnB configuration values");
3844
5701
  configCmd.command("set <key> <value>").description("Set a configuration value").action((key, value) => {
3845
- const allowedKeys = ["registry", "tier1", "tier2", "reserve", "idle-threshold"];
5702
+ const allowedKeys = ["registry", "tier1", "tier2", "reserve", "idle-threshold", "conductor-public"];
3846
5703
  if (!allowedKeys.includes(key)) {
3847
5704
  console.error(`Unknown config key: ${key}. Valid keys: ${allowedKeys.join(", ")}`);
3848
5705
  process.exit(1);
@@ -3907,6 +5764,17 @@ configCmd.command("set <key> <value>").description("Set a configuration value").
3907
5764
  console.log(`Set idle-threshold = ${parsed} (idle rate threshold for auto-share)`);
3908
5765
  return;
3909
5766
  }
5767
+ if (key === "conductor-public") {
5768
+ const boolVal = value === "true";
5769
+ if (value !== "true" && value !== "false") {
5770
+ console.error('Error: conductor-public must be "true" or "false"');
5771
+ process.exit(1);
5772
+ }
5773
+ config.conductor = { public: boolVal };
5774
+ saveConfig(config);
5775
+ console.log(`Set conductor-public = ${boolVal} (conductor card ${boolVal ? "will be" : "will NOT be"} published to registry)`);
5776
+ return;
5777
+ }
3910
5778
  config[key] = value;
3911
5779
  saveConfig(config);
3912
5780
  console.log(`Set ${key} = ${value}`);
@@ -3934,6 +5802,10 @@ configCmd.command("get <key>").description("Get a configuration value").action((
3934
5802
  console.log(val !== void 0 ? String(val) : "0.70");
3935
5803
  return;
3936
5804
  }
5805
+ if (key === "conductor-public") {
5806
+ console.log(String(config.conductor?.public ?? false));
5807
+ return;
5808
+ }
3937
5809
  const value = config[key];
3938
5810
  console.log(value !== void 0 ? String(value) : "(not set)");
3939
5811
  });
@@ -3946,7 +5818,7 @@ openclaw.command("sync").description("Read SOUL.md and publish/update a v2.0 cap
3946
5818
  }
3947
5819
  let content;
3948
5820
  try {
3949
- content = readFileSync5(opts.soulPath, "utf-8");
5821
+ content = readFileSync4(opts.soulPath, "utf-8");
3950
5822
  } catch {
3951
5823
  console.error(`Error: cannot read SOUL.md at ${opts.soulPath}`);
3952
5824
  process.exit(1);
@@ -3955,6 +5827,14 @@ openclaw.command("sync").description("Read SOUL.md and publish/update a v2.0 cap
3955
5827
  try {
3956
5828
  const card = publishFromSoulV2(db, content, config.owner);
3957
5829
  console.log(`Published card ${card.id} with ${card.skills.length} skill(s)`);
5830
+ for (const skill of card.skills) {
5831
+ const stats = getPricingStats(db, skill.name);
5832
+ if (stats.count > 0) {
5833
+ console.log(` ${skill.name}: ${skill.pricing.credits_per_call} cr (market: ${stats.min}-${stats.max} cr, median ${stats.median}, ${stats.count} providers)`);
5834
+ } else {
5835
+ console.log(` ${skill.name}: ${skill.pricing.credits_per_call} cr (no market data yet)`);
5836
+ }
5837
+ }
3958
5838
  } catch (err) {
3959
5839
  const msg = err instanceof Error ? err.message : String(err);
3960
5840
  console.error(`Error: ${msg}`);
@@ -4007,7 +5887,7 @@ openclaw.command("rules").description("Print HEARTBEAT.md rules block (or inject
4007
5887
  }
4008
5888
  });
4009
5889
  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) => {
4010
- const { conductAction } = await import("../conduct-IEQ567ET.js");
5890
+ const { conductAction } = await import("../conduct-FXLVGKD5.js");
4011
5891
  const result = await conductAction(task, opts);
4012
5892
  if (opts.json) {
4013
5893
  console.log(JSON.stringify(result, null, 2));
@@ -4040,4 +5920,8 @@ Total credits spent: ${result.total_credits ?? 0} cr`);
4040
5920
  }
4041
5921
  }
4042
5922
  });
5923
+ program.command("mcp-server").description("Start an MCP (Model Context Protocol) server for IDE integration").action(async () => {
5924
+ const { startMcpServer } = await import("../server-2LWHL24P.js");
5925
+ await startMcpServer();
5926
+ });
4043
5927
  await program.parseAsync(process.argv);