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.
- package/README.md +2 -0
- package/dist/{card-IE5UV5QX.js → card-RSGDCHCV.js} +11 -4
- package/dist/chunk-3MJT4PZG.js +50 -0
- package/dist/{chunk-HEVXCYCY.js → chunk-4P3EMGL4.js} +61 -24
- package/dist/chunk-5AH3CMOX.js +62 -0
- package/dist/{chunk-QO67IGCW.js → chunk-5KFI5X7B.js} +1 -1
- package/dist/chunk-75OC6E4F.js +33 -0
- package/dist/{chunk-CUVIWPQO.js → chunk-7NA43XCG.js} +7 -6
- package/dist/{conduct-IQYAT6ZU.js → chunk-BH6WGYFB.js} +70 -33
- package/dist/{chunk-QVV2P3FN.js → chunk-DNWT5FZQ.js} +22 -2
- package/dist/chunk-FF226TIV.js +148 -0
- package/dist/{chunk-UJWYE7VL.js → chunk-GGYC5U2Z.js} +28 -111
- package/dist/chunk-HH24WMFN.js +373 -0
- package/dist/{websocket-client-5TIQDYQ4.js → chunk-JOY533UH.js} +38 -4
- package/dist/chunk-QITOPASZ.js +96 -0
- package/dist/{chunk-3Y36WQDV.js → chunk-QT7TEVNV.js} +14 -2
- package/dist/{chunk-UOGDK2S2.js → chunk-T7NS2J2B.js} +1 -1
- package/dist/{chunk-XA63SD4T.js → chunk-WGZ5AGOX.js} +37 -0
- package/dist/{chunk-RSX4SCPN.js → chunk-XND2DWTZ.js} +4 -3
- package/dist/cli/index.js +2924 -835
- package/dist/{client-IOTK6GOS.js → client-T5MTY3CS.js} +3 -3
- package/dist/conduct-GZQNFTRP.js +19 -0
- package/dist/conduct-N52JX7RT.js +52 -0
- package/dist/{conductor-mode-XU7ONJWC.js → conductor-mode-XUWGR4ZE.js} +16 -9
- package/dist/execute-PNGQOMYO.js +10 -0
- package/dist/index.d.ts +1148 -915
- package/dist/index.js +589 -127
- package/dist/{peers-G36URZYB.js → peers-K7FSHPN3.js} +2 -1
- package/dist/request-4GQSSM4B.js +196 -0
- package/dist/serve-skill-TPHZH6BS.js +104 -0
- package/dist/server-365V3GYD.js +295 -0
- package/dist/websocket-client-6IIDGXKB.js +7 -0
- package/package.json +3 -6
- package/skills/agentbnb/HEARTBEAT.rules.md +47 -0
- package/skills/agentbnb/SKILL.md +166 -0
- package/skills/agentbnb/auto-request.ts +14 -0
- package/skills/agentbnb/auto-share.ts +10 -0
- package/skills/agentbnb/bootstrap.test.ts +323 -0
- package/skills/agentbnb/bootstrap.ts +126 -0
- package/skills/agentbnb/credit-mgr.ts +11 -0
- package/skills/agentbnb/gateway.ts +12 -0
- package/skills/agentbnb/install.sh +210 -0
- package/dist/chunk-BEI5MTNZ.js +0 -91
- 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-
|
|
14
|
+
} from "../chunk-7NA43XCG.js";
|
|
7
15
|
import {
|
|
8
|
-
|
|
9
|
-
} from "../chunk-
|
|
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-
|
|
33
|
+
} from "../chunk-FF226TIV.js";
|
|
23
34
|
import {
|
|
24
35
|
requestCapability
|
|
25
|
-
} from "../chunk-
|
|
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-
|
|
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-
|
|
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
|
-
|
|
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-
|
|
77
|
+
} from "../chunk-5KFI5X7B.js";
|
|
63
78
|
import {
|
|
64
79
|
AgentBnBError,
|
|
65
80
|
AnyCardSchema,
|
|
66
81
|
CapabilityCardV2Schema
|
|
67
|
-
} from "../chunk-
|
|
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
|
|
89
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
72
90
|
import { createRequire } from "module";
|
|
73
|
-
import { randomBytes } from "crypto";
|
|
74
|
-
import { join as
|
|
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
|
|
97
|
+
import { z } from "zod";
|
|
160
98
|
import { randomUUID } from "crypto";
|
|
161
|
-
var EscrowReceiptSchema =
|
|
162
|
-
requester_owner:
|
|
163
|
-
requester_public_key:
|
|
164
|
-
amount:
|
|
165
|
-
card_id:
|
|
166
|
-
skill_id:
|
|
167
|
-
timestamp:
|
|
168
|
-
nonce:
|
|
169
|
-
signature:
|
|
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
|
|
538
|
-
import { join
|
|
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 :
|
|
654
|
-
if (
|
|
655
|
-
const content =
|
|
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 =
|
|
665
|
-
if (!
|
|
666
|
-
const content =
|
|
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
|
|
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
|
|
690
|
+
import { z as z2 } from "zod";
|
|
795
691
|
import yaml from "js-yaml";
|
|
796
|
-
var PricingSchema =
|
|
797
|
-
credits_per_call:
|
|
798
|
-
credits_per_minute:
|
|
799
|
-
free_tier:
|
|
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 =
|
|
802
|
-
|
|
803
|
-
type:
|
|
804
|
-
token:
|
|
697
|
+
var ApiAuthSchema = z2.discriminatedUnion("type", [
|
|
698
|
+
z2.object({
|
|
699
|
+
type: z2.literal("bearer"),
|
|
700
|
+
token: z2.string()
|
|
805
701
|
}),
|
|
806
|
-
|
|
807
|
-
type:
|
|
808
|
-
header:
|
|
809
|
-
key:
|
|
702
|
+
z2.object({
|
|
703
|
+
type: z2.literal("apikey"),
|
|
704
|
+
header: z2.string().default("X-API-Key"),
|
|
705
|
+
key: z2.string()
|
|
810
706
|
}),
|
|
811
|
-
|
|
812
|
-
type:
|
|
813
|
-
username:
|
|
814
|
-
password:
|
|
707
|
+
z2.object({
|
|
708
|
+
type: z2.literal("basic"),
|
|
709
|
+
username: z2.string(),
|
|
710
|
+
password: z2.string()
|
|
815
711
|
})
|
|
816
712
|
]);
|
|
817
|
-
var ApiSkillConfigSchema =
|
|
818
|
-
id:
|
|
819
|
-
type:
|
|
820
|
-
name:
|
|
821
|
-
endpoint:
|
|
822
|
-
method:
|
|
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:
|
|
825
|
-
output_mapping:
|
|
720
|
+
input_mapping: z2.record(z2.string()).default({}),
|
|
721
|
+
output_mapping: z2.record(z2.string()).default({}),
|
|
826
722
|
pricing: PricingSchema,
|
|
827
|
-
timeout_ms:
|
|
828
|
-
retries:
|
|
829
|
-
provider:
|
|
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 =
|
|
832
|
-
|
|
833
|
-
skill_id:
|
|
834
|
-
input_mapping:
|
|
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
|
-
|
|
837
|
-
command:
|
|
838
|
-
input_mapping:
|
|
732
|
+
z2.object({
|
|
733
|
+
command: z2.string().min(1),
|
|
734
|
+
input_mapping: z2.record(z2.string()).default({})
|
|
839
735
|
})
|
|
840
736
|
]);
|
|
841
|
-
var PipelineSkillConfigSchema =
|
|
842
|
-
id:
|
|
843
|
-
type:
|
|
844
|
-
name:
|
|
845
|
-
steps:
|
|
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:
|
|
743
|
+
timeout_ms: z2.number().positive().optional()
|
|
848
744
|
});
|
|
849
|
-
var OpenClawSkillConfigSchema =
|
|
850
|
-
id:
|
|
851
|
-
type:
|
|
852
|
-
name:
|
|
853
|
-
agent_name:
|
|
854
|
-
channel:
|
|
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:
|
|
752
|
+
timeout_ms: z2.number().positive().optional()
|
|
857
753
|
});
|
|
858
|
-
var CommandSkillConfigSchema =
|
|
859
|
-
id:
|
|
860
|
-
type:
|
|
861
|
-
name:
|
|
862
|
-
command:
|
|
863
|
-
output_type:
|
|
864
|
-
allowed_commands:
|
|
865
|
-
working_dir:
|
|
866
|
-
timeout_ms:
|
|
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 =
|
|
870
|
-
id:
|
|
871
|
-
type:
|
|
872
|
-
name:
|
|
873
|
-
conductor_skill:
|
|
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:
|
|
771
|
+
timeout_ms: z2.number().positive().optional()
|
|
876
772
|
});
|
|
877
|
-
var SkillConfigSchema =
|
|
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 =
|
|
885
|
-
skills:
|
|
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 &&
|
|
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 =
|
|
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-
|
|
1534
|
-
const { registerConductorCard, CONDUCTOR_OWNER } = await import("../card-
|
|
1535
|
-
const { loadPeers: loadPeers2 } = await import("../peers-
|
|
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 =
|
|
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
|
|
1649
|
+
import { join as join2, dirname } from "path";
|
|
1745
1650
|
import { fileURLToPath } from "url";
|
|
1746
|
-
import { existsSync as
|
|
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 =
|
|
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
|
|
1808
|
-
|
|
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
|
-
|
|
1811
|
-
|
|
2079
|
+
db.prepare(
|
|
2080
|
+
"UPDATE capability_cards SET data = ?, updated_at = ? WHERE id = ?"
|
|
2081
|
+
).run(JSON.stringify(card), now, cardId);
|
|
1812
2082
|
} else {
|
|
1813
|
-
|
|
1814
|
-
|
|
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:
|
|
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
|
-
|
|
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.
|
|
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.
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
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: "
|
|
1952
|
-
message:
|
|
2363
|
+
code: "invalid_message",
|
|
2364
|
+
message: `Invalid message: ${parsed.error.issues[0]?.message ?? "unknown error"}`
|
|
1953
2365
|
});
|
|
1954
2366
|
return;
|
|
1955
2367
|
}
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
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
|
|
1994
|
-
import { randomUUID as
|
|
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 =
|
|
1998
|
-
id:
|
|
1999
|
-
github_login:
|
|
2000
|
-
agent_count:
|
|
2001
|
-
credit_pool:
|
|
2002
|
-
created_at:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
3504
|
+
join2(__dirname, "../../hub/dist"),
|
|
2133
3505
|
// When running from dist/registry/server.js
|
|
2134
|
-
|
|
3506
|
+
join2(__dirname, "../../../hub/dist")
|
|
2135
3507
|
// Fallback for alternative layouts
|
|
2136
3508
|
];
|
|
2137
|
-
const hubDistDir = hubDistCandidates.find((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.
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
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
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
const
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
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
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
}
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
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
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
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
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
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
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
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
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
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
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
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
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
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
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
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
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
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
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
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
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
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
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
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
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
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
|
-
|
|
2546
|
-
}
|
|
2547
|
-
}
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
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
|
-
|
|
2563
|
-
|
|
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
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
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
|
|
4558
|
+
import { randomUUID as randomUUID9 } from "crypto";
|
|
2680
4559
|
|
|
2681
4560
|
// src/skills/publish-capability.ts
|
|
2682
|
-
import { randomUUID as
|
|
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
|
-
|
|
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
|
-
|
|
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 :
|
|
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 ??
|
|
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
|
|
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 (!
|
|
2857
|
-
|
|
4749
|
+
if (!existsSync4(heartbeatPath)) {
|
|
4750
|
+
writeFileSync(heartbeatPath, section + "\n", "utf-8");
|
|
2858
4751
|
return;
|
|
2859
4752
|
}
|
|
2860
|
-
let content =
|
|
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
|
-
|
|
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-${
|
|
2949
|
-
const token =
|
|
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 =
|
|
2952
|
-
const creditDbPath =
|
|
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 ??
|
|
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
|
-
|
|
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 =
|
|
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(
|
|
3435
|
-
const creditDb = openCreditDb(
|
|
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
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
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
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
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
|
|
5586
|
+
const settleEscrow2 = async () => {
|
|
3578
5587
|
if (useReceipt && escrowId) {
|
|
3579
|
-
|
|
3580
|
-
|
|
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
|
-
}
|
|
3586
|
-
|
|
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
|
-
|
|
3593
|
-
|
|
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
|
-
}
|
|
3599
|
-
|
|
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-
|
|
3617
|
-
const { requestViaRelay } = await import("../client-
|
|
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:
|
|
5640
|
+
owner: requesterId,
|
|
3621
5641
|
token: config.token,
|
|
3622
|
-
card: { id:
|
|
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
|
-
|
|
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
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
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 ??
|
|
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-
|
|
3805
|
-
const { executeCapabilityRequest: executeCapabilityRequest2 } = await import("../execute-
|
|
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:
|
|
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:
|
|
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 =
|
|
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-
|
|
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);
|