agentbnb 4.0.0 → 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 (36) hide show
  1. package/dist/{card-IE5UV5QX.js → card-4XH4AOTE.js} +11 -4
  2. package/dist/chunk-3MJT4PZG.js +50 -0
  3. package/dist/{conduct-IQYAT6ZU.js → chunk-3UKAVIMC.js} +70 -33
  4. package/dist/chunk-5AH3CMOX.js +62 -0
  5. package/dist/{chunk-UJWYE7VL.js → chunk-6K5WUVF3.js} +28 -111
  6. package/dist/chunk-75OC6E4F.js +33 -0
  7. package/dist/{chunk-QO67IGCW.js → chunk-DVAS2443.js} +1 -1
  8. package/dist/{chunk-XA63SD4T.js → chunk-FNKBHBYK.js} +3 -0
  9. package/dist/{websocket-client-5TIQDYQ4.js → chunk-JOY533UH.js} +38 -4
  10. package/dist/{chunk-RSX4SCPN.js → chunk-KJG2UJV5.js} +3 -3
  11. package/dist/chunk-M3G5NR2Z.js +90 -0
  12. package/dist/{chunk-HEVXCYCY.js → chunk-MQKYGY5I.js} +61 -24
  13. package/dist/chunk-ODBGCCEH.js +358 -0
  14. package/dist/{chunk-CUVIWPQO.js → chunk-Q7HRI666.js} +7 -6
  15. package/dist/chunk-QJEOCKVF.js +148 -0
  16. package/dist/{chunk-3Y36WQDV.js → chunk-QT7TEVNV.js} +14 -2
  17. package/dist/{chunk-UOGDK2S2.js → chunk-TLU7ALCZ.js} +1 -1
  18. package/dist/{chunk-QVV2P3FN.js → chunk-XQHN6ITI.js} +1 -1
  19. package/dist/cli/index.js +2665 -845
  20. package/dist/{client-IOTK6GOS.js → client-BTPIFY7E.js} +3 -3
  21. package/dist/conduct-CW62HBPT.js +52 -0
  22. package/dist/conduct-FXLVGKD5.js +19 -0
  23. package/dist/{conductor-mode-XU7ONJWC.js → conductor-mode-3JS4VWCR.js} +16 -9
  24. package/dist/execute-EXOITLHN.js +10 -0
  25. package/dist/index.d.ts +1005 -916
  26. package/dist/index.js +516 -120
  27. package/dist/{peers-G36URZYB.js → peers-K7FSHPN3.js} +2 -1
  28. package/dist/request-CNZ3XIVX.js +196 -0
  29. package/dist/serve-skill-SUOGUM7N.js +104 -0
  30. package/dist/server-2LWHL24P.js +295 -0
  31. package/dist/types-FGBUZ3QV.js +18 -0
  32. package/dist/websocket-client-6IIDGXKB.js +7 -0
  33. package/package.json +1 -1
  34. package/dist/chunk-BEI5MTNZ.js +0 -91
  35. package/dist/cli/index.d.ts +0 -1
  36. package/dist/execute-GDGBU6DJ.js +0 -10
package/dist/cli/index.js CHANGED
@@ -1,37 +1,50 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- executeCapabilityRequest,
4
- releaseRequesterEscrow,
5
- settleRequesterEscrow
6
- } from "../chunk-CUVIWPQO.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
- resolvePendingRequest,
21
+ resolvePendingRequest
22
+ } from "../chunk-6K5WUVF3.js";
23
+ import {
24
+ fetchRemoteCards,
25
+ filterCards,
26
+ mergeResults,
21
27
  searchCards
22
- } from "../chunk-UJWYE7VL.js";
28
+ } from "../chunk-QJEOCKVF.js";
23
29
  import {
24
30
  requestCapability
25
- } from "../chunk-RSX4SCPN.js";
31
+ } from "../chunk-KJG2UJV5.js";
26
32
  import {
27
33
  findPeer,
28
- getConfigDir,
29
- loadConfig,
30
34
  loadPeers,
31
35
  removePeer,
32
- saveConfig,
33
36
  savePeer
34
- } 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";
35
48
  import {
36
49
  getActivityFeed,
37
50
  getCard,
@@ -44,129 +57,53 @@ import {
44
57
  updateCard,
45
58
  updateSkillAvailability,
46
59
  updateSkillIdleRate
47
- } from "../chunk-UOGDK2S2.js";
60
+ } from "../chunk-TLU7ALCZ.js";
48
61
  import {
49
62
  bootstrapAgent,
50
63
  getBalance,
51
64
  getTransactions,
52
65
  holdEscrow,
53
66
  openCreditDb,
54
- releaseEscrow
55
- } from "../chunk-QVV2P3FN.js";
67
+ releaseEscrow,
68
+ settleEscrow
69
+ } from "../chunk-XQHN6ITI.js";
56
70
  import {
57
71
  generateKeyPair,
58
72
  loadKeyPair,
59
73
  saveKeyPair,
60
74
  signEscrowReceipt,
61
75
  verifyEscrowReceipt
62
- } from "../chunk-QO67IGCW.js";
76
+ } from "../chunk-DVAS2443.js";
63
77
  import {
64
78
  AgentBnBError,
65
79
  AnyCardSchema,
66
80
  CapabilityCardV2Schema
67
- } from "../chunk-XA63SD4T.js";
81
+ } from "../chunk-FNKBHBYK.js";
82
+ import {
83
+ RelayMessageSchema
84
+ } from "../chunk-QT7TEVNV.js";
68
85
 
69
86
  // src/cli/index.ts
70
87
  import { Command } from "commander";
71
- import { readFileSync as readFileSync5 } from "fs";
88
+ import { readFileSync as readFileSync4 } from "fs";
72
89
  import { createRequire } from "module";
73
- import { randomBytes } from "crypto";
74
- import { join as join4 } from "path";
90
+ import { randomBytes as randomBytes2 } from "crypto";
91
+ import { join as join3 } from "path";
75
92
  import { networkInterfaces, homedir } from "os";
76
93
  import { createInterface as createInterface2 } from "readline";
77
94
 
78
- // src/identity/identity.ts
79
- import { z } from "zod";
80
- import { createHash } from "crypto";
81
- import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
82
- import { join } from "path";
83
- var AgentIdentitySchema = z.object({
84
- /** Deterministic ID derived from public key: sha256(hex).slice(0, 16). */
85
- agent_id: z.string().min(1),
86
- /** Human-readable owner name (from config or init). */
87
- owner: z.string().min(1),
88
- /** Hex-encoded Ed25519 public key. */
89
- public_key: z.string().min(1),
90
- /** ISO 8601 timestamp of identity creation. */
91
- created_at: z.string().datetime(),
92
- /** Optional guarantor info if linked to a human. */
93
- guarantor: z.object({
94
- github_login: z.string().min(1),
95
- verified_at: z.string().datetime()
96
- }).optional()
97
- });
98
- var AgentCertificateSchema = z.object({
99
- identity: AgentIdentitySchema,
100
- /** ISO 8601 timestamp of certificate issuance. */
101
- issued_at: z.string().datetime(),
102
- /** ISO 8601 timestamp of certificate expiry. */
103
- expires_at: z.string().datetime(),
104
- /** Hex-encoded public key of the issuer (same as identity for self-signed). */
105
- issuer_public_key: z.string().min(1),
106
- /** Base64url Ed25519 signature over { identity, issued_at, expires_at, issuer_public_key }. */
107
- signature: z.string().min(1)
108
- });
109
- var IDENTITY_FILENAME = "identity.json";
110
- function deriveAgentId(publicKeyHex) {
111
- return createHash("sha256").update(publicKeyHex, "hex").digest("hex").slice(0, 16);
112
- }
113
- function createIdentity(configDir, owner) {
114
- if (!existsSync(configDir)) {
115
- mkdirSync(configDir, { recursive: true });
116
- }
117
- let keys;
118
- try {
119
- keys = loadKeyPair(configDir);
120
- } catch {
121
- keys = generateKeyPair();
122
- saveKeyPair(configDir, keys);
123
- }
124
- const publicKeyHex = keys.publicKey.toString("hex");
125
- const agentId = deriveAgentId(publicKeyHex);
126
- const identity = {
127
- agent_id: agentId,
128
- owner,
129
- public_key: publicKeyHex,
130
- created_at: (/* @__PURE__ */ new Date()).toISOString()
131
- };
132
- saveIdentity(configDir, identity);
133
- return identity;
134
- }
135
- function loadIdentity(configDir) {
136
- const filePath = join(configDir, IDENTITY_FILENAME);
137
- if (!existsSync(filePath)) return null;
138
- try {
139
- const raw = readFileSync(filePath, "utf-8");
140
- return AgentIdentitySchema.parse(JSON.parse(raw));
141
- } catch {
142
- return null;
143
- }
144
- }
145
- function saveIdentity(configDir, identity) {
146
- if (!existsSync(configDir)) {
147
- mkdirSync(configDir, { recursive: true });
148
- }
149
- const filePath = join(configDir, IDENTITY_FILENAME);
150
- writeFileSync(filePath, JSON.stringify(identity, null, 2), "utf-8");
151
- }
152
- function ensureIdentity(configDir, owner) {
153
- const existing = loadIdentity(configDir);
154
- if (existing) return existing;
155
- return createIdentity(configDir, owner);
156
- }
157
-
158
95
  // src/credit/escrow-receipt.ts
159
- import { z as z2 } from "zod";
96
+ import { z } from "zod";
160
97
  import { randomUUID } from "crypto";
161
- var EscrowReceiptSchema = z2.object({
162
- requester_owner: z2.string().min(1),
163
- requester_public_key: z2.string().min(1),
164
- amount: z2.number().positive(),
165
- card_id: z2.string().min(1),
166
- skill_id: z2.string().optional(),
167
- timestamp: z2.string(),
168
- nonce: z2.string().uuid(),
169
- 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)
170
107
  });
171
108
  function createSignedEscrowReceipt(db, privateKey, publicKey, opts) {
172
109
  const escrowId = holdEscrow(db, opts.owner, opts.amount, opts.cardId);
@@ -280,89 +217,6 @@ var IdleMonitor = class {
280
217
  }
281
218
  };
282
219
 
