agentbnb 4.0.0 → 4.0.2

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 (44) hide show
  1. package/README.md +2 -0
  2. package/dist/{card-IE5UV5QX.js → card-RSGDCHCV.js} +11 -4
  3. package/dist/chunk-3MJT4PZG.js +50 -0
  4. package/dist/{chunk-HEVXCYCY.js → chunk-4P3EMGL4.js} +61 -24
  5. package/dist/chunk-5AH3CMOX.js +62 -0
  6. package/dist/{chunk-QO67IGCW.js → chunk-5KFI5X7B.js} +1 -1
  7. package/dist/chunk-75OC6E4F.js +33 -0
  8. package/dist/{chunk-CUVIWPQO.js → chunk-7NA43XCG.js} +7 -6
  9. package/dist/{conduct-IQYAT6ZU.js → chunk-BH6WGYFB.js} +70 -33
  10. package/dist/{chunk-QVV2P3FN.js → chunk-DNWT5FZQ.js} +22 -2
  11. package/dist/chunk-FF226TIV.js +148 -0
  12. package/dist/{chunk-UJWYE7VL.js → chunk-GGYC5U2Z.js} +28 -111
  13. package/dist/chunk-HH24WMFN.js +373 -0
  14. package/dist/{websocket-client-5TIQDYQ4.js → chunk-JOY533UH.js} +38 -4
  15. package/dist/chunk-QITOPASZ.js +96 -0
  16. package/dist/{chunk-3Y36WQDV.js → chunk-QT7TEVNV.js} +14 -2
  17. package/dist/{chunk-UOGDK2S2.js → chunk-T7NS2J2B.js} +1 -1
  18. package/dist/{chunk-XA63SD4T.js → chunk-WGZ5AGOX.js} +37 -0
  19. package/dist/{chunk-RSX4SCPN.js → chunk-XND2DWTZ.js} +4 -3
  20. package/dist/cli/index.js +2924 -835
  21. package/dist/{client-IOTK6GOS.js → client-T5MTY3CS.js} +3 -3
  22. package/dist/conduct-GZQNFTRP.js +19 -0
  23. package/dist/conduct-N52JX7RT.js +52 -0
  24. package/dist/{conductor-mode-XU7ONJWC.js → conductor-mode-XUWGR4ZE.js} +16 -9
  25. package/dist/execute-PNGQOMYO.js +10 -0
  26. package/dist/index.d.ts +1148 -915
  27. package/dist/index.js +589 -127
  28. package/dist/{peers-G36URZYB.js → peers-K7FSHPN3.js} +2 -1
  29. package/dist/request-4GQSSM4B.js +196 -0
  30. package/dist/serve-skill-TPHZH6BS.js +104 -0
  31. package/dist/server-365V3GYD.js +295 -0
  32. package/dist/websocket-client-6IIDGXKB.js +7 -0
  33. package/package.json +3 -6
  34. package/skills/agentbnb/HEARTBEAT.rules.md +47 -0
  35. package/skills/agentbnb/SKILL.md +166 -0
  36. package/skills/agentbnb/auto-request.ts +14 -0
  37. package/skills/agentbnb/auto-share.ts +10 -0
  38. package/skills/agentbnb/bootstrap.test.ts +323 -0
  39. package/skills/agentbnb/bootstrap.ts +126 -0
  40. package/skills/agentbnb/credit-mgr.ts +11 -0
  41. package/skills/agentbnb/gateway.ts +12 -0
  42. package/skills/agentbnb/install.sh +210 -0
  43. package/dist/chunk-BEI5MTNZ.js +0 -91
  44. package/dist/execute-GDGBU6DJ.js +0 -10
package/dist/cli/index.js CHANGED
@@ -1,37 +1,50 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ deriveAgentId,
4
+ ensureIdentity
5
+ } from "../chunk-QITOPASZ.js";
6
+ import {
7
+ createLedger,
8
+ identityAuthPlugin
9
+ } from "../chunk-HH24WMFN.js";
2
10
  import {
3
11
  executeCapabilityRequest,
4
12
  releaseRequesterEscrow,
5
13
  settleRequesterEscrow
6
- } from "../chunk-CUVIWPQO.js";
14
+ } from "../chunk-7NA43XCG.js";
7
15
  import {
8
- RelayMessageSchema
9
- } from "../chunk-3Y36WQDV.js";
16
+ interpolateObject
17
+ } from "../chunk-3MJT4PZG.js";
10
18
  import {
11
19
  AutoRequestor,
12
20
  BudgetManager,
13
21
  DEFAULT_AUTONOMY_CONFIG,
14
22
  DEFAULT_BUDGET_CONFIG,
15
- filterCards,
16
23
  getAutonomyTier,
17
24
  insertAuditEvent,
18
- interpolateObject,
19
25
  listPendingRequests,
20
- resolvePendingRequest,
26
+ resolvePendingRequest
27
+ } from "../chunk-GGYC5U2Z.js";
28
+ import {
29
+ fetchRemoteCards,
30
+ filterCards,
31
+ mergeResults,
21
32
  searchCards
22
- } from "../chunk-UJWYE7VL.js";
33
+ } from "../chunk-FF226TIV.js";
23
34
  import {
24
35
  requestCapability
25
- } from "../chunk-RSX4SCPN.js";
36
+ } from "../chunk-XND2DWTZ.js";
26
37
  import {
27
38
  findPeer,
28
- getConfigDir,
29
- loadConfig,
30
39
  loadPeers,
31
40
  removePeer,
32
- saveConfig,
33
41
  savePeer
34
- } from "../chunk-BEI5MTNZ.js";
42
+ } from "../chunk-5AH3CMOX.js";
43
+ import {
44
+ getConfigDir,
45
+ loadConfig,
46
+ saveConfig
47
+ } from "../chunk-75OC6E4F.js";
35
48
  import {
36
49
  getActivityFeed,
37
50
  getCard,
@@ -44,129 +57,54 @@ import {
44
57
  updateCard,
45
58
  updateSkillAvailability,
46
59
  updateSkillIdleRate
47
- } from "../chunk-UOGDK2S2.js";
60
+ } from "../chunk-T7NS2J2B.js";
48
61
  import {
49
62
  bootstrapAgent,
50
63
  getBalance,
51
64
  getTransactions,
52
65
  holdEscrow,
66
+ migrateOwner,
53
67
  openCreditDb,
54
- releaseEscrow
55
- } from "../chunk-QVV2P3FN.js";
68
+ releaseEscrow,
69
+ settleEscrow
70
+ } from "../chunk-DNWT5FZQ.js";
56
71
  import {
57
72
  generateKeyPair,
58
73
  loadKeyPair,
59
74
  saveKeyPair,
60
75
  signEscrowReceipt,
61
76
  verifyEscrowReceipt
62
- } from "../chunk-QO67IGCW.js";
77
+ } from "../chunk-5KFI5X7B.js";
63
78
  import {
64
79
  AgentBnBError,
65
80
  AnyCardSchema,
66
81
  CapabilityCardV2Schema
67
- } from "../chunk-XA63SD4T.js";
82
+ } from "../chunk-WGZ5AGOX.js";
83
+ import {
84
+ RelayMessageSchema
85
+ } from "../chunk-QT7TEVNV.js";
68
86
 
69
87
  // src/cli/index.ts
70
88
  import { Command } from "commander";
71
- import { readFileSync as readFileSync5 } from "fs";
89
+ import { readFileSync as readFileSync4 } from "fs";
72
90
  import { createRequire } from "module";
73
- import { randomBytes } from "crypto";
74
- import { join as join4 } from "path";
91
+ import { randomBytes as randomBytes2, randomUUID as randomUUID10 } from "crypto";
92
+ import { join as join3 } from "path";
75
93
  import { networkInterfaces, homedir } from "os";
76
94
  import { createInterface as createInterface2 } from "readline";
77
95
 
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
96
  // src/credit/escrow-receipt.ts
159
- import { z as z2 } from "zod";
97
+ import { z } from "zod";
160
98
  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)
99
+ var EscrowReceiptSchema = z.object({
100
+ requester_owner: z.string().min(1),
101
+ requester_public_key: z.string().min(1),
102
+ amount: z.number().positive(),
103
+ card_id: z.string().min(1),
104
+ skill_id: z.string().optional(),
105
+ timestamp: z.string(),
106
+ nonce: z.string().uuid(),
107
+ signature: z.string().min(1)
170
108
  });
171
109
  function createSignedEscrowReceipt(db, privateKey, publicKey, opts) {
172
110
  const escrowId = holdEscrow(db, opts.owner, opts.amount, opts.cardId);
@@ -280,89 +218,6 @@ var IdleMonitor = class {
280
218
  }
281
219
  };
282
220
 
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
221
  // src/cli/onboarding.ts
367
222
  import { randomUUID as randomUUID2 } from "crypto";
368
223
  import { createConnection } from "net";
