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