283
- // src/cli/remote-registry.ts
284
- var RegistryTimeoutError = class extends AgentBnBError {
285
- constructor(url) {
286
- super(
287
- `Registry at ${url} did not respond within 5s. Showing local results only.`,
288
- "REGISTRY_TIMEOUT"
289
- );
290
- this.name = "RegistryTimeoutError";
291
- }
292
- };
293
- var RegistryConnectionError = class extends AgentBnBError {
294
- constructor(url) {
295
- super(
296
- `Cannot reach ${url}. Is the registry running? Showing local results only.`,
297
- "REGISTRY_CONNECTION"
298
- );
299
- this.name = "RegistryConnectionError";
300
- }
301
- };
302
- var RegistryAuthError = class extends AgentBnBError {
303
- constructor(url) {
304
- super(
305
- `Authentication failed for ${url}. Run \`agentbnb config set token <your-token>\`.`,
306
- "REGISTRY_AUTH"
307
- );
308
- this.name = "RegistryAuthError";
309
- }
310
- };
311
- async function fetchRemoteCards(registryUrl, params, timeoutMs = 5e3) {
312
- let cardsUrl;
313
- try {
314
- cardsUrl = new URL("/cards", registryUrl);
315
- } catch {
316
- throw new AgentBnBError(`Invalid registry URL: ${registryUrl}`, "INVALID_REGISTRY_URL");
317
- }
318
- const searchParams = new URLSearchParams();
319
- if (params.q !== void 0) searchParams.set("q", params.q);
320
- if (params.level !== void 0) searchParams.set("level", String(params.level));
321
- if (params.online !== void 0) searchParams.set("online", String(params.online));
322
- if (params.tag !== void 0) searchParams.set("tag", params.tag);
323
- searchParams.set("limit", "100");
324
- cardsUrl.search = searchParams.toString();
325
- const controller = new AbortController();
326
- const timer = setTimeout(() => controller.abort(), timeoutMs);
327
- let response;
328
- try {
329
- response = await fetch(cardsUrl.toString(), { signal: controller.signal });
330
- } catch (err) {
331
- clearTimeout(timer);
332
- const isTimeout = err instanceof Error && err.name === "AbortError";
333
- if (isTimeout) {
334
- throw new RegistryTimeoutError(registryUrl);
335
- }
336
- throw new RegistryConnectionError(registryUrl);
337
- } finally {
338
- clearTimeout(timer);
339
- }
340
- if (response.status === 401 || response.status === 403) {
341
- throw new RegistryAuthError(registryUrl);
342
- }
343
- if (!response.ok) {
344
- throw new RegistryConnectionError(registryUrl);
345
- }
346
- const body = await response.json();
347
- return body.items;
348
- }
349
- function mergeResults(localCards, remoteCards, hasQuery) {
350
- const taggedLocal = localCards.map((c) => ({ ...c, source: "local" }));
351
- const taggedRemote = remoteCards.map((c) => ({ ...c, source: "remote" }));
352
- const localIds = new Set(localCards.map((c) => c.id));
353
- const dedupedRemote = taggedRemote.filter((c) => !localIds.has(c.id));
354
- if (!hasQuery) {
355
- return [...taggedLocal, ...dedupedRemote];
356
- }
357
- const result = [];
358
- const maxLen = Math.max(taggedLocal.length, dedupedRemote.length);
359
- for (let i = 0; i < maxLen; i++) {
360
- if (i < taggedLocal.length) result.push(taggedLocal[i]);
361
- if (i < dedupedRemote.length) result.push(dedupedRemote[i]);
362
- }
363
- return result;
364
- }
365
-
366
220
  // src/cli/onboarding.ts
367
221
  import { randomUUID as randomUUID2 } from "crypto";
368
222
  import { createConnection } from "net";