@@ -534,8 +389,8 @@ function buildDraftCard(apiKey, owner) {
534
389
 
535
390
  // src/onboarding/index.ts
536
391
  import { randomUUID as randomUUID3 } from "crypto";
537
- import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
538
- import { join as join2 } from "path";
392
+ import { existsSync, readFileSync } from "fs";
393
+ import { join } from "path";
539
394
 
540
395
  // src/onboarding/capability-templates.ts
541
396
  var API_PATTERNS = [
@@ -650,9 +505,9 @@ var DOC_FILES = ["SOUL.md", "CLAUDE.md", "AGENTS.md", "README.md"];
650
505
  function detectCapabilities(opts = {}) {
651
506
  const cwd = opts.cwd ?? process.cwd();
652
507
  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");
508
+ const filePath = opts.fromFile.startsWith("/") ? opts.fromFile : join(cwd, opts.fromFile);
509
+ if (existsSync(filePath)) {
510
+ const content = readFileSync(filePath, "utf-8");
656
511
  const capabilities = detectFromDocs(content);
657
512
  if (capabilities.length > 0) {
658
513
  return { source: "docs", capabilities, sourceFile: filePath };
@@ -661,9 +516,9 @@ function detectCapabilities(opts = {}) {
661
516
  return { source: "none", capabilities: [] };
662
517
  }
663
518
  for (const fileName of DOC_FILES) {
664
- const filePath = join2(cwd, fileName);
665
- if (!existsSync2(filePath)) continue;
666
- const content = readFileSync2(filePath, "utf-8");
519
+ const filePath = join(cwd, fileName);
520
+ if (!existsSync(filePath)) continue;
521
+ const content = readFileSync(filePath, "utf-8");
667
522
  if (fileName === "SOUL.md") {
668
523
  return { source: "soul", capabilities: [], soulContent: content, sourceFile: filePath };
669
524
  }
@@ -707,8 +562,49 @@ function capabilitiesToV2Card(capabilities, owner, agentName) {
707
562
  return CapabilityCardV2Schema.parse(card);
708
563
  }
709
564
 
565
+ // src/registry/pricing.ts
566
+ function getPricingStats(db, query) {
567
+ const cards = searchCards(db, query);
568
+ const prices = [];
569
+ const queryLower = query.toLowerCase();
570
+ const queryWords = queryLower.split(/\s+/).filter((w) => w.length > 0);
571
+ for (const card of cards) {
572
+ const v2 = card;
573
+ if (v2.skills && v2.skills.length > 0) {
574
+ for (const skill of v2.skills) {
575
+ const nameMatch = skillMatchesQuery(skill, queryWords);
576
+ if (nameMatch) {
577
+ prices.push(skill.pricing.credits_per_call);
578
+ }
579
+ }
580
+ } else {
581
+ prices.push(card.pricing.credits_per_call);
582
+ }
583
+ }
584
+ if (prices.length === 0) {
585
+ return { min: 0, max: 0, median: 0, mean: 0, count: 0 };
586
+ }
587
+ prices.sort((a, b) => a - b);
588
+ const min = prices[0];
589
+ const max = prices[prices.length - 1];
590
+ const mean = prices.reduce((sum, p) => sum + p, 0) / prices.length;
591
+ const median = computeMedian(prices);
592
+ return { min, max, median, mean, count: prices.length };
593
+ }
594
+ function skillMatchesQuery(skill, queryWords) {
595
+ const text = `${skill.name} ${skill.description}`.toLowerCase();
596
+ return queryWords.some((word) => text.includes(word));
597
+ }
598
+ function computeMedian(sorted) {
599
+ const mid = Math.floor(sorted.length / 2);
600
+ if (sorted.length % 2 === 1) {
601
+ return sorted[mid];
602
+ }
603
+ return (sorted[mid - 1] + sorted[mid]) / 2;
604
+ }
605
+
710
606
  // src/runtime/agent-runtime.ts
711
- import { readFileSync as readFileSync3, existsSync as existsSync3 } from "fs";
607
+ import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
712
608
 
713
609
  // src/skills/executor.ts
714
610
  var SkillExecutor = class {
@@ -735,7 +631,7 @@ var SkillExecutor = class {
735
631
  * @param params - Input parameters for the skill.
736
632
  * @returns ExecutionResult including success, result/error, and latency_ms.
737
633
  */
738
- async execute(skillId, params) {
634
+ async execute(skillId, params, onProgress) {
739
635
  const startTime = Date.now();
740
636
  const config = this.skillMap.get(skillId);
741
637
  if (!config) {
@@ -754,7 +650,7 @@ var SkillExecutor = class {
754
650
  };
755
651
  }
756
652
  try {
757
- const modeResult = await mode.execute(config, params);
653
+ const modeResult = await mode.execute(config, params, onProgress);
758
654
  return {
759
655
  ...modeResult,
760
656
  latency_ms: Date.now() - startTime
@@ -791,98 +687,98 @@ function createSkillExecutor(configs, modes) {
791
687
  }
792
688
 
793
689
  // src/skills/skill-config.ts
794
- import { z as z3 } from "zod";
690
+ import { z as z2 } from "zod";
795
691
  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()
692
+ var PricingSchema = z2.object({
693
+ credits_per_call: z2.number().nonnegative(),
694
+ credits_per_minute: z2.number().nonnegative().optional(),
695
+ free_tier: z2.number().nonnegative().optional()
800
696
  });
801
- var ApiAuthSchema = z3.discriminatedUnion("type", [
802
- z3.object({
803
- type: z3.literal("bearer"),
804
- token: z3.string()
697
+ var ApiAuthSchema = z2.discriminatedUnion("type", [
698
+ z2.object({
699
+ type: z2.literal("bearer"),
700
+ token: z2.string()
805
701
  }),
806
- z3.object({
807
- type: z3.literal("apikey"),
808
- header: z3.string().default("X-API-Key"),
809
- key: z3.string()
702
+ z2.object({
703
+ type: z2.literal("apikey"),
704
+ header: z2.string().default("X-API-Key"),
705
+ key: z2.string()
810
706
  }),
811
- z3.object({
812
- type: z3.literal("basic"),
813
- username: z3.string(),
814
- password: z3.string()
707
+ z2.object({
708
+ type: z2.literal("basic"),
709
+ username: z2.string(),
710
+ password: z2.string()
815
711
  })
816
712
  ]);
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"]),
713
+ var ApiSkillConfigSchema = z2.object({
714
+ id: z2.string().min(1),
715
+ type: z2.literal("api"),
716
+ name: z2.string().min(1),
717
+ endpoint: z2.string().min(1),
718
+ method: z2.enum(["GET", "POST", "PUT", "DELETE"]),
823
719
  auth: ApiAuthSchema.optional(),
824
- input_mapping: z3.record(z3.string()).default({}),
825
- output_mapping: z3.record(z3.string()).default({}),
720
+ input_mapping: z2.record(z2.string()).default({}),
721
+ output_mapping: z2.record(z2.string()).default({}),
826
722
  pricing: PricingSchema,
827
- timeout_ms: z3.number().positive().default(3e4),
828
- retries: z3.number().nonnegative().int().default(0),
829
- provider: z3.string().optional()
723
+ timeout_ms: z2.number().positive().default(3e4),
724
+ retries: z2.number().nonnegative().int().default(0),
725
+ provider: z2.string().optional()
830
726
  });
831
- var PipelineStepSchema = z3.union([
832
- z3.object({
833
- skill_id: z3.string().min(1),
834
- input_mapping: z3.record(z3.string()).default({})
727
+ var PipelineStepSchema = z2.union([
728
+ z2.object({
729
+ skill_id: z2.string().min(1),
730
+ input_mapping: z2.record(z2.string()).default({})
835
731
  }),
836
- z3.object({
837
- command: z3.string().min(1),
838
- input_mapping: z3.record(z3.string()).default({})
732
+ z2.object({
733
+ command: z2.string().min(1),
734
+ input_mapping: z2.record(z2.string()).default({})
839
735
  })
840
736
  ]);
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),
737
+ var PipelineSkillConfigSchema = z2.object({
738
+ id: z2.string().min(1),
739
+ type: z2.literal("pipeline"),
740
+ name: z2.string().min(1),
741
+ steps: z2.array(PipelineStepSchema).min(1),
846
742
  pricing: PricingSchema,
847
- timeout_ms: z3.number().positive().optional()
743
+ timeout_ms: z2.number().positive().optional()
848
744
  });
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"]),
745
+ var OpenClawSkillConfigSchema = z2.object({
746
+ id: z2.string().min(1),
747
+ type: z2.literal("openclaw"),
748
+ name: z2.string().min(1),
749
+ agent_name: z2.string().min(1),
750
+ channel: z2.enum(["telegram", "webhook", "process"]),
855
751
  pricing: PricingSchema,
856
- timeout_ms: z3.number().positive().optional()
752
+ timeout_ms: z2.number().positive().optional()
857
753
  });
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),
754
+ var CommandSkillConfigSchema = z2.object({
755
+ id: z2.string().min(1),
756
+ type: z2.literal("command"),
757
+ name: z2.string().min(1),
758
+ command: z2.string().min(1),
759
+ output_type: z2.enum(["json", "text", "file"]),
760
+ allowed_commands: z2.array(z2.string()).optional(),
761
+ working_dir: z2.string().optional(),
762
+ timeout_ms: z2.number().positive().default(3e4),
867
763
  pricing: PricingSchema
868
764
  });
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"]),
765
+ var ConductorSkillConfigSchema = z2.object({
766
+ id: z2.string().min(1),
767
+ type: z2.literal("conductor"),
768
+ name: z2.string().min(1),
769
+ conductor_skill: z2.enum(["orchestrate", "plan"]),
874
770
  pricing: PricingSchema,
875
- timeout_ms: z3.number().positive().optional()
771
+ timeout_ms: z2.number().positive().optional()
876
772
  });
877
- var SkillConfigSchema = z3.discriminatedUnion("type", [
773
+ var SkillConfigSchema = z2.discriminatedUnion("type", [
878
774
  ApiSkillConfigSchema,
879
775
  PipelineSkillConfigSchema,
880
776
  OpenClawSkillConfigSchema,
881
777
  CommandSkillConfigSchema,
882
778
  ConductorSkillConfigSchema
883
779
  ]);
884
- var SkillsFileSchema = z3.object({
885
- skills: z3.array(SkillConfigSchema)
780
+ var SkillsFileSchema = z2.object({
781
+ skills: z2.array(SkillConfigSchema)
886
782
  });
887
783
  function expandEnvVars(value) {
888
784
  return value.replace(/\$\{([^}]+)\}/g, (_match, varName) => {
@@ -1125,7 +1021,7 @@ var PipelineExecutor = class {
1125
1021
  * @param params - Input parameters from the caller.
1126
1022
  * @returns Partial ExecutionResult (without latency_ms — added by SkillExecutor wrapper).
1127
1023
  */
1128
- async execute(config, params) {
1024
+ async execute(config, params, onProgress) {
1129
1025
  const pipelineConfig = config;
1130
1026
  const steps = pipelineConfig.steps ?? [];
1131
1027
  if (steps.length === 0) {
@@ -1184,6 +1080,13 @@ var PipelineExecutor = class {
1184
1080
  }
1185
1081
  context.steps.push({ result: stepResult });
1186
1082
  context.prev = { result: stepResult };
1083
+ if (onProgress && i < steps.length - 1) {
1084
+ onProgress({
1085
+ step: i + 1,
1086
+ total: steps.length,
1087
+ message: `Completed step ${i + 1}/${steps.length}`
1088
+ });
1089
+ }
1187
1090
  }
1188
1091
  const lastStep = context.steps[context.steps.length - 1];
1189
1092
  return {
@@ -1519,20 +1422,20 @@ var AgentRuntime = class {
1519
1422
  * 3. Populate the Map with all 4 modes — SkillExecutor sees them via reference.
1520
1423
  */
1521
1424
  async initSkillExecutor() {
1522
- const hasSkillsYaml = this.skillsYamlPath && existsSync3(this.skillsYamlPath);
1425
+ const hasSkillsYaml = this.skillsYamlPath && existsSync2(this.skillsYamlPath);
1523
1426
  if (!hasSkillsYaml && !this.conductorEnabled) {
1524
1427
  return;
1525
1428
  }
1526
1429
  let configs = [];
1527
1430
  if (hasSkillsYaml) {
1528
- const yamlContent = readFileSync3(this.skillsYamlPath, "utf8");
1431
+ const yamlContent = readFileSync2(this.skillsYamlPath, "utf8");
1529
1432
  configs = parseSkillsFile(yamlContent);
1530
1433
  }
1531
1434
  const modes = /* @__PURE__ */ new Map();
1532
1435
  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");
1436
+ const { ConductorMode } = await import("../conductor-mode-XUWGR4ZE.js");
1437
+ const { registerConductorCard, CONDUCTOR_OWNER } = await import("../card-RSGDCHCV.js");
1438
+ const { loadPeers: loadPeers2 } = await import("../peers-K7FSHPN3.js");
1536
1439
  registerConductorCard(this.registryDb);
1537
1440
  const resolveAgentUrl = (owner) => {
1538
1441
  const peers = loadPeers2();
@@ -1644,7 +1547,7 @@ function createGatewayServer(opts) {
1644
1547
  creditDb,
1645
1548
  tokens,
1646
1549
  handlerUrl,
1647
- timeoutMs = 3e4,
1550
+ timeoutMs = 3e5,
1648
1551
  silent = false,
1649
1552
  skillExecutor
1650
1553
  } = opts;
@@ -1739,21 +1642,378 @@ function createGatewayServer(opts) {
1739
1642
  // src/registry/server.ts
1740
1643
  import Fastify2 from "fastify";
1741
1644
  import cors from "@fastify/cors";
1645
+ import swagger from "@fastify/swagger";
1646
+ import swaggerUi from "@fastify/swagger-ui";
1742
1647
  import fastifyStatic from "@fastify/static";
1743
1648
  import fastifyWebsocket from "@fastify/websocket";
1744
- import { join as join3, dirname } from "path";
1649
+ import { join as join2, dirname } from "path";
1745
1650
  import { fileURLToPath } from "url";
1746
- import { existsSync as existsSync4 } from "fs";
1651
+ import { existsSync as existsSync3 } from "fs";
1747
1652
 
1748
1653
  // src/relay/websocket-relay.ts
1654
+ import { randomUUID as randomUUID6 } from "crypto";
1655
+
1656
+ // src/relay/relay-credit.ts
1657
+ function lookupCardPrice(registryDb, cardId, skillId) {
1658
+ const row = registryDb.prepare("SELECT data FROM capability_cards WHERE id = ?").get(cardId);
1659
+ if (!row) return null;
1660
+ let card;
1661
+ try {
1662
+ card = JSON.parse(row.data);
1663
+ } catch {
1664
+ return null;
1665
+ }
1666
+ if (Array.isArray(card.skills) && card.skills.length > 0) {
1667
+ const skills = card.skills;
1668
+ if (skillId) {
1669
+ const skill = skills.find((s) => s.id === skillId);
1670
+ if (skill) {
1671
+ const skillPricing = skill.pricing;
1672
+ if (skillPricing && typeof skillPricing.credits_per_call === "number") {
1673
+ return skillPricing.credits_per_call;
1674
+ }
1675
+ }
1676
+ } else {
1677
+ let minPrice = null;
1678
+ for (const s of skills) {
1679
+ const sp = s.pricing;
1680
+ if (sp && typeof sp.credits_per_call === "number" && sp.credits_per_call > 0) {
1681
+ if (minPrice === null || sp.credits_per_call < minPrice) {
1682
+ minPrice = sp.credits_per_call;
1683
+ }
1684
+ }
1685
+ }
1686
+ if (minPrice !== null) return minPrice;
1687
+ }
1688
+ }
1689
+ const pricing = card.pricing;
1690
+ if (!pricing || typeof pricing.credits_per_call !== "number") {
1691
+ return null;
1692
+ }
1693
+ return pricing.credits_per_call;
1694
+ }
1695
+ function holdForRelay(creditDb, owner, amount, cardId) {
1696
+ return holdEscrow(creditDb, owner, amount, cardId);
1697
+ }
1698
+ function settleForRelay(creditDb, escrowId, recipientOwner) {
1699
+ settleEscrow(creditDb, escrowId, recipientOwner);
1700
+ }
1701
+ function calculateConductorFee(totalSubTaskCost) {
1702
+ if (totalSubTaskCost <= 0) return 0;
1703
+ const fee = Math.ceil(totalSubTaskCost * 0.1);
1704
+ return Math.max(1, Math.min(20, fee));
1705
+ }
1706
+ function releaseForRelay(creditDb, escrowId) {
1707
+ if (escrowId === void 0) return;
1708
+ releaseEscrow(creditDb, escrowId);
1709
+ }
1710
+
1711
+ // src/hub-agent/relay-bridge.ts
1712
+ import { randomUUID as randomUUID5 } from "crypto";
1713
+
1714
+ // src/hub-agent/job-queue.ts
1749
1715
  import { randomUUID as randomUUID4 } from "crypto";
1716
+ function initJobQueue(db) {
1717
+ db.exec(`
1718
+ CREATE TABLE IF NOT EXISTS hub_agent_jobs (
1719
+ id TEXT PRIMARY KEY,
1720
+ hub_agent_id TEXT NOT NULL,
1721
+ skill_id TEXT NOT NULL,
1722
+ requester_owner TEXT NOT NULL,
1723
+ params TEXT,
1724
+ status TEXT NOT NULL DEFAULT 'queued',
1725
+ result TEXT,
1726
+ escrow_id TEXT,
1727
+ relay_owner TEXT,
1728
+ created_at TEXT NOT NULL,
1729
+ updated_at TEXT NOT NULL
1730
+ )
1731
+ `);
1732
+ }
1733
+ function insertJob(db, input) {
1734
+ const id = randomUUID4();
1735
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1736
+ const paramsJson = JSON.stringify(input.params);
1737
+ db.prepare(`
1738
+ INSERT INTO hub_agent_jobs (id, hub_agent_id, skill_id, requester_owner, params, status, escrow_id, relay_owner, created_at, updated_at)
1739
+ VALUES (?, ?, ?, ?, ?, 'queued', ?, ?, ?, ?)
1740
+ `).run(
1741
+ id,
1742
+ input.hub_agent_id,
1743
+ input.skill_id,
1744
+ input.requester_owner,
1745
+ paramsJson,
1746
+ input.escrow_id ?? null,
1747
+ input.relay_owner ?? null,
1748
+ now,
1749
+ now
1750
+ );
1751
+ return {
1752
+ id,
1753
+ hub_agent_id: input.hub_agent_id,
1754
+ skill_id: input.skill_id,
1755
+ requester_owner: input.requester_owner,
1756
+ params: paramsJson,
1757
+ status: "queued",
1758
+ result: null,
1759
+ escrow_id: input.escrow_id ?? null,
1760
+ relay_owner: input.relay_owner ?? null,
1761
+ created_at: now,
1762
+ updated_at: now
1763
+ };
1764
+ }
1765
+ function getJob(db, jobId) {
1766
+ const row = db.prepare("SELECT * FROM hub_agent_jobs WHERE id = ?").get(jobId);
1767
+ return row ?? null;
1768
+ }
1769
+ function listJobs(db, hubAgentId, status) {
1770
+ if (status) {
1771
+ return db.prepare(
1772
+ "SELECT * FROM hub_agent_jobs WHERE hub_agent_id = ? AND status = ? ORDER BY created_at DESC"
1773
+ ).all(hubAgentId, status);
1774
+ }
1775
+ return db.prepare(
1776
+ "SELECT * FROM hub_agent_jobs WHERE hub_agent_id = ? ORDER BY created_at DESC"
1777
+ ).all(hubAgentId);
1778
+ }
1779
+ function updateJobStatus(db, jobId, status, result) {
1780
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1781
+ if (result !== void 0) {
1782
+ db.prepare("UPDATE hub_agent_jobs SET status = ?, result = ?, updated_at = ? WHERE id = ?").run(status, result, now, jobId);
1783
+ } else {
1784
+ db.prepare("UPDATE hub_agent_jobs SET status = ?, updated_at = ? WHERE id = ?").run(status, now, jobId);
1785
+ }
1786
+ }
1787
+ function getJobsByRelayOwner(db, relayOwner) {
1788
+ return db.prepare(
1789
+ "SELECT * FROM hub_agent_jobs WHERE relay_owner = ? AND status = ? ORDER BY created_at ASC"
1790
+ ).all(relayOwner, "queued");
1791
+ }
1792
+
1793
+ // src/hub-agent/crypto.ts
1794
+ import { createCipheriv, createDecipheriv, randomBytes } from "crypto";
1795
+ function getMasterKey() {
1796
+ const hex = process.env.HUB_MASTER_KEY;
1797
+ if (!hex || hex.length !== 64) {
1798
+ throw new AgentBnBError(
1799
+ "HUB_MASTER_KEY must be a 64-character hex string (32 bytes)",
1800
+ "MISSING_MASTER_KEY"
1801
+ );
1802
+ }
1803
+ return Buffer.from(hex, "hex");
1804
+ }
1805
+ function encrypt(plaintext, masterKey) {
1806
+ const iv = randomBytes(12);
1807
+ const cipher = createCipheriv("aes-256-gcm", masterKey, iv);
1808
+ const encrypted = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
1809
+ const authTag = cipher.getAuthTag();
1810
+ return `${iv.toString("hex")}:${authTag.toString("hex")}:${encrypted.toString("hex")}`;
1811
+ }
1812
+ function decrypt(encrypted, masterKey) {
1813
+ const [ivHex, authTagHex, ciphertextHex] = encrypted.split(":");
1814
+ const iv = Buffer.from(ivHex, "hex");
1815
+ const authTag = Buffer.from(authTagHex, "hex");
1816
+ const ciphertext = Buffer.from(ciphertextHex, "hex");
1817
+ const decipher = createDecipheriv("aes-256-gcm", masterKey, iv);
1818
+ decipher.setAuthTag(authTag);
1819
+ const decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
1820
+ return decrypted.toString("utf8");
1821
+ }
1822
+
1823
+ // src/hub-agent/store.ts
1824
+ function initHubAgentTable(db) {
1825
+ db.exec(`
1826
+ CREATE TABLE IF NOT EXISTS hub_agents (
1827
+ agent_id TEXT PRIMARY KEY,
1828
+ name TEXT NOT NULL,
1829
+ owner_public_key TEXT NOT NULL,
1830
+ public_key TEXT NOT NULL,
1831
+ private_key_enc TEXT NOT NULL,
1832
+ secrets_enc TEXT,
1833
+ skill_routes TEXT NOT NULL DEFAULT '[]',
1834
+ status TEXT NOT NULL DEFAULT 'active',
1835
+ created_at TEXT NOT NULL,
1836
+ updated_at TEXT NOT NULL
1837
+ )
1838
+ `);
1839
+ }
1840
+ function createHubAgent(db, req, ownerPublicKey) {
1841
+ const masterKey = getMasterKey();
1842
+ const keys = generateKeyPair();
1843
+ const publicKeyHex = keys.publicKey.toString("hex");
1844
+ const agentId = deriveAgentId(publicKeyHex);
1845
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1846
+ const privateKeyEnc = encrypt(keys.privateKey.toString("hex"), masterKey);
1847
+ const secretsEnc = req.secrets ? encrypt(JSON.stringify(req.secrets), masterKey) : null;
1848
+ db.prepare(`
1849
+ INSERT INTO hub_agents (agent_id, name, owner_public_key, public_key, private_key_enc, secrets_enc, skill_routes, status, created_at, updated_at)
1850
+ VALUES (?, ?, ?, ?, ?, ?, ?, 'active', ?, ?)
1851
+ `).run(
1852
+ agentId,
1853
+ req.name,
1854
+ ownerPublicKey,
1855
+ publicKeyHex,
1856
+ privateKeyEnc,
1857
+ secretsEnc,
1858
+ JSON.stringify(req.skill_routes),
1859
+ now,
1860
+ now
1861
+ );
1862
+ return {
1863
+ agent_id: agentId,
1864
+ name: req.name,
1865
+ owner_public_key: ownerPublicKey,
1866
+ public_key: publicKeyHex,
1867
+ skill_routes: req.skill_routes,
1868
+ status: "active",
1869
+ created_at: now,
1870
+ updated_at: now
1871
+ };
1872
+ }
1873
+ function getHubAgent(db, agentId) {
1874
+ const row = db.prepare("SELECT * FROM hub_agents WHERE agent_id = ?").get(agentId);
1875
+ if (!row) return null;
1876
+ const masterKey = getMasterKey();
1877
+ const secrets = row.secrets_enc ? JSON.parse(decrypt(row.secrets_enc, masterKey)) : void 0;
1878
+ return {
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
+ ...secrets ? { secrets } : {}
1888
+ };
1889
+ }
1890
+ function listHubAgents(db) {
1891
+ 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();
1892
+ return rows.map((row) => ({
1893
+ agent_id: row.agent_id,
1894
+ name: row.name,
1895
+ owner_public_key: row.owner_public_key,
1896
+ public_key: row.public_key,
1897
+ skill_routes: JSON.parse(row.skill_routes),
1898
+ status: row.status,
1899
+ created_at: row.created_at,
1900
+ updated_at: row.updated_at
1901
+ }));
1902
+ }
1903
+ function updateHubAgent(db, agentId, updates) {
1904
+ const existing = db.prepare("SELECT * FROM hub_agents WHERE agent_id = ?").get(agentId);
1905
+ if (!existing) return null;
1906
+ const masterKey = getMasterKey();
1907
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1908
+ const newName = updates.name ?? existing.name;
1909
+ const newSkillRoutes = updates.skill_routes ? JSON.stringify(updates.skill_routes) : existing.skill_routes;
1910
+ const newSecretsEnc = updates.secrets !== void 0 ? encrypt(JSON.stringify(updates.secrets), masterKey) : existing.secrets_enc;
1911
+ db.prepare(`
1912
+ UPDATE hub_agents SET name = ?, skill_routes = ?, secrets_enc = ?, updated_at = ?
1913
+ WHERE agent_id = ?
1914
+ `).run(newName, newSkillRoutes, newSecretsEnc, now, agentId);
1915
+ const secrets = newSecretsEnc ? JSON.parse(decrypt(newSecretsEnc, masterKey)) : void 0;
1916
+ return {
1917
+ agent_id: existing.agent_id,
1918
+ name: newName,
1919
+ owner_public_key: existing.owner_public_key,
1920
+ public_key: existing.public_key,
1921
+ skill_routes: JSON.parse(newSkillRoutes),
1922
+ status: existing.status,
1923
+ created_at: existing.created_at,
1924
+ updated_at: now,
1925
+ ...secrets ? { secrets } : {}
1926
+ };
1927
+ }
1928
+ function deleteHubAgent(db, agentId) {
1929
+ const result = db.prepare("DELETE FROM hub_agents WHERE agent_id = ?").run(agentId);
1930
+ return result.changes > 0;
1931
+ }
1932
+
1933
+ // src/hub-agent/relay-bridge.ts
1934
+ var JOB_DISPATCH_TIMEOUT_MS = 3e5;
1935
+ function createRelayBridge(opts) {
1936
+ const { registryDb, creditDb, sendMessage, pendingRequests, connections } = opts;
1937
+ function onAgentOnline(owner) {
1938
+ const jobs = getJobsByRelayOwner(registryDb, owner);
1939
+ if (jobs.length === 0) return;
1940
+ const targetWs = connections.get(owner);
1941
+ if (!targetWs) return;
1942
+ for (const job of jobs) {
1943
+ updateJobStatus(registryDb, job.id, "dispatched");
1944
+ const agent = getHubAgent(registryDb, job.hub_agent_id);
1945
+ const cardId = agent ? agent.agent_id.padEnd(32, "0").replace(/^(.{8})(.{4})(.{4})(.{4})(.{12}).*$/, "$1-$2-$3-$4-$5") : job.hub_agent_id;
1946
+ const requestId = randomUUID5();
1947
+ let params = {};
1948
+ try {
1949
+ params = JSON.parse(job.params);
1950
+ } catch {
1951
+ }
1952
+ const timeout = setTimeout(() => {
1953
+ const pending = pendingRequests.get(requestId);
1954
+ if (pending) {
1955
+ pendingRequests.delete(requestId);
1956
+ updateJobStatus(registryDb, job.id, "failed", JSON.stringify({ error: "dispatch timeout" }));
1957
+ if (job.escrow_id) {
1958
+ try {
1959
+ releaseForRelay(creditDb, job.escrow_id);
1960
+ } catch (e) {
1961
+ console.error("[relay-bridge] escrow release on timeout failed:", e);
1962
+ }
1963
+ }
1964
+ }
1965
+ }, JOB_DISPATCH_TIMEOUT_MS);
1966
+ pendingRequests.set(requestId, {
1967
+ originOwner: job.requester_owner,
1968
+ timeout,
1969
+ escrowId: job.escrow_id ?? void 0,
1970
+ targetOwner: owner,
1971
+ jobId: job.id
1972
+ });
1973
+ sendMessage(targetWs, {
1974
+ type: "incoming_request",
1975
+ id: requestId,
1976
+ from_owner: job.requester_owner,
1977
+ card_id: cardId,
1978
+ skill_id: job.skill_id,
1979
+ params
1980
+ });
1981
+ }
1982
+ }
1983
+ return { onAgentOnline };
1984
+ }
1985
+ function handleJobRelayResponse(opts) {
1986
+ const { registryDb, creditDb, jobId, escrowId, relayOwner, result, error } = opts;
1987
+ if (error) {
1988
+ updateJobStatus(registryDb, jobId, "failed", JSON.stringify(error));
1989
+ if (escrowId) {
1990
+ try {
1991
+ releaseForRelay(creditDb, escrowId);
1992
+ } catch (e) {
1993
+ console.error("[relay-bridge] escrow release on error failed:", e);
1994
+ }
1995
+ }
1996
+ } else {
1997
+ updateJobStatus(registryDb, jobId, "completed", JSON.stringify(result));
1998
+ if (escrowId) {
1999
+ try {
2000
+ settleForRelay(creditDb, escrowId, relayOwner);
2001
+ } catch (e) {
2002
+ console.error("[relay-bridge] escrow settle failed:", e);
2003
+ }
2004
+ }
2005
+ }
2006
+ }
2007
+
2008
+ // src/relay/websocket-relay.ts
1750
2009
  var RATE_LIMIT_MAX = 60;
1751
2010
  var RATE_LIMIT_WINDOW_MS = 6e4;
1752
- var RELAY_TIMEOUT_MS = 3e4;
1753
- function registerWebSocketRelay(server, db) {
2011
+ var RELAY_TIMEOUT_MS = 3e5;
2012
+ function registerWebSocketRelay(server, db, creditDb) {
1754
2013
  const connections = /* @__PURE__ */ new Map();
1755
2014
  const pendingRequests = /* @__PURE__ */ new Map();
1756
2015
  const rateLimits = /* @__PURE__ */ new Map();
2016
+ let onAgentOnlineCallback;
1757
2017
  function checkRateLimit(owner) {
1758
2018
  const now = Date.now();
1759
2019
  const entry = rateLimits.get(owner);
@@ -1804,21 +2064,32 @@ function registerWebSocketRelay(server, db) {
1804
2064
  }
1805
2065
  }
1806
2066
  function upsertCard(cardData, owner) {
1807
- const cardId = cardData.id;
1808
- const existing = getCard(db, cardId);
2067
+ const parsed = AnyCardSchema.safeParse(cardData);
2068
+ if (!parsed.success) {
2069
+ throw new AgentBnBError(
2070
+ `Card validation failed: ${parsed.error.message}`,
2071
+ "VALIDATION_ERROR"
2072
+ );
2073
+ }
2074
+ const card = { ...parsed.data, availability: { ...parsed.data.availability, online: true } };
2075
+ const cardId = card.id;
2076
+ const now = (/* @__PURE__ */ new Date()).toISOString();
2077
+ const existing = db.prepare("SELECT id FROM capability_cards WHERE id = ?").get(cardId);
1809
2078
  if (existing) {
1810
- const updates = { ...cardData, availability: { ...cardData.availability ?? {}, online: true } };
1811
- updateCard(db, cardId, owner, updates);
2079
+ db.prepare(
2080
+ "UPDATE capability_cards SET data = ?, updated_at = ? WHERE id = ?"
2081
+ ).run(JSON.stringify(card), now, cardId);
1812
2082
  } else {
1813
- const card = { ...cardData, availability: { ...cardData.availability ?? {}, online: true } };
1814
- insertCard(db, card);
2083
+ db.prepare(
2084
+ "INSERT INTO capability_cards (id, owner, data, created_at, updated_at) VALUES (?, ?, ?, ?, ?)"
2085
+ ).run(cardId, owner, JSON.stringify(card), now, now);
1815
2086
  }
1816
2087
  return cardId;
1817
2088
  }
1818
2089
  function logAgentJoined(owner, cardName, cardId) {
1819
2090
  try {
1820
2091
  insertRequestLog(db, {
1821
- id: randomUUID4(),
2092
+ id: randomUUID6(),
1822
2093
  card_id: cardId,
1823
2094
  card_name: cardName,
1824
2095
  requester: owner,
@@ -1846,13 +2117,34 @@ function registerWebSocketRelay(server, db) {
1846
2117
  }
1847
2118
  }
1848
2119
  connections.set(owner, ws);
1849
- const cardId = upsertCard(card, owner);
2120
+ let cardId;
2121
+ try {
2122
+ cardId = upsertCard(card, owner);
2123
+ } catch (err) {
2124
+ console.error(`[relay] card validation failed for ${owner}:`, err instanceof Error ? err.message : err);
2125
+ cardId = card.id ?? owner;
2126
+ }
1850
2127
  const cardName = card.name ?? card.agent_name ?? owner;
1851
2128
  logAgentJoined(owner, cardName, cardId);
2129
+ if (msg.cards && msg.cards.length > 0) {
2130
+ for (const extraCard of msg.cards) {
2131
+ try {
2132
+ upsertCard(extraCard, owner);
2133
+ } catch {
2134
+ }
2135
+ }
2136
+ }
1852
2137
  markOwnerOnline(owner);
2138
+ if (onAgentOnlineCallback) {
2139
+ try {
2140
+ onAgentOnlineCallback(owner);
2141
+ } catch (e) {
2142
+ console.error("[relay] onAgentOnline callback error:", e);
2143
+ }
2144
+ }
1853
2145
  sendMessage(ws, { type: "registered", agent_id: cardId });
1854
2146
  }
1855
- function handleRelayRequest(ws, msg, fromOwner) {
2147
+ async function handleRelayRequest(ws, msg, fromOwner) {
1856
2148
  if (!checkRateLimit(fromOwner)) {
1857
2149
  sendMessage(ws, {
1858
2150
  type: "error",
@@ -1871,15 +2163,43 @@ function registerWebSocketRelay(server, db) {
1871
2163
  });
1872
2164
  return;
1873
2165
  }
2166
+ const creditOwner = msg.requester ?? fromOwner;
2167
+ let escrowId;
2168
+ if (creditDb) {
2169
+ try {
2170
+ const price = lookupCardPrice(db, msg.card_id, msg.skill_id);
2171
+ if (price !== null && price > 0) {
2172
+ escrowId = holdForRelay(creditDb, creditOwner, price, msg.card_id);
2173
+ }
2174
+ } catch (err) {
2175
+ if (err instanceof AgentBnBError && err.code === "INSUFFICIENT_CREDITS") {
2176
+ sendMessage(ws, {
2177
+ type: "response",
2178
+ id: msg.id,
2179
+ error: { code: -32603, message: "Insufficient credits" }
2180
+ });
2181
+ return;
2182
+ }
2183
+ console.error("[relay] credit hold error (non-fatal):", err);
2184
+ }
2185
+ }
1874
2186
  const timeout = setTimeout(() => {
2187
+ const pending = pendingRequests.get(msg.id);
1875
2188
  pendingRequests.delete(msg.id);
2189
+ if (pending?.escrowId && creditDb) {
2190
+ try {
2191
+ releaseForRelay(creditDb, pending.escrowId);
2192
+ } catch (e) {
2193
+ console.error("[relay] escrow release on timeout failed:", e);
2194
+ }
2195
+ }
1876
2196
  sendMessage(ws, {
1877
2197
  type: "response",
1878
2198
  id: msg.id,
1879
2199
  error: { code: -32603, message: "Relay request timeout" }
1880
2200
  });
1881
2201
  }, RELAY_TIMEOUT_MS);
1882
- pendingRequests.set(msg.id, { originOwner: fromOwner, timeout });
2202
+ pendingRequests.set(msg.id, { originOwner: fromOwner, creditOwner, timeout, escrowId, targetOwner: msg.target_owner });
1883
2203
  sendMessage(targetWs, {
1884
2204
  type: "incoming_request",
1885
2205
  id: msg.id,
@@ -1891,18 +2211,98 @@ function registerWebSocketRelay(server, db) {
1891
2211
  escrow_receipt: msg.escrow_receipt
1892
2212
  });
1893
2213
  }
2214
+ function handleRelayProgress(msg) {
2215
+ const pending = pendingRequests.get(msg.id);
2216
+ if (!pending) return;
2217
+ clearTimeout(pending.timeout);
2218
+ const newTimeout = setTimeout(() => {
2219
+ const p = pendingRequests.get(msg.id);
2220
+ pendingRequests.delete(msg.id);
2221
+ if (p?.escrowId && creditDb) {
2222
+ try {
2223
+ releaseForRelay(creditDb, p.escrowId);
2224
+ } catch (e) {
2225
+ console.error("[relay] escrow release on progress timeout failed:", e);
2226
+ }
2227
+ }
2228
+ const originWs2 = connections.get(pending.originOwner);
2229
+ if (originWs2 && originWs2.readyState === 1) {
2230
+ sendMessage(originWs2, {
2231
+ type: "response",
2232
+ id: msg.id,
2233
+ error: { code: -32603, message: "Relay request timeout" }
2234
+ });
2235
+ }
2236
+ }, RELAY_TIMEOUT_MS);
2237
+ pending.timeout = newTimeout;
2238
+ const originWs = connections.get(pending.originOwner);
2239
+ if (originWs && originWs.readyState === 1) {
2240
+ sendMessage(originWs, {
2241
+ type: "relay_progress",
2242
+ id: msg.id,
2243
+ progress: msg.progress,
2244
+ message: msg.message
2245
+ });
2246
+ }
2247
+ }
1894
2248
  function handleRelayResponse(msg) {
1895
2249
  const pending = pendingRequests.get(msg.id);
1896
2250
  if (!pending) return;
1897
2251
  clearTimeout(pending.timeout);
1898
2252
  pendingRequests.delete(msg.id);
2253
+ if (pending.jobId && creditDb) {
2254
+ try {
2255
+ handleJobRelayResponse({
2256
+ registryDb: db,
2257
+ creditDb,
2258
+ jobId: pending.jobId,
2259
+ escrowId: pending.escrowId,
2260
+ relayOwner: pending.targetOwner ?? "",
2261
+ result: msg.error === void 0 ? msg.result : void 0,
2262
+ error: msg.error
2263
+ });
2264
+ } catch (e) {
2265
+ console.error("[relay] job relay response handling failed:", e);
2266
+ }
2267
+ const originWs2 = connections.get(pending.originOwner);
2268
+ if (originWs2 && originWs2.readyState === 1) {
2269
+ sendMessage(originWs2, { type: "response", id: msg.id, result: msg.result, error: msg.error });
2270
+ }
2271
+ return;
2272
+ }
2273
+ if (pending.escrowId && creditDb) {
2274
+ try {
2275
+ if (msg.error === void 0) {
2276
+ settleForRelay(creditDb, pending.escrowId, pending.targetOwner);
2277
+ } else {
2278
+ releaseForRelay(creditDb, pending.escrowId);
2279
+ }
2280
+ } catch (e) {
2281
+ console.error("[relay] escrow settle/release on response failed:", e);
2282
+ }
2283
+ }
2284
+ let conductorFee = 0;
2285
+ if (creditDb && msg.error === void 0 && typeof msg.result === "object" && msg.result !== null && "total_credits" in msg.result && typeof msg.result.total_credits === "number") {
2286
+ const totalCredits = msg.result.total_credits;
2287
+ conductorFee = calculateConductorFee(totalCredits);
2288
+ if (conductorFee > 0) {
2289
+ try {
2290
+ const feeEscrowId = holdForRelay(creditDb, pending.creditOwner ?? pending.originOwner, conductorFee, msg.id);
2291
+ settleForRelay(creditDb, feeEscrowId, pending.targetOwner);
2292
+ } catch (e) {
2293
+ console.error("[relay] conductor fee settlement failed (non-fatal):", e);
2294
+ conductorFee = 0;
2295
+ }
2296
+ }
2297
+ }
1899
2298
  const originWs = connections.get(pending.originOwner);
1900
2299
  if (originWs && originWs.readyState === 1) {
1901
2300
  sendMessage(originWs, {
1902
2301
  type: "response",
1903
2302
  id: msg.id,
1904
2303
  result: msg.result,
1905
- error: msg.error
2304
+ error: msg.error,
2305
+ ...conductorFee > 0 ? { conductor_fee: conductorFee } : {}
1906
2306
  });
1907
2307
  }
1908
2308
  }
@@ -1912,61 +2312,93 @@ function registerWebSocketRelay(server, db) {
1912
2312
  rateLimits.delete(owner);
1913
2313
  markOwnerOffline(owner);
1914
2314
  for (const [reqId, pending] of pendingRequests) {
1915
- if (pending.originOwner === owner) {
2315
+ if (pending.targetOwner === owner) {
2316
+ clearTimeout(pending.timeout);
2317
+ pendingRequests.delete(reqId);
2318
+ if (pending.escrowId && creditDb) {
2319
+ try {
2320
+ releaseForRelay(creditDb, pending.escrowId);
2321
+ } catch (e) {
2322
+ console.error("[relay] escrow release on disconnect failed:", e);
2323
+ }
2324
+ }
2325
+ const originWs = connections.get(pending.originOwner);
2326
+ if (originWs && originWs.readyState === 1) {
2327
+ sendMessage(originWs, {
2328
+ type: "response",
2329
+ id: reqId,
2330
+ error: { code: -32603, message: "Provider disconnected" }
2331
+ });
2332
+ }
2333
+ } else if (pending.originOwner === owner) {
1916
2334
  clearTimeout(pending.timeout);
1917
2335
  pendingRequests.delete(reqId);
2336
+ if (pending.escrowId && creditDb) {
2337
+ try {
2338
+ releaseForRelay(creditDb, pending.escrowId);
2339
+ } catch (e) {
2340
+ console.error("[relay] escrow release on requester disconnect failed:", e);
2341
+ }
2342
+ }
1918
2343
  }
1919
2344
  }
1920
2345
  }
1921
- server.get("/ws", { websocket: true }, (rawSocket, _request) => {
1922
- const socket = rawSocket;
1923
- let registeredOwner;
1924
- 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) {
2346
+ void server.register(async (app) => {
2347
+ app.get("/ws", { websocket: true }, (rawSocket, _request) => {
2348
+ const socket = rawSocket;
2349
+ let registeredOwner;
2350
+ socket.on("message", (raw) => {
2351
+ void (async () => {
2352
+ let data;
2353
+ try {
2354
+ data = JSON.parse(typeof raw === "string" ? raw : raw.toString("utf-8"));
2355
+ } catch {
2356
+ sendMessage(socket, { type: "error", code: "invalid_json", message: "Invalid JSON" });
2357
+ return;
2358
+ }
2359
+ const parsed = RelayMessageSchema.safeParse(data);
2360
+ if (!parsed.success) {
1949
2361
  sendMessage(socket, {
1950
2362
  type: "error",
1951
- code: "not_registered",
1952
- message: "Must send register message before relay requests"
2363
+ code: "invalid_message",
2364
+ message: `Invalid message: ${parsed.error.issues[0]?.message ?? "unknown error"}`
1953
2365
  });
1954
2366
  return;
1955
2367
  }
1956
- handleRelayRequest(socket, msg, registeredOwner);
1957
- break;
1958
- case "relay_response":
1959
- handleRelayResponse(msg);
1960
- break;
1961
- default:
1962
- break;
1963
- }
1964
- });
1965
- socket.on("close", () => {
1966
- handleDisconnect(registeredOwner);
1967
- });
1968
- socket.on("error", () => {
1969
- handleDisconnect(registeredOwner);
2368
+ const msg = parsed.data;
2369
+ switch (msg.type) {
2370
+ case "register":
2371
+ registeredOwner = msg.owner;
2372
+ handleRegister(socket, msg);
2373
+ break;
2374
+ case "relay_request":
2375
+ if (!registeredOwner) {
2376
+ sendMessage(socket, {
2377
+ type: "error",
2378
+ code: "not_registered",
2379
+ message: "Must send register message before relay requests"
2380
+ });
2381
+ return;
2382
+ }
2383
+ await handleRelayRequest(socket, msg, registeredOwner);
2384
+ break;
2385
+ case "relay_response":
2386
+ handleRelayResponse(msg);
2387
+ break;
2388
+ case "relay_progress":
2389
+ handleRelayProgress(msg);
2390
+ break;
2391
+ default:
2392
+ break;
2393
+ }
2394
+ })();
2395
+ });
2396
+ socket.on("close", () => {
2397
+ handleDisconnect(registeredOwner);
2398
+ });
2399
+ socket.on("error", () => {
2400
+ handleDisconnect(registeredOwner);
2401
+ });
1970
2402
  });
1971
2403
  });
1972
2404
  return {
@@ -1985,21 +2417,29 @@ function registerWebSocketRelay(server, db) {
1985
2417
  }
1986
2418
  pendingRequests.clear();
1987
2419
  rateLimits.clear();
2420
+ },
2421
+ setOnAgentOnline: (cb) => {
2422
+ onAgentOnlineCallback = cb;
2423
+ },
2424
+ getConnections: () => connections,
2425
+ getPendingRequests: () => pendingRequests,
2426
+ sendMessage: (ws, msg) => {
2427
+ sendMessage(ws, msg);
1988
2428
  }
1989
2429
  };
1990
2430
  }
1991
2431
 
1992
2432
  // src/identity/guarantor.ts
1993
- import { z as z4 } from "zod";
1994
- import { randomUUID as randomUUID5 } from "crypto";
2433
+ import { z as z3 } from "zod";
2434
+ import { randomUUID as randomUUID7 } from "crypto";
1995
2435
  var MAX_AGENTS_PER_GUARANTOR = 10;
1996
2436
  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()
2437
+ var GuarantorRecordSchema = z3.object({
2438
+ id: z3.string().uuid(),
2439
+ github_login: z3.string().min(1),
2440
+ agent_count: z3.number().int().nonnegative(),
2441
+ credit_pool: z3.number().int().nonnegative(),
2442
+ created_at: z3.string().datetime()
2003
2443
  });
2004
2444
  var GUARANTOR_SCHEMA = `
2005
2445
  CREATE TABLE IF NOT EXISTS guarantors (
@@ -2030,7 +2470,7 @@ function registerGuarantor(db, githubLogin) {
2030
2470
  );
2031
2471
  }
2032
2472
  const record = {
2033
- id: randomUUID5(),
2473
+ id: randomUUID7(),
2034
2474
  github_login: githubLogin,
2035
2475
  agent_count: 0,
2036
2476
  credit_pool: GUARANTOR_CREDIT_POOL,
@@ -2104,10 +2544,891 @@ function getAgentGuarantor(db, agentId) {
2104
2544
  function initiateGithubAuth() {
2105
2545
  return {
2106
2546
  auth_url: "https://github.com/login/oauth/authorize?client_id=PLACEHOLDER&scope=read:user",
2107
- state: randomUUID5()
2547
+ state: randomUUID7()
2108
2548
  };
2109
2549
  }
2110
2550
 
2551
+ // src/registry/free-tier.ts
2552
+ function initFreeTierTable(db) {
2553
+ db.exec(`
2554
+ CREATE TABLE IF NOT EXISTS credit_free_tier_usage (
2555
+ agent_public_key TEXT NOT NULL,
2556
+ skill_id TEXT NOT NULL,
2557
+ usage_count INTEGER NOT NULL DEFAULT 0,
2558
+ last_used_at TEXT NOT NULL,
2559
+ PRIMARY KEY (agent_public_key, skill_id)
2560
+ )
2561
+ `);
2562
+ }
2563
+
2564
+ // src/registry/credit-routes.ts
2565
+ async function creditRoutesPlugin(fastify, options) {
2566
+ const { creditDb } = options;
2567
+ creditDb.exec(`
2568
+ CREATE TABLE IF NOT EXISTS credit_grants (
2569
+ public_key TEXT PRIMARY KEY,
2570
+ granted_at TEXT NOT NULL,
2571
+ owner TEXT
2572
+ )
2573
+ `);
2574
+ try {
2575
+ creditDb.exec("ALTER TABLE credit_grants ADD COLUMN owner TEXT");
2576
+ } catch {
2577
+ }
2578
+ initFreeTierTable(creditDb);
2579
+ await fastify.register(async (scope) => {
2580
+ identityAuthPlugin(scope);
2581
+ scope.post("/api/credits/hold", {
2582
+ schema: {
2583
+ tags: ["credits"],
2584
+ summary: "Hold credits in escrow during capability execution",
2585
+ security: [{ ed25519Auth: [] }],
2586
+ body: {
2587
+ type: "object",
2588
+ properties: {
2589
+ owner: { type: "string" },
2590
+ amount: { type: "number" },
2591
+ cardId: { type: "string" }
2592
+ },
2593
+ required: ["owner", "amount", "cardId"]
2594
+ },
2595
+ response: {
2596
+ 200: { type: "object", properties: { escrowId: { type: "string" } } },
2597
+ 400: { type: "object", properties: { error: { type: "string" }, code: { type: "string" } } }
2598
+ }
2599
+ }
2600
+ }, async (request, reply) => {
2601
+ const body = request.body;
2602
+ const owner = typeof body.owner === "string" ? body.owner.trim() : "";
2603
+ const amount = typeof body.amount === "number" ? body.amount : NaN;
2604
+ const cardId = typeof body.cardId === "string" ? body.cardId.trim() : "";
2605
+ if (!owner || isNaN(amount) || amount <= 0 || !cardId) {
2606
+ return reply.code(400).send({ error: "Missing or invalid required fields: owner, amount (>0), cardId" });
2607
+ }
2608
+ try {
2609
+ const escrowId = holdEscrow(creditDb, owner, amount, cardId);
2610
+ return reply.send({ escrowId });
2611
+ } catch (err) {
2612
+ if (err instanceof AgentBnBError && err.code === "INSUFFICIENT_CREDITS") {
2613
+ return reply.code(400).send({ error: err.message, code: "INSUFFICIENT_CREDITS" });
2614
+ }
2615
+ throw err;
2616
+ }
2617
+ });
2618
+ scope.post("/api/credits/settle", {
2619
+ schema: {
2620
+ tags: ["credits"],
2621
+ summary: "Transfer held credits to provider on success",
2622
+ security: [{ ed25519Auth: [] }],
2623
+ body: {
2624
+ type: "object",
2625
+ properties: {
2626
+ escrowId: { type: "string" },
2627
+ recipientOwner: { type: "string" }
2628
+ },
2629
+ required: ["escrowId", "recipientOwner"]
2630
+ },
2631
+ response: {
2632
+ 200: { type: "object", properties: { ok: { type: "boolean" } } },
2633
+ 400: { type: "object", properties: { error: { type: "string" }, code: { type: "string" } } }
2634
+ }
2635
+ }
2636
+ }, async (request, reply) => {
2637
+ const body = request.body;
2638
+ const escrowId = typeof body.escrowId === "string" ? body.escrowId.trim() : "";
2639
+ const recipientOwner = typeof body.recipientOwner === "string" ? body.recipientOwner.trim() : "";
2640
+ if (!escrowId || !recipientOwner) {
2641
+ return reply.code(400).send({ error: "escrowId and recipientOwner are required" });
2642
+ }
2643
+ try {
2644
+ settleEscrow(creditDb, escrowId, recipientOwner);
2645
+ return reply.send({ ok: true });
2646
+ } catch (err) {
2647
+ if (err instanceof AgentBnBError) {
2648
+ return reply.code(400).send({ error: err.message, code: err.code });
2649
+ }
2650
+ throw err;
2651
+ }
2652
+ });
2653
+ scope.post("/api/credits/release", {
2654
+ schema: {
2655
+ tags: ["credits"],
2656
+ summary: "Refund held credits to requester on failure",
2657
+ security: [{ ed25519Auth: [] }],
2658
+ body: {
2659
+ type: "object",
2660
+ properties: { escrowId: { type: "string" } },
2661
+ required: ["escrowId"]
2662
+ },
2663
+ response: {
2664
+ 200: { type: "object", properties: { ok: { type: "boolean" } } },
2665
+ 400: { type: "object", properties: { error: { type: "string" }, code: { type: "string" } } }
2666
+ }
2667
+ }
2668
+ }, async (request, reply) => {
2669
+ const body = request.body;
2670
+ const escrowId = typeof body.escrowId === "string" ? body.escrowId.trim() : "";
2671
+ if (!escrowId) {
2672
+ return reply.code(400).send({ error: "escrowId is required" });
2673
+ }
2674
+ try {
2675
+ releaseEscrow(creditDb, escrowId);
2676
+ return reply.send({ ok: true });
2677
+ } catch (err) {
2678
+ if (err instanceof AgentBnBError) {
2679
+ return reply.code(400).send({ error: err.message, code: err.code });
2680
+ }
2681
+ throw err;
2682
+ }
2683
+ });
2684
+ scope.post("/api/credits/grant", {
2685
+ schema: {
2686
+ tags: ["credits"],
2687
+ summary: "Bootstrap grant of 50 credits (once per identity)",
2688
+ security: [{ ed25519Auth: [] }],
2689
+ body: {
2690
+ type: "object",
2691
+ properties: {
2692
+ owner: { type: "string" },
2693
+ amount: { type: "number" }
2694
+ },
2695
+ required: ["owner"]
2696
+ },
2697
+ response: {
2698
+ 200: {
2699
+ type: "object",
2700
+ properties: {
2701
+ ok: { type: "boolean" },
2702
+ granted: { type: "number" },
2703
+ reason: { type: "string" }
2704
+ }
2705
+ },
2706
+ 400: { type: "object", properties: { error: { type: "string" } } }
2707
+ }
2708
+ }
2709
+ }, async (request, reply) => {
2710
+ const body = request.body;
2711
+ const owner = typeof body.owner === "string" ? body.owner.trim() : "";
2712
+ const amount = typeof body.amount === "number" ? body.amount : 50;
2713
+ const publicKey = request.agentPublicKey;
2714
+ if (!owner) {
2715
+ return reply.code(400).send({ error: "owner is required" });
2716
+ }
2717
+ const existing = creditDb.prepare("SELECT public_key, owner FROM credit_grants WHERE public_key = ?").get(publicKey);
2718
+ if (existing) {
2719
+ if (existing.owner && existing.owner !== owner) {
2720
+ migrateOwner(creditDb, existing.owner, owner);
2721
+ creditDb.prepare("UPDATE credit_grants SET owner = ? WHERE public_key = ?").run(owner, publicKey);
2722
+ return reply.send({ ok: true, granted: 0, reason: "renamed", from: existing.owner, to: owner });
2723
+ }
2724
+ return reply.send({ ok: true, granted: 0, reason: "already_granted" });
2725
+ }
2726
+ const now = (/* @__PURE__ */ new Date()).toISOString();
2727
+ bootstrapAgent(creditDb, owner, amount);
2728
+ creditDb.prepare("INSERT INTO credit_grants (public_key, granted_at, owner) VALUES (?, ?, ?)").run(publicKey, now, owner);
2729
+ return reply.send({ ok: true, granted: amount });
2730
+ });
2731
+ scope.get("/api/credits/:owner", {
2732
+ schema: {
2733
+ tags: ["credits"],
2734
+ summary: "Get current credit balance for an agent",
2735
+ security: [{ ed25519Auth: [] }],
2736
+ params: { type: "object", properties: { owner: { type: "string" } }, required: ["owner"] },
2737
+ response: { 200: { type: "object", properties: { balance: { type: "number" } } } }
2738
+ }
2739
+ }, async (request, reply) => {
2740
+ const { owner } = request.params;
2741
+ const balance = getBalance(creditDb, owner);
2742
+ return reply.send({ balance });
2743
+ });
2744
+ scope.get("/api/credits/:owner/history", {
2745
+ schema: {
2746
+ tags: ["credits"],
2747
+ summary: "Get paginated transaction history",
2748
+ security: [{ ed25519Auth: [] }],
2749
+ params: { type: "object", properties: { owner: { type: "string" } }, required: ["owner"] },
2750
+ querystring: {
2751
+ type: "object",
2752
+ properties: { limit: { type: "integer", description: "Max entries (default 20, max 100)" } }
2753
+ },
2754
+ response: {
2755
+ 200: {
2756
+ type: "object",
2757
+ properties: { transactions: { type: "array" }, limit: { type: "integer" } }
2758
+ }
2759
+ }
2760
+ }
2761
+ }, async (request, reply) => {
2762
+ const { owner } = request.params;
2763
+ const query = request.query;
2764
+ const rawLimit = query.limit !== void 0 ? parseInt(query.limit, 10) : 20;
2765
+ const limit = Math.min(isNaN(rawLimit) || rawLimit < 1 ? 20 : rawLimit, 100);
2766
+ const transactions = getTransactions(creditDb, owner, limit);
2767
+ return reply.send({ transactions, limit });
2768
+ });
2769
+ scope.post("/api/credits/rename", {
2770
+ schema: {
2771
+ tags: ["credits"],
2772
+ summary: "Rename owner \u2014 migrate credits from old owner to new owner",
2773
+ security: [{ ed25519Auth: [] }],
2774
+ body: {
2775
+ type: "object",
2776
+ properties: {
2777
+ oldOwner: { type: "string" },
2778
+ newOwner: { type: "string" }
2779
+ },
2780
+ required: ["oldOwner", "newOwner"]
2781
+ },
2782
+ response: {
2783
+ 200: { type: "object", properties: { ok: { type: "boolean" }, migrated: { type: "boolean" } } },
2784
+ 400: { type: "object", properties: { error: { type: "string" } } }
2785
+ }
2786
+ }
2787
+ }, async (request, reply) => {
2788
+ const body = request.body;
2789
+ const oldOwner = typeof body.oldOwner === "string" ? body.oldOwner.trim() : "";
2790
+ const newOwner = typeof body.newOwner === "string" ? body.newOwner.trim() : "";
2791
+ if (!oldOwner || !newOwner || oldOwner === newOwner) {
2792
+ return reply.code(400).send({ error: "oldOwner and newOwner must be different non-empty strings" });
2793
+ }
2794
+ const oldBalance = getBalance(creditDb, oldOwner);
2795
+ if (oldBalance === 0) {
2796
+ return reply.send({ ok: true, migrated: false });
2797
+ }
2798
+ migrateOwner(creditDb, oldOwner, newOwner);
2799
+ return reply.send({ ok: true, migrated: true });
2800
+ });
2801
+ });
2802
+ }
2803
+
2804
+ // src/hub-agent/executor.ts
2805
+ var HubAgentExecutor = class {
2806
+ constructor(registryDb, creditDb) {
2807
+ this.registryDb = registryDb;
2808
+ this.creditDb = creditDb;
2809
+ initJobQueue(this.registryDb);
2810
+ }
2811
+ /**
2812
+ * Execute a skill on a Hub Agent.
2813
+ *
2814
+ * @param agentId - The Hub Agent ID.
2815
+ * @param skillId - The skill_id to execute from the agent's routing table.
2816
+ * @param params - Input parameters for the skill.
2817
+ * @param requesterOwner - Optional requester identifier for credit escrow.
2818
+ * @returns ExecutionResult with success status, result/error, and latency_ms.
2819
+ */
2820
+ async execute(agentId, skillId, params, requesterOwner) {
2821
+ const startTime = Date.now();
2822
+ const agent = getHubAgent(this.registryDb, agentId);
2823
+ if (!agent) {
2824
+ return { success: false, error: "Hub Agent not found", latency_ms: Date.now() - startTime };
2825
+ }
2826
+ if (agent.status === "paused") {
2827
+ return { success: false, error: "Hub Agent is paused", latency_ms: Date.now() - startTime };
2828
+ }
2829
+ const route = agent.skill_routes.find((r) => r.skill_id === skillId);
2830
+ if (!route) {
2831
+ return { success: false, error: "Skill not found in routing table", latency_ms: Date.now() - startTime };
2832
+ }
2833
+ switch (route.mode) {
2834
+ case "relay":
2835
+ return this.executeRelay(route, agent, params, requesterOwner, startTime);
2836
+ case "queue":
2837
+ return this.executeQueue(route, agent, params, requesterOwner, startTime);
2838
+ case "direct_api":
2839
+ return this.executeDirectApi(route, agent, params, requesterOwner, startTime);
2840
+ }
2841
+ }
2842
+ /**
2843
+ * Relay mode: If the target relay agent is offline, queue the job.
2844
+ * If online, still queue (actual dispatch happens via relay bridge).
2845
+ */
2846
+ async executeRelay(route, agent, params, requesterOwner, startTime) {
2847
+ const relayOwner = route.config.relay_owner;
2848
+ if (this.isRelayOwnerOnline(relayOwner)) {
2849
+ return { success: false, error: "relay mode requires connected session agent", latency_ms: 0 };
2850
+ }
2851
+ return this.queueJob(agent, route.skill_id, params, requesterOwner, relayOwner, startTime);
2852
+ }
2853
+ /**
2854
+ * Queue mode: Always queue the job for later dispatch.
2855
+ */
2856
+ async executeQueue(route, agent, params, requesterOwner, startTime) {
2857
+ const relayOwner = route.config.relay_owner;
2858
+ return this.queueJob(agent, route.skill_id, params, requesterOwner, relayOwner, startTime);
2859
+ }
2860
+ /**
2861
+ * Queue a job with optional credit escrow.
2862
+ */
2863
+ queueJob(agent, skillId, params, requesterOwner, relayOwner, startTime) {
2864
+ const cardId = agent.agent_id.padEnd(32, "0").replace(/^(.{8})(.{4})(.{4})(.{4})(.{12}).*$/, "$1-$2-$3-$4-$5");
2865
+ let escrowId;
2866
+ if (requesterOwner) {
2867
+ const price = lookupCardPrice(this.registryDb, cardId, skillId);
2868
+ if (price !== null && price > 0) {
2869
+ escrowId = holdEscrow(this.creditDb, requesterOwner, price, cardId);
2870
+ }
2871
+ }
2872
+ const job = insertJob(this.registryDb, {
2873
+ hub_agent_id: agent.agent_id,
2874
+ skill_id: skillId,
2875
+ requester_owner: requesterOwner ?? "anonymous",
2876
+ params,
2877
+ escrow_id: escrowId,
2878
+ relay_owner: relayOwner
2879
+ });
2880
+ return {
2881
+ success: true,
2882
+ result: { queued: true, job_id: job.id },
2883
+ latency_ms: Date.now() - startTime
2884
+ };
2885
+ }
2886
+ /**
2887
+ * Check if a relay owner has any online cards in the registry.
2888
+ *
2889
+ * @param owner - The relay owner identifier.
2890
+ * @returns true if any card for this owner is online.
2891
+ */
2892
+ isRelayOwnerOnline(owner) {
2893
+ const rows = this.registryDb.prepare(
2894
+ "SELECT data FROM capability_cards WHERE owner = ?"
2895
+ ).all(owner);
2896
+ for (const row of rows) {
2897
+ try {
2898
+ const card = JSON.parse(row.data);
2899
+ const availability = card.availability;
2900
+ if (availability?.online === true) {
2901
+ return true;
2902
+ }
2903
+ } catch {
2904
+ }
2905
+ }
2906
+ return false;
2907
+ }
2908
+ /**
2909
+ * Execute a direct_api skill route via ApiExecutor.
2910
+ * Handles secret injection and credit escrow.
2911
+ */
2912
+ async executeDirectApi(route, agent, params, requesterOwner, startTime) {
2913
+ const config = this.injectSecrets(route.config, agent.secrets);
2914
+ const pricing = route.config.pricing;
2915
+ const creditsPerCall = pricing?.credits_per_call ?? 0;
2916
+ let escrowId;
2917
+ if (requesterOwner && creditsPerCall > 0) {
2918
+ const cardId = agent.agent_id.padEnd(32, "0").replace(/^(.{8})(.{4})(.{4})(.{4})(.{12}).*$/, "$1-$2-$3-$4-$5");
2919
+ escrowId = holdEscrow(this.creditDb, requesterOwner, creditsPerCall, cardId);
2920
+ }
2921
+ try {
2922
+ const apiExecutor = new ApiExecutor();
2923
+ const modeResult = await apiExecutor.execute(config, params);
2924
+ const result = {
2925
+ ...modeResult,
2926
+ latency_ms: Date.now() - startTime
2927
+ };
2928
+ if (escrowId) {
2929
+ if (result.success) {
2930
+ settleEscrow(this.creditDb, escrowId, agent.agent_id);
2931
+ } else {
2932
+ releaseEscrow(this.creditDb, escrowId);
2933
+ }
2934
+ }
2935
+ return result;
2936
+ } catch (err) {
2937
+ if (escrowId) {
2938
+ releaseEscrow(this.creditDb, escrowId);
2939
+ }
2940
+ const message = err instanceof Error ? err.message : String(err);
2941
+ return {
2942
+ success: false,
2943
+ error: message,
2944
+ latency_ms: Date.now() - startTime
2945
+ };
2946
+ }
2947
+ }
2948
+ /**
2949
+ * Injects decrypted secrets into the API skill config's auth field.
2950
+ * If secrets contain 'api_key' and auth type is 'bearer', replaces the token.
2951
+ * If secrets contain 'api_key' and auth type is 'apikey', replaces the key.
2952
+ */
2953
+ injectSecrets(config, secrets) {
2954
+ if (!secrets || Object.keys(secrets).length === 0) {
2955
+ return config;
2956
+ }
2957
+ const injected = JSON.parse(JSON.stringify(config));
2958
+ const apiKey = secrets.api_key ?? secrets.API_KEY;
2959
+ if (apiKey && injected.auth) {
2960
+ switch (injected.auth.type) {
2961
+ case "bearer":
2962
+ injected.auth.token = apiKey;
2963
+ break;
2964
+ case "apikey":
2965
+ injected.auth.key = apiKey;
2966
+ break;
2967
+ }
2968
+ }
2969
+ return injected;
2970
+ }
2971
+ };
2972
+
2973
+ // src/hub-agent/types.ts
2974
+ import { z as z4 } from "zod";
2975
+ var SkillRouteSchema = z4.discriminatedUnion("mode", [
2976
+ z4.object({
2977
+ skill_id: z4.string().min(1),
2978
+ mode: z4.literal("direct_api"),
2979
+ config: ApiSkillConfigSchema
2980
+ }),
2981
+ z4.object({
2982
+ skill_id: z4.string().min(1),
2983
+ mode: z4.literal("relay"),
2984
+ config: z4.object({ relay_owner: z4.string().min(1) })
2985
+ }),
2986
+ z4.object({
2987
+ skill_id: z4.string().min(1),
2988
+ mode: z4.literal("queue"),
2989
+ config: z4.object({ relay_owner: z4.string().min(1) }).passthrough()
2990
+ })
2991
+ ]);
2992
+ var HubAgentSchema = z4.object({
2993
+ agent_id: z4.string().min(1),
2994
+ name: z4.string().min(1),
2995
+ owner_public_key: z4.string().min(1),
2996
+ public_key: z4.string().min(1),
2997
+ skill_routes: z4.array(SkillRouteSchema),
2998
+ status: z4.enum(["active", "paused"]),
2999
+ created_at: z4.string(),
3000
+ updated_at: z4.string()
3001
+ });
3002
+ var CreateAgentRequestSchema = z4.object({
3003
+ name: z4.string().min(1),
3004
+ skill_routes: z4.array(SkillRouteSchema),
3005
+ secrets: z4.record(z4.string()).optional()
3006
+ });
3007
+
3008
+ // src/hub-agent/routes.ts
3009
+ function buildCapabilityCard(agentId, name, publicKey, skillRoutes) {
3010
+ const now = (/* @__PURE__ */ new Date()).toISOString();
3011
+ const skills = skillRoutes.map((route) => ({
3012
+ id: route.skill_id,
3013
+ name: route.mode === "direct_api" ? route.config.name : route.skill_id,
3014
+ description: route.mode === "direct_api" ? `API skill: ${route.config.name}` : `${route.mode} skill: ${route.skill_id}`,
3015
+ level: 1,
3016
+ inputs: [],
3017
+ outputs: [],
3018
+ pricing: route.mode === "direct_api" ? route.config.pricing : { credits_per_call: 10 }
3019
+ }));
3020
+ if (skills.length === 0) {
3021
+ skills.push({
3022
+ id: "default",
3023
+ name,
3024
+ description: `Hub Agent: ${name}`,
3025
+ level: 1,
3026
+ inputs: [],
3027
+ outputs: [],
3028
+ pricing: { credits_per_call: 10 }
3029
+ });
3030
+ }
3031
+ return {
3032
+ spec_version: "2.0",
3033
+ id: agentId.padEnd(32, "0").replace(/^(.{8})(.{4})(.{4})(.{4})(.{12}).*$/, "$1-$2-$3-$4-$5"),
3034
+ owner: publicKey.slice(0, 16),
3035
+ agent_name: name,
3036
+ skills,
3037
+ availability: { online: true },
3038
+ created_at: now,
3039
+ updated_at: now
3040
+ };
3041
+ }
3042
+ function upsertCardRaw(db, cardData, owner) {
3043
+ const parsed = AnyCardSchema.safeParse(cardData);
3044
+ if (!parsed.success) {
3045
+ throw new AgentBnBError(
3046
+ `Card validation failed: ${parsed.error.message}`,
3047
+ "VALIDATION_ERROR"
3048
+ );
3049
+ }
3050
+ const card = parsed.data;
3051
+ const cardId = card.id;
3052
+ const now = (/* @__PURE__ */ new Date()).toISOString();
3053
+ const existing = db.prepare("SELECT id FROM capability_cards WHERE id = ?").get(cardId);
3054
+ if (existing) {
3055
+ db.prepare("UPDATE capability_cards SET data = ?, updated_at = ? WHERE id = ?").run(JSON.stringify(card), now, cardId);
3056
+ } else {
3057
+ db.prepare("INSERT INTO capability_cards (id, owner, data, created_at, updated_at) VALUES (?, ?, ?, ?, ?)").run(cardId, owner, JSON.stringify(card), now, now);
3058
+ }
3059
+ return cardId;
3060
+ }
3061
+ function sanitizeAgent(agent) {
3062
+ const { secrets, private_key_enc, ...safe } = agent;
3063
+ if (secrets && typeof secrets === "object") {
3064
+ return { ...safe, secret_keys: Object.keys(secrets) };
3065
+ }
3066
+ return safe;
3067
+ }
3068
+ async function hubAgentRoutesPlugin(fastify, options) {
3069
+ const { registryDb, creditDb } = options;
3070
+ initHubAgentTable(registryDb);
3071
+ initJobQueue(registryDb);
3072
+ fastify.post("/api/hub-agents", {
3073
+ schema: {
3074
+ tags: ["hub-agents"],
3075
+ summary: "Create a new Hub Agent with Ed25519 identity",
3076
+ body: {
3077
+ type: "object",
3078
+ required: ["name"],
3079
+ properties: {
3080
+ name: { type: "string", minLength: 1 },
3081
+ skill_routes: { type: "array" },
3082
+ secrets: { type: "object" }
3083
+ }
3084
+ },
3085
+ response: {
3086
+ 201: {
3087
+ type: "object",
3088
+ properties: {
3089
+ agent_id: { type: "string" },
3090
+ name: { type: "string" },
3091
+ public_key: { type: "string" },
3092
+ skill_routes: { type: "array" },
3093
+ status: { type: "string" },
3094
+ created_at: { type: "string" },
3095
+ updated_at: { type: "string" }
3096
+ }
3097
+ },
3098
+ 400: {
3099
+ type: "object",
3100
+ properties: { error: { type: "string" } }
3101
+ }
3102
+ }
3103
+ }
3104
+ }, async (request, reply) => {
3105
+ const parseResult = CreateAgentRequestSchema.safeParse(request.body);
3106
+ if (!parseResult.success) {
3107
+ return reply.code(400).send({ error: parseResult.error.message });
3108
+ }
3109
+ const req = parseResult.data;
3110
+ const ownerPublicKey = "hub-server";
3111
+ try {
3112
+ const agent = createHubAgent(registryDb, req, ownerPublicKey);
3113
+ bootstrapAgent(creditDb, agent.agent_id, 50);
3114
+ const cardData = buildCapabilityCard(agent.agent_id, agent.name, agent.public_key, agent.skill_routes);
3115
+ try {
3116
+ upsertCardRaw(registryDb, cardData, agent.agent_id);
3117
+ } catch {
3118
+ }
3119
+ return reply.code(201).send(sanitizeAgent(agent));
3120
+ } catch (err) {
3121
+ if (err instanceof AgentBnBError) {
3122
+ return reply.code(400).send({ error: err.message });
3123
+ }
3124
+ throw err;
3125
+ }
3126
+ });
3127
+ fastify.get("/api/hub-agents", {
3128
+ schema: {
3129
+ tags: ["hub-agents"],
3130
+ summary: "List all Hub Agents",
3131
+ response: {
3132
+ 200: {
3133
+ type: "object",
3134
+ properties: {
3135
+ agents: { type: "array" }
3136
+ }
3137
+ }
3138
+ }
3139
+ }
3140
+ }, async (_request, reply) => {
3141
+ const agents = listHubAgents(registryDb);
3142
+ return reply.send({ agents });
3143
+ });
3144
+ fastify.get("/api/hub-agents/:id", {
3145
+ schema: {
3146
+ tags: ["hub-agents"],
3147
+ summary: "Get a single Hub Agent by ID",
3148
+ params: {
3149
+ type: "object",
3150
+ properties: { id: { type: "string" } },
3151
+ required: ["id"]
3152
+ },
3153
+ response: {
3154
+ 200: {
3155
+ type: "object",
3156
+ properties: {
3157
+ agent_id: { type: "string" },
3158
+ name: { type: "string" },
3159
+ public_key: { type: "string" },
3160
+ skill_routes: { type: "array" },
3161
+ status: { type: "string" },
3162
+ secret_keys: { type: "array", items: { type: "string" } }
3163
+ }
3164
+ },
3165
+ 404: {
3166
+ type: "object",
3167
+ properties: { error: { type: "string" } }
3168
+ }
3169
+ }
3170
+ }
3171
+ }, async (request, reply) => {
3172
+ const { id } = request.params;
3173
+ const agent = getHubAgent(registryDb, id);
3174
+ if (!agent) {
3175
+ return reply.code(404).send({ error: "Hub Agent not found" });
3176
+ }
3177
+ return reply.send(sanitizeAgent(agent));
3178
+ });
3179
+ fastify.put("/api/hub-agents/:id", {
3180
+ schema: {
3181
+ tags: ["hub-agents"],
3182
+ summary: "Update a Hub Agent",
3183
+ params: {
3184
+ type: "object",
3185
+ properties: { id: { type: "string" } },
3186
+ required: ["id"]
3187
+ },
3188
+ body: {
3189
+ type: "object",
3190
+ properties: {
3191
+ name: { type: "string" },
3192
+ skill_routes: { type: "array" },
3193
+ secrets: { type: "object" }
3194
+ }
3195
+ },
3196
+ response: {
3197
+ 200: {
3198
+ type: "object",
3199
+ properties: {
3200
+ agent_id: { type: "string" },
3201
+ name: { type: "string" },
3202
+ skill_routes: { type: "array" },
3203
+ status: { type: "string" }
3204
+ }
3205
+ },
3206
+ 404: {
3207
+ type: "object",
3208
+ properties: { error: { type: "string" } }
3209
+ }
3210
+ }
3211
+ }
3212
+ }, async (request, reply) => {
3213
+ const { id } = request.params;
3214
+ const body = request.body;
3215
+ const updates = {};
3216
+ if (typeof body.name === "string") updates.name = body.name;
3217
+ if (Array.isArray(body.skill_routes)) updates.skill_routes = body.skill_routes;
3218
+ if (body.secrets && typeof body.secrets === "object") updates.secrets = body.secrets;
3219
+ const agent = updateHubAgent(registryDb, id, updates);
3220
+ if (!agent) {
3221
+ return reply.code(404).send({ error: "Hub Agent not found" });
3222
+ }
3223
+ if (updates.skill_routes) {
3224
+ const cardData = buildCapabilityCard(agent.agent_id, agent.name, agent.public_key, agent.skill_routes);
3225
+ try {
3226
+ upsertCardRaw(registryDb, cardData, agent.agent_id);
3227
+ } catch {
3228
+ }
3229
+ }
3230
+ return reply.send(sanitizeAgent(agent));
3231
+ });
3232
+ fastify.delete("/api/hub-agents/:id", {
3233
+ schema: {
3234
+ tags: ["hub-agents"],
3235
+ summary: "Delete a Hub Agent",
3236
+ params: {
3237
+ type: "object",
3238
+ properties: { id: { type: "string" } },
3239
+ required: ["id"]
3240
+ },
3241
+ response: {
3242
+ 200: {
3243
+ type: "object",
3244
+ properties: { ok: { type: "boolean" } }
3245
+ },
3246
+ 404: {
3247
+ type: "object",
3248
+ properties: { error: { type: "string" } }
3249
+ }
3250
+ }
3251
+ }
3252
+ }, async (request, reply) => {
3253
+ const { id } = request.params;
3254
+ const agent = getHubAgent(registryDb, id);
3255
+ if (!agent) {
3256
+ return reply.code(404).send({ error: "Hub Agent not found" });
3257
+ }
3258
+ deleteHubAgent(registryDb, id);
3259
+ const cardId = id.padEnd(32, "0").replace(/^(.{8})(.{4})(.{4})(.{4})(.{12}).*$/, "$1-$2-$3-$4-$5");
3260
+ try {
3261
+ registryDb.prepare("DELETE FROM capability_cards WHERE id = ?").run(cardId);
3262
+ } catch {
3263
+ }
3264
+ return reply.send({ ok: true });
3265
+ });
3266
+ fastify.post("/api/hub-agents/:id/execute", {
3267
+ schema: {
3268
+ tags: ["hub-agents"],
3269
+ summary: "Execute a skill on a Hub Agent",
3270
+ params: {
3271
+ type: "object",
3272
+ properties: { id: { type: "string" } },
3273
+ required: ["id"]
3274
+ },
3275
+ body: {
3276
+ type: "object",
3277
+ required: ["skill_id"],
3278
+ properties: {
3279
+ skill_id: { type: "string" },
3280
+ params: { type: "object" },
3281
+ requester_owner: { type: "string" }
3282
+ }
3283
+ }
3284
+ }
3285
+ }, async (request, reply) => {
3286
+ const { id } = request.params;
3287
+ const body = request.body;
3288
+ const executor = new HubAgentExecutor(registryDb, creditDb);
3289
+ const result = await executor.execute(
3290
+ id,
3291
+ body.skill_id,
3292
+ body.params ?? {},
3293
+ body.requester_owner
3294
+ );
3295
+ if (!result.success && result.error === "Hub Agent not found") {
3296
+ return reply.code(404).send(result);
3297
+ }
3298
+ if (!result.success) {
3299
+ return reply.code(400).send(result);
3300
+ }
3301
+ return reply.send(result);
3302
+ });
3303
+ fastify.get("/api/hub-agents/:id/jobs", {
3304
+ schema: {
3305
+ tags: ["hub-agents"],
3306
+ summary: "List jobs for a Hub Agent",
3307
+ params: {
3308
+ type: "object",
3309
+ properties: { id: { type: "string" } },
3310
+ required: ["id"]
3311
+ },
3312
+ querystring: {
3313
+ type: "object",
3314
+ properties: {
3315
+ status: { type: "string", enum: ["queued", "dispatched", "completed", "failed"] }
3316
+ }
3317
+ },
3318
+ response: {
3319
+ 200: {
3320
+ type: "object",
3321
+ properties: {
3322
+ jobs: { type: "array" }
3323
+ }
3324
+ }
3325
+ }
3326
+ }
3327
+ }, async (request, reply) => {
3328
+ const { id } = request.params;
3329
+ const { status } = request.query ?? {};
3330
+ const jobs = listJobs(registryDb, id, status);
3331
+ return reply.send({ jobs });
3332
+ });
3333
+ fastify.get("/api/hub-agents/:id/jobs/:jobId", {
3334
+ schema: {
3335
+ tags: ["hub-agents"],
3336
+ summary: "Get a single job by ID",
3337
+ params: {
3338
+ type: "object",
3339
+ properties: {
3340
+ id: { type: "string" },
3341
+ jobId: { type: "string" }
3342
+ },
3343
+ required: ["id", "jobId"]
3344
+ },
3345
+ response: {
3346
+ 200: {
3347
+ type: "object",
3348
+ properties: {
3349
+ id: { type: "string" },
3350
+ hub_agent_id: { type: "string" },
3351
+ skill_id: { type: "string" },
3352
+ status: { type: "string" }
3353
+ }
3354
+ },
3355
+ 404: {
3356
+ type: "object",
3357
+ properties: { error: { type: "string" } }
3358
+ }
3359
+ }
3360
+ }
3361
+ }, async (request, reply) => {
3362
+ const { jobId } = request.params;
3363
+ const job = getJob(registryDb, jobId);
3364
+ if (!job) {
3365
+ return reply.code(404).send({ error: "Job not found" });
3366
+ }
3367
+ return reply.send(job);
3368
+ });
3369
+ }
3370
+
3371
+ // src/registry/openapi-gpt-actions.ts
3372
+ function convertToGptActions(openapiSpec, serverUrl) {
3373
+ const spec = JSON.parse(JSON.stringify(openapiSpec));
3374
+ spec.servers = [{ url: serverUrl }];
3375
+ const paths = spec.paths;
3376
+ if (paths) {
3377
+ const filteredPaths = {};
3378
+ for (const [path, methods] of Object.entries(paths)) {
3379
+ if (path.startsWith("/me") || path.startsWith("/draft") || path.startsWith("/docs") || path.startsWith("/ws") || path.startsWith("/api/credits")) {
3380
+ continue;
3381
+ }
3382
+ const filteredMethods = {};
3383
+ for (const [method, operation] of Object.entries(methods)) {
3384
+ if (method === "get" || method === "post") {
3385
+ const op = operation;
3386
+ if (!op.operationId) {
3387
+ op.operationId = deriveOperationId(method, path);
3388
+ }
3389
+ delete op.security;
3390
+ filteredMethods[method] = op;
3391
+ }
3392
+ }
3393
+ if (Object.keys(filteredMethods).length > 0) {
3394
+ filteredPaths[path] = filteredMethods;
3395
+ }
3396
+ }
3397
+ spec.paths = filteredPaths;
3398
+ }
3399
+ const components = spec.components;
3400
+ if (components) {
3401
+ delete components.securitySchemes;
3402
+ }
3403
+ const usedTags = /* @__PURE__ */ new Set();
3404
+ if (spec.paths) {
3405
+ for (const methods of Object.values(spec.paths)) {
3406
+ for (const op of Object.values(methods)) {
3407
+ const operation = op;
3408
+ if (Array.isArray(operation.tags)) {
3409
+ for (const tag of operation.tags) {
3410
+ usedTags.add(tag);
3411
+ }
3412
+ }
3413
+ }
3414
+ }
3415
+ }
3416
+ if (Array.isArray(spec.tags)) {
3417
+ spec.tags = spec.tags.filter((t) => usedTags.has(t.name));
3418
+ }
3419
+ return spec;
3420
+ }
3421
+ function deriveOperationId(method, path) {
3422
+ const segments = path.split("/").filter((s) => s.length > 0).map((s) => {
3423
+ if (s.startsWith("{") || s.startsWith(":")) {
3424
+ const paramName = s.replace(/[{}:]/g, "");
3425
+ return "By" + paramName.charAt(0).toUpperCase() + paramName.slice(1);
3426
+ }
3427
+ return s.split("-").map((part, i) => i === 0 ? part.charAt(0).toUpperCase() + part.slice(1) : part.charAt(0).toUpperCase() + part.slice(1)).join("");
3428
+ });
3429
+ return method + segments.join("");
3430
+ }
3431
+
2111
3432
  // src/registry/server.ts
2112
3433
  function stripInternal(card) {
2113
3434
  const { _internal: _, ...publicCard } = card;
@@ -2116,25 +3437,76 @@ function stripInternal(card) {
2116
3437
  function createRegistryServer(opts) {
2117
3438
  const { registryDb: db, silent = false } = opts;
2118
3439
  const server = Fastify2({ logger: !silent });
3440
+ void server.register(swagger, {
3441
+ openapi: {
3442
+ openapi: "3.0.3",
3443
+ info: {
3444
+ title: "AgentBnB Registry API",
3445
+ description: "P2P Agent Capability Sharing Protocol \u2014 discover, publish, and exchange agent capabilities",
3446
+ version: "3.1.6"
3447
+ },
3448
+ servers: [{ url: "/", description: "Registry server" }],
3449
+ tags: [
3450
+ { name: "cards", description: "Capability card CRUD" },
3451
+ { name: "credits", description: "Credit hold/settle/release (Ed25519 auth required)" },
3452
+ { name: "agents", description: "Agent profiles and reputation" },
3453
+ { name: "identity", description: "Agent identity and guarantor registration" },
3454
+ { name: "owner", description: "Owner-only endpoints (Bearer auth required)" },
3455
+ { name: "system", description: "Health and stats" },
3456
+ { name: "pricing", description: "Market pricing statistics" }
3457
+ ],
3458
+ components: {
3459
+ securitySchemes: {
3460
+ bearerAuth: { type: "http", scheme: "bearer" },
3461
+ ed25519Auth: {
3462
+ type: "apiKey",
3463
+ in: "header",
3464
+ name: "X-Agent-PublicKey",
3465
+ description: "Ed25519 public key (hex). Also requires X-Agent-Signature and X-Agent-Timestamp headers."
3466
+ }
3467
+ }
3468
+ }
3469
+ }
3470
+ });
3471
+ void server.register(swaggerUi, {
3472
+ routePrefix: "/docs",
3473
+ uiConfig: { docExpansion: "list", deepLinking: true }
3474
+ });
2119
3475
  void server.register(cors, {
2120
3476
  origin: true,
2121
3477
  methods: ["GET", "POST", "PATCH", "OPTIONS"],
2122
- allowedHeaders: ["Content-Type", "Authorization"]
3478
+ allowedHeaders: ["Content-Type", "Authorization", "X-Agent-PublicKey", "X-Agent-Signature", "X-Agent-Timestamp"]
2123
3479
  });
2124
3480
  void server.register(fastifyWebsocket);
2125
3481
  let relayState = null;
2126
3482
  if (opts.creditDb) {
2127
- relayState = registerWebSocketRelay(server, db);
3483
+ relayState = registerWebSocketRelay(server, db, opts.creditDb);
3484
+ }
3485
+ if (opts.creditDb) {
3486
+ void server.register(creditRoutesPlugin, { creditDb: opts.creditDb });
3487
+ }
3488
+ if (opts.creditDb) {
3489
+ void server.register(hubAgentRoutesPlugin, { registryDb: db, creditDb: opts.creditDb });
3490
+ if (relayState?.setOnAgentOnline && relayState.getConnections && relayState.getPendingRequests && relayState.sendMessage) {
3491
+ const bridge = createRelayBridge({
3492
+ registryDb: db,
3493
+ creditDb: opts.creditDb,
3494
+ sendMessage: relayState.sendMessage,
3495
+ pendingRequests: relayState.getPendingRequests(),
3496
+ connections: relayState.getConnections()
3497
+ });
3498
+ relayState.setOnAgentOnline(bridge.onAgentOnline);
3499
+ }
2128
3500
  }
2129
3501
  const __filename = fileURLToPath(import.meta.url);
2130
3502
  const __dirname = dirname(__filename);
2131
3503
  const hubDistCandidates = [
2132
- join3(__dirname, "../../hub/dist"),
3504
+ join2(__dirname, "../../hub/dist"),
2133
3505
  // When running from dist/registry/server.js
2134
- join3(__dirname, "../../../hub/dist")
3506
+ join2(__dirname, "../../../hub/dist")
2135
3507
  // Fallback for alternative layouts
2136
3508
  ];
2137
- const hubDistDir = hubDistCandidates.find((p) => existsSync4(p));
3509
+ const hubDistDir = hubDistCandidates.find((p) => existsSync3(p));
2138
3510
  if (hubDistDir) {
2139
3511
  void server.register(fastifyStatic, {
2140
3512
  root: hubDistDir,
@@ -2153,44 +3525,77 @@ function createRegistryServer(opts) {
2153
3525
  return reply.code(404).send({ error: "Not found" });
2154
3526
  });
2155
3527
  }
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(`
3528
+ void server.register(async (api) => {
3529
+ api.get("/health", {
3530
+ schema: {
3531
+ tags: ["system"],
3532
+ summary: "Liveness probe",
3533
+ response: { 200: { type: "object", properties: { status: { type: "string" } } } }
3534
+ }
3535
+ }, async (_request, reply) => {
3536
+ return reply.send({ status: "ok" });
3537
+ });
3538
+ api.get("/cards", {
3539
+ schema: {
3540
+ tags: ["cards"],
3541
+ summary: "List and search capability cards",
3542
+ querystring: {
3543
+ type: "object",
3544
+ properties: {
3545
+ q: { type: "string", description: "Full-text search query" },
3546
+ level: { type: "integer", enum: [1, 2, 3], description: "Capability level filter" },
3547
+ online: { type: "string", enum: ["true", "false"], description: "Availability filter" },
3548
+ tag: { type: "string", description: "Filter by metadata tag" },
3549
+ min_success_rate: { type: "number", description: "Minimum success rate (0-1)" },
3550
+ max_latency_ms: { type: "number", description: "Maximum average latency in ms" },
3551
+ sort: { type: "string", enum: ["popular", "rated", "success_rate", "cheapest", "newest", "latency"], description: "Sort order" },
3552
+ limit: { type: "integer", default: 20, description: "Max items per page (max 100)" },
3553
+ offset: { type: "integer", default: 0, description: "Pagination offset" }
3554
+ }
3555
+ },
3556
+ response: {
3557
+ 200: {
3558
+ type: "object",
3559
+ properties: {
3560
+ total: { type: "integer" },
3561
+ limit: { type: "integer" },
3562
+ offset: { type: "integer" },
3563
+ items: { type: "array" },
3564
+ uses_this_week: { type: "object", additionalProperties: { type: "number" } }
3565
+ }
3566
+ }
3567
+ }
3568
+ }
3569
+ }, async (request, reply) => {
3570
+ const query = request.query;
3571
+ const q = query.q?.trim() ?? "";
3572
+ const levelRaw = query.level !== void 0 ? parseInt(query.level, 10) : void 0;
3573
+ const level = levelRaw === 1 || levelRaw === 2 || levelRaw === 3 ? levelRaw : void 0;
3574
+ const onlineRaw = query.online;
3575
+ const online = onlineRaw === "true" ? true : onlineRaw === "false" ? false : void 0;
3576
+ const tag = query.tag?.trim();
3577
+ const minSuccessRate = query.min_success_rate !== void 0 ? parseFloat(query.min_success_rate) : void 0;
3578
+ const maxLatencyMs = query.max_latency_ms !== void 0 ? parseFloat(query.max_latency_ms) : void 0;
3579
+ const sort = query.sort;
3580
+ const rawLimit = query.limit !== void 0 ? parseInt(query.limit, 10) : 20;
3581
+ const limit = Math.min(isNaN(rawLimit) || rawLimit < 1 ? 20 : rawLimit, 100);
3582
+ const rawOffset = query.offset !== void 0 ? parseInt(query.offset, 10) : 0;
3583
+ const offset = isNaN(rawOffset) || rawOffset < 0 ? 0 : rawOffset;
3584
+ let cards;
3585
+ if (q.length > 0) {
3586
+ cards = searchCards(db, q, { level, online });
3587
+ } else {
3588
+ cards = filterCards(db, { level, online });
3589
+ }
3590
+ if (tag !== void 0 && tag.length > 0) {
3591
+ cards = cards.filter((c) => c.metadata?.tags?.includes(tag));
3592
+ }
3593
+ if (maxLatencyMs !== void 0 && !isNaN(maxLatencyMs)) {
3594
+ cards = cards.filter(
3595
+ (c) => (c.metadata?.avg_latency_ms ?? Infinity) <= maxLatencyMs
3596
+ );
3597
+ }
3598
+ const usesStmt = db.prepare(`
2194
3599
  SELECT card_id, skill_id, COUNT(*) as cnt
2195
3600
  FROM request_log
2196
3601
  WHERE status = 'success'
@@ -2198,57 +3603,118 @@ function createRegistryServer(opts) {
2198
3603
  AND (action_type IS NULL OR action_type = 'auto_share')
2199
3604
  GROUP BY card_id, skill_id
2200
3605
  `);
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;
3606
+ const usesRows = usesStmt.all();
3607
+ const usesMap = /* @__PURE__ */ new Map();
3608
+ for (const row of usesRows) {
3609
+ usesMap.set(row.card_id, (usesMap.get(row.card_id) ?? 0) + row.cnt);
3610
+ if (row.skill_id) {
3611
+ usesMap.set(row.skill_id, (usesMap.get(row.skill_id) ?? 0) + row.cnt);
3612
+ }
3613
+ }
3614
+ const ownerTrustMap = /* @__PURE__ */ new Map();
3615
+ const uniqueOwners = [...new Set(cards.map((c) => c.owner))];
3616
+ if (uniqueOwners.length > 0) {
3617
+ const placeholders = uniqueOwners.map(() => "?").join(",");
3618
+ const trustStmt = db.prepare(`
3619
+ SELECT cc.owner,
3620
+ COUNT(rl.id) as total_exec,
3621
+ SUM(CASE WHEN rl.status IN ('success','failure','timeout','refunded') THEN 1 ELSE 0 END) as terminal_exec,
3622
+ SUM(CASE WHEN rl.status = 'success' THEN 1 ELSE 0 END) as success_exec,
3623
+ AVG(CASE WHEN rl.status = 'success' THEN rl.latency_ms END) as avg_latency
3624
+ FROM capability_cards cc
3625
+ LEFT JOIN request_log rl ON rl.card_id = cc.id AND rl.action_type IS NULL
3626
+ WHERE cc.owner IN (${placeholders})
3627
+ GROUP BY cc.owner
3628
+ `);
3629
+ const trustRows = trustStmt.all(...uniqueOwners);
3630
+ for (const row of trustRows) {
3631
+ const terminalExec = row.terminal_exec ?? 0;
3632
+ const successExec = row.success_exec ?? 0;
3633
+ const successRate = terminalExec > 0 ? successExec / terminalExec : 0;
3634
+ let tier = 0;
3635
+ if (row.total_exec > 10) tier = 1;
3636
+ if (row.total_exec > 50 && successRate >= 0.85) tier = 2;
3637
+ ownerTrustMap.set(row.owner, {
3638
+ performance_tier: tier,
3639
+ authority_source: "self",
3640
+ // Phase 1: all self-declared
3641
+ success_rate: successRate,
3642
+ avg_latency_ms: Math.round(row.avg_latency ?? 0),
3643
+ terminal_exec: terminalExec
3644
+ });
3645
+ }
3646
+ }
3647
+ if (minSuccessRate !== void 0 && !isNaN(minSuccessRate)) {
3648
+ cards = cards.filter((c) => {
3649
+ const trust = ownerTrustMap.get(c.owner);
3650
+ if (!trust || trust.terminal_exec === 0) return false;
3651
+ return trust.success_rate >= minSuccessRate;
3652
+ });
3653
+ }
3654
+ if (sort === "popular") {
3655
+ cards = [...cards].sort((a, b) => {
3656
+ const aUses = usesMap.get(a.id) ?? 0;
3657
+ const bUses = usesMap.get(b.id) ?? 0;
3658
+ return bUses - aUses;
3659
+ });
3660
+ } else if (sort === "rated" || sort === "success_rate") {
3661
+ cards = [...cards].sort((a, b) => {
3662
+ const aRate = a.metadata?.success_rate ?? -1;
3663
+ const bRate = b.metadata?.success_rate ?? -1;
3664
+ return bRate - aRate;
3665
+ });
3666
+ } else if (sort === "cheapest") {
3667
+ cards = [...cards].sort((a, b) => {
3668
+ return a.pricing.credits_per_call - b.pricing.credits_per_call;
3669
+ });
3670
+ } else if (sort === "newest") {
3671
+ const createdStmt = db.prepare("SELECT id, created_at FROM capability_cards");
3672
+ const createdRows = createdStmt.all();
3673
+ const createdMap = new Map(createdRows.map((r) => [r.id, r.created_at]));
3674
+ cards = [...cards].sort((a, b) => {
3675
+ const aDate = createdMap.get(a.id) ?? "";
3676
+ const bDate = createdMap.get(b.id) ?? "";
3677
+ return bDate.localeCompare(aDate);
3678
+ });
3679
+ } else if (sort === "latency") {
3680
+ cards = [...cards].sort((a, b) => {
3681
+ const aLatency = a.metadata?.avg_latency_ms ?? Infinity;
3682
+ const bLatency = b.metadata?.avg_latency_ms ?? Infinity;
3683
+ return aLatency - bLatency;
3684
+ });
3685
+ }
3686
+ const total = cards.length;
3687
+ const pagedCards = cards.slice(offset, offset + limit);
3688
+ const items = pagedCards.map((card) => {
3689
+ const trust = ownerTrustMap.get(card.owner);
3690
+ const stripped = stripInternal(card);
3691
+ return {
3692
+ ...stripped,
3693
+ performance_tier: trust?.performance_tier ?? 0,
3694
+ authority_source: trust?.authority_source ?? "self",
3695
+ // Enrich metadata with live execution-based success_rate if available
3696
+ metadata: trust && trust.terminal_exec > 0 ? {
3697
+ ...stripped.metadata,
3698
+ success_rate: trust.success_rate,
3699
+ avg_latency_ms: trust.avg_latency_ms || stripped.metadata?.avg_latency_ms
3700
+ } : stripped.metadata
3701
+ };
2239
3702
  });
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(`
3703
+ const usesThisWeek = {};
3704
+ for (const [key, count] of usesMap) {
3705
+ if (count > 0) usesThisWeek[key] = count;
3706
+ }
3707
+ const result = { total, limit, offset, items, uses_this_week: usesThisWeek };
3708
+ return reply.send(result);
3709
+ });
3710
+ api.get("/api/cards/trending", {
3711
+ schema: {
3712
+ tags: ["cards"],
3713
+ summary: "Top 10 trending skills by recent usage",
3714
+ response: { 200: { type: "object", properties: { items: { type: "array" } } } }
3715
+ }
3716
+ }, async (_request, reply) => {
3717
+ const trendingStmt = db.prepare(`
2252
3718
  SELECT rl.card_id, COUNT(*) as recent_requests
2253
3719
  FROM request_log rl
2254
3720
  WHERE rl.status = 'success'
@@ -2258,135 +3724,284 @@ function createRegistryServer(opts) {
2258
3724
  ORDER BY recent_requests DESC
2259
3725
  LIMIT 10
2260
3726
  `);
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
2287
- });
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
2296
- };
2297
- db.prepare(
2298
- `INSERT OR REPLACE INTO capability_cards (id, owner, data, created_at, updated_at)
3727
+ const trendingRows = trendingStmt.all();
3728
+ const items = trendingRows.map((row) => {
3729
+ const card = getCard(db, row.card_id);
3730
+ if (!card) return null;
3731
+ return { ...stripInternal(card), uses_this_week: row.recent_requests };
3732
+ }).filter((item) => item !== null);
3733
+ return reply.send({ items });
3734
+ });
3735
+ api.get("/api/pricing", {
3736
+ schema: {
3737
+ tags: ["pricing"],
3738
+ summary: "Aggregate pricing statistics for skills matching a query",
3739
+ querystring: {
3740
+ type: "object",
3741
+ properties: { q: { type: "string", description: "Search query (required)" } },
3742
+ required: ["q"]
3743
+ },
3744
+ response: {
3745
+ 200: {
3746
+ type: "object",
3747
+ properties: {
3748
+ query: { type: "string" },
3749
+ min: { type: "number" },
3750
+ max: { type: "number" },
3751
+ median: { type: "number" },
3752
+ mean: { type: "number" },
3753
+ count: { type: "integer" }
3754
+ }
3755
+ },
3756
+ 400: { type: "object", properties: { error: { type: "string" } } }
3757
+ }
3758
+ }
3759
+ }, async (request, reply) => {
3760
+ const query = request.query;
3761
+ const q = query.q?.trim();
3762
+ if (!q) {
3763
+ return reply.code(400).send({ error: "q parameter is required" });
3764
+ }
3765
+ const stats = getPricingStats(db, q);
3766
+ return reply.send({ query: q, ...stats });
3767
+ });
3768
+ api.get("/cards/:id", {
3769
+ schema: {
3770
+ tags: ["cards"],
3771
+ summary: "Get a capability card by ID",
3772
+ params: { type: "object", properties: { id: { type: "string" } }, required: ["id"] },
3773
+ response: {
3774
+ 200: { type: "object", additionalProperties: true },
3775
+ 404: { type: "object", properties: { error: { type: "string" } } }
3776
+ }
3777
+ }
3778
+ }, async (request, reply) => {
3779
+ const { id } = request.params;
3780
+ const card = getCard(db, id);
3781
+ if (!card) {
3782
+ return reply.code(404).send({ error: "Not found" });
3783
+ }
3784
+ return reply.send(stripInternal(card));
3785
+ });
3786
+ api.post("/cards", {
3787
+ schema: {
3788
+ tags: ["cards"],
3789
+ summary: "Publish a capability card",
3790
+ body: { type: "object", additionalProperties: true, description: "Capability card JSON (v1.0 or v2.0)" },
3791
+ response: {
3792
+ 201: { type: "object", properties: { ok: { type: "boolean" }, id: { type: "string" } } },
3793
+ 400: { type: "object", properties: { error: { type: "string" }, issues: { type: "array" } } }
3794
+ }
3795
+ }
3796
+ }, async (request, reply) => {
3797
+ const body = request.body;
3798
+ if (!body.spec_version) {
3799
+ body.spec_version = "1.0";
3800
+ }
3801
+ const result = AnyCardSchema.safeParse(body);
3802
+ if (!result.success) {
3803
+ return reply.code(400).send({
3804
+ error: "Card validation failed",
3805
+ issues: result.error.issues
3806
+ });
3807
+ }
3808
+ const card = result.data;
3809
+ const now = (/* @__PURE__ */ new Date()).toISOString();
3810
+ if (card.spec_version === "2.0") {
3811
+ const cardWithTimestamps = {
3812
+ ...card,
3813
+ created_at: card.created_at ?? now,
3814
+ updated_at: now
3815
+ };
3816
+ db.prepare(
3817
+ `INSERT OR REPLACE INTO capability_cards (id, owner, data, created_at, updated_at)
2299
3818
  VALUES (?, ?, ?, ?, ?)`
2300
- ).run(
2301
- cardWithTimestamps.id,
2302
- cardWithTimestamps.owner,
2303
- JSON.stringify(cardWithTimestamps),
2304
- cardWithTimestamps.created_at,
2305
- cardWithTimestamps.updated_at
2306
- );
2307
- } else {
2308
- try {
2309
- insertCard(db, card);
2310
- } catch (err) {
2311
- if (err instanceof AgentBnBError && err.code === "VALIDATION_ERROR") {
2312
- return reply.code(400).send({ error: err.message });
3819
+ ).run(
3820
+ cardWithTimestamps.id,
3821
+ cardWithTimestamps.owner,
3822
+ JSON.stringify(cardWithTimestamps),
3823
+ cardWithTimestamps.created_at,
3824
+ cardWithTimestamps.updated_at
3825
+ );
3826
+ } else {
3827
+ try {
3828
+ insertCard(db, card);
3829
+ } catch (err) {
3830
+ if (err instanceof AgentBnBError && err.code === "VALIDATION_ERROR") {
3831
+ return reply.code(400).send({ error: err.message });
3832
+ }
3833
+ throw err;
2313
3834
  }
2314
- throw err;
2315
3835
  }
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(`
3836
+ return reply.code(201).send({ ok: true, id: card.id });
3837
+ });
3838
+ api.delete("/cards/:id", {
3839
+ schema: {
3840
+ tags: ["cards"],
3841
+ summary: "Delete a capability card",
3842
+ params: { type: "object", properties: { id: { type: "string" } }, required: ["id"] },
3843
+ response: {
3844
+ 200: { type: "object", properties: { ok: { type: "boolean" }, id: { type: "string" } } },
3845
+ 404: { type: "object", properties: { error: { type: "string" } } }
3846
+ }
3847
+ }
3848
+ }, async (request, reply) => {
3849
+ const { id } = request.params;
3850
+ const card = getCard(db, id);
3851
+ if (!card) {
3852
+ return reply.code(404).send({ error: "Not found" });
3853
+ }
3854
+ db.prepare("DELETE FROM capability_cards WHERE id = ?").run(id);
3855
+ return reply.send({ ok: true, id });
3856
+ });
3857
+ api.get("/api/agents", {
3858
+ schema: {
3859
+ tags: ["agents"],
3860
+ summary: "List all agent profiles sorted by reputation",
3861
+ response: {
3862
+ 200: {
3863
+ type: "object",
3864
+ properties: { items: { type: "array" }, total: { type: "integer" } }
3865
+ }
3866
+ }
3867
+ }
3868
+ }, async (_request, reply) => {
3869
+ const allCards = listCards(db);
3870
+ const ownerMap = /* @__PURE__ */ new Map();
3871
+ for (const card of allCards) {
3872
+ const existing = ownerMap.get(card.owner) ?? [];
3873
+ existing.push(card);
3874
+ ownerMap.set(card.owner, existing);
3875
+ }
3876
+ const creditsStmt = db.prepare(`
2337
3877
  SELECT cc.owner,
2338
3878
  SUM(CASE WHEN rl.status = 'success' THEN rl.credits_charged ELSE 0 END) as credits_earned
2339
3879
  FROM capability_cards cc
2340
3880
  LEFT JOIN request_log rl ON rl.card_id = cc.id
2341
3881
  GROUP BY cc.owner
2342
3882
  `);
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;
3883
+ const creditsRows = creditsStmt.all();
3884
+ const creditsMap = new Map(creditsRows.map((r) => [r.owner, r.credits_earned ?? 0]));
3885
+ const agents = Array.from(ownerMap.entries()).map(([owner, cards]) => {
3886
+ const skillCount = cards.reduce((sum, card) => sum + (card.skills?.length ?? 1), 0);
3887
+ const successRates = cards.map((c) => c.metadata?.success_rate).filter((r) => r != null);
3888
+ const avgSuccessRate = successRates.length > 0 ? successRates.reduce((a, b) => a + b, 0) / successRates.length : null;
3889
+ const memberStmt = db.prepare(
3890
+ "SELECT MIN(created_at) as earliest FROM capability_cards WHERE owner = ?"
3891
+ );
3892
+ const memberRow = memberStmt.get(owner);
3893
+ return {
3894
+ owner,
3895
+ skill_count: skillCount,
3896
+ success_rate: avgSuccessRate,
3897
+ total_earned: creditsMap.get(owner) ?? 0,
3898
+ member_since: memberRow?.earliest ?? (/* @__PURE__ */ new Date()).toISOString()
3899
+ };
3900
+ });
3901
+ agents.sort((a, b) => {
3902
+ const aRate = a.success_rate ?? -1;
3903
+ const bRate = b.success_rate ?? -1;
3904
+ if (bRate !== aRate) return bRate - aRate;
3905
+ return b.total_earned - a.total_earned;
3906
+ });
3907
+ return reply.send({ items: agents, total: agents.length });
3908
+ });
3909
+ api.get("/api/agents/:owner", {
3910
+ schema: {
3911
+ tags: ["agents"],
3912
+ summary: "Get agent profile, skills, and recent activity (AgentProfileV2)",
3913
+ params: { type: "object", properties: { owner: { type: "string" } }, required: ["owner"] },
3914
+ response: {
3915
+ 200: { type: "object", additionalProperties: true },
3916
+ 404: { type: "object", properties: { error: { type: "string" } } }
3917
+ }
3918
+ }
3919
+ }, async (request, reply) => {
3920
+ const { owner } = request.params;
3921
+ const ownerCards = listCards(db, owner);
3922
+ if (ownerCards.length === 0) {
3923
+ return reply.status(404).send({ error: "Agent not found" });
3924
+ }
2349
3925
  const memberStmt = db.prepare(
2350
- "SELECT MIN(created_at) as earliest FROM capability_cards WHERE owner = ?"
3926
+ "SELECT MIN(created_at) as earliest, MAX(created_at) as latest FROM capability_cards WHERE owner = ?"
2351
3927
  );
2352
3928
  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
3929
+ const joinedAt = memberRow?.earliest ?? (/* @__PURE__ */ new Date()).toISOString();
3930
+ const lastActiveStmt = db.prepare(`
3931
+ SELECT MAX(rl.created_at) as last_req
3932
+ FROM request_log rl
3933
+ INNER JOIN capability_cards cc ON rl.card_id = cc.id
2382
3934
  WHERE cc.owner = ?
2383
3935
  `);
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(`
3936
+ const lastActiveRow = lastActiveStmt.get(owner);
3937
+ const lastActive = lastActiveRow?.last_req ?? memberRow?.latest ?? joinedAt;
3938
+ const metricsStmt = db.prepare(`
3939
+ SELECT
3940
+ COUNT(*) as total,
3941
+ SUM(CASE WHEN rl.status = 'success' THEN 1 ELSE 0 END) as successes,
3942
+ AVG(CASE WHEN rl.status = 'success' THEN rl.latency_ms END) as avg_latency,
3943
+ COUNT(DISTINCT rl.requester) as unique_requesters,
3944
+ COUNT(DISTINCT CASE WHEN rl.status = 'success' THEN rl.requester END) as repeat_success_requesters
3945
+ FROM request_log rl
3946
+ INNER JOIN capability_cards cc ON rl.card_id = cc.id
3947
+ WHERE cc.owner = ? AND rl.action_type IS NULL
3948
+ `);
3949
+ const metricsRow = metricsStmt.get(owner);
3950
+ const totalExec = metricsRow?.total ?? 0;
3951
+ const successExec = metricsRow?.successes ?? 0;
3952
+ const successRate = totalExec > 0 ? successExec / totalExec : 0;
3953
+ const avgLatency = metricsRow?.avg_latency ?? 0;
3954
+ const refundRate = totalExec > 0 ? (totalExec - successExec) / totalExec : 0;
3955
+ const uniqueReq = metricsRow?.unique_requesters ?? 0;
3956
+ const repeatRate = uniqueReq > 0 ? (metricsRow?.repeat_success_requesters ?? 0) / uniqueReq : 0;
3957
+ const trendStmt = db.prepare(`
3958
+ SELECT
3959
+ DATE(rl.created_at) as day,
3960
+ COUNT(*) as count,
3961
+ SUM(CASE WHEN rl.status = 'success' THEN 1 ELSE 0 END) as success
3962
+ FROM request_log rl
3963
+ INNER JOIN capability_cards cc ON rl.card_id = cc.id
3964
+ WHERE cc.owner = ? AND rl.action_type IS NULL
3965
+ AND rl.created_at >= DATE('now', '-7 days')
3966
+ GROUP BY DATE(rl.created_at)
3967
+ ORDER BY day ASC
3968
+ `);
3969
+ const trend_7d = trendStmt.all(owner).map((r) => ({ date: r.day, count: r.count, success: r.success }));
3970
+ let performanceTier = 0;
3971
+ if (totalExec > 10) performanceTier = 1;
3972
+ if (totalExec > 50 && successRate >= 0.85) performanceTier = 2;
3973
+ const proofsStmt = db.prepare(`
3974
+ SELECT rl.card_name, rl.status, rl.latency_ms, rl.id, rl.created_at
3975
+ FROM request_log rl
3976
+ INNER JOIN capability_cards cc ON rl.card_id = cc.id
3977
+ WHERE cc.owner = ? AND rl.action_type IS NULL
3978
+ ORDER BY rl.created_at DESC
3979
+ LIMIT 10
3980
+ `);
3981
+ const proofRows = proofsStmt.all(owner);
3982
+ const statusToOutcomeClass = (s) => {
3983
+ if (s === "success") return "completed";
3984
+ if (s === "timeout") return "cancelled";
3985
+ return "failed";
3986
+ };
3987
+ const executionProofs = proofRows.map((r) => ({
3988
+ action: r.card_name,
3989
+ status: r.status === "timeout" ? "timeout" : r.status,
3990
+ outcome_class: statusToOutcomeClass(r.status),
3991
+ latency_ms: r.latency_ms,
3992
+ receipt_id: r.id,
3993
+ proof_source: "request_log",
3994
+ timestamp: r.created_at
3995
+ }));
3996
+ const v2Card = ownerCards.find((c) => c.spec_version === "2.0");
3997
+ const suitability = v2Card?.suitability;
3998
+ const learning = {
3999
+ known_limitations: v2Card?.learning?.known_limitations ?? [],
4000
+ common_failure_patterns: v2Card?.learning?.common_failure_patterns ?? [],
4001
+ recent_improvements: v2Card?.learning?.recent_improvements ?? [],
4002
+ critiques: v2Card?.learning?.critiques ?? []
4003
+ };
4004
+ const activityStmt = db.prepare(`
2390
4005
  SELECT rl.id, rl.card_name, rl.requester, rl.status, rl.credits_charged, rl.created_at
2391
4006
  FROM request_log rl
2392
4007
  INNER JOIN capability_cards cc ON rl.card_id = cc.id
@@ -2394,214 +4009,478 @@ function createRegistryServer(opts) {
2394
4009
  ORDER BY rl.created_at DESC
2395
4010
  LIMIT 10
2396
4011
  `);
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
4012
+ const recentActivity = activityStmt.all(owner);
4013
+ const skillCount = ownerCards.reduce((sum, card) => sum + (card.skills?.length ?? 1), 0);
4014
+ const creditsStmt = db.prepare(`
4015
+ SELECT SUM(CASE WHEN rl.status = 'success' THEN rl.credits_charged ELSE 0 END) as credits_earned
4016
+ FROM capability_cards cc
4017
+ LEFT JOIN request_log rl ON rl.card_id = cc.id
4018
+ WHERE cc.owner = ?
4019
+ `);
4020
+ const creditsRow = creditsStmt.get(owner);
4021
+ const response = {
4022
+ owner,
4023
+ agent_name: v2Card?.agent_name,
4024
+ short_description: v2Card?.short_description,
4025
+ joined_at: joinedAt,
4026
+ last_active: lastActive,
4027
+ performance_tier: performanceTier,
4028
+ verification_badges: [],
4029
+ // Phase 1: no verification mechanism yet
4030
+ authority: {
4031
+ authority_source: "self",
4032
+ verification_status: "none"
4033
+ },
4034
+ suitability,
4035
+ trust_metrics: {
4036
+ total_executions: totalExec,
4037
+ successful_executions: successExec,
4038
+ success_rate: successRate,
4039
+ avg_latency_ms: Math.round(avgLatency),
4040
+ refund_rate: refundRate,
4041
+ repeat_use_rate: repeatRate,
4042
+ trend_7d,
4043
+ snapshot_at: null,
4044
+ aggregation_window: "all"
4045
+ },
4046
+ execution_proofs: executionProofs,
4047
+ learning,
4048
+ skills: ownerCards,
4049
+ recent_activity: recentActivity
4050
+ };
4051
+ return reply.send({
4052
+ ...response,
4053
+ profile: {
4054
+ owner,
4055
+ skill_count: skillCount,
4056
+ success_rate: successRate > 0 ? successRate : null,
4057
+ total_earned: creditsRow?.credits_earned ?? 0,
4058
+ member_since: joinedAt
4059
+ }
4060
+ });
4061
+ });
4062
+ api.get("/api/activity", {
4063
+ schema: {
4064
+ tags: ["system"],
4065
+ summary: "Paginated public activity feed of exchange events",
4066
+ querystring: {
4067
+ type: "object",
4068
+ properties: {
4069
+ limit: { type: "integer", default: 20, description: "Max items (max 100)" },
4070
+ since: { type: "string", description: "ISO 8601 timestamp for polling" }
4071
+ }
4072
+ },
4073
+ response: {
4074
+ 200: {
4075
+ type: "object",
4076
+ properties: {
4077
+ items: { type: "array" },
4078
+ total: { type: "integer" },
4079
+ limit: { type: "integer" }
4080
+ }
4081
+ }
4082
+ }
4083
+ }
4084
+ }, async (request, reply) => {
4085
+ const query = request.query;
4086
+ const rawLimit = query.limit !== void 0 ? parseInt(query.limit, 10) : 20;
4087
+ const limit = Math.min(isNaN(rawLimit) || rawLimit < 1 ? 20 : rawLimit, 100);
4088
+ const since = query.since?.trim() || void 0;
4089
+ const items = getActivityFeed(db, limit, since);
4090
+ return reply.send({ items, total: items.length, limit });
4091
+ });
4092
+ api.get("/api/stats", {
4093
+ schema: {
4094
+ tags: ["system"],
4095
+ summary: "Aggregate network statistics",
4096
+ response: {
4097
+ 200: {
4098
+ type: "object",
4099
+ properties: {
4100
+ agents_online: { type: "integer" },
4101
+ total_capabilities: { type: "integer" },
4102
+ total_exchanges: { type: "integer" },
4103
+ executions_7d: { type: "integer" },
4104
+ verified_providers_count: { type: "integer" }
4105
+ }
4106
+ }
4107
+ }
4108
+ }
4109
+ }, async (_request, reply) => {
4110
+ const allCards = listCards(db);
4111
+ const onlineOwners = /* @__PURE__ */ new Set();
4112
+ if (relayState) {
4113
+ for (const owner of relayState.getOnlineOwners()) {
4114
+ onlineOwners.add(owner);
4115
+ }
4116
+ }
4117
+ for (const card of allCards) {
4118
+ if (card.availability.online) {
4119
+ onlineOwners.add(card.owner);
4120
+ }
4121
+ }
4122
+ const exchangeStmt = db.prepare(
4123
+ "SELECT COUNT(*) as count FROM request_log WHERE status = 'success' AND (action_type IS NULL OR action_type = 'auto_share')"
4124
+ );
4125
+ const exchangeRow = exchangeStmt.get();
4126
+ const exec7dStmt = db.prepare(
4127
+ "SELECT COUNT(*) as count FROM request_log WHERE action_type IS NULL AND created_at >= DATE('now', '-7 days')"
4128
+ );
4129
+ const exec7dRow = exec7dStmt.get();
4130
+ return reply.send({
4131
+ agents_online: onlineOwners.size,
4132
+ total_capabilities: allCards.reduce((sum, card) => {
4133
+ const v2 = card;
4134
+ return sum + (v2.skills?.length ?? 1);
4135
+ }, 0),
4136
+ total_exchanges: exchangeRow.count,
4137
+ executions_7d: exec7dRow.count,
4138
+ verified_providers_count: 0
4139
+ // Phase 1: no verification mechanism yet
4140
+ });
2409
4141
  });
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);
4142
+ api.post("/api/identity/register", {
4143
+ schema: {
4144
+ tags: ["identity"],
4145
+ summary: "Register a human guarantor via GitHub login",
4146
+ body: {
4147
+ type: "object",
4148
+ properties: { github_login: { type: "string" } },
4149
+ required: ["github_login"]
4150
+ },
4151
+ response: {
4152
+ 201: { type: "object", additionalProperties: true },
4153
+ 400: { type: "object", properties: { error: { type: "string" } } },
4154
+ 409: { type: "object", properties: { error: { type: "string" } } },
4155
+ 503: { type: "object", properties: { error: { type: "string" } } }
4156
+ }
2425
4157
  }
2426
- }
2427
- for (const card of allCards) {
2428
- if (card.availability.online) {
2429
- onlineOwners.add(card.owner);
4158
+ }, async (request, reply) => {
4159
+ if (!opts.creditDb) {
4160
+ return reply.code(503).send({ error: "Credit database not configured" });
4161
+ }
4162
+ const body = request.body;
4163
+ const githubLogin = typeof body.github_login === "string" ? body.github_login.trim() : "";
4164
+ if (!githubLogin) {
4165
+ return reply.code(400).send({ error: "github_login is required" });
4166
+ }
4167
+ try {
4168
+ const record = registerGuarantor(opts.creditDb, githubLogin);
4169
+ const auth = initiateGithubAuth();
4170
+ return reply.code(201).send({ guarantor: record, oauth: auth });
4171
+ } catch (err) {
4172
+ if (err instanceof AgentBnBError && err.code === "GUARANTOR_EXISTS") {
4173
+ return reply.code(409).send({ error: err.message });
4174
+ }
4175
+ throw err;
2430
4176
  }
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
2443
4177
  });
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 });
4178
+ api.post("/api/identity/link", {
4179
+ schema: {
4180
+ tags: ["identity"],
4181
+ summary: "Link an agent to a human guarantor",
4182
+ body: {
4183
+ type: "object",
4184
+ properties: {
4185
+ agent_id: { type: "string" },
4186
+ github_login: { type: "string" }
4187
+ },
4188
+ required: ["agent_id", "github_login"]
4189
+ },
4190
+ response: {
4191
+ 200: { type: "object", additionalProperties: true },
4192
+ 400: { type: "object", properties: { error: { type: "string" } } },
4193
+ 404: { type: "object", properties: { error: { type: "string" } } },
4194
+ 409: { type: "object", properties: { error: { type: "string" } } },
4195
+ 503: { type: "object", properties: { error: { type: "string" } } }
4196
+ }
2461
4197
  }
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 });
4198
+ }, async (request, reply) => {
4199
+ if (!opts.creditDb) {
4200
+ return reply.code(503).send({ error: "Credit database not configured" });
2487
4201
  }
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" });
4202
+ const body = request.body;
4203
+ const agentId = typeof body.agent_id === "string" ? body.agent_id.trim() : "";
4204
+ const githubLogin = typeof body.github_login === "string" ? body.github_login.trim() : "";
4205
+ if (!agentId || !githubLogin) {
4206
+ return reply.code(400).send({ error: "agent_id and github_login are required" });
4207
+ }
4208
+ try {
4209
+ const record = linkAgentToGuarantor(opts.creditDb, agentId, githubLogin);
4210
+ return reply.send({ guarantor: record });
4211
+ } catch (err) {
4212
+ if (err instanceof AgentBnBError) {
4213
+ const statusMap = {
4214
+ GUARANTOR_NOT_FOUND: 404,
4215
+ MAX_AGENTS_EXCEEDED: 409,
4216
+ AGENT_ALREADY_LINKED: 409
4217
+ };
4218
+ const status = statusMap[err.code] ?? 400;
4219
+ return reply.code(status).send({ error: err.message });
2508
4220
  }
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" });
4221
+ throw err;
4222
+ }
4223
+ });
4224
+ api.get("/api/identity/:agent_id", {
4225
+ schema: {
4226
+ tags: ["identity"],
4227
+ summary: "Get guarantor info for an agent",
4228
+ params: { type: "object", properties: { agent_id: { type: "string" } }, required: ["agent_id"] },
4229
+ response: {
4230
+ 200: {
4231
+ type: "object",
4232
+ properties: {
4233
+ agent_id: { type: "string" },
4234
+ guarantor: { oneOf: [{ type: "object", additionalProperties: true }, { type: "null" }] }
4235
+ }
4236
+ },
4237
+ 503: { type: "object", properties: { error: { type: "string" } } }
2534
4238
  }
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" });
4239
+ }
4240
+ }, async (request, reply) => {
4241
+ if (!opts.creditDb) {
4242
+ return reply.code(503).send({ error: "Credit database not configured" });
4243
+ }
4244
+ const { agent_id } = request.params;
4245
+ const guarantor = getAgentGuarantor(opts.creditDb, agent_id);
4246
+ return reply.send({ agent_id, guarantor });
4247
+ });
4248
+ api.get("/api/openapi/gpt-actions", {
4249
+ schema: {
4250
+ tags: ["system"],
4251
+ summary: "GPT Actions-compatible OpenAPI schema",
4252
+ description: "Returns a GPT Builder-importable OpenAPI spec with only public GET/POST endpoints",
4253
+ querystring: {
4254
+ type: "object",
4255
+ properties: {
4256
+ server_url: { type: "string", description: "Base URL for the server (required for absolute URLs in GPT Actions)" }
2544
4257
  }
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") {
4258
+ },
4259
+ response: { 200: { type: "object", additionalProperties: true } }
4260
+ }
4261
+ }, async (request, reply) => {
4262
+ const query = request.query;
4263
+ const serverUrl = query.server_url?.trim() || `${request.protocol}://${request.hostname}`;
4264
+ const openapiSpec = server.swagger();
4265
+ const gptActions = convertToGptActions(openapiSpec, serverUrl);
4266
+ return reply.send(gptActions);
4267
+ });
4268
+ if (opts.ownerApiKey && opts.ownerName) {
4269
+ const ownerApiKey = opts.ownerApiKey;
4270
+ const ownerName = opts.ownerName;
4271
+ void api.register(async (ownerRoutes) => {
4272
+ ownerRoutes.addHook("onRequest", async (request, reply) => {
4273
+ const auth = request.headers.authorization;
4274
+ const token = auth?.startsWith("Bearer ") ? auth.slice(7).trim() : null;
4275
+ if (!token || token !== ownerApiKey) {
4276
+ return reply.status(401).send({ error: "Unauthorized" });
4277
+ }
4278
+ });
4279
+ ownerRoutes.get("/me", {
4280
+ schema: {
4281
+ tags: ["owner"],
4282
+ summary: "Get owner identity and credit balance",
4283
+ security: [{ bearerAuth: [] }],
4284
+ response: {
4285
+ 200: { type: "object", properties: { owner: { type: "string" }, balance: { type: "number" } } }
4286
+ }
4287
+ }
4288
+ }, async (_request, reply) => {
4289
+ let balance = 0;
4290
+ if (opts.creditDb) {
4291
+ const ledger = createLedger({ db: opts.creditDb });
4292
+ balance = await ledger.getBalance(ownerName);
4293
+ }
4294
+ return reply.send({ owner: ownerName, balance });
4295
+ });
4296
+ ownerRoutes.get("/requests", {
4297
+ schema: {
4298
+ tags: ["owner"],
4299
+ summary: "Paginated request log entries",
4300
+ security: [{ bearerAuth: [] }],
4301
+ querystring: {
4302
+ type: "object",
4303
+ properties: {
4304
+ limit: { type: "integer", description: "Max entries (default 10, max 100)" },
4305
+ since: { type: "string", enum: ["24h", "7d", "30d"], description: "Time window" }
4306
+ }
4307
+ },
4308
+ response: { 200: { type: "object", properties: { items: { type: "array" }, limit: { type: "integer" } } } }
4309
+ }
4310
+ }, async (request, reply) => {
4311
+ const query = request.query;
4312
+ const rawLimit = query.limit !== void 0 ? parseInt(query.limit, 10) : 10;
4313
+ const limit = Math.min(isNaN(rawLimit) || rawLimit < 1 ? 10 : rawLimit, 100);
4314
+ const sinceRaw = query.since;
4315
+ const validSince = ["24h", "7d", "30d"];
4316
+ const since = sinceRaw && validSince.includes(sinceRaw) ? sinceRaw : void 0;
4317
+ const items = getRequestLog(db, limit, since);
4318
+ return reply.send({ items, limit });
4319
+ });
4320
+ ownerRoutes.get("/draft", {
4321
+ schema: {
4322
+ tags: ["owner"],
4323
+ summary: "Draft capability cards from auto-detected API keys",
4324
+ security: [{ bearerAuth: [] }],
4325
+ response: { 200: { type: "object", properties: { cards: { type: "array" } } } }
4326
+ }
4327
+ }, async (_request, reply) => {
4328
+ const detectedKeys = detectApiKeys(KNOWN_API_KEYS);
4329
+ const cards = detectedKeys.map((key) => buildDraftCard(key, ownerName)).filter((card) => card !== null);
4330
+ return reply.send({ cards });
4331
+ });
4332
+ ownerRoutes.post("/cards/:id/toggle-online", {
4333
+ schema: {
4334
+ tags: ["owner"],
4335
+ summary: "Toggle card online/offline status",
4336
+ security: [{ bearerAuth: [] }],
4337
+ params: { type: "object", properties: { id: { type: "string" } }, required: ["id"] },
4338
+ response: {
4339
+ 200: { type: "object", properties: { ok: { type: "boolean" }, online: { type: "boolean" } } },
4340
+ 403: { type: "object", properties: { error: { type: "string" } } },
4341
+ 404: { type: "object", properties: { error: { type: "string" } } }
4342
+ }
4343
+ }
4344
+ }, async (request, reply) => {
4345
+ const { id } = request.params;
4346
+ const card = getCard(db, id);
4347
+ if (!card) {
4348
+ return reply.code(404).send({ error: "Not found" });
4349
+ }
4350
+ try {
4351
+ const newOnline = !card.availability.online;
4352
+ updateCard(db, id, ownerName, {
4353
+ availability: { ...card.availability, online: newOnline }
4354
+ });
4355
+ return reply.send({ ok: true, online: newOnline });
4356
+ } catch (err) {
4357
+ if (err instanceof AgentBnBError && err.code === "FORBIDDEN") {
2560
4358
  return reply.code(403).send({ error: "Forbidden" });
2561
4359
  }
2562
- if (err.code === "NOT_FOUND") {
2563
- return reply.code(404).send({ error: "Not found" });
4360
+ throw err;
4361
+ }
4362
+ });
4363
+ ownerRoutes.patch("/cards/:id", {
4364
+ schema: {
4365
+ tags: ["owner"],
4366
+ summary: "Update card description or pricing",
4367
+ security: [{ bearerAuth: [] }],
4368
+ params: { type: "object", properties: { id: { type: "string" } }, required: ["id"] },
4369
+ body: {
4370
+ type: "object",
4371
+ properties: {
4372
+ description: { type: "string" },
4373
+ pricing: { type: "object", additionalProperties: true }
4374
+ },
4375
+ additionalProperties: true
4376
+ },
4377
+ response: {
4378
+ 200: { type: "object", properties: { ok: { type: "boolean" } } },
4379
+ 403: { type: "object", properties: { error: { type: "string" } } },
4380
+ 404: { type: "object", properties: { error: { type: "string" } } }
2564
4381
  }
2565
4382
  }
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 });
4383
+ }, async (request, reply) => {
4384
+ const { id } = request.params;
4385
+ const body = request.body;
4386
+ const updates = {};
4387
+ if (body.description !== void 0) updates.description = body.description;
4388
+ if (body.pricing !== void 0) updates.pricing = body.pricing;
4389
+ try {
4390
+ updateCard(db, id, ownerName, updates);
4391
+ return reply.send({ ok: true });
4392
+ } catch (err) {
4393
+ if (err instanceof AgentBnBError) {
4394
+ if (err.code === "FORBIDDEN") {
4395
+ return reply.code(403).send({ error: "Forbidden" });
4396
+ }
4397
+ if (err.code === "NOT_FOUND") {
4398
+ return reply.code(404).send({ error: "Not found" });
4399
+ }
4400
+ }
4401
+ throw err;
4402
+ }
4403
+ });
4404
+ ownerRoutes.get("/me/pending-requests", {
4405
+ schema: {
4406
+ tags: ["owner"],
4407
+ summary: "List pending Tier 3 approval queue entries",
4408
+ security: [{ bearerAuth: [] }],
4409
+ response: { 200: { type: "array" } }
4410
+ }
4411
+ }, async (_request, reply) => {
4412
+ const rows = listPendingRequests(db);
4413
+ return reply.send(rows);
4414
+ });
4415
+ ownerRoutes.post("/me/pending-requests/:id/approve", {
4416
+ schema: {
4417
+ tags: ["owner"],
4418
+ summary: "Approve a pending Tier 3 request",
4419
+ security: [{ bearerAuth: [] }],
4420
+ params: { type: "object", properties: { id: { type: "string" } }, required: ["id"] },
4421
+ response: {
4422
+ 200: { type: "object", properties: { status: { type: "string" }, id: { type: "string" } } },
4423
+ 404: { type: "object", properties: { error: { type: "string" } } }
4424
+ }
4425
+ }
4426
+ }, async (request, reply) => {
4427
+ const { id } = request.params;
4428
+ try {
4429
+ resolvePendingRequest(db, id, "approved");
4430
+ return reply.send({ status: "approved", id });
4431
+ } catch (err) {
4432
+ if (err instanceof AgentBnBError) return reply.status(404).send({ error: err.message });
4433
+ throw err;
4434
+ }
4435
+ });
4436
+ ownerRoutes.post("/me/pending-requests/:id/reject", {
4437
+ schema: {
4438
+ tags: ["owner"],
4439
+ summary: "Reject a pending Tier 3 request",
4440
+ security: [{ bearerAuth: [] }],
4441
+ params: { type: "object", properties: { id: { type: "string" } }, required: ["id"] },
4442
+ response: {
4443
+ 200: { type: "object", properties: { status: { type: "string" }, id: { type: "string" } } },
4444
+ 404: { type: "object", properties: { error: { type: "string" } } }
4445
+ }
4446
+ }
4447
+ }, async (request, reply) => {
4448
+ const { id } = request.params;
4449
+ try {
4450
+ resolvePendingRequest(db, id, "rejected");
4451
+ return reply.send({ status: "rejected", id });
4452
+ } catch (err) {
4453
+ if (err instanceof AgentBnBError) return reply.status(404).send({ error: err.message });
4454
+ throw err;
4455
+ }
4456
+ });
4457
+ ownerRoutes.get("/me/transactions", {
4458
+ schema: {
4459
+ tags: ["owner"],
4460
+ summary: "Paginated credit transaction history",
4461
+ security: [{ bearerAuth: [] }],
4462
+ querystring: {
4463
+ type: "object",
4464
+ properties: { limit: { type: "integer", description: "Max entries (default 20, max 100)" } }
4465
+ },
4466
+ response: {
4467
+ 200: { type: "object", properties: { items: { type: "array" }, limit: { type: "integer" } } }
4468
+ }
4469
+ }
4470
+ }, async (request, reply) => {
4471
+ const query = request.query;
4472
+ const rawLimit = query.limit !== void 0 ? parseInt(query.limit, 10) : 20;
4473
+ const limit = Math.min(isNaN(rawLimit) || rawLimit < 1 ? 20 : rawLimit, 100);
4474
+ if (!opts.creditDb) {
4475
+ return reply.send({ items: [], limit });
4476
+ }
4477
+ const ledger = createLedger({ db: opts.creditDb });
4478
+ const items = await ledger.getHistory(ownerName, limit);
4479
+ return reply.send({ items, limit });
4480
+ });
2602
4481
  });
2603
- });
2604
- }
4482
+ }
4483
+ });
2605
4484
  return { server, relayState };
2606
4485
  }
2607
4486
 
@@ -2676,10 +4555,10 @@ async function stopAnnouncement() {
2676
4555
  }
2677
4556
 
2678
4557
  // src/openclaw/soul-sync.ts
2679
- import { randomUUID as randomUUID7 } from "crypto";
4558
+ import { randomUUID as randomUUID9 } from "crypto";
2680
4559
 
2681
4560
  // src/skills/publish-capability.ts
2682
- import { randomUUID as randomUUID6 } from "crypto";
4561
+ import { randomUUID as randomUUID8 } from "crypto";
2683
4562
  function parseSoulMd(content) {
2684
4563
  const lines = content.split("\n");
2685
4564
  let name = "";
@@ -2689,17 +4568,23 @@ function parseSoulMd(content) {
2689
4568
  let currentSection = null;
2690
4569
  let currentCapabilityName = "";
2691
4570
  let currentCapabilityLines = [];
4571
+ let currentCapabilityPricing = void 0;
2692
4572
  let descriptionLines = [];
2693
4573
  let pastFirstH1 = false;
2694
4574
  let pastFirstH2 = false;
2695
4575
  const flushCapability = () => {
2696
4576
  if (currentCapabilityName) {
2697
- capabilities.push({
4577
+ const cap = {
2698
4578
  name: currentCapabilityName,
2699
4579
  description: currentCapabilityLines.join(" ").trim()
2700
- });
4580
+ };
4581
+ if (currentCapabilityPricing !== void 0) {
4582
+ cap.pricing = currentCapabilityPricing;
4583
+ }
4584
+ capabilities.push(cap);
2701
4585
  currentCapabilityName = "";
2702
4586
  currentCapabilityLines = [];
4587
+ currentCapabilityPricing = void 0;
2703
4588
  }
2704
4589
  };
2705
4590
  for (const line of lines) {
@@ -2729,7 +4614,15 @@ function parseSoulMd(content) {
2729
4614
  if (currentSection === "preamble" && !pastFirstH2) {
2730
4615
  descriptionLines.push(trimmed);
2731
4616
  } else if (currentSection === "capability") {
2732
- currentCapabilityLines.push(trimmed);
4617
+ const pricingMatch = trimmed.match(/^pricing:\s*(\d+(?:\.\d+)?)$/i);
4618
+ if (pricingMatch) {
4619
+ const val = parseFloat(pricingMatch[1]);
4620
+ if (!isNaN(val) && val >= 0) {
4621
+ currentCapabilityPricing = val;
4622
+ }
4623
+ } else {
4624
+ currentCapabilityLines.push(trimmed);
4625
+ }
2733
4626
  }
2734
4627
  }
2735
4628
  flushCapability();
@@ -2750,7 +4643,7 @@ function parseSoulMdV2(content) {
2750
4643
  const parsed = parseSoulMd(content);
2751
4644
  const skills = parsed.capabilities.map((cap) => {
2752
4645
  const sanitizedId = cap.name.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
2753
- const id = sanitizedId.length > 0 ? sanitizedId : randomUUID7();
4646
+ const id = sanitizedId.length > 0 ? sanitizedId : randomUUID9();
2754
4647
  return {
2755
4648
  id,
2756
4649
  name: cap.name,
@@ -2772,7 +4665,7 @@ function parseSoulMdV2(content) {
2772
4665
  required: true
2773
4666
  }
2774
4667
  ],
2775
- pricing: { credits_per_call: 10 },
4668
+ pricing: { credits_per_call: cap.pricing !== void 0 ? cap.pricing : 10 },
2776
4669
  availability: { online: true }
2777
4670
  };
2778
4671
  });
@@ -2792,7 +4685,7 @@ function publishFromSoulV2(db, soulContent, owner) {
2792
4685
  (c) => c.spec_version === "2.0"
2793
4686
  );
2794
4687
  const now = (/* @__PURE__ */ new Date()).toISOString();
2795
- const cardId = existingV2?.id ?? randomUUID7();
4688
+ const cardId = existingV2?.id ?? randomUUID9();
2796
4689
  const card = {
2797
4690
  spec_version: "2.0",
2798
4691
  id: cardId,
@@ -2817,7 +4710,7 @@ function publishFromSoulV2(db, soulContent, owner) {
2817
4710
  }
2818
4711
 
2819
4712
  // src/openclaw/heartbeat-writer.ts
2820
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, existsSync as existsSync5 } from "fs";
4713
+ import { readFileSync as readFileSync3, writeFileSync, existsSync as existsSync4 } from "fs";
2821
4714
  var HEARTBEAT_MARKER_START = "<!-- agentbnb:start -->";
2822
4715
  var HEARTBEAT_MARKER_END = "<!-- agentbnb:end -->";
2823
4716
  function generateHeartbeatSection(autonomy, budget) {
@@ -2853,11 +4746,11 @@ function generateHeartbeatSection(autonomy, budget) {
2853
4746
  ].join("\n");
2854
4747
  }
2855
4748
  function injectHeartbeatSection(heartbeatPath, section) {
2856
- if (!existsSync5(heartbeatPath)) {
2857
- writeFileSync2(heartbeatPath, section + "\n", "utf-8");
4749
+ if (!existsSync4(heartbeatPath)) {
4750
+ writeFileSync(heartbeatPath, section + "\n", "utf-8");
2858
4751
  return;
2859
4752
  }
2860
- let content = readFileSync4(heartbeatPath, "utf-8");
4753
+ let content = readFileSync3(heartbeatPath, "utf-8");
2861
4754
  const startIdx = content.indexOf(HEARTBEAT_MARKER_START);
2862
4755
  const endIdx = content.indexOf(HEARTBEAT_MARKER_END);
2863
4756
  if (startIdx !== -1 && endIdx !== -1) {
@@ -2865,7 +4758,7 @@ function injectHeartbeatSection(heartbeatPath, section) {
2865
4758
  } else {
2866
4759
  content = content + "\n" + section + "\n";
2867
4760
  }
2868
- writeFileSync2(heartbeatPath, content, "utf-8");
4761
+ writeFileSync(heartbeatPath, content, "utf-8");
2869
4762
  }
2870
4763
 
2871
4764
  // src/openclaw/skill.ts
@@ -2945,11 +4838,11 @@ function getLanIp() {
2945
4838
  var program = new Command();
2946
4839
  program.name("agentbnb").description("P2P Agent Capability Sharing Protocol \u2014 Airbnb for AI agent pipelines").version(pkg.version);
2947
4840
  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");
4841
+ const owner = opts.owner ?? `agent-${randomBytes2(4).toString("hex")}`;
4842
+ const token = randomBytes2(32).toString("hex");
2950
4843
  const configDir = getConfigDir();
2951
- const dbPath = join4(configDir, "registry.db");
2952
- const creditDbPath = join4(configDir, "credit.db");
4844
+ const dbPath = join3(configDir, "registry.db");
4845
+ const creditDbPath = join3(configDir, "credit.db");
2953
4846
  const port = parseInt(opts.port, 10);
2954
4847
  const ip = opts.host ?? getLanIp();
2955
4848
  const existingConfig = loadConfig();
@@ -2963,7 +4856,7 @@ program.command("init").description("Initialize AgentBnB config and create agent
2963
4856
  credit_db_path: creditDbPath,
2964
4857
  token: existingConfig?.token ?? token,
2965
4858
  // Preserve existing token
2966
- api_key: existingConfig?.api_key ?? randomBytes(32).toString("hex")
4859
+ api_key: existingConfig?.api_key ?? randomBytes2(32).toString("hex")
2967
4860
  };
2968
4861
  saveConfig(config);
2969
4862
  let keypairStatus = "existing";
@@ -2976,8 +4869,68 @@ program.command("init").description("Initialize AgentBnB config and create agent
2976
4869
  }
2977
4870
  const identity = ensureIdentity(configDir, owner);
2978
4871
  const creditDb = openCreditDb(creditDbPath);
4872
+ if (existingConfig?.owner && existingConfig.owner !== owner) {
4873
+ migrateOwner(creditDb, existingConfig.owner, owner);
4874
+ const regDb = openDatabase(dbPath);
4875
+ try {
4876
+ const rows = regDb.prepare("SELECT id, owner, data FROM capability_cards WHERE owner != ?").all(owner);
4877
+ for (const row of rows) {
4878
+ try {
4879
+ const card = JSON.parse(row.data);
4880
+ card.owner = owner;
4881
+ regDb.prepare("UPDATE capability_cards SET owner = ?, data = ? WHERE id = ?").run(owner, JSON.stringify(card), row.id);
4882
+ } catch {
4883
+ }
4884
+ }
4885
+ if (!opts.json && rows.length > 0) {
4886
+ console.log(`Migrated ${rows.length} card(s) \u2192 ${owner}`);
4887
+ }
4888
+ } finally {
4889
+ regDb.close();
4890
+ }
4891
+ const allOwners = creditDb.prepare("SELECT owner FROM credit_balances WHERE owner != ?").all(owner);
4892
+ for (const { owner: oldOwner } of allOwners) {
4893
+ migrateOwner(creditDb, oldOwner, owner);
4894
+ }
4895
+ if (existingConfig.registry) {
4896
+ try {
4897
+ const renameAuth = loadIdentityAuth(owner);
4898
+ const renameLedger = createLedger({
4899
+ registryUrl: existingConfig.registry,
4900
+ ownerPublicKey: renameAuth.publicKey,
4901
+ privateKey: renameAuth.privateKey
4902
+ });
4903
+ await renameLedger.rename(existingConfig.owner, owner);
4904
+ if (!opts.json) {
4905
+ console.log(`Migrated Registry credits: ${existingConfig.owner} \u2192 ${owner}`);
4906
+ }
4907
+ } catch (err) {
4908
+ if (!opts.json) {
4909
+ console.warn(`Warning: could not migrate Registry credits: ${err.message}`);
4910
+ }
4911
+ }
4912
+ }
4913
+ if (!opts.json) {
4914
+ console.log(`Migrated local credits: ${existingConfig.owner} \u2192 ${owner}`);
4915
+ }
4916
+ }
2979
4917
  bootstrapAgent(creditDb, owner, 100);
2980
4918
  creditDb.close();
4919
+ let registryBalance;
4920
+ if (existingConfig?.registry) {
4921
+ try {
4922
+ const identityAuth = loadIdentityAuth(owner);
4923
+ const ledger = createLedger({
4924
+ registryUrl: existingConfig.registry,
4925
+ ownerPublicKey: identityAuth.publicKey,
4926
+ privateKey: identityAuth.privateKey
4927
+ });
4928
+ await ledger.grant(owner, 50);
4929
+ registryBalance = await ledger.getBalance(owner);
4930
+ } catch (err) {
4931
+ console.warn(`Warning: could not connect to Registry for credit grant: ${err.message}`);
4932
+ }
4933
+ }
2981
4934
  const skipDetect = opts.detect === false;
2982
4935
  const publishedCards = [];
2983
4936
  let detectedSource = "none";
@@ -3123,6 +5076,9 @@ Publish these ${card.skills.length} capabilities? [y/N] `);
3123
5076
  keypair: keypairStatus,
3124
5077
  agent_id: identity.agent_id
3125
5078
  };
5079
+ if (registryBalance !== void 0) {
5080
+ jsonOutput.registry_balance = registryBalance;
5081
+ }
3126
5082
  if (!skipDetect) {
3127
5083
  jsonOutput.detected_source = detectedSource;
3128
5084
  jsonOutput.published_cards = publishedCards;
@@ -3133,7 +5089,11 @@ Publish these ${card.skills.length} capabilities? [y/N] `);
3133
5089
  console.log(` Owner: ${owner}`);
3134
5090
  console.log(` Token: ${token}`);
3135
5091
  console.log(` Config: ${configDir}/config.json`);
3136
- console.log(` Credits: 100 (starter grant)`);
5092
+ if (registryBalance !== void 0) {
5093
+ console.log(` Registry balance: ${registryBalance} credits`);
5094
+ } else {
5095
+ console.log(` Credits: 100 (starter grant)`);
5096
+ }
3137
5097
  console.log(` Keypair: ${keypairStatus === "generated" ? "generated (Ed25519)" : "preserved (existing)"}`);
3138
5098
  console.log(` Agent ID: ${identity.agent_id}`);
3139
5099
  console.log(` Gateway: http://${ip}:${port}`);
@@ -3147,7 +5107,7 @@ program.command("publish <card.json>").description("Publish a Capability Card to
3147
5107
  }
3148
5108
  let raw;
3149
5109
  try {
3150
- raw = readFileSync5(cardPath, "utf-8");
5110
+ raw = readFileSync4(cardPath, "utf-8");
3151
5111
  } catch {
3152
5112
  console.error(`Error: cannot read file: ${cardPath}`);
3153
5113
  process.exit(1);
@@ -3176,6 +5136,27 @@ program.command("publish <card.json>").description("Publish a Capability Card to
3176
5136
  }
3177
5137
  const card = result.data;
3178
5138
  const cardName = card.spec_version === "2.0" ? card.agent_name : card.name;
5139
+ if (card.spec_version === "2.0") {
5140
+ const v2card = card;
5141
+ const invalidSkill = v2card.skills?.find((s) => s.pricing.credits_per_call < 1);
5142
+ if (invalidSkill) {
5143
+ if (opts.json) {
5144
+ console.log(JSON.stringify({ success: false, error: "Minimum price is 1 credit per call", skill_id: invalidSkill.id }, null, 2));
5145
+ } else {
5146
+ console.error(`Error: Minimum price is 1 credit per call (skill "${invalidSkill.id}" has credits_per_call=${invalidSkill.pricing.credits_per_call})`);
5147
+ }
5148
+ process.exit(1);
5149
+ }
5150
+ } else {
5151
+ if (card.pricing.credits_per_call < 1) {
5152
+ if (opts.json) {
5153
+ console.log(JSON.stringify({ success: false, error: "Minimum price is 1 credit per call" }, null, 2));
5154
+ } else {
5155
+ console.error(`Error: Minimum price is 1 credit per call (card has credits_per_call=${card.pricing.credits_per_call})`);
5156
+ }
5157
+ process.exit(1);
5158
+ }
5159
+ }
3179
5160
  const db = openDatabase(config.db_path);
3180
5161
  try {
3181
5162
  if (card.spec_version === "2.0") {
@@ -3431,8 +5412,8 @@ program.command("request [card-id]").description("Request a capability from anot
3431
5412
  process.exit(1);
3432
5413
  }
3433
5414
  }
3434
- const registryDb = openDatabase(join4(getConfigDir(), "registry.db"));
3435
- const creditDb = openCreditDb(join4(getConfigDir(), "credit.db"));
5415
+ const registryDb = openDatabase(join3(getConfigDir(), "registry.db"));
5416
+ const creditDb = openCreditDb(join3(getConfigDir(), "credit.db"));
3436
5417
  registryDb.pragma("busy_timeout = 5000");
3437
5418
  creditDb.pragma("busy_timeout = 5000");
3438
5419
  try {
@@ -3442,7 +5423,8 @@ program.command("request [card-id]").description("Request a capability from anot
3442
5423
  registryDb,
3443
5424
  creditDb,
3444
5425
  autonomyConfig: config.autonomy ?? DEFAULT_AUTONOMY_CONFIG,
3445
- budgetManager
5426
+ budgetManager,
5427
+ registryUrl: config.registry
3446
5428
  });
3447
5429
  const result = await requestor.requestWithAutonomy({
3448
5430
  query: opts.query,
@@ -3535,68 +5517,105 @@ program.command("request [card-id]").description("Request a capability from anot
3535
5517
  }
3536
5518
  }
3537
5519
  const useReceipt = isRemoteRequest && opts.receipt !== false;
5520
+ const useRegistryLedger = isRemoteRequest && !!config.registry && !!gatewayUrl;
3538
5521
  if (useReceipt && !opts.cost) {
3539
5522
  console.error("Error: --cost <credits> is required for remote requests. Specify the credits to commit.");
3540
5523
  process.exit(1);
3541
5524
  }
3542
5525
  let escrowId;
3543
5526
  let escrowReceipt;
5527
+ let requestLedger;
3544
5528
  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.");
5529
+ const amount = Number(opts.cost);
5530
+ if (isNaN(amount) || amount <= 0) {
5531
+ console.error("Error: --cost must be a positive number.");
5532
+ process.exit(1);
5533
+ }
5534
+ if (useRegistryLedger) {
5535
+ const reqIdentityAuth = loadIdentityAuth(config.owner);
5536
+ requestLedger = createLedger({
5537
+ registryUrl: config.registry,
5538
+ ownerPublicKey: reqIdentityAuth.publicKey,
5539
+ privateKey: reqIdentityAuth.privateKey
5540
+ });
5541
+ try {
5542
+ const { escrowId: heldId } = await requestLedger.hold(config.owner, amount, cardId);
5543
+ escrowId = heldId;
5544
+ if (!opts.json) {
5545
+ console.log(`Escrow: ${amount} credits held via Registry (ID: ${escrowId.slice(0, 8)}...)`);
5546
+ }
5547
+ } catch (err) {
5548
+ const msg = err instanceof Error ? err.message : String(err);
5549
+ if (opts.json) {
5550
+ console.log(JSON.stringify({ success: false, error: msg }, null, 2));
5551
+ } else {
5552
+ console.error(`Error creating escrow via Registry: ${msg}`);
5553
+ }
3553
5554
  process.exit(1);
3554
5555
  }
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)}...)`);
5556
+ } else if (gatewayUrl) {
5557
+ const configDir = getConfigDir();
5558
+ const creditDb = openCreditDb(join3(configDir, "credit.db"));
5559
+ creditDb.pragma("busy_timeout = 5000");
5560
+ try {
5561
+ const keys = loadKeyPair(configDir);
5562
+ const receiptResult = createSignedEscrowReceipt(creditDb, keys.privateKey, keys.publicKey, {
5563
+ owner: config.owner,
5564
+ amount,
5565
+ cardId,
5566
+ skillId: opts.skill
5567
+ });
5568
+ escrowId = receiptResult.escrowId;
5569
+ escrowReceipt = receiptResult.receipt;
5570
+ if (!opts.json) {
5571
+ console.log(`Escrow: ${amount} credits held (ID: ${escrowId.slice(0, 8)}...)`);
5572
+ }
5573
+ } catch (err) {
5574
+ creditDb.close();
5575
+ const msg = err instanceof Error ? err.message : String(err);
5576
+ if (opts.json) {
5577
+ console.log(JSON.stringify({ success: false, error: msg }, null, 2));
5578
+ } else {
5579
+ console.error(`Error creating escrow receipt: ${msg}`);
5580
+ }
5581
+ process.exit(1);
3565
5582
  }
3566
- } catch (err) {
3567
5583
  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
5584
  }
3576
5585
  }
3577
- const settleEscrow = () => {
5586
+ const settleEscrow2 = async () => {
3578
5587
  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);
5588
+ if (requestLedger) {
5589
+ await requestLedger.settle(escrowId, targetOwner ?? config.owner);
3584
5590
  if (!opts.json) console.log(`Escrow settled: ${opts.cost} credits deducted.`);
3585
- } finally {
3586
- creditDb.close();
5591
+ } else if (escrowReceipt) {
5592
+ const configDir = getConfigDir();
5593
+ const creditDb = openCreditDb(join3(configDir, "credit.db"));
5594
+ creditDb.pragma("busy_timeout = 5000");
5595
+ try {
5596
+ settleRequesterEscrow(creditDb, escrowId);
5597
+ if (!opts.json) console.log(`Escrow settled: ${opts.cost} credits deducted.`);
5598
+ } finally {
5599
+ creditDb.close();
5600
+ }
3587
5601
  }
3588
5602
  }
3589
5603
  };
3590
- const releaseEscrow2 = () => {
5604
+ const releaseEscrow2 = async () => {
3591
5605
  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);
5606
+ if (requestLedger) {
5607
+ await requestLedger.release(escrowId);
3597
5608
  if (!opts.json) console.log("Escrow released: credits refunded.");
3598
- } finally {
3599
- creditDb.close();
5609
+ } else if (escrowReceipt) {
5610
+ const configDir = getConfigDir();
5611
+ const creditDb = openCreditDb(join3(configDir, "credit.db"));
5612
+ creditDb.pragma("busy_timeout = 5000");
5613
+ try {
5614
+ releaseRequesterEscrow(creditDb, escrowId);
5615
+ if (!opts.json) console.log("Escrow released: credits refunded.");
5616
+ } finally {
5617
+ creditDb.close();
5618
+ }
3600
5619
  }
3601
5620
  }
3602
5621
  };
@@ -3613,13 +5632,14 @@ program.command("request [card-id]").description("Request a capability from anot
3613
5632
  return msg.includes("NETWORK_ERROR") || msg.includes("ECONNREFUSED") || msg.includes("fetch failed") || msg.includes("Network error");
3614
5633
  };
3615
5634
  const tryViaRelay = async () => {
3616
- const { RelayClient } = await import("../websocket-client-5TIQDYQ4.js");
3617
- const { requestViaRelay } = await import("../client-IOTK6GOS.js");
5635
+ const { RelayClient } = await import("../websocket-client-6IIDGXKB.js");
5636
+ const { requestViaRelay } = await import("../client-T5MTY3CS.js");
5637
+ const requesterId = `${config.owner}:req:${randomUUID10()}`;
3618
5638
  const tempRelay = new RelayClient({
3619
5639
  registryUrl: config.registry,
3620
- owner: config.owner,
5640
+ owner: requesterId,
3621
5641
  token: config.token,
3622
- card: { id: config.owner, owner: config.owner },
5642
+ card: { id: randomUUID10(), owner: requesterId, name: requesterId, description: "Requester", level: 1, spec_version: "1.0", inputs: [], outputs: [], pricing: { credits_per_call: 1 }, availability: { online: false } },
3623
5643
  onRequest: async () => ({ error: { code: -32601, message: "Not serving" } }),
3624
5644
  silent: true
3625
5645
  });
@@ -3630,6 +5650,8 @@ program.command("request [card-id]").description("Request a capability from anot
3630
5650
  cardId,
3631
5651
  skillId: opts.skill,
3632
5652
  params: { ...params, ...opts.skill ? { skill_id: opts.skill } : {} },
5653
+ requester: config.owner,
5654
+ // actual owner for credit tracking on relay server
3633
5655
  escrowReceipt
3634
5656
  });
3635
5657
  return result;
@@ -3661,10 +5683,10 @@ program.command("request [card-id]").description("Request a capability from anot
3661
5683
  }
3662
5684
  }
3663
5685
  }
3664
- settleEscrow();
5686
+ await settleEscrow2();
3665
5687
  printResult(result);
3666
5688
  } catch (err) {
3667
- releaseEscrow2();
5689
+ await releaseEscrow2();
3668
5690
  const msg = err instanceof Error ? err.message : String(err);
3669
5691
  if (opts.json) {
3670
5692
  console.log(JSON.stringify({ success: false, error: msg }, null, 2));
@@ -3684,12 +5706,28 @@ program.command("status").description("Show credit balance and recent transactio
3684
5706
  let balance;
3685
5707
  let transactions;
3686
5708
  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();
5709
+ if (config.registry) {
5710
+ const statusIdentityAuth = loadIdentityAuth(config.owner);
5711
+ const statusLedger = createLedger({
5712
+ registryUrl: config.registry,
5713
+ ownerPublicKey: statusIdentityAuth.publicKey,
5714
+ privateKey: statusIdentityAuth.privateKey
5715
+ });
5716
+ try {
5717
+ balance = await statusLedger.getBalance(config.owner);
5718
+ transactions = await statusLedger.getHistory(config.owner, 5);
5719
+ heldEscrows = creditDb.prepare("SELECT id, amount, card_id, created_at FROM credit_escrow WHERE owner = ? AND status = ?").all(config.owner, "held");
5720
+ } finally {
5721
+ creditDb.close();
5722
+ }
5723
+ } else {
5724
+ try {
5725
+ balance = getBalance(creditDb, config.owner);
5726
+ transactions = getTransactions(creditDb, config.owner, 5);
5727
+ heldEscrows = creditDb.prepare("SELECT id, amount, card_id, created_at FROM credit_escrow WHERE owner = ? AND status = ?").all(config.owner, "held");
5728
+ } finally {
5729
+ creditDb.close();
5730
+ }
3693
5731
  }
3694
5732
  if (opts.json) {
3695
5733
  console.log(JSON.stringify({ owner: config.owner, balance, held_escrows: heldEscrows, recent_transactions: transactions }, null, 2));
@@ -3722,7 +5760,7 @@ program.command("serve").description("Start the AgentBnB gateway server").option
3722
5760
  }
3723
5761
  const port = opts.port ? parseInt(opts.port, 10) : config.gateway_port;
3724
5762
  const registryPort = parseInt(opts.registryPort, 10);
3725
- const skillsYamlPath = opts.skillsYaml ?? join4(homedir(), ".agentbnb", "skills.yaml");
5763
+ const skillsYamlPath = opts.skillsYaml ?? join3(homedir(), ".agentbnb", "skills.yaml");
3726
5764
  const runtime = new AgentRuntime({
3727
5765
  registryDbPath: config.db_path,
3728
5766
  creditDbPath: config.credit_db_path,
@@ -3738,6 +5776,18 @@ program.command("serve").description("Start the AgentBnB gateway server").option
3738
5776
  if (opts.conductor) {
3739
5777
  console.log("Conductor mode enabled \u2014 orchestrate/plan skills available via gateway");
3740
5778
  }
5779
+ if (opts.conductor && config.conductor?.public) {
5780
+ const { buildConductorCard } = await import("../card-RSGDCHCV.js");
5781
+ const conductorCard = buildConductorCard(config.owner);
5782
+ const now = (/* @__PURE__ */ new Date()).toISOString();
5783
+ const existing = runtime.registryDb.prepare("SELECT id FROM capability_cards WHERE id = ?").get(conductorCard.id);
5784
+ if (existing) {
5785
+ runtime.registryDb.prepare("UPDATE capability_cards SET data = ?, updated_at = ? WHERE id = ?").run(JSON.stringify(conductorCard), now, conductorCard.id);
5786
+ } else {
5787
+ 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);
5788
+ }
5789
+ console.log("Conductor card registered locally (conductor.public: true)");
5790
+ }
3741
5791
  const autonomyConfig = config.autonomy ?? DEFAULT_AUTONOMY_CONFIG;
3742
5792
  const idleMonitor = new IdleMonitor({
3743
5793
  owner: config.owner,
@@ -3801,11 +5851,11 @@ program.command("serve").description("Start the AgentBnB gateway server").option
3801
5851
  }
3802
5852
  const relayUrl = opts.registry ?? config.registry;
3803
5853
  if (relayUrl && opts.relay !== false) {
3804
- const { RelayClient } = await import("../websocket-client-5TIQDYQ4.js");
3805
- const { executeCapabilityRequest: executeCapabilityRequest2 } = await import("../execute-GDGBU6DJ.js");
5854
+ const { RelayClient } = await import("../websocket-client-6IIDGXKB.js");
5855
+ const { executeCapabilityRequest: executeCapabilityRequest2 } = await import("../execute-PNGQOMYO.js");
3806
5856
  const cards = listCards(runtime.registryDb, config.owner);
3807
5857
  const card = cards[0] ?? {
3808
- id: config.owner,
5858
+ id: randomUUID10(),
3809
5859
  owner: config.owner,
3810
5860
  name: config.owner,
3811
5861
  description: "Agent registered via CLI",
@@ -3813,15 +5863,26 @@ program.command("serve").description("Start the AgentBnB gateway server").option
3813
5863
  level: 1,
3814
5864
  inputs: [],
3815
5865
  outputs: [],
3816
- pricing: { credits_per_call: 0 },
5866
+ pricing: { credits_per_call: 1 },
3817
5867
  availability: { online: true }
3818
5868
  };
5869
+ const additionalCards = [];
5870
+ if (config.conductor?.public) {
5871
+ const { buildConductorCard } = await import("../card-RSGDCHCV.js");
5872
+ const conductorCard = buildConductorCard(config.owner);
5873
+ additionalCards.push(conductorCard);
5874
+ console.log("Conductor card will be published to registry (conductor.public: true)");
5875
+ }
3819
5876
  relayClient = new RelayClient({
3820
5877
  registryUrl: relayUrl,
3821
5878
  owner: config.owner,
3822
5879
  token: config.token,
3823
5880
  card,
5881
+ cards: additionalCards.length > 0 ? additionalCards : void 0,
3824
5882
  onRequest: async (req) => {
5883
+ const onProgress = (info) => {
5884
+ relayClient.sendProgress(req.id, info);
5885
+ };
3825
5886
  const result = await executeCapabilityRequest2({
3826
5887
  registryDb: runtime.registryDb,
3827
5888
  creditDb: runtime.creditDb,
@@ -3831,7 +5892,8 @@ program.command("serve").description("Start the AgentBnB gateway server").option
3831
5892
  requester: req.requester ?? req.from_owner,
3832
5893
  escrowReceipt: req.escrow_receipt,
3833
5894
  skillExecutor: runtime.skillExecutor,
3834
- handlerUrl: opts.handlerUrl
5895
+ handlerUrl: opts.handlerUrl,
5896
+ onProgress
3835
5897
  });
3836
5898
  if (result.success) {
3837
5899
  return { result: result.result };
@@ -3906,7 +5968,7 @@ peersCommand.command("remove <name>").description("Remove a registered peer").ac
3906
5968
  });
3907
5969
  var configCmd = program.command("config").description("Get or set AgentBnB configuration values");
3908
5970
  configCmd.command("set <key> <value>").description("Set a configuration value").action((key, value) => {
3909
- const allowedKeys = ["registry", "tier1", "tier2", "reserve", "idle-threshold"];
5971
+ const allowedKeys = ["registry", "tier1", "tier2", "reserve", "idle-threshold", "conductor-public"];
3910
5972
  if (!allowedKeys.includes(key)) {
3911
5973
  console.error(`Unknown config key: ${key}. Valid keys: ${allowedKeys.join(", ")}`);
3912
5974
  process.exit(1);
@@ -3971,6 +6033,17 @@ configCmd.command("set <key> <value>").description("Set a configuration value").
3971
6033
  console.log(`Set idle-threshold = ${parsed} (idle rate threshold for auto-share)`);
3972
6034
  return;
3973
6035
  }
6036
+ if (key === "conductor-public") {
6037
+ const boolVal = value === "true";
6038
+ if (value !== "true" && value !== "false") {
6039
+ console.error('Error: conductor-public must be "true" or "false"');
6040
+ process.exit(1);
6041
+ }
6042
+ config.conductor = { public: boolVal };
6043
+ saveConfig(config);
6044
+ console.log(`Set conductor-public = ${boolVal} (conductor card ${boolVal ? "will be" : "will NOT be"} published to registry)`);
6045
+ return;
6046
+ }
3974
6047
  config[key] = value;
3975
6048
  saveConfig(config);
3976
6049
  console.log(`Set ${key} = ${value}`);
@@ -3998,6 +6071,10 @@ configCmd.command("get <key>").description("Get a configuration value").action((
3998
6071
  console.log(val !== void 0 ? String(val) : "0.70");
3999
6072
  return;
4000
6073
  }
6074
+ if (key === "conductor-public") {
6075
+ console.log(String(config.conductor?.public ?? false));
6076
+ return;
6077
+ }
4001
6078
  const value = config[key];
4002
6079
  console.log(value !== void 0 ? String(value) : "(not set)");
4003
6080
  });
@@ -4010,7 +6087,7 @@ openclaw.command("sync").description("Read SOUL.md and publish/update a v2.0 cap
4010
6087
  }
4011
6088
  let content;
4012
6089
  try {
4013
- content = readFileSync5(opts.soulPath, "utf-8");
6090
+ content = readFileSync4(opts.soulPath, "utf-8");
4014
6091
  } catch {
4015
6092
  console.error(`Error: cannot read SOUL.md at ${opts.soulPath}`);
4016
6093
  process.exit(1);
@@ -4019,6 +6096,14 @@ openclaw.command("sync").description("Read SOUL.md and publish/update a v2.0 cap
4019
6096
  try {
4020
6097
  const card = publishFromSoulV2(db, content, config.owner);
4021
6098
  console.log(`Published card ${card.id} with ${card.skills.length} skill(s)`);
6099
+ for (const skill of card.skills) {
6100
+ const stats = getPricingStats(db, skill.name);
6101
+ if (stats.count > 0) {
6102
+ console.log(` ${skill.name}: ${skill.pricing.credits_per_call} cr (market: ${stats.min}-${stats.max} cr, median ${stats.median}, ${stats.count} providers)`);
6103
+ } else {
6104
+ console.log(` ${skill.name}: ${skill.pricing.credits_per_call} cr (no market data yet)`);
6105
+ }
6106
+ }
4022
6107
  } catch (err) {
4023
6108
  const msg = err instanceof Error ? err.message : String(err);
4024
6109
  console.error(`Error: ${msg}`);
@@ -4071,7 +6156,7 @@ openclaw.command("rules").description("Print HEARTBEAT.md rules block (or inject
4071
6156
  }
4072
6157
  });
4073
6158
  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");
6159
+ const { conductAction } = await import("../conduct-GZQNFTRP.js");
4075
6160
  const result = await conductAction(task, opts);
4076
6161
  if (opts.json) {
4077
6162
  console.log(JSON.stringify(result, null, 2));
@@ -4104,4 +6189,8 @@ Total credits spent: ${result.total_credits ?? 0} cr`);
4104
6189
  }
4105
6190
  }
4106
6191
  });
6192
+ program.command("mcp-server").description("Start an MCP (Model Context Protocol) server for IDE integration").action(async () => {
6193
+ const { startMcpServer } = await import("../server-365V3GYD.js");
6194
+ await startMcpServer();
6195
+ });
4107
6196
  await program.parseAsync(process.argv);