@@ -534,8 +388,8 @@ function buildDraftCard(apiKey, owner) {
534
388
 
535
389
  // src/onboarding/index.ts
536
390
  import { randomUUID as randomUUID3 } from "crypto";
537
- import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
538
- import { join as join2 } from "path";
391
+ import { existsSync, readFileSync } from "fs";
392
+ import { join } from "path";
539
393
 
540
394
  // src/onboarding/capability-templates.ts
541
395
  var API_PATTERNS = [
@@ -650,9 +504,9 @@ var DOC_FILES = ["SOUL.md", "CLAUDE.md", "AGENTS.md", "README.md"];
650
504
  function detectCapabilities(opts = {}) {
651
505
  const cwd = opts.cwd ?? process.cwd();
652
506
  if (opts.fromFile) {
653
- const filePath = opts.fromFile.startsWith("/") ? opts.fromFile : join2(cwd, opts.fromFile);
654
- if (existsSync2(filePath)) {
655
- 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");
656
510
  const capabilities = detectFromDocs(content);
657
511
  if (capabilities.length > 0) {
658
512
  return { source: "docs", capabilities, sourceFile: filePath };
@@ -661,9 +515,9 @@ function detectCapabilities(opts = {}) {
661
515
  return { source: "none", capabilities: [] };
662
516
  }
663
517
  for (const fileName of DOC_FILES) {
664
- const filePath = join2(cwd, fileName);
665
- if (!existsSync2(filePath)) continue;
666
- const content = readFileSync2(filePath, "utf-8");
518
+ const filePath = join(cwd, fileName);
519
+ if (!existsSync(filePath)) continue;
520
+ const content = readFileSync(filePath, "utf-8");
667
521
  if (fileName === "SOUL.md") {
668
522
  return { source: "soul", capabilities: [], soulContent: content, sourceFile: filePath };
669
523
  }
@@ -707,8 +561,49 @@ function capabilitiesToV2Card(capabilities, owner, agentName) {
707
561
  return CapabilityCardV2Schema.parse(card);
708
562
  }
709
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
+
710
605
  // src/runtime/agent-runtime.ts
711
- import { readFileSync as readFileSync3, existsSync as existsSync3 } from "fs";
606
+ import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
712
607
 
713
608
  // src/skills/executor.ts
714
609
  var SkillExecutor = class {
@@ -735,7 +630,7 @@ var SkillExecutor = class {
735
630
  * @param params - Input parameters for the skill.
736
631
  * @returns ExecutionResult including success, result/error, and latency_ms.
737
632
  */
738
- async execute(skillId, params) {
633
+ async execute(skillId, params, onProgress) {
739
634
  const startTime = Date.now();
740
635
  const config = this.skillMap.get(skillId);
741
636
  if (!config) {
@@ -754,7 +649,7 @@ var SkillExecutor = class {
754
649
  };
755
650
  }
756
651
  try {
757
- const modeResult = await mode.execute(config, params);
652
+ const modeResult = await mode.execute(config, params, onProgress);
758
653
  return {
759
654
  ...modeResult,
760
655
  latency_ms: Date.now() - startTime
@@ -791,98 +686,98 @@ function createSkillExecutor(configs, modes) {
791
686
  }
792
687
 
793
688
  // src/skills/skill-config.ts
794
- import { z as z3 } from "zod";
689
+ import { z as z2 } from "zod";
795
690
  import yaml from "js-yaml";
796
- var PricingSchema = z3.object({
797
- credits_per_call: z3.number().nonnegative(),
798
- credits_per_minute: z3.number().nonnegative().optional(),
799
- 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()
800
695
  });
801
- var ApiAuthSchema = z3.discriminatedUnion("type", [
802
- z3.object({
803
- type: z3.literal("bearer"),
804
- token: z3.string()
696
+ var ApiAuthSchema = z2.discriminatedUnion("type", [
697
+ z2.object({
698
+ type: z2.literal("bearer"),
699
+ token: z2.string()
805
700
  }),
806
- z3.object({
807
- type: z3.literal("apikey"),
808
- header: z3.string().default("X-API-Key"),
809
- key: z3.string()
701
+ z2.object({
702
+ type: z2.literal("apikey"),
703
+ header: z2.string().default("X-API-Key"),
704
+ key: z2.string()
810
705
  }),
811
- z3.object({
812
- type: z3.literal("basic"),
813
- username: z3.string(),
814
- password: z3.string()
706
+ z2.object({
707
+ type: z2.literal("basic"),
708
+ username: z2.string(),
709
+ password: z2.string()
815
710
  })
816
711
  ]);
817
- var ApiSkillConfigSchema = z3.object({
818
- id: z3.string().min(1),
819
- type: z3.literal("api"),
820
- name: z3.string().min(1),
821
- endpoint: z3.string().min(1),
822
- 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"]),
823
718
  auth: ApiAuthSchema.optional(),
824
- input_mapping: z3.record(z3.string()).default({}),
825
- output_mapping: z3.record(z3.string()).default({}),
719
+ input_mapping: z2.record(z2.string()).default({}),
720
+ output_mapping: z2.record(z2.string()).default({}),
826
721
  pricing: PricingSchema,
827
- timeout_ms: z3.number().positive().default(3e4),
828
- retries: z3.number().nonnegative().int().default(0),
829
- 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()
830
725
  });
831
- var PipelineStepSchema = z3.union([
832
- z3.object({
833
- skill_id: z3.string().min(1),
834
- 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({})
835
730
  }),
836
- z3.object({
837
- command: z3.string().min(1),
838
- input_mapping: z3.record(z3.string()).default({})
731
+ z2.object({
732
+ command: z2.string().min(1),
733
+ input_mapping: z2.record(z2.string()).default({})
839
734
  })
840
735
  ]);
841
- var PipelineSkillConfigSchema = z3.object({
842
- id: z3.string().min(1),
843
- type: z3.literal("pipeline"),
844
- name: z3.string().min(1),
845
- 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),
846
741
  pricing: PricingSchema,
847
- timeout_ms: z3.number().positive().optional()
742
+ timeout_ms: z2.number().positive().optional()
848
743
  });
849
- var OpenClawSkillConfigSchema = z3.object({
850
- id: z3.string().min(1),
851
- type: z3.literal("openclaw"),
852
- name: z3.string().min(1),
853
- agent_name: z3.string().min(1),
854
- 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"]),
855
750
  pricing: PricingSchema,
856
- timeout_ms: z3.number().positive().optional()
751
+ timeout_ms: z2.number().positive().optional()
857
752
  });
858
- var CommandSkillConfigSchema = z3.object({
859
- id: z3.string().min(1),
860
- type: z3.literal("command"),
861
- name: z3.string().min(1),
862
- command: z3.string().min(1),
863
- output_type: z3.enum(["json", "text", "file"]),
864
- allowed_commands: z3.array(z3.string()).optional(),
865
- working_dir: z3.string().optional(),
866
- 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),
867
762
  pricing: PricingSchema
868
763
  });
869
- var ConductorSkillConfigSchema = z3.object({
870
- id: z3.string().min(1),
871
- type: z3.literal("conductor"),
872
- name: z3.string().min(1),
873
- 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"]),
874
769
  pricing: PricingSchema,
875
- timeout_ms: z3.number().positive().optional()
770
+ timeout_ms: z2.number().positive().optional()
876
771
  });
877
- var SkillConfigSchema = z3.discriminatedUnion("type", [
772
+ var SkillConfigSchema = z2.discriminatedUnion("type", [
878
773
  ApiSkillConfigSchema,
879
774
  PipelineSkillConfigSchema,
880
775
  OpenClawSkillConfigSchema,
881
776
  CommandSkillConfigSchema,
882
777
  ConductorSkillConfigSchema
883
778
  ]);
884
- var SkillsFileSchema = z3.object({
885
- skills: z3.array(SkillConfigSchema)
779
+ var SkillsFileSchema = z2.object({
780
+ skills: z2.array(SkillConfigSchema)
886
781
  });
887
782
  function expandEnvVars(value) {
888
783
  return value.replace(/\$\{([^}]+)\}/g, (_match, varName) => {
@@ -1125,7 +1020,7 @@ var PipelineExecutor = class {
1125
1020
  * @param params - Input parameters from the caller.
1126
1021
  * @returns Partial ExecutionResult (without latency_ms — added by SkillExecutor wrapper).
1127
1022
  */
1128
- async execute(config, params) {
1023
+ async execute(config, params, onProgress) {
1129
1024
  const pipelineConfig = config;
1130
1025
  const steps = pipelineConfig.steps ?? [];
1131
1026
  if (steps.length === 0) {
@@ -1184,6 +1079,13 @@ var PipelineExecutor = class {
1184
1079
  }
1185
1080
  context.steps.push({ result: stepResult });
1186
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
+ }
1187
1089
  }
1188
1090
  const lastStep = context.steps[context.steps.length - 1];
1189
1091
  return {
@@ -1519,20 +1421,20 @@ var AgentRuntime = class {
1519
1421
  * 3. Populate the Map with all 4 modes — SkillExecutor sees them via reference.
1520
1422
  */
1521
1423
  async initSkillExecutor() {
1522
- const hasSkillsYaml = this.skillsYamlPath && existsSync3(this.skillsYamlPath);
1424
+ const hasSkillsYaml = this.skillsYamlPath && existsSync2(this.skillsYamlPath);
1523
1425
  if (!hasSkillsYaml && !this.conductorEnabled) {
1524
1426
  return;
1525
1427
  }
1526
1428
  let configs = [];
1527
1429
  if (hasSkillsYaml) {
1528
- const yamlContent = readFileSync3(this.skillsYamlPath, "utf8");
1430
+ const yamlContent = readFileSync2(this.skillsYamlPath, "utf8");
1529
1431
  configs = parseSkillsFile(yamlContent);
1530
1432
  }
1531
1433
  const modes = /* @__PURE__ */ new Map();
1532
1434
  if (this.conductorEnabled) {
1533
- const { ConductorMode } = await import("../conductor-mode-XU7ONJWC.js");
1534
- const { registerConductorCard, CONDUCTOR_OWNER } = await import("../card-IE5UV5QX.js");
1535
- 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");
1536
1438
  registerConductorCard(this.registryDb);
1537
1439
  const resolveAgentUrl = (owner) => {
1538
1440
  const peers = loadPeers2();
@@ -1644,7 +1546,7 @@ function createGatewayServer(opts) {
1644
1546
  creditDb,
1645
1547
  tokens,
1646
1548
  handlerUrl,
1647
- timeoutMs = 3e4,
1549
+ timeoutMs = 3e5,
1648
1550
  silent = false,
1649
1551
  skillExecutor
1650
1552
  } = opts;
@@ -1739,21 +1641,365 @@ function createGatewayServer(opts) {
1739
1641
  // src/registry/server.ts
1740
1642
  import Fastify2 from "fastify";
1741
1643
  import cors from "@fastify/cors";
1644
+ import swagger from "@fastify/swagger";
1645
+ import swaggerUi from "@fastify/swagger-ui";
1742
1646
  import fastifyStatic from "@fastify/static";
1743
1647
  import fastifyWebsocket from "@fastify/websocket";
1744
- import { join as join3, dirname } from "path";
1648
+ import { join as join2, dirname } from "path";
1745
1649
  import { fileURLToPath } from "url";
1746
- import { existsSync as existsSync4 } from "fs";
1650
+ import { existsSync as existsSync3 } from "fs";
1747
1651
 
1748
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
1749
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
1750
1995
  var RATE_LIMIT_MAX = 60;
1751
1996
  var RATE_LIMIT_WINDOW_MS = 6e4;
1752
- var RELAY_TIMEOUT_MS = 3e4;
1753
- function registerWebSocketRelay(server, db) {
1997
+ var RELAY_TIMEOUT_MS = 3e5;
1998
+ function registerWebSocketRelay(server, db, creditDb) {
1754
1999
  const connections = /* @__PURE__ */ new Map();
1755
2000
  const pendingRequests = /* @__PURE__ */ new Map();
1756
2001
  const rateLimits = /* @__PURE__ */ new Map();
2002
+ let onAgentOnlineCallback;
1757
2003
  function checkRateLimit(owner) {
1758
2004
  const now = Date.now();
1759
2005
  const entry = rateLimits.get(owner);
@@ -1804,21 +2050,32 @@ function registerWebSocketRelay(server, db) {
1804
2050
  }
1805
2051
  }
1806
2052
  function upsertCard(cardData, owner) {
1807
- const cardId = cardData.id;
1808
- 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);
1809
2064
  if (existing) {
1810
- const updates = { ...cardData, availability: { ...cardData.availability ?? {}, online: true } };
1811
- 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);
1812
2068
  } else {
1813
- const card = { ...cardData, availability: { ...cardData.availability ?? {}, online: true } };
1814
- 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);
1815
2072
  }
1816
2073
  return cardId;
1817
2074
  }
1818
2075
  function logAgentJoined(owner, cardName, cardId) {
1819
2076
  try {
1820
2077
  insertRequestLog(db, {
1821
- id: randomUUID4(),
2078
+ id: randomUUID6(),
1822
2079
  card_id: cardId,
1823
2080
  card_name: cardName,
1824
2081
  requester: owner,
@@ -1849,10 +2106,25 @@ function registerWebSocketRelay(server, db) {
1849
2106
  const cardId = upsertCard(card, owner);
1850
2107
  const cardName = card.name ?? card.agent_name ?? owner;
1851
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
+ }
1852
2117
  markOwnerOnline(owner);
2118
+ if (onAgentOnlineCallback) {
2119
+ try {
2120
+ onAgentOnlineCallback(owner);
2121
+ } catch (e) {
2122
+ console.error("[relay] onAgentOnline callback error:", e);
2123
+ }
2124
+ }
1853
2125
  sendMessage(ws, { type: "registered", agent_id: cardId });
1854
2126
  }
1855
- function handleRelayRequest(ws, msg, fromOwner) {
2127
+ async function handleRelayRequest(ws, msg, fromOwner) {
1856
2128
  if (!checkRateLimit(fromOwner)) {
1857
2129
  sendMessage(ws, {
1858
2130
  type: "error",
@@ -1871,15 +2143,42 @@ function registerWebSocketRelay(server, db) {
1871
2143
  });
1872
2144
  return;
1873
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
+ }
1874
2165
  const timeout = setTimeout(() => {
2166
+ const pending = pendingRequests.get(msg.id);
1875
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
+ }
1876
2175
  sendMessage(ws, {
1877
2176
  type: "response",
1878
2177
  id: msg.id,
1879
2178
  error: { code: -32603, message: "Relay request timeout" }
1880
2179
  });
1881
2180
  }, RELAY_TIMEOUT_MS);
1882
- pendingRequests.set(msg.id, { originOwner: fromOwner, timeout });
2181
+ pendingRequests.set(msg.id, { originOwner: fromOwner, timeout, escrowId, targetOwner: msg.target_owner });
1883
2182
  sendMessage(targetWs, {
1884
2183
  type: "incoming_request",
1885
2184
  id: msg.id,
@@ -1891,18 +2190,98 @@ function registerWebSocketRelay(server, db) {
1891
2190
  escrow_receipt: msg.escrow_receipt
1892
2191
  });
1893
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
+ }
1894
2227
  function handleRelayResponse(msg) {
1895
2228
  const pending = pendingRequests.get(msg.id);
1896
2229
  if (!pending) return;
1897
2230
  clearTimeout(pending.timeout);
1898
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
+ }
1899
2277
  const originWs = connections.get(pending.originOwner);
1900
2278
  if (originWs && originWs.readyState === 1) {
1901
2279
  sendMessage(originWs, {
1902
2280
  type: "response",
1903
2281
  id: msg.id,
1904
2282
  result: msg.result,
1905
- error: msg.error
2283
+ error: msg.error,
2284
+ ...conductorFee > 0 ? { conductor_fee: conductorFee } : {}
1906
2285
  });
1907
2286
  }
1908
2287
  }
@@ -1912,9 +2291,34 @@ function registerWebSocketRelay(server, db) {
1912
2291
  rateLimits.delete(owner);
1913
2292
  markOwnerOffline(owner);
1914
2293
  for (const [reqId, pending] of pendingRequests) {
1915
- if (pending.originOwner === owner) {
2294
+ if (pending.targetOwner === owner) {
2295
+ clearTimeout(pending.timeout);
2296
+ pendingRequests.delete(reqId);
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);
2302
+ }
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) {
1916
2313
  clearTimeout(pending.timeout);
1917
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
+ }
1918
2322
  }
1919
2323
  }
1920
2324
  }
@@ -1922,45 +2326,50 @@ function registerWebSocketRelay(server, db) {
1922
2326
  const socket = rawSocket;
1923
2327
  let registeredOwner;
1924
2328
  socket.on("message", (raw) => {
1925
- let data;
1926
- try {
1927
- data = JSON.parse(typeof raw === "string" ? raw : raw.toString("utf-8"));
1928
- } catch {
1929
- sendMessage(socket, { type: "error", code: "invalid_json", message: "Invalid JSON" });
1930
- return;
1931
- }
1932
- const parsed = RelayMessageSchema.safeParse(data);
1933
- if (!parsed.success) {
1934
- sendMessage(socket, {
1935
- type: "error",
1936
- code: "invalid_message",
1937
- message: `Invalid message: ${parsed.error.issues[0]?.message ?? "unknown error"}`
1938
- });
1939
- return;
1940
- }
1941
- const msg = parsed.data;
1942
- switch (msg.type) {
1943
- case "register":
1944
- registeredOwner = msg.owner;
1945
- handleRegister(socket, msg);
1946
- break;
1947
- case "relay_request":
1948
- if (!registeredOwner) {
1949
- sendMessage(socket, {
1950
- type: "error",
1951
- code: "not_registered",
1952
- message: "Must send register message before relay requests"
1953
- });
1954
- return;
1955
- }
1956
- handleRelayRequest(socket, msg, registeredOwner);
1957
- break;
1958
- case "relay_response":
1959
- handleRelayResponse(msg);
1960
- break;
1961
- default:
1962
- break;
1963
- }
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
+ })();
1964
2373
  });
1965
2374
  socket.on("close", () => {
1966
2375
  handleDisconnect(registeredOwner);
@@ -1985,21 +2394,29 @@ function registerWebSocketRelay(server, db) {
1985
2394
  }
1986
2395
  pendingRequests.clear();
1987
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);
1988
2405
  }
1989
2406
  };
1990
2407
  }
1991
2408
 
1992
2409
  // src/identity/guarantor.ts
1993
- import { z as z4 } from "zod";
1994
- import { randomUUID as randomUUID5 } from "crypto";
2410
+ import { z as z3 } from "zod";
2411
+ import { randomUUID as randomUUID7 } from "crypto";
1995
2412
  var MAX_AGENTS_PER_GUARANTOR = 10;
1996
2413
  var GUARANTOR_CREDIT_POOL = 50;
1997
- var GuarantorRecordSchema = z4.object({
1998
- id: z4.string().uuid(),
1999
- github_login: z4.string().min(1),
2000
- agent_count: z4.number().int().nonnegative(),
2001
- credit_pool: z4.number().int().nonnegative(),
2002
- 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()
2003
2420
  });
2004
2421
  var GUARANTOR_SCHEMA = `
2005
2422
  CREATE TABLE IF NOT EXISTS guarantors (
@@ -2030,7 +2447,7 @@ function registerGuarantor(db, githubLogin) {
2030
2447
  );
2031
2448
  }
2032
2449
  const record = {
2033
- id: randomUUID5(),
2450
+ id: randomUUID7(),
2034
2451
  github_login: githubLogin,
2035
2452
  agent_count: 0,
2036
2453
  credit_pool: GUARANTOR_CREDIT_POOL,
@@ -2104,10 +2521,849 @@ function getAgentGuarantor(db, agentId) {
2104
2521
  function initiateGithubAuth() {
2105
2522
  return {
2106
2523
  auth_url: "https://github.com/login/oauth/authorize?client_id=PLACEHOLDER&scope=read:user",
2107
- state: randomUUID5()
2524
+ state: randomUUID7()
2108
2525
  };
2109
2526
  }
2110
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
+
2111
3367
  // src/registry/server.ts
2112
3368
  function stripInternal(card) {
2113
3369
  const { _internal: _, ...publicCard } = card;
@@ -2116,25 +3372,76 @@ function stripInternal(card) {
2116
3372
  function createRegistryServer(opts) {
2117
3373
  const { registryDb: db, silent = false } = opts;
2118
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
+ });
2119
3410
  void server.register(cors, {
2120
3411
  origin: true,
2121
3412
  methods: ["GET", "POST", "PATCH", "OPTIONS"],
2122
- allowedHeaders: ["Content-Type", "Authorization"]
3413
+ allowedHeaders: ["Content-Type", "Authorization", "X-Agent-PublicKey", "X-Agent-Signature", "X-Agent-Timestamp"]
2123
3414
  });
2124
3415
  void server.register(fastifyWebsocket);
2125
3416
  let relayState = null;
2126
3417
  if (opts.creditDb) {
2127
- 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
+ }
2128
3435
  }
2129
3436
  const __filename = fileURLToPath(import.meta.url);
2130
3437
  const __dirname = dirname(__filename);
2131
3438
  const hubDistCandidates = [
2132
- join3(__dirname, "../../hub/dist"),
3439
+ join2(__dirname, "../../hub/dist"),
2133
3440
  // When running from dist/registry/server.js
2134
- join3(__dirname, "../../../hub/dist")
3441
+ join2(__dirname, "../../../hub/dist")
2135
3442
  // Fallback for alternative layouts
2136
3443
  ];
2137
- const hubDistDir = hubDistCandidates.find((p) => existsSync4(p));
3444
+ const hubDistDir = hubDistCandidates.find((p) => existsSync3(p));
2138
3445
  if (hubDistDir) {
2139
3446
  void server.register(fastifyStatic, {
2140
3447
  root: hubDistDir,
@@ -2153,44 +3460,82 @@ function createRegistryServer(opts) {
2153
3460
  return reply.code(404).send({ error: "Not found" });
2154
3461
  });
2155
3462
  }
2156
- server.get("/health", async (_request, reply) => {
2157
- return reply.send({ status: "ok" });
2158
- });
2159
- server.get("/cards", async (request, reply) => {
2160
- const query = request.query;
2161
- const q = query.q?.trim() ?? "";
2162
- const levelRaw = query.level !== void 0 ? parseInt(query.level, 10) : void 0;
2163
- const level = levelRaw === 1 || levelRaw === 2 || levelRaw === 3 ? levelRaw : void 0;
2164
- const onlineRaw = query.online;
2165
- const online = onlineRaw === "true" ? true : onlineRaw === "false" ? false : void 0;
2166
- const tag = query.tag?.trim();
2167
- const minSuccessRate = query.min_success_rate !== void 0 ? parseFloat(query.min_success_rate) : void 0;
2168
- const maxLatencyMs = query.max_latency_ms !== void 0 ? parseFloat(query.max_latency_ms) : void 0;
2169
- const sort = query.sort;
2170
- const rawLimit = query.limit !== void 0 ? parseInt(query.limit, 10) : 20;
2171
- const limit = Math.min(isNaN(rawLimit) || rawLimit < 1 ? 20 : rawLimit, 100);
2172
- const rawOffset = query.offset !== void 0 ? parseInt(query.offset, 10) : 0;
2173
- const offset = isNaN(rawOffset) || rawOffset < 0 ? 0 : rawOffset;
2174
- let cards;
2175
- if (q.length > 0) {
2176
- cards = searchCards(db, q, { level, online });
2177
- } else {
2178
- cards = filterCards(db, { level, online });
2179
- }
2180
- if (tag !== void 0 && tag.length > 0) {
2181
- cards = cards.filter((c) => c.metadata?.tags?.includes(tag));
2182
- }
2183
- if (minSuccessRate !== void 0 && !isNaN(minSuccessRate)) {
2184
- cards = cards.filter(
2185
- (c) => (c.metadata?.success_rate ?? -1) >= minSuccessRate
2186
- );
2187
- }
2188
- if (maxLatencyMs !== void 0 && !isNaN(maxLatencyMs)) {
2189
- cards = cards.filter(
2190
- (c) => (c.metadata?.avg_latency_ms ?? Infinity) <= maxLatencyMs
2191
- );
2192
- }
2193
- 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(`
2194
3539
  SELECT card_id, skill_id, COUNT(*) as cnt
2195
3540
  FROM request_log
2196
3541
  WHERE status = 'success'
@@ -2198,57 +3543,63 @@ function createRegistryServer(opts) {
2198
3543
  AND (action_type IS NULL OR action_type = 'auto_share')
2199
3544
  GROUP BY card_id, skill_id
2200
3545
  `);
2201
- const usesRows = usesStmt.all();
2202
- const usesMap = /* @__PURE__ */ new Map();
2203
- for (const row of usesRows) {
2204
- usesMap.set(row.card_id, (usesMap.get(row.card_id) ?? 0) + row.cnt);
2205
- if (row.skill_id) {
2206
- usesMap.set(row.skill_id, (usesMap.get(row.skill_id) ?? 0) + row.cnt);
2207
- }
2208
- }
2209
- if (sort === "popular") {
2210
- cards = [...cards].sort((a, b) => {
2211
- const aUses = usesMap.get(a.id) ?? 0;
2212
- const bUses = usesMap.get(b.id) ?? 0;
2213
- return bUses - aUses;
2214
- });
2215
- } else if (sort === "rated" || sort === "success_rate") {
2216
- cards = [...cards].sort((a, b) => {
2217
- const aRate = a.metadata?.success_rate ?? -1;
2218
- const bRate = b.metadata?.success_rate ?? -1;
2219
- return bRate - aRate;
2220
- });
2221
- } else if (sort === "cheapest") {
2222
- cards = [...cards].sort((a, b) => {
2223
- return a.pricing.credits_per_call - b.pricing.credits_per_call;
2224
- });
2225
- } else if (sort === "newest") {
2226
- const createdStmt = db.prepare("SELECT id, created_at FROM capability_cards");
2227
- const createdRows = createdStmt.all();
2228
- const createdMap = new Map(createdRows.map((r) => [r.id, r.created_at]));
2229
- cards = [...cards].sort((a, b) => {
2230
- const aDate = createdMap.get(a.id) ?? "";
2231
- const bDate = createdMap.get(b.id) ?? "";
2232
- return bDate.localeCompare(aDate);
2233
- });
2234
- } else if (sort === "latency") {
2235
- cards = [...cards].sort((a, b) => {
2236
- const aLatency = a.metadata?.avg_latency_ms ?? Infinity;
2237
- const bLatency = b.metadata?.avg_latency_ms ?? Infinity;
2238
- return aLatency - bLatency;
2239
- });
2240
- }
2241
- const total = cards.length;
2242
- const items = cards.slice(offset, offset + limit).map(stripInternal);
2243
- const usesThisWeek = {};
2244
- for (const [key, count] of usesMap) {
2245
- if (count > 0) usesThisWeek[key] = count;
2246
- }
2247
- const result = { total, limit, offset, items, uses_this_week: usesThisWeek };
2248
- return reply.send(result);
2249
- });
2250
- server.get("/api/cards/trending", async (_request, reply) => {
2251
- 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(`
2252
3603
  SELECT rl.card_id, COUNT(*) as recent_requests
2253
3604
  FROM request_log rl
2254
3605
  WHERE rl.status = 'success'
@@ -2258,350 +3609,656 @@ function createRegistryServer(opts) {
2258
3609
  ORDER BY recent_requests DESC
2259
3610
  LIMIT 10
2260
3611
  `);
2261
- const trendingRows = trendingStmt.all();
2262
- const items = trendingRows.map((row) => {
2263
- const card = getCard(db, row.card_id);
2264
- if (!card) return null;
2265
- return { ...stripInternal(card), uses_this_week: row.recent_requests };
2266
- }).filter((item) => item !== null);
2267
- return reply.send({ items });
2268
- });
2269
- server.get("/cards/:id", async (request, reply) => {
2270
- const { id } = request.params;
2271
- const card = getCard(db, id);
2272
- if (!card) {
2273
- return reply.code(404).send({ error: "Not found" });
2274
- }
2275
- return reply.send(stripInternal(card));
2276
- });
2277
- server.post("/cards", async (request, reply) => {
2278
- const body = request.body;
2279
- if (!body.spec_version) {
2280
- body.spec_version = "1.0";
2281
- }
2282
- const result = AnyCardSchema.safeParse(body);
2283
- if (!result.success) {
2284
- return reply.code(400).send({
2285
- error: "Card validation failed",
2286
- issues: result.error.issues
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)
3703
+ VALUES (?, ?, ?, ?, ?)`
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;
3719
+ }
3720
+ }
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(`
3762
+ SELECT cc.owner,
3763
+ SUM(CASE WHEN rl.status = 'success' THEN rl.credits_charged ELSE 0 END) as credits_earned
3764
+ FROM capability_cards cc
3765
+ LEFT JOIN request_log rl ON rl.card_id = cc.id
3766
+ GROUP BY cc.owner
3767
+ `);
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
+ };
2287
3785
  });
2288
- }
2289
- const card = result.data;
2290
- const now = (/* @__PURE__ */ new Date()).toISOString();
2291
- if (card.spec_version === "2.0") {
2292
- const cardWithTimestamps = {
2293
- ...card,
2294
- created_at: card.created_at ?? now,
2295
- updated_at: now
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 });
3793
+ });
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(`
3821
+ SELECT SUM(CASE WHEN rl.status = 'success' THEN rl.credits_charged ELSE 0 END) as credits_earned
3822
+ FROM capability_cards cc
3823
+ LEFT JOIN request_log rl ON rl.card_id = cc.id
3824
+ WHERE cc.owner = ?
3825
+ `);
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(`
3832
+ SELECT rl.id, rl.card_name, rl.requester, rl.status, rl.credits_charged, rl.created_at
3833
+ FROM request_log rl
3834
+ INNER JOIN capability_cards cc ON rl.card_id = cc.id
3835
+ WHERE cc.owner = ?
3836
+ ORDER BY rl.created_at DESC
3837
+ LIMIT 10
3838
+ `);
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()
2296
3846
  };
2297
- db.prepare(
2298
- `INSERT OR REPLACE INTO capability_cards (id, owner, data, created_at, updated_at)
2299
- VALUES (?, ?, ?, ?, ?)`
2300
- ).run(
2301
- cardWithTimestamps.id,
2302
- cardWithTimestamps.owner,
2303
- JSON.stringify(cardWithTimestamps),
2304
- cardWithTimestamps.created_at,
2305
- cardWithTimestamps.updated_at
3847
+ return reply.send({
3848
+ profile,
3849
+ skills: ownerCards,
3850
+ recent_activity: recentActivity
3851
+ });
3852
+ });
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')"
2306
3913
  );
2307
- } else {
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
+ }
3938
+ }
3939
+ }, async (request, reply) => {
3940
+ if (!opts.creditDb) {
3941
+ return reply.code(503).send({ error: "Credit database not configured" });
3942
+ }
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" });
3947
+ }
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;
3957
+ }
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" } } }
3976
+ }
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
+ }
2308
3988
  try {
2309
- insertCard(db, card);
3989
+ const record = linkAgentToGuarantor(opts.creditDb, agentId, githubLogin);
3990
+ return reply.send({ guarantor: record });
2310
3991
  } catch (err) {
2311
- if (err instanceof AgentBnBError && err.code === "VALIDATION_ERROR") {
2312
- return reply.code(400).send({ error: err.message });
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 });
2313
4000
  }
2314
4001
  throw err;
2315
4002
  }
2316
- }
2317
- return reply.code(201).send({ ok: true, id: card.id });
2318
- });
2319
- server.delete("/cards/:id", async (request, reply) => {
2320
- const { id } = request.params;
2321
- const card = getCard(db, id);
2322
- if (!card) {
2323
- return reply.code(404).send({ error: "Not found" });
2324
- }
2325
- db.prepare("DELETE FROM capability_cards WHERE id = ?").run(id);
2326
- return reply.send({ ok: true, id });
2327
- });
2328
- server.get("/api/agents", async (_request, reply) => {
2329
- const allCards = listCards(db);
2330
- const ownerMap = /* @__PURE__ */ new Map();
2331
- for (const card of allCards) {
2332
- const existing = ownerMap.get(card.owner) ?? [];
2333
- existing.push(card);
2334
- ownerMap.set(card.owner, existing);
2335
- }
2336
- const creditsStmt = db.prepare(`
2337
- SELECT cc.owner,
2338
- SUM(CASE WHEN rl.status = 'success' THEN rl.credits_charged ELSE 0 END) as credits_earned
2339
- FROM capability_cards cc
2340
- LEFT JOIN request_log rl ON rl.card_id = cc.id
2341
- GROUP BY cc.owner
2342
- `);
2343
- const creditsRows = creditsStmt.all();
2344
- const creditsMap = new Map(creditsRows.map((r) => [r.owner, r.credits_earned ?? 0]));
2345
- const agents = Array.from(ownerMap.entries()).map(([owner, cards]) => {
2346
- const skillCount = cards.reduce((sum, card) => sum + (card.skills?.length ?? 1), 0);
2347
- const successRates = cards.map((c) => c.metadata?.success_rate).filter((r) => r != null);
2348
- const avgSuccessRate = successRates.length > 0 ? successRates.reduce((a, b) => a + b, 0) / successRates.length : null;
2349
- const memberStmt = db.prepare(
2350
- "SELECT MIN(created_at) as earliest FROM capability_cards WHERE owner = ?"
2351
- );
2352
- const memberRow = memberStmt.get(owner);
2353
- return {
2354
- owner,
2355
- skill_count: skillCount,
2356
- success_rate: avgSuccessRate,
2357
- total_earned: creditsMap.get(owner) ?? 0,
2358
- member_since: memberRow?.earliest ?? (/* @__PURE__ */ new Date()).toISOString()
2359
- };
2360
- });
2361
- agents.sort((a, b) => {
2362
- const aRate = a.success_rate ?? -1;
2363
- const bRate = b.success_rate ?? -1;
2364
- if (bRate !== aRate) return bRate - aRate;
2365
- return b.total_earned - a.total_earned;
2366
- });
2367
- return reply.send({ items: agents, total: agents.length });
2368
- });
2369
- server.get("/api/agents/:owner", async (request, reply) => {
2370
- const { owner } = request.params;
2371
- const ownerCards = listCards(db, owner);
2372
- if (ownerCards.length === 0) {
2373
- return reply.status(404).send({ error: "Agent not found" });
2374
- }
2375
- const skillCount = ownerCards.reduce((sum, card) => sum + (card.skills?.length ?? 1), 0);
2376
- const successRates = ownerCards.map((c) => c.metadata?.success_rate).filter((r) => r != null);
2377
- const avgSuccessRate = successRates.length > 0 ? successRates.reduce((a, b) => a + b, 0) / successRates.length : null;
2378
- const creditsStmt = db.prepare(`
2379
- SELECT SUM(CASE WHEN rl.status = 'success' THEN rl.credits_charged ELSE 0 END) as credits_earned
2380
- FROM capability_cards cc
2381
- LEFT JOIN request_log rl ON rl.card_id = cc.id
2382
- WHERE cc.owner = ?
2383
- `);
2384
- const creditsRow = creditsStmt.get(owner);
2385
- const memberStmt = db.prepare(
2386
- "SELECT MIN(created_at) as earliest FROM capability_cards WHERE owner = ?"
2387
- );
2388
- const memberRow = memberStmt.get(owner);
2389
- const activityStmt = db.prepare(`
2390
- SELECT rl.id, rl.card_name, rl.requester, rl.status, rl.credits_charged, rl.created_at
2391
- FROM request_log rl
2392
- INNER JOIN capability_cards cc ON rl.card_id = cc.id
2393
- WHERE cc.owner = ?
2394
- ORDER BY rl.created_at DESC
2395
- LIMIT 10
2396
- `);
2397
- const recentActivity = activityStmt.all(owner);
2398
- const profile = {
2399
- owner,
2400
- skill_count: skillCount,
2401
- success_rate: avgSuccessRate,
2402
- total_earned: creditsRow?.credits_earned ?? 0,
2403
- member_since: memberRow?.earliest ?? (/* @__PURE__ */ new Date()).toISOString()
2404
- };
2405
- return reply.send({
2406
- profile,
2407
- skills: ownerCards,
2408
- recent_activity: recentActivity
2409
4003
  });
2410
- });
2411
- server.get("/api/activity", async (request, reply) => {
2412
- const query = request.query;
2413
- const rawLimit = query.limit !== void 0 ? parseInt(query.limit, 10) : 20;
2414
- const limit = Math.min(isNaN(rawLimit) || rawLimit < 1 ? 20 : rawLimit, 100);
2415
- const since = query.since?.trim() || void 0;
2416
- const items = getActivityFeed(db, limit, since);
2417
- return reply.send({ items, total: items.length, limit });
2418
- });
2419
- server.get("/api/stats", async (_request, reply) => {
2420
- const allCards = listCards(db);
2421
- const onlineOwners = /* @__PURE__ */ new Set();
2422
- if (relayState) {
2423
- for (const owner of relayState.getOnlineOwners()) {
2424
- onlineOwners.add(owner);
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
+ }
4016
+ }
4017
+ }
2425
4018
  }
2426
- }
2427
- for (const card of allCards) {
2428
- if (card.availability.online) {
2429
- onlineOwners.add(card.owner);
4019
+ }, async (request, reply) => {
4020
+ if (!opts.creditDb) {
4021
+ return reply.code(503).send({ error: "Credit database not configured" });
2430
4022
  }
2431
- }
2432
- const exchangeStmt = db.prepare(
2433
- "SELECT COUNT(*) as count FROM request_log WHERE status = 'success' AND (action_type IS NULL OR action_type = 'auto_share')"
2434
- );
2435
- const exchangeRow = exchangeStmt.get();
2436
- return reply.send({
2437
- agents_online: onlineOwners.size,
2438
- total_capabilities: allCards.reduce((sum, card) => {
2439
- const v2 = card;
2440
- return sum + (v2.skills?.length ?? 1);
2441
- }, 0),
2442
- total_exchanges: exchangeRow.count
4023
+ const { agent_id } = request.params;
4024
+ const guarantor = getAgentGuarantor(opts.creditDb, agent_id);
4025
+ return reply.send({ agent_id, guarantor });
2443
4026
  });
2444
- });
2445
- server.post("/api/identity/register", async (request, reply) => {
2446
- if (!opts.creditDb) {
2447
- return reply.code(503).send({ error: "Credit database not configured" });
2448
- }
2449
- const body = request.body;
2450
- const githubLogin = typeof body.github_login === "string" ? body.github_login.trim() : "";
2451
- if (!githubLogin) {
2452
- return reply.code(400).send({ error: "github_login is required" });
2453
- }
2454
- try {
2455
- const record = registerGuarantor(opts.creditDb, githubLogin);
2456
- const auth = initiateGithubAuth();
2457
- return reply.code(201).send({ guarantor: record, oauth: auth });
2458
- } catch (err) {
2459
- if (err instanceof AgentBnBError && err.code === "GUARANTOR_EXISTS") {
2460
- return reply.code(409).send({ error: err.message });
2461
- }
2462
- throw err;
2463
- }
2464
- });
2465
- server.post("/api/identity/link", async (request, reply) => {
2466
- if (!opts.creditDb) {
2467
- return reply.code(503).send({ error: "Credit database not configured" });
2468
- }
2469
- const body = request.body;
2470
- const agentId = typeof body.agent_id === "string" ? body.agent_id.trim() : "";
2471
- const githubLogin = typeof body.github_login === "string" ? body.github_login.trim() : "";
2472
- if (!agentId || !githubLogin) {
2473
- return reply.code(400).send({ error: "agent_id and github_login are required" });
2474
- }
2475
- try {
2476
- const record = linkAgentToGuarantor(opts.creditDb, agentId, githubLogin);
2477
- return reply.send({ guarantor: record });
2478
- } catch (err) {
2479
- if (err instanceof AgentBnBError) {
2480
- const statusMap = {
2481
- GUARANTOR_NOT_FOUND: 404,
2482
- MAX_AGENTS_EXCEEDED: 409,
2483
- AGENT_ALREADY_LINKED: 409
2484
- };
2485
- const status = statusMap[err.code] ?? 400;
2486
- return reply.code(status).send({ error: err.message });
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 } }
2487
4039
  }
2488
- throw err;
2489
- }
2490
- });
2491
- server.get("/api/identity/:agent_id", async (request, reply) => {
2492
- if (!opts.creditDb) {
2493
- return reply.code(503).send({ error: "Credit database not configured" });
2494
- }
2495
- const { agent_id } = request.params;
2496
- const guarantor = getAgentGuarantor(opts.creditDb, agent_id);
2497
- return reply.send({ agent_id, guarantor });
2498
- });
2499
- if (opts.ownerApiKey && opts.ownerName) {
2500
- const ownerApiKey = opts.ownerApiKey;
2501
- const ownerName = opts.ownerName;
2502
- void server.register(async (ownerRoutes) => {
2503
- ownerRoutes.addHook("onRequest", async (request, reply) => {
2504
- const auth = request.headers.authorization;
2505
- const token = auth?.startsWith("Bearer ") ? auth.slice(7).trim() : null;
2506
- if (!token || token !== ownerApiKey) {
2507
- return reply.status(401).send({ error: "Unauthorized" });
2508
- }
2509
- });
2510
- ownerRoutes.get("/me", async (_request, reply) => {
2511
- const balance = opts.creditDb ? getBalance(opts.creditDb, ownerName) : 0;
2512
- return reply.send({ owner: ownerName, balance });
2513
- });
2514
- ownerRoutes.get("/requests", async (request, reply) => {
2515
- const query = request.query;
2516
- const rawLimit = query.limit !== void 0 ? parseInt(query.limit, 10) : 10;
2517
- const limit = Math.min(isNaN(rawLimit) || rawLimit < 1 ? 10 : rawLimit, 100);
2518
- const sinceRaw = query.since;
2519
- const validSince = ["24h", "7d", "30d"];
2520
- const since = sinceRaw && validSince.includes(sinceRaw) ? sinceRaw : void 0;
2521
- const items = getRequestLog(db, limit, since);
2522
- return reply.send({ items, limit });
2523
- });
2524
- ownerRoutes.get("/draft", async (_request, reply) => {
2525
- const detectedKeys = detectApiKeys(KNOWN_API_KEYS);
2526
- const cards = detectedKeys.map((key) => buildDraftCard(key, ownerName)).filter((card) => card !== null);
2527
- return reply.send({ cards });
2528
- });
2529
- ownerRoutes.post("/cards/:id/toggle-online", async (request, reply) => {
2530
- const { id } = request.params;
2531
- const card = getCard(db, id);
2532
- if (!card) {
2533
- return reply.code(404).send({ error: "Not found" });
2534
- }
2535
- try {
2536
- const newOnline = !card.availability.online;
2537
- updateCard(db, id, ownerName, {
2538
- availability: { ...card.availability, online: newOnline }
2539
- });
2540
- return reply.send({ ok: true, online: newOnline });
2541
- } catch (err) {
2542
- if (err instanceof AgentBnBError && err.code === "FORBIDDEN") {
2543
- return reply.code(403).send({ error: "Forbidden" });
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" });
2544
4056
  }
2545
- throw err;
2546
- }
2547
- });
2548
- ownerRoutes.patch("/cards/:id", async (request, reply) => {
2549
- const { id } = request.params;
2550
- const body = request.body;
2551
- const updates = {};
2552
- if (body.description !== void 0) updates.description = body.description;
2553
- if (body.pricing !== void 0) updates.pricing = body.pricing;
2554
- try {
2555
- updateCard(db, id, ownerName, updates);
2556
- return reply.send({ ok: true });
2557
- } catch (err) {
2558
- if (err instanceof AgentBnBError) {
2559
- if (err.code === "FORBIDDEN") {
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") {
2560
4136
  return reply.code(403).send({ error: "Forbidden" });
2561
4137
  }
2562
- if (err.code === "NOT_FOUND") {
2563
- 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" } } }
2564
4159
  }
2565
4160
  }
2566
- throw err;
2567
- }
2568
- });
2569
- ownerRoutes.get("/me/pending-requests", async (_request, reply) => {
2570
- const rows = listPendingRequests(db);
2571
- return reply.send(rows);
2572
- });
2573
- ownerRoutes.post("/me/pending-requests/:id/approve", async (request, reply) => {
2574
- const { id } = request.params;
2575
- try {
2576
- resolvePendingRequest(db, id, "approved");
2577
- return reply.send({ status: "approved", id });
2578
- } catch (err) {
2579
- if (err instanceof AgentBnBError) return reply.status(404).send({ error: err.message });
2580
- throw err;
2581
- }
2582
- });
2583
- ownerRoutes.post("/me/pending-requests/:id/reject", async (request, reply) => {
2584
- const { id } = request.params;
2585
- try {
2586
- resolvePendingRequest(db, id, "rejected");
2587
- return reply.send({ status: "rejected", id });
2588
- } catch (err) {
2589
- if (err instanceof AgentBnBError) return reply.status(404).send({ error: err.message });
2590
- throw err;
2591
- }
2592
- });
2593
- ownerRoutes.get("/me/transactions", async (request, reply) => {
2594
- const query = request.query;
2595
- const rawLimit = query.limit !== void 0 ? parseInt(query.limit, 10) : 20;
2596
- const limit = Math.min(isNaN(rawLimit) || rawLimit < 1 ? 20 : rawLimit, 100);
2597
- if (!opts.creditDb) {
2598
- return reply.send({ items: [], limit });
2599
- }
2600
- const items = getTransactions(opts.creditDb, ownerName, limit);
2601
- 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
+ });
2602
4259
  });
2603
- });
2604
- }
4260
+ }
4261
+ });
2605
4262
  return { server, relayState };
2606
4263
  }
2607
4264
 
@@ -2676,10 +4333,10 @@ async function stopAnnouncement() {
2676
4333
  }
2677
4334
 
2678
4335
  // src/openclaw/soul-sync.ts
2679
- import { randomUUID as randomUUID7 } from "crypto";
4336
+ import { randomUUID as randomUUID9 } from "crypto";
2680
4337
 
2681
4338
  // src/skills/publish-capability.ts
2682
- import { randomUUID as randomUUID6 } from "crypto";
4339
+ import { randomUUID as randomUUID8 } from "crypto";
2683
4340
  function parseSoulMd(content) {
2684
4341
  const lines = content.split("\n");
2685
4342
  let name = "";
@@ -2689,17 +4346,23 @@ function parseSoulMd(content) {
2689
4346
  let currentSection = null;
2690
4347
  let currentCapabilityName = "";
2691
4348
  let currentCapabilityLines = [];
4349
+ let currentCapabilityPricing = void 0;
2692
4350
  let descriptionLines = [];
2693
4351
  let pastFirstH1 = false;
2694
4352
  let pastFirstH2 = false;
2695
4353
  const flushCapability = () => {
2696
4354
  if (currentCapabilityName) {
2697
- capabilities.push({
4355
+ const cap = {
2698
4356
  name: currentCapabilityName,
2699
4357
  description: currentCapabilityLines.join(" ").trim()
2700
- });
4358
+ };
4359
+ if (currentCapabilityPricing !== void 0) {
4360
+ cap.pricing = currentCapabilityPricing;
4361
+ }
4362
+ capabilities.push(cap);
2701
4363
  currentCapabilityName = "";
2702
4364
  currentCapabilityLines = [];
4365
+ currentCapabilityPricing = void 0;
2703
4366
  }
2704
4367
  };
2705
4368
  for (const line of lines) {
@@ -2729,7 +4392,15 @@ function parseSoulMd(content) {
2729
4392
  if (currentSection === "preamble" && !pastFirstH2) {
2730
4393
  descriptionLines.push(trimmed);
2731
4394
  } else if (currentSection === "capability") {
2732
- 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
+ }
2733
4404
  }
2734
4405
  }
2735
4406
  flushCapability();
@@ -2750,7 +4421,7 @@ function parseSoulMdV2(content) {
2750
4421
  const parsed = parseSoulMd(content);
2751
4422
  const skills = parsed.capabilities.map((cap) => {
2752
4423
  const sanitizedId = cap.name.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
2753
- const id = sanitizedId.length > 0 ? sanitizedId : randomUUID7();
4424
+ const id = sanitizedId.length > 0 ? sanitizedId : randomUUID9();
2754
4425
  return {
2755
4426
  id,
2756
4427
  name: cap.name,
@@ -2772,7 +4443,7 @@ function parseSoulMdV2(content) {
2772
4443
  required: true
2773
4444
  }
2774
4445
  ],
2775
- pricing: { credits_per_call: 10 },
4446
+ pricing: { credits_per_call: cap.pricing !== void 0 ? cap.pricing : 10 },
2776
4447
  availability: { online: true }
2777
4448
  };
2778
4449
  });
@@ -2792,7 +4463,7 @@ function publishFromSoulV2(db, soulContent, owner) {
2792
4463
  (c) => c.spec_version === "2.0"
2793
4464
  );
2794
4465
  const now = (/* @__PURE__ */ new Date()).toISOString();
2795
- const cardId = existingV2?.id ?? randomUUID7();
4466
+ const cardId = existingV2?.id ?? randomUUID9();
2796
4467
  const card = {
2797
4468
  spec_version: "2.0",
2798
4469
  id: cardId,
@@ -2817,7 +4488,7 @@ function publishFromSoulV2(db, soulContent, owner) {
2817
4488
  }
2818
4489
 
2819
4490
  // src/openclaw/heartbeat-writer.ts
2820
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, existsSync as existsSync5 } from "fs";
4491
+ import { readFileSync as readFileSync3, writeFileSync, existsSync as existsSync4 } from "fs";
2821
4492
  var HEARTBEAT_MARKER_START = "<!-- agentbnb:start -->";
2822
4493
  var HEARTBEAT_MARKER_END = "<!-- agentbnb:end -->";
2823
4494
  function generateHeartbeatSection(autonomy, budget) {
@@ -2853,11 +4524,11 @@ function generateHeartbeatSection(autonomy, budget) {
2853
4524
  ].join("\n");
2854
4525
  }
2855
4526
  function injectHeartbeatSection(heartbeatPath, section) {
2856
- if (!existsSync5(heartbeatPath)) {
2857
- writeFileSync2(heartbeatPath, section + "\n", "utf-8");
4527
+ if (!existsSync4(heartbeatPath)) {
4528
+ writeFileSync(heartbeatPath, section + "\n", "utf-8");
2858
4529
  return;
2859
4530
  }
2860
- let content = readFileSync4(heartbeatPath, "utf-8");
4531
+ let content = readFileSync3(heartbeatPath, "utf-8");
2861
4532
  const startIdx = content.indexOf(HEARTBEAT_MARKER_START);
2862
4533
  const endIdx = content.indexOf(HEARTBEAT_MARKER_END);
2863
4534
  if (startIdx !== -1 && endIdx !== -1) {
@@ -2865,7 +4536,7 @@ function injectHeartbeatSection(heartbeatPath, section) {
2865
4536
  } else {
2866
4537
  content = content + "\n" + section + "\n";
2867
4538
  }
2868
- writeFileSync2(heartbeatPath, content, "utf-8");
4539
+ writeFileSync(heartbeatPath, content, "utf-8");
2869
4540
  }
2870
4541
 
2871
4542
  // src/openclaw/skill.ts
@@ -2945,11 +4616,11 @@ function getLanIp() {
2945
4616
  var program = new Command();
2946
4617
  program.name("agentbnb").description("P2P Agent Capability Sharing Protocol \u2014 Airbnb for AI agent pipelines").version(pkg.version);
2947
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) => {
2948
- const owner = opts.owner ?? `agent-${randomBytes(4).toString("hex")}`;
2949
- const token = randomBytes(32).toString("hex");
4619
+ const owner = opts.owner ?? `agent-${randomBytes2(4).toString("hex")}`;
4620
+ const token = randomBytes2(32).toString("hex");
2950
4621
  const configDir = getConfigDir();
2951
- const dbPath = join4(configDir, "registry.db");
2952
- const creditDbPath = join4(configDir, "credit.db");
4622
+ const dbPath = join3(configDir, "registry.db");
4623
+ const creditDbPath = join3(configDir, "credit.db");
2953
4624
  const port = parseInt(opts.port, 10);
2954
4625
  const ip = opts.host ?? getLanIp();
2955
4626
  const existingConfig = loadConfig();
@@ -2963,7 +4634,7 @@ program.command("init").description("Initialize AgentBnB config and create agent
2963
4634
  credit_db_path: creditDbPath,
2964
4635
  token: existingConfig?.token ?? token,
2965
4636
  // Preserve existing token
2966
- api_key: existingConfig?.api_key ?? randomBytes(32).toString("hex")
4637
+ api_key: existingConfig?.api_key ?? randomBytes2(32).toString("hex")
2967
4638
  };
2968
4639
  saveConfig(config);
2969
4640
  let keypairStatus = "existing";
@@ -2978,6 +4649,21 @@ program.command("init").description("Initialize AgentBnB config and create agent
2978
4649
  const creditDb = openCreditDb(creditDbPath);
2979
4650
  bootstrapAgent(creditDb, owner, 100);
2980
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
+ }
2981
4667
  const skipDetect = opts.detect === false;
2982
4668
  const publishedCards = [];
2983
4669
  let detectedSource = "none";
@@ -3123,6 +4809,9 @@ Publish these ${card.skills.length} capabilities? [y/N] `);
3123
4809
  keypair: keypairStatus,
3124
4810
  agent_id: identity.agent_id
3125
4811
  };
4812
+ if (registryBalance !== void 0) {
4813
+ jsonOutput.registry_balance = registryBalance;
4814
+ }
3126
4815
  if (!skipDetect) {
3127
4816
  jsonOutput.detected_source = detectedSource;
3128
4817
  jsonOutput.published_cards = publishedCards;
@@ -3133,7 +4822,11 @@ Publish these ${card.skills.length} capabilities? [y/N] `);
3133
4822
  console.log(` Owner: ${owner}`);
3134
4823
  console.log(` Token: ${token}`);
3135
4824
  console.log(` Config: ${configDir}/config.json`);
3136
- 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
+ }
3137
4830
  console.log(` Keypair: ${keypairStatus === "generated" ? "generated (Ed25519)" : "preserved (existing)"}`);
3138
4831
  console.log(` Agent ID: ${identity.agent_id}`);
3139
4832
  console.log(` Gateway: http://${ip}:${port}`);
@@ -3147,7 +4840,7 @@ program.command("publish <card.json>").description("Publish a Capability Card to
3147
4840
  }
3148
4841
  let raw;
3149
4842
  try {
3150
- raw = readFileSync5(cardPath, "utf-8");
4843
+ raw = readFileSync4(cardPath, "utf-8");
3151
4844
  } catch {
3152
4845
  console.error(`Error: cannot read file: ${cardPath}`);
3153
4846
  process.exit(1);
@@ -3176,6 +4869,27 @@ program.command("publish <card.json>").description("Publish a Capability Card to
3176
4869
  }
3177
4870
  const card = result.data;
3178
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
+ }
3179
4893
  const db = openDatabase(config.db_path);
3180
4894
  try {
3181
4895
  if (card.spec_version === "2.0") {
@@ -3431,8 +5145,8 @@ program.command("request [card-id]").description("Request a capability from anot
3431
5145
  process.exit(1);
3432
5146
  }
3433
5147
  }
3434
- const registryDb = openDatabase(join4(getConfigDir(), "registry.db"));
3435
- const creditDb = openCreditDb(join4(getConfigDir(), "credit.db"));
5148
+ const registryDb = openDatabase(join3(getConfigDir(), "registry.db"));
5149
+ const creditDb = openCreditDb(join3(getConfigDir(), "credit.db"));
3436
5150
  registryDb.pragma("busy_timeout = 5000");
3437
5151
  creditDb.pragma("busy_timeout = 5000");
3438
5152
  try {
@@ -3442,7 +5156,8 @@ program.command("request [card-id]").description("Request a capability from anot
3442
5156
  registryDb,
3443
5157
  creditDb,
3444
5158
  autonomyConfig: config.autonomy ?? DEFAULT_AUTONOMY_CONFIG,
3445
- budgetManager
5159
+ budgetManager,
5160
+ registryUrl: config.registry
3446
5161
  });
3447
5162
  const result = await requestor.requestWithAutonomy({
3448
5163
  query: opts.query,
@@ -3535,68 +5250,105 @@ program.command("request [card-id]").description("Request a capability from anot
3535
5250
  }
3536
5251
  }
3537
5252
  const useReceipt = isRemoteRequest && opts.receipt !== false;
5253
+ const useRegistryLedger = isRemoteRequest && !!config.registry && !!gatewayUrl;
3538
5254
  if (useReceipt && !opts.cost) {
3539
5255
  console.error("Error: --cost <credits> is required for remote requests. Specify the credits to commit.");
3540
5256
  process.exit(1);
3541
5257
  }
3542
5258
  let escrowId;
3543
5259
  let escrowReceipt;
5260
+ let requestLedger;
3544
5261
  if (useReceipt) {
3545
- const configDir = getConfigDir();
3546
- const creditDb = openCreditDb(join4(configDir, "credit.db"));
3547
- creditDb.pragma("busy_timeout = 5000");
3548
- try {
3549
- const keys = loadKeyPair(configDir);
3550
- const amount = Number(opts.cost);
3551
- if (isNaN(amount) || amount <= 0) {
3552
- console.error("Error: --cost must be a positive number.");
5262
+ const amount = Number(opts.cost);
5263
+ if (isNaN(amount) || amount <= 0) {
5264
+ console.error("Error: --cost must be a positive number.");
5265
+ process.exit(1);
5266
+ }
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
+ }
3553
5287
  process.exit(1);
3554
5288
  }
3555
- const receiptResult = createSignedEscrowReceipt(creditDb, keys.privateKey, keys.publicKey, {
3556
- owner: config.owner,
3557
- amount,
3558
- cardId,
3559
- skillId: opts.skill
3560
- });
3561
- escrowId = receiptResult.escrowId;
3562
- escrowReceipt = receiptResult.receipt;
3563
- if (!opts.json) {
3564
- console.log(`Escrow: ${amount} credits held (ID: ${escrowId.slice(0, 8)}...)`);
5289
+ } else if (gatewayUrl) {
5290
+ const configDir = getConfigDir();
5291
+ const creditDb = openCreditDb(join3(configDir, "credit.db"));
5292
+ creditDb.pragma("busy_timeout = 5000");
5293
+ try {
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;
5303
+ if (!opts.json) {
5304
+ console.log(`Escrow: ${amount} credits held (ID: ${escrowId.slice(0, 8)}...)`);
5305
+ }
5306
+ } catch (err) {
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);
3565
5315
  }
3566
- } catch (err) {
3567
5316
  creditDb.close();
3568
- const msg = err instanceof Error ? err.message : String(err);
3569
- if (opts.json) {
3570
- console.log(JSON.stringify({ success: false, error: msg }, null, 2));
3571
- } else {
3572
- console.error(`Error creating escrow receipt: ${msg}`);
3573
- }
3574
- process.exit(1);
3575
5317
  }
3576
5318
  }
3577
- const settleEscrow = () => {
5319
+ const settleEscrow2 = async () => {
3578
5320
  if (useReceipt && escrowId) {
3579
- const configDir = getConfigDir();
3580
- const creditDb = openCreditDb(join4(configDir, "credit.db"));
3581
- creditDb.pragma("busy_timeout = 5000");
3582
- try {
3583
- settleRequesterEscrow(creditDb, escrowId);
5321
+ if (requestLedger) {
5322
+ await requestLedger.settle(escrowId, targetOwner ?? config.owner);
3584
5323
  if (!opts.json) console.log(`Escrow settled: ${opts.cost} credits deducted.`);
3585
- } finally {
3586
- creditDb.close();
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
+ }
3587
5334
  }
3588
5335
  }
3589
5336
  };
3590
- const releaseEscrow2 = () => {
5337
+ const releaseEscrow2 = async () => {
3591
5338
  if (useReceipt && escrowId) {
3592
- const configDir = getConfigDir();
3593
- const creditDb = openCreditDb(join4(configDir, "credit.db"));
3594
- creditDb.pragma("busy_timeout = 5000");
3595
- try {
3596
- releaseRequesterEscrow(creditDb, escrowId);
5339
+ if (requestLedger) {
5340
+ await requestLedger.release(escrowId);
3597
5341
  if (!opts.json) console.log("Escrow released: credits refunded.");
3598
- } finally {
3599
- creditDb.close();
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
+ }
3600
5352
  }
3601
5353
  }
3602
5354
  };
@@ -3613,8 +5365,8 @@ program.command("request [card-id]").description("Request a capability from anot
3613
5365
  return msg.includes("NETWORK_ERROR") || msg.includes("ECONNREFUSED") || msg.includes("fetch failed") || msg.includes("Network error");
3614
5366
  };
3615
5367
  const tryViaRelay = async () => {
3616
- const { RelayClient } = await import("../websocket-client-5TIQDYQ4.js");
3617
- const { requestViaRelay } = await import("../client-IOTK6GOS.js");
5368
+ const { RelayClient } = await import("../websocket-client-6IIDGXKB.js");
5369
+ const { requestViaRelay } = await import("../client-BTPIFY7E.js");
3618
5370
  const tempRelay = new RelayClient({
3619
5371
  registryUrl: config.registry,
3620
5372
  owner: config.owner,
@@ -3661,10 +5413,10 @@ program.command("request [card-id]").description("Request a capability from anot
3661
5413
  }
3662
5414
  }
3663
5415
  }
3664
- settleEscrow();
5416
+ await settleEscrow2();
3665
5417
  printResult(result);
3666
5418
  } catch (err) {
3667
- releaseEscrow2();
5419
+ await releaseEscrow2();
3668
5420
  const msg = err instanceof Error ? err.message : String(err);
3669
5421
  if (opts.json) {
3670
5422
  console.log(JSON.stringify({ success: false, error: msg }, null, 2));
@@ -3684,12 +5436,28 @@ program.command("status").description("Show credit balance and recent transactio
3684
5436
  let balance;
3685
5437
  let transactions;
3686
5438
  let heldEscrows;
3687
- try {
3688
- balance = getBalance(creditDb, config.owner);
3689
- transactions = getTransactions(creditDb, config.owner, 5);
3690
- heldEscrows = creditDb.prepare("SELECT id, amount, card_id, created_at FROM credit_escrow WHERE owner = ? AND status = ?").all(config.owner, "held");
3691
- } finally {
3692
- 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
+ }
3693
5461
  }
3694
5462
  if (opts.json) {
3695
5463
  console.log(JSON.stringify({ owner: config.owner, balance, held_escrows: heldEscrows, recent_transactions: transactions }, null, 2));
@@ -3722,7 +5490,7 @@ program.command("serve").description("Start the AgentBnB gateway server").option
3722
5490
  }
3723
5491
  const port = opts.port ? parseInt(opts.port, 10) : config.gateway_port;
3724
5492
  const registryPort = parseInt(opts.registryPort, 10);
3725
- const skillsYamlPath = opts.skillsYaml ?? join4(homedir(), ".agentbnb", "skills.yaml");
5493
+ const skillsYamlPath = opts.skillsYaml ?? join3(homedir(), ".agentbnb", "skills.yaml");
3726
5494
  const runtime = new AgentRuntime({
3727
5495
  registryDbPath: config.db_path,
3728
5496
  creditDbPath: config.credit_db_path,
@@ -3738,6 +5506,19 @@ program.command("serve").description("Start the AgentBnB gateway server").option
3738
5506
  if (opts.conductor) {
3739
5507
  console.log("Conductor mode enabled \u2014 orchestrate/plan skills available via gateway");
3740
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
+ }
3741
5522
  const autonomyConfig = config.autonomy ?? DEFAULT_AUTONOMY_CONFIG;
3742
5523
  const idleMonitor = new IdleMonitor({
3743
5524
  owner: config.owner,
@@ -3801,8 +5582,8 @@ program.command("serve").description("Start the AgentBnB gateway server").option
3801
5582
  }
3802
5583
  const relayUrl = opts.registry ?? config.registry;
3803
5584
  if (relayUrl && opts.relay !== false) {
3804
- const { RelayClient } = await import("../websocket-client-5TIQDYQ4.js");
3805
- const { executeCapabilityRequest: executeCapabilityRequest2 } = await import("../execute-GDGBU6DJ.js");
5585
+ const { RelayClient } = await import("../websocket-client-6IIDGXKB.js");
5586
+ const { executeCapabilityRequest: executeCapabilityRequest2 } = await import("../execute-EXOITLHN.js");
3806
5587
  const cards = listCards(runtime.registryDb, config.owner);
3807
5588
  const card = cards[0] ?? {
3808
5589
  id: config.owner,
@@ -3816,12 +5597,23 @@ program.command("serve").description("Start the AgentBnB gateway server").option
3816
5597
  pricing: { credits_per_call: 0 },
3817
5598
  availability: { online: true }
3818
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
+ }
3819
5607
  relayClient = new RelayClient({
3820
5608
  registryUrl: relayUrl,
3821
5609
  owner: config.owner,
3822
5610
  token: config.token,
3823
5611
  card,
5612
+ cards: additionalCards.length > 0 ? additionalCards : void 0,
3824
5613
  onRequest: async (req) => {
5614
+ const onProgress = (info) => {
5615
+ relayClient.sendProgress(req.id, info);
5616
+ };
3825
5617
  const result = await executeCapabilityRequest2({
3826
5618
  registryDb: runtime.registryDb,
3827
5619
  creditDb: runtime.creditDb,
@@ -3831,7 +5623,8 @@ program.command("serve").description("Start the AgentBnB gateway server").option
3831
5623
  requester: req.requester ?? req.from_owner,
3832
5624
  escrowReceipt: req.escrow_receipt,
3833
5625
  skillExecutor: runtime.skillExecutor,
3834
- handlerUrl: opts.handlerUrl
5626
+ handlerUrl: opts.handlerUrl,
5627
+ onProgress
3835
5628
  });
3836
5629
  if (result.success) {
3837
5630
  return { result: result.result };
@@ -3906,7 +5699,7 @@ peersCommand.command("remove <name>").description("Remove a registered peer").ac
3906
5699
  });
3907
5700
  var configCmd = program.command("config").description("Get or set AgentBnB configuration values");
3908
5701
  configCmd.command("set <key> <value>").description("Set a configuration value").action((key, value) => {
3909
- const allowedKeys = ["registry", "tier1", "tier2", "reserve", "idle-threshold"];
5702
+ const allowedKeys = ["registry", "tier1", "tier2", "reserve", "idle-threshold", "conductor-public"];
3910
5703
  if (!allowedKeys.includes(key)) {
3911
5704
  console.error(`Unknown config key: ${key}. Valid keys: ${allowedKeys.join(", ")}`);
3912
5705
  process.exit(1);
@@ -3971,6 +5764,17 @@ configCmd.command("set <key> <value>").description("Set a configuration value").
3971
5764
  console.log(`Set idle-threshold = ${parsed} (idle rate threshold for auto-share)`);
3972
5765
  return;
3973
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
+ }
3974
5778
  config[key] = value;
3975
5779
  saveConfig(config);
3976
5780
  console.log(`Set ${key} = ${value}`);
@@ -3998,6 +5802,10 @@ configCmd.command("get <key>").description("Get a configuration value").action((
3998
5802
  console.log(val !== void 0 ? String(val) : "0.70");
3999
5803
  return;
4000
5804
  }
5805
+ if (key === "conductor-public") {
5806
+ console.log(String(config.conductor?.public ?? false));
5807
+ return;
5808
+ }
4001
5809
  const value = config[key];
4002
5810
  console.log(value !== void 0 ? String(value) : "(not set)");
4003
5811
  });
@@ -4010,7 +5818,7 @@ openclaw.command("sync").description("Read SOUL.md and publish/update a v2.0 cap
4010
5818
  }
4011
5819
  let content;
4012
5820
  try {
4013
- content = readFileSync5(opts.soulPath, "utf-8");
5821
+ content = readFileSync4(opts.soulPath, "utf-8");
4014
5822
  } catch {
4015
5823
  console.error(`Error: cannot read SOUL.md at ${opts.soulPath}`);
4016
5824
  process.exit(1);
@@ -4019,6 +5827,14 @@ openclaw.command("sync").description("Read SOUL.md and publish/update a v2.0 cap
4019
5827
  try {
4020
5828
  const card = publishFromSoulV2(db, content, config.owner);
4021
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
+ }
4022
5838
  } catch (err) {
4023
5839
  const msg = err instanceof Error ? err.message : String(err);
4024
5840
  console.error(`Error: ${msg}`);
@@ -4071,7 +5887,7 @@ openclaw.command("rules").description("Print HEARTBEAT.md rules block (or inject
4071
5887
  }
4072
5888
  });
4073
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) => {
4074
- const { conductAction } = await import("../conduct-IQYAT6ZU.js");
5890
+ const { conductAction } = await import("../conduct-FXLVGKD5.js");
4075
5891
  const result = await conductAction(task, opts);
4076
5892
  if (opts.json) {
4077
5893
  console.log(JSON.stringify(result, null, 2));
@@ -4104,4 +5920,8 @@ Total credits spent: ${result.total_credits ?? 0} cr`);
4104
5920
  }
4105
5921
  }
4106
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
+ });
4107
5927
  await program.parseAsync(process.argv);