agentbnb 2.2.0 → 3.1.0
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 +81 -0
- package/dist/chunk-2LLXUKMY.js +489 -0
- package/dist/chunk-3Y36WQDV.js +70 -0
- package/dist/chunk-4Q7D24DP.js +257 -0
- package/dist/chunk-BEI5MTNZ.js +91 -0
- package/dist/chunk-QVIGMCHA.js +486 -0
- package/dist/chunk-T7ZJPQHD.js +372 -0
- package/dist/chunk-TQMI73LL.js +125 -0
- package/dist/chunk-ZJCIBK6O.js +192 -0
- package/dist/cli/index.js +715 -1538
- package/dist/conduct-M57F72RK.js +117 -0
- package/dist/conductor-mode-CF6PSRRA.js +112 -0
- package/dist/execute-3T5RF3DP.js +9 -0
- package/dist/index.js +2432 -183
- package/dist/peers-G36URZYB.js +12 -0
- package/dist/websocket-client-5TIQDYQ4.js +275 -0
- package/package.json +5 -2
- package/dist/index.d.ts +0 -676
package/dist/cli/index.js
CHANGED
|
@@ -1,709 +1,109 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
executeCapabilityRequest,
|
|
4
|
+
generateKeyPair,
|
|
5
|
+
loadKeyPair,
|
|
6
|
+
releaseRequesterEscrow,
|
|
7
|
+
saveKeyPair,
|
|
8
|
+
settleRequesterEscrow,
|
|
9
|
+
signEscrowReceipt
|
|
10
|
+
} from "../chunk-4Q7D24DP.js";
|
|
11
|
+
import {
|
|
12
|
+
RelayMessageSchema
|
|
13
|
+
} from "../chunk-3Y36WQDV.js";
|
|
14
|
+
import {
|
|
15
|
+
AutoRequestor,
|
|
16
|
+
BudgetManager,
|
|
17
|
+
DEFAULT_AUTONOMY_CONFIG,
|
|
18
|
+
DEFAULT_BUDGET_CONFIG,
|
|
19
|
+
filterCards,
|
|
20
|
+
getAutonomyTier,
|
|
21
|
+
insertAuditEvent,
|
|
22
|
+
interpolateObject,
|
|
23
|
+
listPendingRequests,
|
|
24
|
+
requestCapability,
|
|
25
|
+
resolvePendingRequest,
|
|
26
|
+
searchCards
|
|
27
|
+
} from "../chunk-QVIGMCHA.js";
|
|
28
|
+
import {
|
|
29
|
+
findPeer,
|
|
30
|
+
getConfigDir,
|
|
31
|
+
loadConfig,
|
|
32
|
+
loadPeers,
|
|
33
|
+
removePeer,
|
|
34
|
+
saveConfig,
|
|
35
|
+
savePeer
|
|
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-2LLXUKMY.js";
|
|
50
|
+
import {
|
|
51
|
+
bootstrapAgent,
|
|
52
|
+
getBalance,
|
|
53
|
+
getTransactions,
|
|
54
|
+
holdEscrow,
|
|
55
|
+
openCreditDb,
|
|
56
|
+
releaseEscrow
|
|
57
|
+
} from "../chunk-ZJCIBK6O.js";
|
|
58
|
+
import {
|
|
59
|
+
AgentBnBError,
|
|
60
|
+
CapabilityCardSchema,
|
|
61
|
+
CapabilityCardV2Schema
|
|
62
|
+
} from "../chunk-TQMI73LL.js";
|
|
2
63
|
|
|
3
64
|
// src/cli/index.ts
|
|
4
65
|
import { Command } from "commander";
|
|
5
|
-
import { readFileSync as
|
|
66
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
6
67
|
import { createRequire } from "module";
|
|
7
68
|
import { randomBytes } from "crypto";
|
|
8
|
-
import { join as join5 } from "path";
|
|
9
|
-
import { networkInterfaces, homedir as homedir2 } from "os";
|
|
10
|
-
import { createInterface } from "readline";
|
|
11
|
-
|
|
12
|
-
// src/cli/config.ts
|
|
13
|
-
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
|
|
14
|
-
import { homedir } from "os";
|
|
15
|
-
import { join } from "path";
|
|
16
|
-
function getConfigDir() {
|
|
17
|
-
return process.env["AGENTBNB_DIR"] ?? join(homedir(), ".agentbnb");
|
|
18
|
-
}
|
|
19
|
-
function getConfigPath() {
|
|
20
|
-
return join(getConfigDir(), "config.json");
|
|
21
|
-
}
|
|
22
|
-
function loadConfig() {
|
|
23
|
-
const configPath = getConfigPath();
|
|
24
|
-
if (!existsSync(configPath)) return null;
|
|
25
|
-
try {
|
|
26
|
-
const raw = readFileSync(configPath, "utf-8");
|
|
27
|
-
return JSON.parse(raw);
|
|
28
|
-
} catch {
|
|
29
|
-
return null;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
function saveConfig(config) {
|
|
33
|
-
const dir = getConfigDir();
|
|
34
|
-
if (!existsSync(dir)) {
|
|
35
|
-
mkdirSync(dir, { recursive: true });
|
|
36
|
-
}
|
|
37
|
-
writeFileSync(getConfigPath(), JSON.stringify(config, null, 2), "utf-8");
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// src/credit/signing.ts
|
|
41
|
-
import { generateKeyPairSync, sign, verify, createPublicKey, createPrivateKey } from "crypto";
|
|
42
|
-
import { writeFileSync as writeFileSync2, readFileSync as readFileSync2, existsSync as existsSync2, chmodSync } from "fs";
|
|
43
69
|
import { join as join2 } from "path";
|
|
70
|
+
import { networkInterfaces, homedir } from "os";
|
|
71
|
+
import { createInterface } from "readline";
|
|
44
72
|
|
|
45
|
-
// src/
|
|
73
|
+
// src/credit/escrow-receipt.ts
|
|
46
74
|
import { z } from "zod";
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
model: z.string().optional(),
|
|
58
|
-
tier: z.string().optional()
|
|
59
|
-
});
|
|
60
|
-
var CapabilityCardSchema = z.object({
|
|
61
|
-
spec_version: z.literal("1.0").default("1.0"),
|
|
62
|
-
id: z.string().uuid(),
|
|
63
|
-
owner: z.string().min(1),
|
|
64
|
-
name: z.string().min(1).max(100),
|
|
65
|
-
description: z.string().max(500),
|
|
66
|
-
level: z.union([z.literal(1), z.literal(2), z.literal(3)]),
|
|
67
|
-
inputs: z.array(IOSchemaSchema),
|
|
68
|
-
outputs: z.array(IOSchemaSchema),
|
|
69
|
-
pricing: z.object({
|
|
70
|
-
credits_per_call: z.number().nonnegative(),
|
|
71
|
-
credits_per_minute: z.number().nonnegative().optional(),
|
|
72
|
-
/** Number of free monthly calls. Shown as a "N free/mo" badge in the Hub. */
|
|
73
|
-
free_tier: z.number().nonnegative().optional()
|
|
74
|
-
}),
|
|
75
|
-
availability: z.object({
|
|
76
|
-
online: z.boolean(),
|
|
77
|
-
schedule: z.string().optional()
|
|
78
|
-
// cron expression
|
|
79
|
-
}),
|
|
80
|
-
powered_by: z.array(PoweredBySchema).optional(),
|
|
81
|
-
/**
|
|
82
|
-
* Private per-card metadata. Stripped from all API and CLI responses —
|
|
83
|
-
* never transmitted beyond the local store.
|
|
84
|
-
*/
|
|
85
|
-
_internal: z.record(z.unknown()).optional(),
|
|
86
|
-
metadata: z.object({
|
|
87
|
-
apis_used: z.array(z.string()).optional(),
|
|
88
|
-
avg_latency_ms: z.number().nonnegative().optional(),
|
|
89
|
-
success_rate: z.number().min(0).max(1).optional(),
|
|
90
|
-
tags: z.array(z.string()).optional()
|
|
91
|
-
}).optional(),
|
|
92
|
-
created_at: z.string().datetime().optional(),
|
|
93
|
-
updated_at: z.string().datetime().optional()
|
|
94
|
-
});
|
|
95
|
-
var SkillSchema = z.object({
|
|
96
|
-
/** Stable skill identifier, e.g. 'tts-elevenlabs'. Used for gateway routing and idle tracking. */
|
|
97
|
-
id: z.string().min(1),
|
|
98
|
-
name: z.string().min(1).max(100),
|
|
99
|
-
description: z.string().max(500),
|
|
100
|
-
level: z.union([z.literal(1), z.literal(2), z.literal(3)]),
|
|
101
|
-
/** Optional grouping category, e.g. 'tts' | 'video_gen' | 'code_review'. */
|
|
102
|
-
category: z.string().optional(),
|
|
103
|
-
inputs: z.array(IOSchemaSchema),
|
|
104
|
-
outputs: z.array(IOSchemaSchema),
|
|
105
|
-
pricing: z.object({
|
|
106
|
-
credits_per_call: z.number().nonnegative(),
|
|
107
|
-
credits_per_minute: z.number().nonnegative().optional(),
|
|
108
|
-
free_tier: z.number().nonnegative().optional()
|
|
109
|
-
}),
|
|
110
|
-
/** Per-skill online flag — overrides card-level availability for this skill. */
|
|
111
|
-
availability: z.object({ online: z.boolean() }).optional(),
|
|
112
|
-
powered_by: z.array(PoweredBySchema).optional(),
|
|
113
|
-
metadata: z.object({
|
|
114
|
-
apis_used: z.array(z.string()).optional(),
|
|
115
|
-
avg_latency_ms: z.number().nonnegative().optional(),
|
|
116
|
-
success_rate: z.number().min(0).max(1).optional(),
|
|
117
|
-
tags: z.array(z.string()).optional(),
|
|
118
|
-
capacity: z.object({
|
|
119
|
-
calls_per_hour: z.number().positive().default(60)
|
|
120
|
-
}).optional()
|
|
121
|
-
}).optional(),
|
|
122
|
-
/**
|
|
123
|
-
* Private per-skill metadata. Stripped from all API and CLI responses —
|
|
124
|
-
* never transmitted beyond the local store.
|
|
125
|
-
*/
|
|
126
|
-
_internal: z.record(z.unknown()).optional()
|
|
127
|
-
});
|
|
128
|
-
var CapabilityCardV2Schema = z.object({
|
|
129
|
-
spec_version: z.literal("2.0"),
|
|
130
|
-
id: z.string().uuid(),
|
|
131
|
-
owner: z.string().min(1),
|
|
132
|
-
/** Agent display name — was 'name' in v1.0. */
|
|
133
|
-
agent_name: z.string().min(1).max(100),
|
|
134
|
-
/** At least one skill is required. */
|
|
135
|
-
skills: z.array(SkillSchema).min(1),
|
|
136
|
-
availability: z.object({
|
|
137
|
-
online: z.boolean(),
|
|
138
|
-
schedule: z.string().optional()
|
|
139
|
-
}),
|
|
140
|
-
/** Optional deployment environment metadata. */
|
|
141
|
-
environment: z.object({
|
|
142
|
-
runtime: z.string(),
|
|
143
|
-
region: z.string().optional()
|
|
144
|
-
}).optional(),
|
|
145
|
-
/**
|
|
146
|
-
* Private per-card metadata. Stripped from all API and CLI responses —
|
|
147
|
-
* never transmitted beyond the local store.
|
|
148
|
-
*/
|
|
149
|
-
_internal: z.record(z.unknown()).optional(),
|
|
150
|
-
created_at: z.string().datetime().optional(),
|
|
151
|
-
updated_at: z.string().datetime().optional()
|
|
75
|
+
import { randomUUID } from "crypto";
|
|
76
|
+
var EscrowReceiptSchema = z.object({
|
|
77
|
+
requester_owner: z.string().min(1),
|
|
78
|
+
requester_public_key: z.string().min(1),
|
|
79
|
+
amount: z.number().positive(),
|
|
80
|
+
card_id: z.string().min(1),
|
|
81
|
+
skill_id: z.string().optional(),
|
|
82
|
+
timestamp: z.string(),
|
|
83
|
+
nonce: z.string().uuid(),
|
|
84
|
+
signature: z.string().min(1)
|
|
152
85
|
});
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
};
|
|
164
|
-
|
|
165
|
-
// src/credit/signing.ts
|
|
166
|
-
function generateKeyPair() {
|
|
167
|
-
const { publicKey, privateKey } = generateKeyPairSync("ed25519", {
|
|
168
|
-
publicKeyEncoding: { type: "spki", format: "der" },
|
|
169
|
-
privateKeyEncoding: { type: "pkcs8", format: "der" }
|
|
170
|
-
});
|
|
171
|
-
return {
|
|
172
|
-
publicKey: Buffer.from(publicKey),
|
|
173
|
-
privateKey: Buffer.from(privateKey)
|
|
86
|
+
function createSignedEscrowReceipt(db, privateKey, publicKey, opts) {
|
|
87
|
+
const escrowId = holdEscrow(db, opts.owner, opts.amount, opts.cardId);
|
|
88
|
+
const receiptData = {
|
|
89
|
+
requester_owner: opts.owner,
|
|
90
|
+
requester_public_key: publicKey.toString("hex"),
|
|
91
|
+
amount: opts.amount,
|
|
92
|
+
card_id: opts.cardId,
|
|
93
|
+
...opts.skillId ? { skill_id: opts.skillId } : {},
|
|
94
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
95
|
+
nonce: randomUUID()
|
|
174
96
|
};
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
writeFileSync2(privatePath, keys.privateKey);
|
|
180
|
-
chmodSync(privatePath, 384);
|
|
181
|
-
writeFileSync2(publicPath, keys.publicKey);
|
|
182
|
-
}
|
|
183
|
-
function loadKeyPair(configDir) {
|
|
184
|
-
const privatePath = join2(configDir, "private.key");
|
|
185
|
-
const publicPath = join2(configDir, "public.key");
|
|
186
|
-
if (!existsSync2(privatePath) || !existsSync2(publicPath)) {
|
|
187
|
-
throw new AgentBnBError("Keypair not found. Run `agentbnb init` to generate one.", "KEYPAIR_NOT_FOUND");
|
|
188
|
-
}
|
|
189
|
-
return {
|
|
190
|
-
publicKey: readFileSync2(publicPath),
|
|
191
|
-
privateKey: readFileSync2(privatePath)
|
|
97
|
+
const signature = signEscrowReceipt(receiptData, privateKey);
|
|
98
|
+
const receipt = {
|
|
99
|
+
...receiptData,
|
|
100
|
+
signature
|
|
192
101
|
};
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// src/autonomy/tiers.ts
|
|
196
|
-
import { randomUUID } from "crypto";
|
|
197
|
-
var DEFAULT_AUTONOMY_CONFIG = {
|
|
198
|
-
tier1_max_credits: 0,
|
|
199
|
-
tier2_max_credits: 0
|
|
200
|
-
};
|
|
201
|
-
function getAutonomyTier(creditAmount, config) {
|
|
202
|
-
if (creditAmount < config.tier1_max_credits) return 1;
|
|
203
|
-
if (creditAmount < config.tier2_max_credits) return 2;
|
|
204
|
-
return 3;
|
|
205
|
-
}
|
|
206
|
-
function insertAuditEvent(db, event) {
|
|
207
|
-
const isShareEvent = event.type === "auto_share" || event.type === "auto_share_notify" || event.type === "auto_share_pending";
|
|
208
|
-
const cardId = isShareEvent ? "system" : event.card_id;
|
|
209
|
-
const creditsCharged = isShareEvent ? 0 : event.credits;
|
|
210
|
-
const stmt = db.prepare(`
|
|
211
|
-
INSERT INTO request_log (
|
|
212
|
-
id, card_id, card_name, requester, status, latency_ms, credits_charged,
|
|
213
|
-
created_at, skill_id, action_type, tier_invoked
|
|
214
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
215
|
-
`);
|
|
216
|
-
stmt.run(
|
|
217
|
-
randomUUID(),
|
|
218
|
-
cardId,
|
|
219
|
-
"autonomy-audit",
|
|
220
|
-
"self",
|
|
221
|
-
"success",
|
|
222
|
-
0,
|
|
223
|
-
creditsCharged,
|
|
224
|
-
(/* @__PURE__ */ new Date()).toISOString(),
|
|
225
|
-
event.skill_id,
|
|
226
|
-
event.type,
|
|
227
|
-
event.tier_invoked
|
|
228
|
-
);
|
|
102
|
+
return { escrowId, receipt };
|
|
229
103
|
}
|
|
230
104
|
|
|
231
105
|
// src/autonomy/idle-monitor.ts
|
|
232
106
|
import { Cron } from "croner";
|
|
233
|
-
|
|
234
|
-
// src/registry/store.ts
|
|
235
|
-
import Database from "better-sqlite3";
|
|
236
|
-
|
|
237
|
-
// src/registry/request-log.ts
|
|
238
|
-
var SINCE_MS = {
|
|
239
|
-
"24h": 864e5,
|
|
240
|
-
"7d": 6048e5,
|
|
241
|
-
"30d": 2592e6
|
|
242
|
-
};
|
|
243
|
-
function createRequestLogTable(db) {
|
|
244
|
-
db.exec(`
|
|
245
|
-
CREATE TABLE IF NOT EXISTS request_log (
|
|
246
|
-
id TEXT PRIMARY KEY,
|
|
247
|
-
card_id TEXT NOT NULL,
|
|
248
|
-
card_name TEXT NOT NULL,
|
|
249
|
-
requester TEXT NOT NULL,
|
|
250
|
-
status TEXT NOT NULL CHECK(status IN ('success', 'failure', 'timeout')),
|
|
251
|
-
latency_ms INTEGER NOT NULL,
|
|
252
|
-
credits_charged INTEGER NOT NULL,
|
|
253
|
-
created_at TEXT NOT NULL
|
|
254
|
-
);
|
|
255
|
-
|
|
256
|
-
CREATE INDEX IF NOT EXISTS request_log_created_at_idx
|
|
257
|
-
ON request_log (created_at DESC);
|
|
258
|
-
`);
|
|
259
|
-
try {
|
|
260
|
-
db.exec("ALTER TABLE request_log ADD COLUMN skill_id TEXT");
|
|
261
|
-
} catch {
|
|
262
|
-
}
|
|
263
|
-
try {
|
|
264
|
-
db.exec("ALTER TABLE request_log ADD COLUMN action_type TEXT");
|
|
265
|
-
} catch {
|
|
266
|
-
}
|
|
267
|
-
try {
|
|
268
|
-
db.exec("ALTER TABLE request_log ADD COLUMN tier_invoked INTEGER");
|
|
269
|
-
} catch {
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
function insertRequestLog(db, entry) {
|
|
273
|
-
const stmt = db.prepare(`
|
|
274
|
-
INSERT INTO request_log (id, card_id, card_name, requester, status, latency_ms, credits_charged, created_at, skill_id, action_type, tier_invoked)
|
|
275
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
276
|
-
`);
|
|
277
|
-
stmt.run(
|
|
278
|
-
entry.id,
|
|
279
|
-
entry.card_id,
|
|
280
|
-
entry.card_name,
|
|
281
|
-
entry.requester,
|
|
282
|
-
entry.status,
|
|
283
|
-
entry.latency_ms,
|
|
284
|
-
entry.credits_charged,
|
|
285
|
-
entry.created_at,
|
|
286
|
-
entry.skill_id ?? null,
|
|
287
|
-
entry.action_type ?? null,
|
|
288
|
-
entry.tier_invoked ?? null
|
|
289
|
-
);
|
|
290
|
-
}
|
|
291
|
-
function getSkillRequestCount(db, skillId, windowMs) {
|
|
292
|
-
const cutoff = new Date(Date.now() - windowMs).toISOString();
|
|
293
|
-
const stmt = db.prepare(
|
|
294
|
-
`SELECT COUNT(*) as cnt FROM request_log
|
|
295
|
-
WHERE skill_id = ? AND created_at >= ? AND status = 'success' AND action_type IS NULL`
|
|
296
|
-
);
|
|
297
|
-
const row = stmt.get(skillId, cutoff);
|
|
298
|
-
return row.cnt;
|
|
299
|
-
}
|
|
300
|
-
function getActivityFeed(db, limit = 20, since) {
|
|
301
|
-
const effectiveLimit = Math.min(limit, 100);
|
|
302
|
-
if (since !== void 0) {
|
|
303
|
-
const stmt2 = db.prepare(`
|
|
304
|
-
SELECT r.id, r.card_name, r.requester, c.owner AS provider,
|
|
305
|
-
r.status, r.credits_charged, r.latency_ms, r.created_at, r.action_type
|
|
306
|
-
FROM request_log r
|
|
307
|
-
LEFT JOIN capability_cards c ON r.card_id = c.id
|
|
308
|
-
WHERE (r.action_type IS NULL OR r.action_type = 'auto_share')
|
|
309
|
-
AND r.created_at > ?
|
|
310
|
-
ORDER BY r.created_at DESC
|
|
311
|
-
LIMIT ?
|
|
312
|
-
`);
|
|
313
|
-
return stmt2.all(since, effectiveLimit);
|
|
314
|
-
}
|
|
315
|
-
const stmt = db.prepare(`
|
|
316
|
-
SELECT r.id, r.card_name, r.requester, c.owner AS provider,
|
|
317
|
-
r.status, r.credits_charged, r.latency_ms, r.created_at, r.action_type
|
|
318
|
-
FROM request_log r
|
|
319
|
-
LEFT JOIN capability_cards c ON r.card_id = c.id
|
|
320
|
-
WHERE (r.action_type IS NULL OR r.action_type = 'auto_share')
|
|
321
|
-
ORDER BY r.created_at DESC
|
|
322
|
-
LIMIT ?
|
|
323
|
-
`);
|
|
324
|
-
return stmt.all(effectiveLimit);
|
|
325
|
-
}
|
|
326
|
-
function getRequestLog(db, limit = 10, since) {
|
|
327
|
-
if (since !== void 0) {
|
|
328
|
-
const cutoff = new Date(Date.now() - SINCE_MS[since]).toISOString();
|
|
329
|
-
const stmt2 = db.prepare(`
|
|
330
|
-
SELECT id, card_id, card_name, requester, status, latency_ms, credits_charged, created_at, skill_id, action_type, tier_invoked
|
|
331
|
-
FROM request_log
|
|
332
|
-
WHERE created_at >= ?
|
|
333
|
-
ORDER BY created_at DESC
|
|
334
|
-
LIMIT ?
|
|
335
|
-
`);
|
|
336
|
-
return stmt2.all(cutoff, limit);
|
|
337
|
-
}
|
|
338
|
-
const stmt = db.prepare(`
|
|
339
|
-
SELECT id, card_id, card_name, requester, status, latency_ms, credits_charged, created_at, skill_id, action_type, tier_invoked
|
|
340
|
-
FROM request_log
|
|
341
|
-
ORDER BY created_at DESC
|
|
342
|
-
LIMIT ?
|
|
343
|
-
`);
|
|
344
|
-
return stmt.all(limit);
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
// src/registry/store.ts
|
|
348
|
-
var V2_FTS_TRIGGERS = `
|
|
349
|
-
DROP TRIGGER IF EXISTS cards_ai;
|
|
350
|
-
DROP TRIGGER IF EXISTS cards_au;
|
|
351
|
-
DROP TRIGGER IF EXISTS cards_ad;
|
|
352
|
-
|
|
353
|
-
CREATE TRIGGER cards_ai AFTER INSERT ON capability_cards BEGIN
|
|
354
|
-
INSERT INTO cards_fts(rowid, id, owner, name, description, tags)
|
|
355
|
-
VALUES (
|
|
356
|
-
new.rowid,
|
|
357
|
-
new.id,
|
|
358
|
-
new.owner,
|
|
359
|
-
COALESCE(
|
|
360
|
-
(SELECT group_concat(json_extract(value, '$.name'), ' ')
|
|
361
|
-
FROM json_each(json_extract(new.data, '$.skills'))),
|
|
362
|
-
json_extract(new.data, '$.name'),
|
|
363
|
-
''
|
|
364
|
-
),
|
|
365
|
-
COALESCE(
|
|
366
|
-
(SELECT group_concat(json_extract(value, '$.description'), ' ')
|
|
367
|
-
FROM json_each(json_extract(new.data, '$.skills'))),
|
|
368
|
-
json_extract(new.data, '$.description'),
|
|
369
|
-
''
|
|
370
|
-
),
|
|
371
|
-
COALESCE(
|
|
372
|
-
(SELECT group_concat(json_extract(value, '$.metadata.tags'), ' ')
|
|
373
|
-
FROM json_each(json_extract(new.data, '$.skills'))),
|
|
374
|
-
(SELECT group_concat(value, ' ')
|
|
375
|
-
FROM json_each(json_extract(new.data, '$.metadata.tags'))),
|
|
376
|
-
''
|
|
377
|
-
)
|
|
378
|
-
);
|
|
379
|
-
END;
|
|
380
|
-
|
|
381
|
-
CREATE TRIGGER cards_au AFTER UPDATE ON capability_cards BEGIN
|
|
382
|
-
INSERT INTO cards_fts(cards_fts, rowid, id, owner, name, description, tags)
|
|
383
|
-
VALUES (
|
|
384
|
-
'delete',
|
|
385
|
-
old.rowid,
|
|
386
|
-
old.id,
|
|
387
|
-
old.owner,
|
|
388
|
-
COALESCE(
|
|
389
|
-
(SELECT group_concat(json_extract(value, '$.name'), ' ')
|
|
390
|
-
FROM json_each(json_extract(old.data, '$.skills'))),
|
|
391
|
-
json_extract(old.data, '$.name'),
|
|
392
|
-
''
|
|
393
|
-
),
|
|
394
|
-
COALESCE(
|
|
395
|
-
(SELECT group_concat(json_extract(value, '$.description'), ' ')
|
|
396
|
-
FROM json_each(json_extract(old.data, '$.skills'))),
|
|
397
|
-
json_extract(old.data, '$.description'),
|
|
398
|
-
''
|
|
399
|
-
),
|
|
400
|
-
COALESCE(
|
|
401
|
-
(SELECT group_concat(json_extract(value, '$.metadata.tags'), ' ')
|
|
402
|
-
FROM json_each(json_extract(old.data, '$.skills'))),
|
|
403
|
-
(SELECT group_concat(value, ' ')
|
|
404
|
-
FROM json_each(json_extract(old.data, '$.metadata.tags'))),
|
|
405
|
-
''
|
|
406
|
-
)
|
|
407
|
-
);
|
|
408
|
-
INSERT INTO cards_fts(rowid, id, owner, name, description, tags)
|
|
409
|
-
VALUES (
|
|
410
|
-
new.rowid,
|
|
411
|
-
new.id,
|
|
412
|
-
new.owner,
|
|
413
|
-
COALESCE(
|
|
414
|
-
(SELECT group_concat(json_extract(value, '$.name'), ' ')
|
|
415
|
-
FROM json_each(json_extract(new.data, '$.skills'))),
|
|
416
|
-
json_extract(new.data, '$.name'),
|
|
417
|
-
''
|
|
418
|
-
),
|
|
419
|
-
COALESCE(
|
|
420
|
-
(SELECT group_concat(json_extract(value, '$.description'), ' ')
|
|
421
|
-
FROM json_each(json_extract(new.data, '$.skills'))),
|
|
422
|
-
json_extract(new.data, '$.description'),
|
|
423
|
-
''
|
|
424
|
-
),
|
|
425
|
-
COALESCE(
|
|
426
|
-
(SELECT group_concat(json_extract(value, '$.metadata.tags'), ' ')
|
|
427
|
-
FROM json_each(json_extract(new.data, '$.skills'))),
|
|
428
|
-
(SELECT group_concat(value, ' ')
|
|
429
|
-
FROM json_each(json_extract(new.data, '$.metadata.tags'))),
|
|
430
|
-
''
|
|
431
|
-
)
|
|
432
|
-
);
|
|
433
|
-
END;
|
|
434
|
-
|
|
435
|
-
CREATE TRIGGER cards_ad AFTER DELETE ON capability_cards BEGIN
|
|
436
|
-
INSERT INTO cards_fts(cards_fts, rowid, id, owner, name, description, tags)
|
|
437
|
-
VALUES (
|
|
438
|
-
'delete',
|
|
439
|
-
old.rowid,
|
|
440
|
-
old.id,
|
|
441
|
-
old.owner,
|
|
442
|
-
COALESCE(
|
|
443
|
-
(SELECT group_concat(json_extract(value, '$.name'), ' ')
|
|
444
|
-
FROM json_each(json_extract(old.data, '$.skills'))),
|
|
445
|
-
json_extract(old.data, '$.name'),
|
|
446
|
-
''
|
|
447
|
-
),
|
|
448
|
-
COALESCE(
|
|
449
|
-
(SELECT group_concat(json_extract(value, '$.description'), ' ')
|
|
450
|
-
FROM json_each(json_extract(old.data, '$.skills'))),
|
|
451
|
-
json_extract(old.data, '$.description'),
|
|
452
|
-
''
|
|
453
|
-
),
|
|
454
|
-
COALESCE(
|
|
455
|
-
(SELECT group_concat(json_extract(value, '$.metadata.tags'), ' ')
|
|
456
|
-
FROM json_each(json_extract(old.data, '$.skills'))),
|
|
457
|
-
(SELECT group_concat(value, ' ')
|
|
458
|
-
FROM json_each(json_extract(old.data, '$.metadata.tags'))),
|
|
459
|
-
''
|
|
460
|
-
)
|
|
461
|
-
);
|
|
462
|
-
END;
|
|
463
|
-
`;
|
|
464
|
-
function openDatabase(path = ":memory:") {
|
|
465
|
-
const db = new Database(path);
|
|
466
|
-
db.pragma("journal_mode = WAL");
|
|
467
|
-
db.pragma("foreign_keys = ON");
|
|
468
|
-
db.exec(`
|
|
469
|
-
CREATE TABLE IF NOT EXISTS capability_cards (
|
|
470
|
-
id TEXT PRIMARY KEY,
|
|
471
|
-
owner TEXT NOT NULL,
|
|
472
|
-
data TEXT NOT NULL,
|
|
473
|
-
created_at TEXT NOT NULL,
|
|
474
|
-
updated_at TEXT NOT NULL
|
|
475
|
-
);
|
|
476
|
-
|
|
477
|
-
CREATE TABLE IF NOT EXISTS pending_requests (
|
|
478
|
-
id TEXT PRIMARY KEY,
|
|
479
|
-
skill_query TEXT NOT NULL,
|
|
480
|
-
max_cost_credits REAL NOT NULL,
|
|
481
|
-
selected_peer TEXT,
|
|
482
|
-
selected_card_id TEXT,
|
|
483
|
-
selected_skill_id TEXT,
|
|
484
|
-
credits REAL NOT NULL,
|
|
485
|
-
status TEXT NOT NULL DEFAULT 'pending',
|
|
486
|
-
params TEXT,
|
|
487
|
-
created_at TEXT NOT NULL,
|
|
488
|
-
resolved_at TEXT
|
|
489
|
-
);
|
|
490
|
-
|
|
491
|
-
CREATE VIRTUAL TABLE IF NOT EXISTS cards_fts USING fts5(
|
|
492
|
-
id UNINDEXED,
|
|
493
|
-
owner,
|
|
494
|
-
name,
|
|
495
|
-
description,
|
|
496
|
-
tags,
|
|
497
|
-
content=""
|
|
498
|
-
);
|
|
499
|
-
`);
|
|
500
|
-
createRequestLogTable(db);
|
|
501
|
-
runMigrations(db);
|
|
502
|
-
return db;
|
|
503
|
-
}
|
|
504
|
-
function runMigrations(db) {
|
|
505
|
-
const version = db.pragma("user_version")[0]?.user_version ?? 0;
|
|
506
|
-
if (version < 2) {
|
|
507
|
-
migrateV1toV2(db);
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
function migrateV1toV2(db) {
|
|
511
|
-
const migrate = db.transaction(() => {
|
|
512
|
-
const rows = db.prepare("SELECT rowid, id, data FROM capability_cards").all();
|
|
513
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
514
|
-
for (const row of rows) {
|
|
515
|
-
const parsed = JSON.parse(row.data);
|
|
516
|
-
if (parsed["spec_version"] === "2.0") continue;
|
|
517
|
-
const v1 = parsed;
|
|
518
|
-
const v2 = {
|
|
519
|
-
spec_version: "2.0",
|
|
520
|
-
id: v1.id,
|
|
521
|
-
owner: v1.owner,
|
|
522
|
-
agent_name: v1.name,
|
|
523
|
-
skills: [
|
|
524
|
-
{
|
|
525
|
-
id: `skill-${v1.id}`,
|
|
526
|
-
name: v1.name,
|
|
527
|
-
description: v1.description,
|
|
528
|
-
level: v1.level,
|
|
529
|
-
inputs: v1.inputs,
|
|
530
|
-
outputs: v1.outputs,
|
|
531
|
-
pricing: v1.pricing,
|
|
532
|
-
availability: { online: v1.availability.online },
|
|
533
|
-
powered_by: v1.powered_by,
|
|
534
|
-
metadata: v1.metadata,
|
|
535
|
-
_internal: v1._internal
|
|
536
|
-
}
|
|
537
|
-
],
|
|
538
|
-
availability: v1.availability,
|
|
539
|
-
created_at: v1.created_at,
|
|
540
|
-
updated_at: now
|
|
541
|
-
};
|
|
542
|
-
db.prepare("UPDATE capability_cards SET data = ?, updated_at = ? WHERE id = ?").run(
|
|
543
|
-
JSON.stringify(v2),
|
|
544
|
-
now,
|
|
545
|
-
v2.id
|
|
546
|
-
);
|
|
547
|
-
}
|
|
548
|
-
db.exec(V2_FTS_TRIGGERS);
|
|
549
|
-
db.exec(`INSERT INTO cards_fts(cards_fts) VALUES('delete-all')`);
|
|
550
|
-
const allRows = db.prepare("SELECT rowid, id, owner, data FROM capability_cards").all();
|
|
551
|
-
const ftsInsert = db.prepare(
|
|
552
|
-
"INSERT INTO cards_fts(rowid, id, owner, name, description, tags) VALUES (?, ?, ?, ?, ?, ?)"
|
|
553
|
-
);
|
|
554
|
-
for (const row of allRows) {
|
|
555
|
-
const data = JSON.parse(row.data);
|
|
556
|
-
const skills = data["skills"] ?? [];
|
|
557
|
-
let name;
|
|
558
|
-
let description;
|
|
559
|
-
let tags;
|
|
560
|
-
if (skills.length > 0) {
|
|
561
|
-
name = skills.map((s) => String(s["name"] ?? "")).join(" ");
|
|
562
|
-
description = skills.map((s) => String(s["description"] ?? "")).join(" ");
|
|
563
|
-
tags = skills.flatMap((s) => {
|
|
564
|
-
const meta = s["metadata"];
|
|
565
|
-
return meta?.["tags"] ?? [];
|
|
566
|
-
}).join(" ");
|
|
567
|
-
} else {
|
|
568
|
-
name = String(data["name"] ?? "");
|
|
569
|
-
description = String(data["description"] ?? "");
|
|
570
|
-
const meta = data["metadata"];
|
|
571
|
-
const rawTags = meta?.["tags"] ?? [];
|
|
572
|
-
tags = rawTags.join(" ");
|
|
573
|
-
}
|
|
574
|
-
ftsInsert.run(row.rowid, row.id, row.owner, name, description, tags);
|
|
575
|
-
}
|
|
576
|
-
db.pragma("user_version = 2");
|
|
577
|
-
});
|
|
578
|
-
migrate();
|
|
579
|
-
}
|
|
580
|
-
function insertCard(db, card) {
|
|
581
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
582
|
-
const withTimestamps = { ...card, created_at: card.created_at ?? now, updated_at: now };
|
|
583
|
-
const parsed = CapabilityCardSchema.safeParse(withTimestamps);
|
|
584
|
-
if (!parsed.success) {
|
|
585
|
-
throw new AgentBnBError(
|
|
586
|
-
`Card validation failed: ${parsed.error.message}`,
|
|
587
|
-
"VALIDATION_ERROR"
|
|
588
|
-
);
|
|
589
|
-
}
|
|
590
|
-
const stmt = db.prepare(`
|
|
591
|
-
INSERT INTO capability_cards (id, owner, data, created_at, updated_at)
|
|
592
|
-
VALUES (?, ?, ?, ?, ?)
|
|
593
|
-
`);
|
|
594
|
-
stmt.run(
|
|
595
|
-
parsed.data.id,
|
|
596
|
-
parsed.data.owner,
|
|
597
|
-
JSON.stringify(parsed.data),
|
|
598
|
-
parsed.data.created_at ?? now,
|
|
599
|
-
parsed.data.updated_at ?? now
|
|
600
|
-
);
|
|
601
|
-
}
|
|
602
|
-
function getCard(db, id) {
|
|
603
|
-
const stmt = db.prepare("SELECT data FROM capability_cards WHERE id = ?");
|
|
604
|
-
const row = stmt.get(id);
|
|
605
|
-
if (!row) return null;
|
|
606
|
-
return JSON.parse(row.data);
|
|
607
|
-
}
|
|
608
|
-
function updateCard(db, id, owner, updates) {
|
|
609
|
-
const existing = getCard(db, id);
|
|
610
|
-
if (!existing) {
|
|
611
|
-
throw new AgentBnBError(`Card not found: ${id}`, "NOT_FOUND");
|
|
612
|
-
}
|
|
613
|
-
if (existing.owner !== owner) {
|
|
614
|
-
throw new AgentBnBError("Forbidden: you do not own this card", "FORBIDDEN");
|
|
615
|
-
}
|
|
616
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
617
|
-
const merged = { ...existing, ...updates, updated_at: now };
|
|
618
|
-
const parsed = CapabilityCardSchema.safeParse(merged);
|
|
619
|
-
if (!parsed.success) {
|
|
620
|
-
throw new AgentBnBError(
|
|
621
|
-
`Card validation failed: ${parsed.error.message}`,
|
|
622
|
-
"VALIDATION_ERROR"
|
|
623
|
-
);
|
|
624
|
-
}
|
|
625
|
-
const stmt = db.prepare(`
|
|
626
|
-
UPDATE capability_cards
|
|
627
|
-
SET data = ?, updated_at = ?
|
|
628
|
-
WHERE id = ?
|
|
629
|
-
`);
|
|
630
|
-
stmt.run(JSON.stringify(parsed.data), now, id);
|
|
631
|
-
}
|
|
632
|
-
function updateReputation(db, cardId, success, latencyMs) {
|
|
633
|
-
const existing = getCard(db, cardId);
|
|
634
|
-
if (!existing) return;
|
|
635
|
-
const ALPHA = 0.1;
|
|
636
|
-
const observed = success ? 1 : 0;
|
|
637
|
-
const prevSuccessRate = existing.metadata?.success_rate;
|
|
638
|
-
const prevLatency = existing.metadata?.avg_latency_ms;
|
|
639
|
-
const newSuccessRate = prevSuccessRate === void 0 ? observed : ALPHA * observed + (1 - ALPHA) * prevSuccessRate;
|
|
640
|
-
const newLatency = prevLatency === void 0 ? latencyMs : ALPHA * latencyMs + (1 - ALPHA) * prevLatency;
|
|
641
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
642
|
-
const updatedMetadata = {
|
|
643
|
-
...existing.metadata,
|
|
644
|
-
success_rate: Math.round(newSuccessRate * 1e3) / 1e3,
|
|
645
|
-
avg_latency_ms: Math.round(newLatency)
|
|
646
|
-
};
|
|
647
|
-
const updatedCard = { ...existing, metadata: updatedMetadata, updated_at: now };
|
|
648
|
-
const stmt = db.prepare(`
|
|
649
|
-
UPDATE capability_cards
|
|
650
|
-
SET data = ?, updated_at = ?
|
|
651
|
-
WHERE id = ?
|
|
652
|
-
`);
|
|
653
|
-
stmt.run(JSON.stringify(updatedCard), now, cardId);
|
|
654
|
-
}
|
|
655
|
-
function updateSkillAvailability(db, cardId, skillId, online) {
|
|
656
|
-
const row = db.prepare("SELECT data FROM capability_cards WHERE id = ?").get(cardId);
|
|
657
|
-
if (!row) return;
|
|
658
|
-
const card = JSON.parse(row.data);
|
|
659
|
-
const skills = card["skills"];
|
|
660
|
-
if (!skills) return;
|
|
661
|
-
const skill = skills.find((s) => s["id"] === skillId);
|
|
662
|
-
if (!skill) return;
|
|
663
|
-
const existing = skill["availability"] ?? {};
|
|
664
|
-
skill["availability"] = { ...existing, online };
|
|
665
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
666
|
-
db.prepare("UPDATE capability_cards SET data = ?, updated_at = ? WHERE id = ?").run(
|
|
667
|
-
JSON.stringify(card),
|
|
668
|
-
now,
|
|
669
|
-
cardId
|
|
670
|
-
);
|
|
671
|
-
}
|
|
672
|
-
function updateSkillIdleRate(db, cardId, skillId, idleRate) {
|
|
673
|
-
const row = db.prepare("SELECT data FROM capability_cards WHERE id = ?").get(cardId);
|
|
674
|
-
if (!row) return;
|
|
675
|
-
const card = JSON.parse(row.data);
|
|
676
|
-
const skills = card["skills"];
|
|
677
|
-
if (!skills) return;
|
|
678
|
-
const skill = skills.find((s) => s["id"] === skillId);
|
|
679
|
-
if (!skill) return;
|
|
680
|
-
const existing = skill["_internal"] ?? {};
|
|
681
|
-
skill["_internal"] = {
|
|
682
|
-
...existing,
|
|
683
|
-
idle_rate: idleRate,
|
|
684
|
-
idle_rate_computed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
685
|
-
};
|
|
686
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
687
|
-
db.prepare("UPDATE capability_cards SET data = ?, updated_at = ? WHERE id = ?").run(
|
|
688
|
-
JSON.stringify(card),
|
|
689
|
-
now,
|
|
690
|
-
cardId
|
|
691
|
-
);
|
|
692
|
-
}
|
|
693
|
-
function listCards(db, owner) {
|
|
694
|
-
let stmt;
|
|
695
|
-
let rows;
|
|
696
|
-
if (owner !== void 0) {
|
|
697
|
-
stmt = db.prepare("SELECT data FROM capability_cards WHERE owner = ?");
|
|
698
|
-
rows = stmt.all(owner);
|
|
699
|
-
} else {
|
|
700
|
-
stmt = db.prepare("SELECT data FROM capability_cards");
|
|
701
|
-
rows = stmt.all();
|
|
702
|
-
}
|
|
703
|
-
return rows.map((row) => JSON.parse(row.data));
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
// src/autonomy/idle-monitor.ts
|
|
707
107
|
var IdleMonitor = class {
|
|
708
108
|
job;
|
|
709
109
|
owner;
|
|
@@ -795,566 +195,6 @@ var IdleMonitor = class {
|
|
|
795
195
|
}
|
|
796
196
|
};
|
|
797
197
|
|
|
798
|
-
// src/credit/ledger.ts
|
|
799
|
-
import Database2 from "better-sqlite3";
|
|
800
|
-
import { randomUUID as randomUUID2 } from "crypto";
|
|
801
|
-
var CREDIT_SCHEMA = `
|
|
802
|
-
CREATE TABLE IF NOT EXISTS credit_balances (
|
|
803
|
-
owner TEXT PRIMARY KEY,
|
|
804
|
-
balance INTEGER NOT NULL DEFAULT 0,
|
|
805
|
-
updated_at TEXT NOT NULL
|
|
806
|
-
);
|
|
807
|
-
|
|
808
|
-
CREATE TABLE IF NOT EXISTS credit_transactions (
|
|
809
|
-
id TEXT PRIMARY KEY,
|
|
810
|
-
owner TEXT NOT NULL,
|
|
811
|
-
amount INTEGER NOT NULL,
|
|
812
|
-
reason TEXT NOT NULL,
|
|
813
|
-
reference_id TEXT,
|
|
814
|
-
created_at TEXT NOT NULL
|
|
815
|
-
);
|
|
816
|
-
|
|
817
|
-
CREATE TABLE IF NOT EXISTS credit_escrow (
|
|
818
|
-
id TEXT PRIMARY KEY,
|
|
819
|
-
owner TEXT NOT NULL,
|
|
820
|
-
amount INTEGER NOT NULL,
|
|
821
|
-
card_id TEXT NOT NULL,
|
|
822
|
-
status TEXT NOT NULL DEFAULT 'held',
|
|
823
|
-
created_at TEXT NOT NULL,
|
|
824
|
-
settled_at TEXT
|
|
825
|
-
);
|
|
826
|
-
|
|
827
|
-
CREATE INDEX IF NOT EXISTS idx_transactions_owner ON credit_transactions(owner, created_at);
|
|
828
|
-
CREATE INDEX IF NOT EXISTS idx_escrow_owner ON credit_escrow(owner);
|
|
829
|
-
`;
|
|
830
|
-
function openCreditDb(path = ":memory:") {
|
|
831
|
-
const db = new Database2(path);
|
|
832
|
-
db.pragma("journal_mode = WAL");
|
|
833
|
-
db.pragma("foreign_keys = ON");
|
|
834
|
-
db.exec(CREDIT_SCHEMA);
|
|
835
|
-
return db;
|
|
836
|
-
}
|
|
837
|
-
function bootstrapAgent(db, owner, amount = 100) {
|
|
838
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
839
|
-
db.transaction(() => {
|
|
840
|
-
const result = db.prepare("INSERT OR IGNORE INTO credit_balances (owner, balance, updated_at) VALUES (?, ?, ?)").run(owner, amount, now);
|
|
841
|
-
if (result.changes > 0) {
|
|
842
|
-
db.prepare(
|
|
843
|
-
"INSERT INTO credit_transactions (id, owner, amount, reason, reference_id, created_at) VALUES (?, ?, ?, ?, ?, ?)"
|
|
844
|
-
).run(randomUUID2(), owner, amount, "bootstrap", null, now);
|
|
845
|
-
}
|
|
846
|
-
})();
|
|
847
|
-
}
|
|
848
|
-
function getBalance(db, owner) {
|
|
849
|
-
const row = db.prepare("SELECT balance FROM credit_balances WHERE owner = ?").get(owner);
|
|
850
|
-
return row?.balance ?? 0;
|
|
851
|
-
}
|
|
852
|
-
function getTransactions(db, owner, limit = 100) {
|
|
853
|
-
return db.prepare(
|
|
854
|
-
"SELECT id, owner, amount, reason, reference_id, created_at FROM credit_transactions WHERE owner = ? ORDER BY created_at DESC LIMIT ?"
|
|
855
|
-
).all(owner, limit);
|
|
856
|
-
}
|
|
857
|
-
|
|
858
|
-
// src/credit/budget.ts
|
|
859
|
-
var DEFAULT_BUDGET_CONFIG = {
|
|
860
|
-
reserve_credits: 20
|
|
861
|
-
};
|
|
862
|
-
var BudgetManager = class {
|
|
863
|
-
/**
|
|
864
|
-
* Creates a new BudgetManager.
|
|
865
|
-
*
|
|
866
|
-
* @param creditDb - The credit SQLite database instance.
|
|
867
|
-
* @param owner - Agent owner identifier.
|
|
868
|
-
* @param config - Budget configuration. Defaults to DEFAULT_BUDGET_CONFIG (20 credit reserve).
|
|
869
|
-
*/
|
|
870
|
-
constructor(creditDb, owner, config = DEFAULT_BUDGET_CONFIG) {
|
|
871
|
-
this.creditDb = creditDb;
|
|
872
|
-
this.owner = owner;
|
|
873
|
-
this.config = config;
|
|
874
|
-
}
|
|
875
|
-
/**
|
|
876
|
-
* Returns the number of credits available for spending.
|
|
877
|
-
* Computed as: max(0, balance - reserve_credits).
|
|
878
|
-
* Always returns a non-negative number — never goes below zero.
|
|
879
|
-
*
|
|
880
|
-
* @returns Available credits (balance minus reserve, floored at 0).
|
|
881
|
-
*/
|
|
882
|
-
availableCredits() {
|
|
883
|
-
const balance = getBalance(this.creditDb, this.owner);
|
|
884
|
-
return Math.max(0, balance - this.config.reserve_credits);
|
|
885
|
-
}
|
|
886
|
-
/**
|
|
887
|
-
* Returns true if spending `amount` credits is permitted by budget rules.
|
|
888
|
-
*
|
|
889
|
-
* Rules:
|
|
890
|
-
* - Zero-cost calls (amount <= 0) always return true.
|
|
891
|
-
* - Any positive amount requires availableCredits() >= amount.
|
|
892
|
-
* - If balance is at or below the reserve floor, all positive-cost calls return false.
|
|
893
|
-
*
|
|
894
|
-
* @param amount - Number of credits to spend.
|
|
895
|
-
* @returns true if the spend is allowed, false if it would breach the reserve floor.
|
|
896
|
-
*/
|
|
897
|
-
canSpend(amount) {
|
|
898
|
-
if (amount <= 0) return true;
|
|
899
|
-
return this.availableCredits() >= amount;
|
|
900
|
-
}
|
|
901
|
-
};
|
|
902
|
-
|
|
903
|
-
// src/registry/matcher.ts
|
|
904
|
-
function searchCards(db, query, filters = {}) {
|
|
905
|
-
const words = query.trim().split(/\s+/).map((w) => w.replace(/"/g, "")).filter((w) => w.length > 0);
|
|
906
|
-
if (words.length === 0) return [];
|
|
907
|
-
const ftsQuery = words.map((w) => `"${w}"`).join(" OR ");
|
|
908
|
-
const conditions = [];
|
|
909
|
-
const params = [ftsQuery];
|
|
910
|
-
if (filters.level !== void 0) {
|
|
911
|
-
conditions.push(`json_extract(cc.data, '$.level') = ?`);
|
|
912
|
-
params.push(filters.level);
|
|
913
|
-
}
|
|
914
|
-
if (filters.online !== void 0) {
|
|
915
|
-
conditions.push(`json_extract(cc.data, '$.availability.online') = ?`);
|
|
916
|
-
params.push(filters.online ? 1 : 0);
|
|
917
|
-
}
|
|
918
|
-
const whereClause = conditions.length > 0 ? `AND ${conditions.join(" AND ")}` : "";
|
|
919
|
-
const sql = `
|
|
920
|
-
SELECT cc.data
|
|
921
|
-
FROM capability_cards cc
|
|
922
|
-
JOIN cards_fts ON cc.rowid = cards_fts.rowid
|
|
923
|
-
WHERE cards_fts MATCH ?
|
|
924
|
-
${whereClause}
|
|
925
|
-
ORDER BY bm25(cards_fts)
|
|
926
|
-
LIMIT 50
|
|
927
|
-
`;
|
|
928
|
-
const stmt = db.prepare(sql);
|
|
929
|
-
const rows = stmt.all(...params);
|
|
930
|
-
const results = rows.map((row) => JSON.parse(row.data));
|
|
931
|
-
if (filters.apis_used && filters.apis_used.length > 0) {
|
|
932
|
-
const requiredApis = filters.apis_used;
|
|
933
|
-
return results.filter((card) => {
|
|
934
|
-
const cardApis = card.metadata?.apis_used ?? [];
|
|
935
|
-
return requiredApis.every((api) => cardApis.includes(api));
|
|
936
|
-
});
|
|
937
|
-
}
|
|
938
|
-
return results;
|
|
939
|
-
}
|
|
940
|
-
function filterCards(db, filters) {
|
|
941
|
-
const conditions = [];
|
|
942
|
-
const params = [];
|
|
943
|
-
if (filters.level !== void 0) {
|
|
944
|
-
conditions.push(`json_extract(data, '$.level') = ?`);
|
|
945
|
-
params.push(filters.level);
|
|
946
|
-
}
|
|
947
|
-
if (filters.online !== void 0) {
|
|
948
|
-
conditions.push(`json_extract(data, '$.availability.online') = ?`);
|
|
949
|
-
params.push(filters.online ? 1 : 0);
|
|
950
|
-
}
|
|
951
|
-
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
952
|
-
const sql = `SELECT data FROM capability_cards ${whereClause}`;
|
|
953
|
-
const stmt = db.prepare(sql);
|
|
954
|
-
const rows = stmt.all(...params);
|
|
955
|
-
return rows.map((row) => JSON.parse(row.data));
|
|
956
|
-
}
|
|
957
|
-
|
|
958
|
-
// src/credit/escrow.ts
|
|
959
|
-
import { randomUUID as randomUUID3 } from "crypto";
|
|
960
|
-
function holdEscrow(db, owner, amount, cardId) {
|
|
961
|
-
const escrowId = randomUUID3();
|
|
962
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
963
|
-
const hold = db.transaction(() => {
|
|
964
|
-
const row = db.prepare("SELECT balance FROM credit_balances WHERE owner = ?").get(owner);
|
|
965
|
-
if (!row || row.balance < amount) {
|
|
966
|
-
throw new AgentBnBError("Insufficient credits", "INSUFFICIENT_CREDITS");
|
|
967
|
-
}
|
|
968
|
-
db.prepare(
|
|
969
|
-
"UPDATE credit_balances SET balance = balance - ?, updated_at = ? WHERE owner = ? AND balance >= ?"
|
|
970
|
-
).run(amount, now, owner, amount);
|
|
971
|
-
db.prepare(
|
|
972
|
-
"INSERT INTO credit_escrow (id, owner, amount, card_id, status, created_at) VALUES (?, ?, ?, ?, ?, ?)"
|
|
973
|
-
).run(escrowId, owner, amount, cardId, "held", now);
|
|
974
|
-
db.prepare(
|
|
975
|
-
"INSERT INTO credit_transactions (id, owner, amount, reason, reference_id, created_at) VALUES (?, ?, ?, ?, ?, ?)"
|
|
976
|
-
).run(randomUUID3(), owner, -amount, "escrow_hold", escrowId, now);
|
|
977
|
-
});
|
|
978
|
-
hold();
|
|
979
|
-
return escrowId;
|
|
980
|
-
}
|
|
981
|
-
function settleEscrow(db, escrowId, recipientOwner) {
|
|
982
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
983
|
-
const settle = db.transaction(() => {
|
|
984
|
-
const escrow = db.prepare("SELECT id, owner, amount, status FROM credit_escrow WHERE id = ?").get(escrowId);
|
|
985
|
-
if (!escrow) {
|
|
986
|
-
throw new AgentBnBError(`Escrow not found: ${escrowId}`, "ESCROW_NOT_FOUND");
|
|
987
|
-
}
|
|
988
|
-
if (escrow.status !== "held") {
|
|
989
|
-
throw new AgentBnBError(
|
|
990
|
-
`Escrow ${escrowId} is already ${escrow.status}`,
|
|
991
|
-
"ESCROW_ALREADY_SETTLED"
|
|
992
|
-
);
|
|
993
|
-
}
|
|
994
|
-
db.prepare(
|
|
995
|
-
"INSERT OR IGNORE INTO credit_balances (owner, balance, updated_at) VALUES (?, 0, ?)"
|
|
996
|
-
).run(recipientOwner, now);
|
|
997
|
-
db.prepare(
|
|
998
|
-
"UPDATE credit_balances SET balance = balance + ?, updated_at = ? WHERE owner = ?"
|
|
999
|
-
).run(escrow.amount, now, recipientOwner);
|
|
1000
|
-
db.prepare(
|
|
1001
|
-
"UPDATE credit_escrow SET status = ?, settled_at = ? WHERE id = ?"
|
|
1002
|
-
).run("settled", now, escrowId);
|
|
1003
|
-
db.prepare(
|
|
1004
|
-
"INSERT INTO credit_transactions (id, owner, amount, reason, reference_id, created_at) VALUES (?, ?, ?, ?, ?, ?)"
|
|
1005
|
-
).run(randomUUID3(), recipientOwner, escrow.amount, "settlement", escrowId, now);
|
|
1006
|
-
});
|
|
1007
|
-
settle();
|
|
1008
|
-
}
|
|
1009
|
-
function releaseEscrow(db, escrowId) {
|
|
1010
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1011
|
-
const release = db.transaction(() => {
|
|
1012
|
-
const escrow = db.prepare("SELECT id, owner, amount, status FROM credit_escrow WHERE id = ?").get(escrowId);
|
|
1013
|
-
if (!escrow) {
|
|
1014
|
-
throw new AgentBnBError(`Escrow not found: ${escrowId}`, "ESCROW_NOT_FOUND");
|
|
1015
|
-
}
|
|
1016
|
-
if (escrow.status !== "held") {
|
|
1017
|
-
throw new AgentBnBError(
|
|
1018
|
-
`Escrow ${escrowId} is already ${escrow.status}`,
|
|
1019
|
-
"ESCROW_ALREADY_SETTLED"
|
|
1020
|
-
);
|
|
1021
|
-
}
|
|
1022
|
-
db.prepare(
|
|
1023
|
-
"UPDATE credit_balances SET balance = balance + ?, updated_at = ? WHERE owner = ?"
|
|
1024
|
-
).run(escrow.amount, now, escrow.owner);
|
|
1025
|
-
db.prepare(
|
|
1026
|
-
"UPDATE credit_escrow SET status = ?, settled_at = ? WHERE id = ?"
|
|
1027
|
-
).run("released", now, escrowId);
|
|
1028
|
-
db.prepare(
|
|
1029
|
-
"INSERT INTO credit_transactions (id, owner, amount, reason, reference_id, created_at) VALUES (?, ?, ?, ?, ?, ?)"
|
|
1030
|
-
).run(randomUUID3(), escrow.owner, escrow.amount, "refund", escrowId, now);
|
|
1031
|
-
});
|
|
1032
|
-
release();
|
|
1033
|
-
}
|
|
1034
|
-
|
|
1035
|
-
// src/gateway/client.ts
|
|
1036
|
-
import { randomUUID as randomUUID4 } from "crypto";
|
|
1037
|
-
async function requestCapability(opts) {
|
|
1038
|
-
const { gatewayUrl, token, cardId, params = {}, timeoutMs = 3e4 } = opts;
|
|
1039
|
-
const id = randomUUID4();
|
|
1040
|
-
const payload = {
|
|
1041
|
-
jsonrpc: "2.0",
|
|
1042
|
-
id,
|
|
1043
|
-
method: "capability.execute",
|
|
1044
|
-
params: { card_id: cardId, ...params }
|
|
1045
|
-
};
|
|
1046
|
-
const controller = new AbortController();
|
|
1047
|
-
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
1048
|
-
let response;
|
|
1049
|
-
try {
|
|
1050
|
-
response = await fetch(`${gatewayUrl}/rpc`, {
|
|
1051
|
-
method: "POST",
|
|
1052
|
-
headers: {
|
|
1053
|
-
"Content-Type": "application/json",
|
|
1054
|
-
Authorization: `Bearer ${token}`
|
|
1055
|
-
},
|
|
1056
|
-
body: JSON.stringify(payload),
|
|
1057
|
-
signal: controller.signal
|
|
1058
|
-
});
|
|
1059
|
-
} catch (err) {
|
|
1060
|
-
clearTimeout(timer);
|
|
1061
|
-
const isTimeout = err instanceof Error && err.name === "AbortError";
|
|
1062
|
-
throw new AgentBnBError(
|
|
1063
|
-
isTimeout ? "Request timed out" : `Network error: ${String(err)}`,
|
|
1064
|
-
isTimeout ? "TIMEOUT" : "NETWORK_ERROR"
|
|
1065
|
-
);
|
|
1066
|
-
} finally {
|
|
1067
|
-
clearTimeout(timer);
|
|
1068
|
-
}
|
|
1069
|
-
const body = await response.json();
|
|
1070
|
-
if (body.error) {
|
|
1071
|
-
throw new AgentBnBError(body.error.message, `RPC_ERROR_${body.error.code}`);
|
|
1072
|
-
}
|
|
1073
|
-
return body.result;
|
|
1074
|
-
}
|
|
1075
|
-
|
|
1076
|
-
// src/autonomy/pending-requests.ts
|
|
1077
|
-
import { randomUUID as randomUUID5 } from "crypto";
|
|
1078
|
-
function createPendingRequest(db, opts) {
|
|
1079
|
-
const id = randomUUID5();
|
|
1080
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1081
|
-
const paramsJson = opts.params !== void 0 ? JSON.stringify(opts.params) : null;
|
|
1082
|
-
db.prepare(`
|
|
1083
|
-
INSERT INTO pending_requests (
|
|
1084
|
-
id, skill_query, max_cost_credits, selected_peer, selected_card_id,
|
|
1085
|
-
selected_skill_id, credits, status, params, created_at, resolved_at
|
|
1086
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, 'pending', ?, ?, NULL)
|
|
1087
|
-
`).run(
|
|
1088
|
-
id,
|
|
1089
|
-
opts.skill_query,
|
|
1090
|
-
opts.max_cost_credits,
|
|
1091
|
-
opts.selected_peer ?? null,
|
|
1092
|
-
opts.selected_card_id ?? null,
|
|
1093
|
-
opts.selected_skill_id ?? null,
|
|
1094
|
-
opts.credits,
|
|
1095
|
-
paramsJson,
|
|
1096
|
-
now
|
|
1097
|
-
);
|
|
1098
|
-
return id;
|
|
1099
|
-
}
|
|
1100
|
-
function listPendingRequests(db) {
|
|
1101
|
-
const rows = db.prepare(`SELECT * FROM pending_requests WHERE status = 'pending' ORDER BY created_at DESC`).all();
|
|
1102
|
-
return rows;
|
|
1103
|
-
}
|
|
1104
|
-
function resolvePendingRequest(db, id, resolution) {
|
|
1105
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1106
|
-
const result = db.prepare(
|
|
1107
|
-
`UPDATE pending_requests SET status = ?, resolved_at = ? WHERE id = ?`
|
|
1108
|
-
).run(resolution, now, id);
|
|
1109
|
-
if (result.changes === 0) {
|
|
1110
|
-
throw new AgentBnBError(
|
|
1111
|
-
`Pending request not found: ${id}`,
|
|
1112
|
-
"NOT_FOUND"
|
|
1113
|
-
);
|
|
1114
|
-
}
|
|
1115
|
-
}
|
|
1116
|
-
|
|
1117
|
-
// src/cli/peers.ts
|
|
1118
|
-
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
|
|
1119
|
-
import { join as join3 } from "path";
|
|
1120
|
-
function getPeersPath() {
|
|
1121
|
-
return join3(getConfigDir(), "peers.json");
|
|
1122
|
-
}
|
|
1123
|
-
function loadPeers() {
|
|
1124
|
-
const peersPath = getPeersPath();
|
|
1125
|
-
if (!existsSync3(peersPath)) {
|
|
1126
|
-
return [];
|
|
1127
|
-
}
|
|
1128
|
-
try {
|
|
1129
|
-
const raw = readFileSync3(peersPath, "utf-8");
|
|
1130
|
-
return JSON.parse(raw);
|
|
1131
|
-
} catch {
|
|
1132
|
-
return [];
|
|
1133
|
-
}
|
|
1134
|
-
}
|
|
1135
|
-
function writePeers(peers) {
|
|
1136
|
-
const dir = getConfigDir();
|
|
1137
|
-
if (!existsSync3(dir)) {
|
|
1138
|
-
mkdirSync2(dir, { recursive: true });
|
|
1139
|
-
}
|
|
1140
|
-
writeFileSync3(getPeersPath(), JSON.stringify(peers, null, 2), "utf-8");
|
|
1141
|
-
}
|
|
1142
|
-
function savePeer(peer) {
|
|
1143
|
-
const peers = loadPeers();
|
|
1144
|
-
const lowerName = peer.name.toLowerCase();
|
|
1145
|
-
const existing = peers.findIndex((p) => p.name.toLowerCase() === lowerName);
|
|
1146
|
-
if (existing >= 0) {
|
|
1147
|
-
peers[existing] = peer;
|
|
1148
|
-
} else {
|
|
1149
|
-
peers.push(peer);
|
|
1150
|
-
}
|
|
1151
|
-
writePeers(peers);
|
|
1152
|
-
}
|
|
1153
|
-
function removePeer(name) {
|
|
1154
|
-
const peers = loadPeers();
|
|
1155
|
-
const lowerName = name.toLowerCase();
|
|
1156
|
-
const filtered = peers.filter((p) => p.name.toLowerCase() !== lowerName);
|
|
1157
|
-
if (filtered.length === peers.length) {
|
|
1158
|
-
return false;
|
|
1159
|
-
}
|
|
1160
|
-
writePeers(filtered);
|
|
1161
|
-
return true;
|
|
1162
|
-
}
|
|
1163
|
-
function findPeer(name) {
|
|
1164
|
-
const peers = loadPeers();
|
|
1165
|
-
const lowerName = name.toLowerCase();
|
|
1166
|
-
return peers.find((p) => p.name.toLowerCase() === lowerName) ?? null;
|
|
1167
|
-
}
|
|
1168
|
-
|
|
1169
|
-
// src/autonomy/auto-request.ts
|
|
1170
|
-
function minMaxNormalize(values) {
|
|
1171
|
-
if (values.length === 0) return [];
|
|
1172
|
-
if (values.length === 1) return [1];
|
|
1173
|
-
const min = Math.min(...values);
|
|
1174
|
-
const max = Math.max(...values);
|
|
1175
|
-
if (max === min) {
|
|
1176
|
-
return values.map(() => 1);
|
|
1177
|
-
}
|
|
1178
|
-
return values.map((v) => (v - min) / (max - min));
|
|
1179
|
-
}
|
|
1180
|
-
function scorePeers(candidates, selfOwner) {
|
|
1181
|
-
const eligible = candidates.filter((c) => c.card.owner !== selfOwner);
|
|
1182
|
-
if (eligible.length === 0) return [];
|
|
1183
|
-
const successRates = eligible.map((c) => c.card.metadata?.success_rate ?? 0.5);
|
|
1184
|
-
const costEfficiencies = eligible.map((c) => c.cost === 0 ? 1 : 1 / c.cost);
|
|
1185
|
-
const idleRates = eligible.map((c) => {
|
|
1186
|
-
const internal = c.card._internal;
|
|
1187
|
-
const idleRate = internal?.idle_rate;
|
|
1188
|
-
return typeof idleRate === "number" ? idleRate : 1;
|
|
1189
|
-
});
|
|
1190
|
-
const normSuccess = minMaxNormalize(successRates);
|
|
1191
|
-
const normCost = minMaxNormalize(costEfficiencies);
|
|
1192
|
-
const normIdle = minMaxNormalize(idleRates);
|
|
1193
|
-
const scored = eligible.map((c, i) => ({
|
|
1194
|
-
...c,
|
|
1195
|
-
rawScore: (normSuccess[i] ?? 0) * (normCost[i] ?? 0) * (normIdle[i] ?? 0)
|
|
1196
|
-
}));
|
|
1197
|
-
scored.sort((a, b) => b.rawScore - a.rawScore);
|
|
1198
|
-
return scored;
|
|
1199
|
-
}
|
|
1200
|
-
var AutoRequestor = class {
|
|
1201
|
-
owner;
|
|
1202
|
-
registryDb;
|
|
1203
|
-
creditDb;
|
|
1204
|
-
autonomyConfig;
|
|
1205
|
-
budgetManager;
|
|
1206
|
-
/**
|
|
1207
|
-
* Creates a new AutoRequestor.
|
|
1208
|
-
*
|
|
1209
|
-
* @param opts - Configuration for this AutoRequestor instance.
|
|
1210
|
-
*/
|
|
1211
|
-
constructor(opts) {
|
|
1212
|
-
this.owner = opts.owner;
|
|
1213
|
-
this.registryDb = opts.registryDb;
|
|
1214
|
-
this.creditDb = opts.creditDb;
|
|
1215
|
-
this.autonomyConfig = opts.autonomyConfig;
|
|
1216
|
-
this.budgetManager = opts.budgetManager;
|
|
1217
|
-
}
|
|
1218
|
-
/**
|
|
1219
|
-
* Executes an autonomous capability request.
|
|
1220
|
-
*
|
|
1221
|
-
* Performs the full flow:
|
|
1222
|
-
* 1. Search for matching capability cards
|
|
1223
|
-
* 2. Filter self-owned and over-budget candidates
|
|
1224
|
-
* 3. Score candidates using min-max normalized composite scoring
|
|
1225
|
-
* 4. Resolve peer gateway config
|
|
1226
|
-
* 5. Check autonomy tier (Tier 3 queues to pending_requests)
|
|
1227
|
-
* 6. Check budget reserve
|
|
1228
|
-
* 7. Hold escrow
|
|
1229
|
-
* 8. Execute via peer gateway
|
|
1230
|
-
* 9. Settle or release escrow based on outcome
|
|
1231
|
-
* 10. Log audit event (for Tier 2 notifications and all failures)
|
|
1232
|
-
*
|
|
1233
|
-
* @param need - The capability need to fulfill.
|
|
1234
|
-
* @returns The result of the auto-request attempt.
|
|
1235
|
-
*/
|
|
1236
|
-
async requestWithAutonomy(need) {
|
|
1237
|
-
const cards = searchCards(this.registryDb, need.query, { online: true });
|
|
1238
|
-
const candidates = [];
|
|
1239
|
-
for (const card of cards) {
|
|
1240
|
-
const cardAsV2 = card;
|
|
1241
|
-
if (Array.isArray(cardAsV2.skills)) {
|
|
1242
|
-
for (const skill of cardAsV2.skills) {
|
|
1243
|
-
const cost = skill.pricing.credits_per_call;
|
|
1244
|
-
if (cost <= need.maxCostCredits) {
|
|
1245
|
-
candidates.push({ card, cost, skillId: skill.id });
|
|
1246
|
-
}
|
|
1247
|
-
}
|
|
1248
|
-
} else {
|
|
1249
|
-
const cost = card.pricing.credits_per_call;
|
|
1250
|
-
if (cost <= need.maxCostCredits) {
|
|
1251
|
-
candidates.push({ card, cost, skillId: void 0 });
|
|
1252
|
-
}
|
|
1253
|
-
}
|
|
1254
|
-
}
|
|
1255
|
-
const scored = scorePeers(candidates, this.owner);
|
|
1256
|
-
if (scored.length === 0) {
|
|
1257
|
-
this.logFailure("auto_request_failed", "system", "none", 3, 0, "none", "No eligible peer found");
|
|
1258
|
-
return { status: "no_peer", reason: "No eligible peer found" };
|
|
1259
|
-
}
|
|
1260
|
-
const top = scored[0];
|
|
1261
|
-
const peerConfig = findPeer(top.card.owner);
|
|
1262
|
-
if (!peerConfig) {
|
|
1263
|
-
this.logFailure("auto_request_failed", top.card.id, top.skillId ?? "none", 3, top.cost, top.card.owner, "No gateway config for peer");
|
|
1264
|
-
return { status: "no_peer", reason: "No gateway config for peer" };
|
|
1265
|
-
}
|
|
1266
|
-
const tier = getAutonomyTier(top.cost, this.autonomyConfig);
|
|
1267
|
-
if (tier === 3) {
|
|
1268
|
-
createPendingRequest(this.registryDb, {
|
|
1269
|
-
skill_query: need.query,
|
|
1270
|
-
max_cost_credits: need.maxCostCredits,
|
|
1271
|
-
credits: top.cost,
|
|
1272
|
-
selected_peer: top.card.owner,
|
|
1273
|
-
selected_card_id: top.card.id,
|
|
1274
|
-
selected_skill_id: top.skillId,
|
|
1275
|
-
params: need.params
|
|
1276
|
-
});
|
|
1277
|
-
insertAuditEvent(this.registryDb, {
|
|
1278
|
-
type: "auto_request_pending",
|
|
1279
|
-
card_id: top.card.id,
|
|
1280
|
-
skill_id: top.skillId ?? top.card.id,
|
|
1281
|
-
tier_invoked: 3,
|
|
1282
|
-
credits: top.cost,
|
|
1283
|
-
peer: top.card.owner
|
|
1284
|
-
});
|
|
1285
|
-
return {
|
|
1286
|
-
status: "tier_blocked",
|
|
1287
|
-
reason: "Tier 3: owner approval required",
|
|
1288
|
-
peer: top.card.owner
|
|
1289
|
-
};
|
|
1290
|
-
}
|
|
1291
|
-
if (!this.budgetManager.canSpend(top.cost)) {
|
|
1292
|
-
this.logFailure("auto_request_failed", top.card.id, top.skillId ?? "none", tier, top.cost, top.card.owner, "Budget reserve would be breached");
|
|
1293
|
-
return { status: "budget_blocked", reason: "Insufficient credits \u2014 reserve floor would be breached" };
|
|
1294
|
-
}
|
|
1295
|
-
const escrowId = holdEscrow(this.creditDb, this.owner, top.cost, top.card.id);
|
|
1296
|
-
try {
|
|
1297
|
-
const execResult = await requestCapability({
|
|
1298
|
-
gatewayUrl: peerConfig.url,
|
|
1299
|
-
token: peerConfig.token,
|
|
1300
|
-
cardId: top.card.id,
|
|
1301
|
-
params: top.skillId ? { skill_id: top.skillId, ...need.params } : need.params
|
|
1302
|
-
});
|
|
1303
|
-
settleEscrow(this.creditDb, escrowId, top.card.owner);
|
|
1304
|
-
if (tier === 2) {
|
|
1305
|
-
insertAuditEvent(this.registryDb, {
|
|
1306
|
-
type: "auto_request_notify",
|
|
1307
|
-
card_id: top.card.id,
|
|
1308
|
-
skill_id: top.skillId ?? top.card.id,
|
|
1309
|
-
tier_invoked: 2,
|
|
1310
|
-
credits: top.cost,
|
|
1311
|
-
peer: top.card.owner
|
|
1312
|
-
});
|
|
1313
|
-
} else {
|
|
1314
|
-
insertAuditEvent(this.registryDb, {
|
|
1315
|
-
type: "auto_request",
|
|
1316
|
-
card_id: top.card.id,
|
|
1317
|
-
skill_id: top.skillId ?? top.card.id,
|
|
1318
|
-
tier_invoked: 1,
|
|
1319
|
-
credits: top.cost,
|
|
1320
|
-
peer: top.card.owner
|
|
1321
|
-
});
|
|
1322
|
-
}
|
|
1323
|
-
return {
|
|
1324
|
-
status: "success",
|
|
1325
|
-
result: execResult,
|
|
1326
|
-
escrowId,
|
|
1327
|
-
peer: top.card.owner,
|
|
1328
|
-
creditsSpent: top.cost
|
|
1329
|
-
};
|
|
1330
|
-
} catch (err) {
|
|
1331
|
-
releaseEscrow(this.creditDb, escrowId);
|
|
1332
|
-
const reason = err instanceof Error ? err.message : String(err);
|
|
1333
|
-
this.logFailure("auto_request_failed", top.card.id, top.skillId ?? "none", tier, top.cost, top.card.owner, `Execution failed: ${reason}`);
|
|
1334
|
-
return {
|
|
1335
|
-
status: "failed",
|
|
1336
|
-
reason: `Execution failed: ${reason}`,
|
|
1337
|
-
peer: top.card.owner
|
|
1338
|
-
};
|
|
1339
|
-
}
|
|
1340
|
-
}
|
|
1341
|
-
/**
|
|
1342
|
-
* Logs a failure audit event to request_log.
|
|
1343
|
-
* Used for all non-success paths to satisfy REQ-06.
|
|
1344
|
-
*/
|
|
1345
|
-
logFailure(type, cardId, skillId, tier, credits, peer, reason) {
|
|
1346
|
-
insertAuditEvent(this.registryDb, {
|
|
1347
|
-
type,
|
|
1348
|
-
card_id: cardId,
|
|
1349
|
-
skill_id: skillId,
|
|
1350
|
-
tier_invoked: tier,
|
|
1351
|
-
credits,
|
|
1352
|
-
peer,
|
|
1353
|
-
reason
|
|
1354
|
-
});
|
|
1355
|
-
}
|
|
1356
|
-
};
|
|
1357
|
-
|
|
1358
198
|
// src/cli/remote-registry.ts
|
|
1359
199
|
var RegistryTimeoutError = class extends AgentBnBError {
|
|
1360
200
|
constructor(url) {
|
|
@@ -1439,7 +279,7 @@ function mergeResults(localCards, remoteCards, hasQuery) {
|
|
|
1439
279
|
}
|
|
1440
280
|
|
|
1441
281
|
// src/cli/onboarding.ts
|
|
1442
|
-
import { randomUUID as
|
|
282
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
1443
283
|
import { createConnection } from "net";
|
|
1444
284
|
var KNOWN_API_KEYS = [
|
|
1445
285
|
"OPENAI_API_KEY",
|
|
@@ -1588,7 +428,7 @@ function buildDraftCard(apiKey, owner) {
|
|
|
1588
428
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1589
429
|
return {
|
|
1590
430
|
spec_version: "1.0",
|
|
1591
|
-
id:
|
|
431
|
+
id: randomUUID2(),
|
|
1592
432
|
owner,
|
|
1593
433
|
name: template.name,
|
|
1594
434
|
description: template.description,
|
|
@@ -1608,7 +448,7 @@ function buildDraftCard(apiKey, owner) {
|
|
|
1608
448
|
}
|
|
1609
449
|
|
|
1610
450
|
// src/runtime/agent-runtime.ts
|
|
1611
|
-
import { readFileSync
|
|
451
|
+
import { readFileSync, existsSync } from "fs";
|
|
1612
452
|
|
|
1613
453
|
// src/skills/executor.ts
|
|
1614
454
|
var SkillExecutor = class {
|
|
@@ -1766,17 +606,29 @@ var CommandSkillConfigSchema = z2.object({
|
|
|
1766
606
|
timeout_ms: z2.number().positive().default(3e4),
|
|
1767
607
|
pricing: PricingSchema
|
|
1768
608
|
});
|
|
609
|
+
var ConductorSkillConfigSchema = z2.object({
|
|
610
|
+
id: z2.string().min(1),
|
|
611
|
+
type: z2.literal("conductor"),
|
|
612
|
+
name: z2.string().min(1),
|
|
613
|
+
conductor_skill: z2.enum(["orchestrate", "plan"]),
|
|
614
|
+
pricing: PricingSchema,
|
|
615
|
+
timeout_ms: z2.number().positive().optional()
|
|
616
|
+
});
|
|
1769
617
|
var SkillConfigSchema = z2.discriminatedUnion("type", [
|
|
1770
618
|
ApiSkillConfigSchema,
|
|
1771
619
|
PipelineSkillConfigSchema,
|
|
1772
620
|
OpenClawSkillConfigSchema,
|
|
1773
|
-
CommandSkillConfigSchema
|
|
621
|
+
CommandSkillConfigSchema,
|
|
622
|
+
ConductorSkillConfigSchema
|
|
1774
623
|
]);
|
|
1775
624
|
var SkillsFileSchema = z2.object({
|
|
1776
625
|
skills: z2.array(SkillConfigSchema)
|
|
1777
626
|
});
|
|
1778
627
|
function expandEnvVars(value) {
|
|
1779
628
|
return value.replace(/\$\{([^}]+)\}/g, (_match, varName) => {
|
|
629
|
+
if (/[.a-z]/.test(varName)) {
|
|
630
|
+
return _match;
|
|
631
|
+
}
|
|
1780
632
|
const envValue = process.env[varName];
|
|
1781
633
|
if (envValue === void 0) {
|
|
1782
634
|
throw new Error(`Environment variable "${varName}" is not defined`);
|
|
@@ -1867,7 +719,7 @@ function applyInputMapping(params, mapping) {
|
|
|
1867
719
|
pathParams[key] = String(value);
|
|
1868
720
|
break;
|
|
1869
721
|
case "header":
|
|
1870
|
-
headers[key] = String(value);
|
|
722
|
+
headers[key] = String(value).replace(/[\r\n]/g, "");
|
|
1871
723
|
break;
|
|
1872
724
|
}
|
|
1873
725
|
}
|
|
@@ -1962,58 +814,34 @@ var ApiExecutor = class {
|
|
|
1962
814
|
};
|
|
1963
815
|
|
|
1964
816
|
// src/skills/pipeline-executor.ts
|
|
1965
|
-
import {
|
|
817
|
+
import { execFile } from "child_process";
|
|
1966
818
|
import { promisify } from "util";
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
}
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
}
|
|
1989
|
-
if (typeof resolved === "object") {
|
|
1990
|
-
return JSON.stringify(resolved);
|
|
819
|
+
var execFileAsync = promisify(execFile);
|
|
820
|
+
function shellEscape(value) {
|
|
821
|
+
return "'" + value.replace(/'/g, "'\\''") + "'";
|
|
822
|
+
}
|
|
823
|
+
function safeInterpolateCommand(template, context) {
|
|
824
|
+
return template.replace(/\$\{([^}]+)\}/g, (_match, expr) => {
|
|
825
|
+
const parts = expr.split(".");
|
|
826
|
+
let current = context;
|
|
827
|
+
for (const part of parts) {
|
|
828
|
+
if (current === null || typeof current !== "object") return "";
|
|
829
|
+
const bracketMatch = part.match(/^(\w+)\[(\d+)\]$/);
|
|
830
|
+
if (bracketMatch) {
|
|
831
|
+
current = current[bracketMatch[1]];
|
|
832
|
+
if (Array.isArray(current)) {
|
|
833
|
+
current = current[parseInt(bracketMatch[2], 10)];
|
|
834
|
+
} else {
|
|
835
|
+
return "";
|
|
836
|
+
}
|
|
837
|
+
} else {
|
|
838
|
+
current = current[part];
|
|
839
|
+
}
|
|
1991
840
|
}
|
|
1992
|
-
|
|
841
|
+
if (current === void 0 || current === null) return "";
|
|
842
|
+
return shellEscape(String(current));
|
|
1993
843
|
});
|
|
1994
844
|
}
|
|
1995
|
-
function interpolateObject(obj, context) {
|
|
1996
|
-
const result = {};
|
|
1997
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
1998
|
-
result[key] = interpolateValue(value, context);
|
|
1999
|
-
}
|
|
2000
|
-
return result;
|
|
2001
|
-
}
|
|
2002
|
-
function interpolateValue(value, context) {
|
|
2003
|
-
if (typeof value === "string") {
|
|
2004
|
-
return interpolate(value, context);
|
|
2005
|
-
}
|
|
2006
|
-
if (Array.isArray(value)) {
|
|
2007
|
-
return value.map((item) => interpolateValue(item, context));
|
|
2008
|
-
}
|
|
2009
|
-
if (value !== null && typeof value === "object") {
|
|
2010
|
-
return interpolateObject(value, context);
|
|
2011
|
-
}
|
|
2012
|
-
return value;
|
|
2013
|
-
}
|
|
2014
|
-
|
|
2015
|
-
// src/skills/pipeline-executor.ts
|
|
2016
|
-
var execAsync = promisify(exec);
|
|
2017
845
|
var PipelineExecutor = class {
|
|
2018
846
|
/**
|
|
2019
847
|
* @param skillExecutor - The parent SkillExecutor used to dispatch sub-skill calls.
|
|
@@ -2074,12 +902,12 @@ var PipelineExecutor = class {
|
|
|
2074
902
|
}
|
|
2075
903
|
stepResult = subResult.result;
|
|
2076
904
|
} else if ("command" in step && step.command) {
|
|
2077
|
-
const interpolatedCommand =
|
|
905
|
+
const interpolatedCommand = safeInterpolateCommand(
|
|
2078
906
|
step.command,
|
|
2079
907
|
context
|
|
2080
908
|
);
|
|
2081
909
|
try {
|
|
2082
|
-
const { stdout } = await
|
|
910
|
+
const { stdout } = await execFileAsync("/bin/sh", ["-c", interpolatedCommand], { timeout: 3e4 });
|
|
2083
911
|
stepResult = stdout.trim();
|
|
2084
912
|
} catch (err) {
|
|
2085
913
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -2106,7 +934,7 @@ var PipelineExecutor = class {
|
|
|
2106
934
|
};
|
|
2107
935
|
|
|
2108
936
|
// src/skills/openclaw-bridge.ts
|
|
2109
|
-
import {
|
|
937
|
+
import { execFileSync } from "child_process";
|
|
2110
938
|
var DEFAULT_BASE_URL = "http://localhost:3000";
|
|
2111
939
|
var DEFAULT_TIMEOUT_MS = 6e4;
|
|
2112
940
|
function buildPayload(config, params) {
|
|
@@ -2151,12 +979,22 @@ async function executeWebhook(config, payload) {
|
|
|
2151
979
|
clearTimeout(timer);
|
|
2152
980
|
}
|
|
2153
981
|
}
|
|
982
|
+
function validateAgentName(name) {
|
|
983
|
+
return /^[a-zA-Z0-9._-]+$/.test(name);
|
|
984
|
+
}
|
|
2154
985
|
function executeProcess(config, payload) {
|
|
2155
986
|
const timeoutMs = config.timeout_ms ?? DEFAULT_TIMEOUT_MS;
|
|
987
|
+
if (!validateAgentName(config.agent_name)) {
|
|
988
|
+
return {
|
|
989
|
+
success: false,
|
|
990
|
+
error: `Invalid agent name: "${config.agent_name}" (only alphanumeric, hyphens, underscores, dots allowed)`
|
|
991
|
+
};
|
|
992
|
+
}
|
|
2156
993
|
const inputJson = JSON.stringify(payload);
|
|
2157
|
-
const cmd = `openclaw run ${config.agent_name} --input '${inputJson}'`;
|
|
2158
994
|
try {
|
|
2159
|
-
const stdout =
|
|
995
|
+
const stdout = execFileSync("openclaw", ["run", config.agent_name, "--input", inputJson], {
|
|
996
|
+
timeout: timeoutMs
|
|
997
|
+
});
|
|
2160
998
|
const text = stdout.toString().trim();
|
|
2161
999
|
const result = JSON.parse(text);
|
|
2162
1000
|
return { success: true, result };
|
|
@@ -2229,10 +1067,35 @@ var OpenClawBridge = class {
|
|
|
2229
1067
|
};
|
|
2230
1068
|
|
|
2231
1069
|
// src/skills/command-executor.ts
|
|
2232
|
-
import {
|
|
2233
|
-
function
|
|
1070
|
+
import { execFile as execFile2 } from "child_process";
|
|
1071
|
+
function shellEscape2(value) {
|
|
1072
|
+
return "'" + value.replace(/'/g, "'\\''") + "'";
|
|
1073
|
+
}
|
|
1074
|
+
function safeInterpolateCommand2(template, context) {
|
|
1075
|
+
return template.replace(/\$\{([^}]+)\}/g, (_match, expr) => {
|
|
1076
|
+
const parts = expr.split(".");
|
|
1077
|
+
let current = context;
|
|
1078
|
+
for (const part of parts) {
|
|
1079
|
+
if (current === null || typeof current !== "object") return "";
|
|
1080
|
+
const bracketMatch = part.match(/^(\w+)\[(\d+)\]$/);
|
|
1081
|
+
if (bracketMatch) {
|
|
1082
|
+
current = current[bracketMatch[1]];
|
|
1083
|
+
if (Array.isArray(current)) {
|
|
1084
|
+
current = current[parseInt(bracketMatch[2], 10)];
|
|
1085
|
+
} else {
|
|
1086
|
+
return "";
|
|
1087
|
+
}
|
|
1088
|
+
} else {
|
|
1089
|
+
current = current[part];
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
if (current === void 0 || current === null) return "";
|
|
1093
|
+
return shellEscape2(String(current));
|
|
1094
|
+
});
|
|
1095
|
+
}
|
|
1096
|
+
function execFileAsync2(file, args, options) {
|
|
2234
1097
|
return new Promise((resolve, reject) => {
|
|
2235
|
-
|
|
1098
|
+
execFile2(file, args, options, (error, stdout, stderr) => {
|
|
2236
1099
|
const stdoutStr = typeof stdout === "string" ? stdout : stdout.toString();
|
|
2237
1100
|
const stderrStr = typeof stderr === "string" ? stderr : stderr.toString();
|
|
2238
1101
|
if (error) {
|
|
@@ -2269,17 +1132,16 @@ var CommandExecutor = class {
|
|
|
2269
1132
|
};
|
|
2270
1133
|
}
|
|
2271
1134
|
}
|
|
2272
|
-
const interpolatedCommand =
|
|
1135
|
+
const interpolatedCommand = safeInterpolateCommand2(cmdConfig.command, { params });
|
|
2273
1136
|
const timeout = cmdConfig.timeout_ms ?? 3e4;
|
|
2274
1137
|
const cwd = cmdConfig.working_dir ?? process.cwd();
|
|
2275
1138
|
let stdout;
|
|
2276
1139
|
try {
|
|
2277
|
-
const result = await
|
|
1140
|
+
const result = await execFileAsync2("/bin/sh", ["-c", interpolatedCommand], {
|
|
2278
1141
|
timeout,
|
|
2279
1142
|
cwd,
|
|
2280
|
-
maxBuffer: 10 * 1024 * 1024
|
|
1143
|
+
maxBuffer: 10 * 1024 * 1024
|
|
2281
1144
|
// 10 MB
|
|
2282
|
-
shell: "/bin/sh"
|
|
2283
1145
|
});
|
|
2284
1146
|
stdout = result.stdout;
|
|
2285
1147
|
} catch (err) {
|
|
@@ -2346,6 +1208,8 @@ var AgentRuntime = class {
|
|
|
2346
1208
|
draining = false;
|
|
2347
1209
|
orphanedEscrowAgeMinutes;
|
|
2348
1210
|
skillsYamlPath;
|
|
1211
|
+
conductorEnabled;
|
|
1212
|
+
conductorToken;
|
|
2349
1213
|
/**
|
|
2350
1214
|
* Creates a new AgentRuntime instance.
|
|
2351
1215
|
* Opens both databases with WAL mode, foreign_keys=ON, and busy_timeout=5000.
|
|
@@ -2357,6 +1221,8 @@ var AgentRuntime = class {
|
|
|
2357
1221
|
this.owner = options.owner;
|
|
2358
1222
|
this.orphanedEscrowAgeMinutes = options.orphanedEscrowAgeMinutes ?? 10;
|
|
2359
1223
|
this.skillsYamlPath = options.skillsYamlPath;
|
|
1224
|
+
this.conductorEnabled = options.conductorEnabled ?? false;
|
|
1225
|
+
this.conductorToken = options.conductorToken ?? "";
|
|
2360
1226
|
this.registryDb = openDatabase(options.registryDbPath);
|
|
2361
1227
|
this.creditDb = openCreditDb(options.creditDbPath);
|
|
2362
1228
|
this.registryDb.pragma("busy_timeout = 5000");
|
|
@@ -2393,18 +1259,70 @@ var AgentRuntime = class {
|
|
|
2393
1259
|
* 3. Populate the Map with all 4 modes — SkillExecutor sees them via reference.
|
|
2394
1260
|
*/
|
|
2395
1261
|
async initSkillExecutor() {
|
|
2396
|
-
|
|
1262
|
+
const hasSkillsYaml = this.skillsYamlPath && existsSync(this.skillsYamlPath);
|
|
1263
|
+
if (!hasSkillsYaml && !this.conductorEnabled) {
|
|
2397
1264
|
return;
|
|
2398
1265
|
}
|
|
2399
|
-
|
|
2400
|
-
|
|
1266
|
+
let configs = [];
|
|
1267
|
+
if (hasSkillsYaml) {
|
|
1268
|
+
const yamlContent = readFileSync(this.skillsYamlPath, "utf8");
|
|
1269
|
+
configs = parseSkillsFile(yamlContent);
|
|
1270
|
+
}
|
|
2401
1271
|
const modes = /* @__PURE__ */ new Map();
|
|
1272
|
+
if (this.conductorEnabled) {
|
|
1273
|
+
const { ConductorMode } = await import("../conductor-mode-CF6PSRRA.js");
|
|
1274
|
+
const { registerConductorCard, CONDUCTOR_OWNER } = await import("../card-P5C36VBD.js");
|
|
1275
|
+
const { loadPeers: loadPeers2 } = await import("../peers-G36URZYB.js");
|
|
1276
|
+
registerConductorCard(this.registryDb);
|
|
1277
|
+
const resolveAgentUrl = (owner) => {
|
|
1278
|
+
const peers = loadPeers2();
|
|
1279
|
+
const peer = peers.find((p) => p.name.toLowerCase() === owner.toLowerCase());
|
|
1280
|
+
if (!peer) {
|
|
1281
|
+
throw new Error(
|
|
1282
|
+
`No peer found for agent owner "${owner}". Add with: agentbnb peers add ${owner} <url> <token>`
|
|
1283
|
+
);
|
|
1284
|
+
}
|
|
1285
|
+
const stmt = this.registryDb.prepare(
|
|
1286
|
+
"SELECT id FROM capability_cards WHERE owner = ? LIMIT 1"
|
|
1287
|
+
);
|
|
1288
|
+
const row = stmt.get(owner);
|
|
1289
|
+
const cardId = row?.id ?? owner;
|
|
1290
|
+
return { url: peer.url, cardId };
|
|
1291
|
+
};
|
|
1292
|
+
const conductorMode = new ConductorMode({
|
|
1293
|
+
db: this.registryDb,
|
|
1294
|
+
creditDb: this.creditDb,
|
|
1295
|
+
conductorOwner: CONDUCTOR_OWNER,
|
|
1296
|
+
gatewayToken: this.conductorToken,
|
|
1297
|
+
resolveAgentUrl,
|
|
1298
|
+
maxBudget: 100
|
|
1299
|
+
});
|
|
1300
|
+
modes.set("conductor", conductorMode);
|
|
1301
|
+
configs.push(
|
|
1302
|
+
{
|
|
1303
|
+
id: "orchestrate",
|
|
1304
|
+
type: "conductor",
|
|
1305
|
+
name: "Orchestrate",
|
|
1306
|
+
conductor_skill: "orchestrate",
|
|
1307
|
+
pricing: { credits_per_call: 5 }
|
|
1308
|
+
},
|
|
1309
|
+
{
|
|
1310
|
+
id: "plan",
|
|
1311
|
+
type: "conductor",
|
|
1312
|
+
name: "Plan",
|
|
1313
|
+
conductor_skill: "plan",
|
|
1314
|
+
pricing: { credits_per_call: 1 }
|
|
1315
|
+
}
|
|
1316
|
+
);
|
|
1317
|
+
}
|
|
2402
1318
|
const executor = createSkillExecutor(configs, modes);
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
1319
|
+
if (hasSkillsYaml) {
|
|
1320
|
+
const pipelineExecutor = new PipelineExecutor(executor);
|
|
1321
|
+
modes.set("api", new ApiExecutor());
|
|
1322
|
+
modes.set("pipeline", pipelineExecutor);
|
|
1323
|
+
modes.set("openclaw", new OpenClawBridge());
|
|
1324
|
+
modes.set("command", new CommandExecutor());
|
|
1325
|
+
}
|
|
2408
1326
|
this.skillExecutor = executor;
|
|
2409
1327
|
}
|
|
2410
1328
|
/**
|
|
@@ -2459,7 +1377,6 @@ var AgentRuntime = class {
|
|
|
2459
1377
|
|
|
2460
1378
|
// src/gateway/server.ts
|
|
2461
1379
|
import Fastify from "fastify";
|
|
2462
|
-
import { randomUUID as randomUUID7 } from "crypto";
|
|
2463
1380
|
var VERSION = "0.0.1";
|
|
2464
1381
|
function createGatewayServer(opts) {
|
|
2465
1382
|
const {
|
|
@@ -2523,213 +1440,282 @@ function createGatewayServer(opts) {
|
|
|
2523
1440
|
error: { code: -32602, message: "Invalid params: card_id required" }
|
|
2524
1441
|
});
|
|
2525
1442
|
}
|
|
2526
|
-
const card = getCard(registryDb, cardId);
|
|
2527
|
-
if (!card) {
|
|
2528
|
-
return reply.send({
|
|
2529
|
-
jsonrpc: "2.0",
|
|
2530
|
-
id,
|
|
2531
|
-
error: { code: -32602, message: `Card not found: ${cardId}` }
|
|
2532
|
-
});
|
|
2533
|
-
}
|
|
2534
1443
|
const requester = params.requester ?? "unknown";
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
cardName = skill.name;
|
|
2551
|
-
resolvedSkillId = skill.id;
|
|
1444
|
+
const receipt = params.escrow_receipt;
|
|
1445
|
+
const result = await executeCapabilityRequest({
|
|
1446
|
+
registryDb,
|
|
1447
|
+
creditDb,
|
|
1448
|
+
cardId,
|
|
1449
|
+
skillId,
|
|
1450
|
+
params,
|
|
1451
|
+
requester,
|
|
1452
|
+
escrowReceipt: receipt,
|
|
1453
|
+
skillExecutor,
|
|
1454
|
+
handlerUrl,
|
|
1455
|
+
timeoutMs
|
|
1456
|
+
});
|
|
1457
|
+
if (result.success) {
|
|
1458
|
+
return reply.send({ jsonrpc: "2.0", id, result: result.result });
|
|
2552
1459
|
} else {
|
|
2553
|
-
|
|
2554
|
-
cardName = card.name;
|
|
1460
|
+
return reply.send({ jsonrpc: "2.0", id, error: result.error });
|
|
2555
1461
|
}
|
|
2556
|
-
|
|
1462
|
+
});
|
|
1463
|
+
return fastify;
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
// src/registry/server.ts
|
|
1467
|
+
import Fastify2 from "fastify";
|
|
1468
|
+
import cors from "@fastify/cors";
|
|
1469
|
+
import fastifyStatic from "@fastify/static";
|
|
1470
|
+
import fastifyWebsocket from "@fastify/websocket";
|
|
1471
|
+
import { join, dirname } from "path";
|
|
1472
|
+
import { fileURLToPath } from "url";
|
|
1473
|
+
import { existsSync as existsSync2 } from "fs";
|
|
1474
|
+
|
|
1475
|
+
// src/relay/websocket-relay.ts
|
|
1476
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
1477
|
+
var RATE_LIMIT_MAX = 60;
|
|
1478
|
+
var RATE_LIMIT_WINDOW_MS = 6e4;
|
|
1479
|
+
var RELAY_TIMEOUT_MS = 3e4;
|
|
1480
|
+
function registerWebSocketRelay(server, db) {
|
|
1481
|
+
const connections = /* @__PURE__ */ new Map();
|
|
1482
|
+
const pendingRequests = /* @__PURE__ */ new Map();
|
|
1483
|
+
const rateLimits = /* @__PURE__ */ new Map();
|
|
1484
|
+
function checkRateLimit(owner) {
|
|
1485
|
+
const now = Date.now();
|
|
1486
|
+
const entry = rateLimits.get(owner);
|
|
1487
|
+
if (!entry || now >= entry.resetTime) {
|
|
1488
|
+
rateLimits.set(owner, { count: 1, resetTime: now + RATE_LIMIT_WINDOW_MS });
|
|
1489
|
+
return true;
|
|
1490
|
+
}
|
|
1491
|
+
if (entry.count >= RATE_LIMIT_MAX) {
|
|
1492
|
+
return false;
|
|
1493
|
+
}
|
|
1494
|
+
entry.count++;
|
|
1495
|
+
return true;
|
|
1496
|
+
}
|
|
1497
|
+
function markOwnerOffline(owner) {
|
|
2557
1498
|
try {
|
|
2558
|
-
const
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
jsonrpc: "2.0",
|
|
2562
|
-
id,
|
|
2563
|
-
error: { code: -32603, message: "Insufficient credits" }
|
|
2564
|
-
});
|
|
2565
|
-
}
|
|
2566
|
-
escrowId = holdEscrow(creditDb, requester, creditsNeeded, cardId);
|
|
2567
|
-
} catch (err) {
|
|
2568
|
-
const msg = err instanceof AgentBnBError ? err.message : "Failed to hold escrow";
|
|
2569
|
-
return reply.send({
|
|
2570
|
-
jsonrpc: "2.0",
|
|
2571
|
-
id,
|
|
2572
|
-
error: { code: -32603, message: msg }
|
|
2573
|
-
});
|
|
2574
|
-
}
|
|
2575
|
-
const startMs = Date.now();
|
|
2576
|
-
if (skillExecutor) {
|
|
2577
|
-
const targetSkillId = resolvedSkillId ?? skillId ?? cardId;
|
|
2578
|
-
let execResult;
|
|
2579
|
-
try {
|
|
2580
|
-
execResult = await skillExecutor.execute(targetSkillId, params);
|
|
2581
|
-
} catch (err) {
|
|
2582
|
-
releaseEscrow(creditDb, escrowId);
|
|
2583
|
-
updateReputation(registryDb, cardId, false, Date.now() - startMs);
|
|
1499
|
+
const stmt = db.prepare("SELECT id, data FROM capability_cards WHERE owner = ?");
|
|
1500
|
+
const rows = stmt.all(owner);
|
|
1501
|
+
for (const row of rows) {
|
|
2584
1502
|
try {
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
latency_ms: Date.now() - startMs,
|
|
2593
|
-
credits_charged: 0,
|
|
2594
|
-
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
2595
|
-
});
|
|
1503
|
+
const card = JSON.parse(row.data);
|
|
1504
|
+
if (card.availability?.online) {
|
|
1505
|
+
card.availability.online = false;
|
|
1506
|
+
card.updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
1507
|
+
const updateStmt = db.prepare("UPDATE capability_cards SET data = ?, updated_at = ? WHERE id = ?");
|
|
1508
|
+
updateStmt.run(JSON.stringify(card), card.updated_at, row.id);
|
|
1509
|
+
}
|
|
2596
1510
|
} catch {
|
|
2597
1511
|
}
|
|
2598
|
-
const message = err instanceof Error ? err.message : "Execution error";
|
|
2599
|
-
return reply.send({
|
|
2600
|
-
jsonrpc: "2.0",
|
|
2601
|
-
id,
|
|
2602
|
-
error: { code: -32603, message }
|
|
2603
|
-
});
|
|
2604
1512
|
}
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
1513
|
+
} catch {
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
function markOwnerOnline(owner) {
|
|
1517
|
+
try {
|
|
1518
|
+
const stmt = db.prepare("SELECT id, data FROM capability_cards WHERE owner = ?");
|
|
1519
|
+
const rows = stmt.all(owner);
|
|
1520
|
+
for (const row of rows) {
|
|
2608
1521
|
try {
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
requester,
|
|
2615
|
-
status: "failure",
|
|
2616
|
-
latency_ms: execResult.latency_ms,
|
|
2617
|
-
credits_charged: 0,
|
|
2618
|
-
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
2619
|
-
});
|
|
1522
|
+
const card = JSON.parse(row.data);
|
|
1523
|
+
card.availability = { ...card.availability, online: true };
|
|
1524
|
+
card.updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
1525
|
+
const updateStmt = db.prepare("UPDATE capability_cards SET data = ?, updated_at = ? WHERE id = ?");
|
|
1526
|
+
updateStmt.run(JSON.stringify(card), card.updated_at, row.id);
|
|
2620
1527
|
} catch {
|
|
2621
1528
|
}
|
|
2622
|
-
return reply.send({
|
|
2623
|
-
jsonrpc: "2.0",
|
|
2624
|
-
id,
|
|
2625
|
-
error: { code: -32603, message: execResult.error ?? "Execution failed" }
|
|
2626
|
-
});
|
|
2627
1529
|
}
|
|
2628
|
-
|
|
2629
|
-
|
|
1530
|
+
} catch {
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
function upsertCard(cardData, owner) {
|
|
1534
|
+
const cardId = cardData.id;
|
|
1535
|
+
const existing = getCard(db, cardId);
|
|
1536
|
+
if (existing) {
|
|
1537
|
+
const updates = { ...cardData, availability: { ...cardData.availability ?? {}, online: true } };
|
|
1538
|
+
updateCard(db, cardId, owner, updates);
|
|
1539
|
+
} else {
|
|
1540
|
+
const card = { ...cardData, availability: { ...cardData.availability ?? {}, online: true } };
|
|
1541
|
+
insertCard(db, card);
|
|
1542
|
+
}
|
|
1543
|
+
return cardId;
|
|
1544
|
+
}
|
|
1545
|
+
function logAgentJoined(owner, cardName, cardId) {
|
|
1546
|
+
try {
|
|
1547
|
+
insertRequestLog(db, {
|
|
1548
|
+
id: randomUUID3(),
|
|
1549
|
+
card_id: cardId,
|
|
1550
|
+
card_name: cardName,
|
|
1551
|
+
requester: owner,
|
|
1552
|
+
status: "success",
|
|
1553
|
+
latency_ms: 0,
|
|
1554
|
+
credits_charged: 0,
|
|
1555
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1556
|
+
action_type: "agent_joined"
|
|
1557
|
+
});
|
|
1558
|
+
} catch {
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
function sendMessage(ws, msg) {
|
|
1562
|
+
if (ws.readyState === 1) {
|
|
1563
|
+
ws.send(JSON.stringify(msg));
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
function handleRegister(ws, msg) {
|
|
1567
|
+
const { owner, card } = msg;
|
|
1568
|
+
const existing = connections.get(owner);
|
|
1569
|
+
if (existing && existing !== ws) {
|
|
2630
1570
|
try {
|
|
2631
|
-
|
|
2632
|
-
id: randomUUID7(),
|
|
2633
|
-
card_id: cardId,
|
|
2634
|
-
card_name: cardName,
|
|
2635
|
-
skill_id: resolvedSkillId,
|
|
2636
|
-
requester,
|
|
2637
|
-
status: "success",
|
|
2638
|
-
latency_ms: execResult.latency_ms,
|
|
2639
|
-
credits_charged: creditsNeeded,
|
|
2640
|
-
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
2641
|
-
});
|
|
1571
|
+
existing.close(1e3, "Replaced by new connection");
|
|
2642
1572
|
} catch {
|
|
2643
1573
|
}
|
|
2644
|
-
return reply.send({ jsonrpc: "2.0", id, result: execResult.result });
|
|
2645
1574
|
}
|
|
2646
|
-
|
|
2647
|
-
const
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
1575
|
+
connections.set(owner, ws);
|
|
1576
|
+
const cardId = upsertCard(card, owner);
|
|
1577
|
+
const cardName = card.name ?? card.agent_name ?? owner;
|
|
1578
|
+
logAgentJoined(owner, cardName, cardId);
|
|
1579
|
+
markOwnerOnline(owner);
|
|
1580
|
+
sendMessage(ws, { type: "registered", agent_id: cardId });
|
|
1581
|
+
}
|
|
1582
|
+
function handleRelayRequest(ws, msg, fromOwner) {
|
|
1583
|
+
if (!checkRateLimit(fromOwner)) {
|
|
1584
|
+
sendMessage(ws, {
|
|
1585
|
+
type: "error",
|
|
1586
|
+
code: "rate_limited",
|
|
1587
|
+
message: `Rate limit exceeded: max ${RATE_LIMIT_MAX} requests per minute`,
|
|
1588
|
+
request_id: msg.id
|
|
2654
1589
|
});
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
1590
|
+
return;
|
|
1591
|
+
}
|
|
1592
|
+
const targetWs = connections.get(msg.target_owner);
|
|
1593
|
+
if (!targetWs || targetWs.readyState !== 1) {
|
|
1594
|
+
sendMessage(ws, {
|
|
1595
|
+
type: "response",
|
|
1596
|
+
id: msg.id,
|
|
1597
|
+
error: { code: -32603, message: `Agent offline: ${msg.target_owner}` }
|
|
1598
|
+
});
|
|
1599
|
+
return;
|
|
1600
|
+
}
|
|
1601
|
+
const timeout = setTimeout(() => {
|
|
1602
|
+
pendingRequests.delete(msg.id);
|
|
1603
|
+
sendMessage(ws, {
|
|
1604
|
+
type: "response",
|
|
1605
|
+
id: msg.id,
|
|
1606
|
+
error: { code: -32603, message: "Relay request timeout" }
|
|
1607
|
+
});
|
|
1608
|
+
}, RELAY_TIMEOUT_MS);
|
|
1609
|
+
pendingRequests.set(msg.id, { originOwner: fromOwner, timeout });
|
|
1610
|
+
sendMessage(targetWs, {
|
|
1611
|
+
type: "incoming_request",
|
|
1612
|
+
id: msg.id,
|
|
1613
|
+
from_owner: fromOwner,
|
|
1614
|
+
card_id: msg.card_id,
|
|
1615
|
+
skill_id: msg.skill_id,
|
|
1616
|
+
params: msg.params,
|
|
1617
|
+
requester: msg.requester ?? fromOwner,
|
|
1618
|
+
escrow_receipt: msg.escrow_receipt
|
|
1619
|
+
});
|
|
1620
|
+
}
|
|
1621
|
+
function handleRelayResponse(msg) {
|
|
1622
|
+
const pending = pendingRequests.get(msg.id);
|
|
1623
|
+
if (!pending) return;
|
|
1624
|
+
clearTimeout(pending.timeout);
|
|
1625
|
+
pendingRequests.delete(msg.id);
|
|
1626
|
+
const originWs = connections.get(pending.originOwner);
|
|
1627
|
+
if (originWs && originWs.readyState === 1) {
|
|
1628
|
+
sendMessage(originWs, {
|
|
1629
|
+
type: "response",
|
|
1630
|
+
id: msg.id,
|
|
1631
|
+
result: msg.result,
|
|
1632
|
+
error: msg.error
|
|
1633
|
+
});
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
function handleDisconnect(owner) {
|
|
1637
|
+
if (!owner) return;
|
|
1638
|
+
connections.delete(owner);
|
|
1639
|
+
rateLimits.delete(owner);
|
|
1640
|
+
markOwnerOffline(owner);
|
|
1641
|
+
for (const [reqId, pending] of pendingRequests) {
|
|
1642
|
+
if (pending.originOwner === owner) {
|
|
1643
|
+
clearTimeout(pending.timeout);
|
|
1644
|
+
pendingRequests.delete(reqId);
|
|
2678
1645
|
}
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
server.get("/ws", { websocket: true }, (socket) => {
|
|
1649
|
+
let registeredOwner;
|
|
1650
|
+
socket.on("message", (raw) => {
|
|
1651
|
+
let data;
|
|
2682
1652
|
try {
|
|
2683
|
-
|
|
2684
|
-
id: randomUUID7(),
|
|
2685
|
-
card_id: cardId,
|
|
2686
|
-
card_name: cardName,
|
|
2687
|
-
skill_id: resolvedSkillId,
|
|
2688
|
-
requester,
|
|
2689
|
-
status: "success",
|
|
2690
|
-
latency_ms: Date.now() - startMs,
|
|
2691
|
-
credits_charged: creditsNeeded,
|
|
2692
|
-
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
2693
|
-
});
|
|
1653
|
+
data = JSON.parse(typeof raw === "string" ? raw : raw.toString("utf-8"));
|
|
2694
1654
|
} catch {
|
|
1655
|
+
sendMessage(socket, { type: "error", code: "invalid_json", message: "Invalid JSON" });
|
|
1656
|
+
return;
|
|
2695
1657
|
}
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
try {
|
|
2703
|
-
insertRequestLog(registryDb, {
|
|
2704
|
-
id: randomUUID7(),
|
|
2705
|
-
card_id: cardId,
|
|
2706
|
-
card_name: cardName,
|
|
2707
|
-
skill_id: resolvedSkillId,
|
|
2708
|
-
requester,
|
|
2709
|
-
status: isTimeout ? "timeout" : "failure",
|
|
2710
|
-
latency_ms: Date.now() - startMs,
|
|
2711
|
-
credits_charged: 0,
|
|
2712
|
-
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1658
|
+
const parsed = RelayMessageSchema.safeParse(data);
|
|
1659
|
+
if (!parsed.success) {
|
|
1660
|
+
sendMessage(socket, {
|
|
1661
|
+
type: "error",
|
|
1662
|
+
code: "invalid_message",
|
|
1663
|
+
message: `Invalid message: ${parsed.error.issues[0]?.message ?? "unknown error"}`
|
|
2713
1664
|
});
|
|
2714
|
-
|
|
1665
|
+
return;
|
|
2715
1666
|
}
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
1667
|
+
const msg = parsed.data;
|
|
1668
|
+
switch (msg.type) {
|
|
1669
|
+
case "register":
|
|
1670
|
+
registeredOwner = msg.owner;
|
|
1671
|
+
handleRegister(socket, msg);
|
|
1672
|
+
break;
|
|
1673
|
+
case "relay_request":
|
|
1674
|
+
if (!registeredOwner) {
|
|
1675
|
+
sendMessage(socket, {
|
|
1676
|
+
type: "error",
|
|
1677
|
+
code: "not_registered",
|
|
1678
|
+
message: "Must send register message before relay requests"
|
|
1679
|
+
});
|
|
1680
|
+
return;
|
|
1681
|
+
}
|
|
1682
|
+
handleRelayRequest(socket, msg, registeredOwner);
|
|
1683
|
+
break;
|
|
1684
|
+
case "relay_response":
|
|
1685
|
+
handleRelayResponse(msg);
|
|
1686
|
+
break;
|
|
1687
|
+
default:
|
|
1688
|
+
break;
|
|
1689
|
+
}
|
|
1690
|
+
});
|
|
1691
|
+
socket.on("close", () => {
|
|
1692
|
+
handleDisconnect(registeredOwner);
|
|
1693
|
+
});
|
|
1694
|
+
socket.on("error", () => {
|
|
1695
|
+
handleDisconnect(registeredOwner);
|
|
1696
|
+
});
|
|
2722
1697
|
});
|
|
2723
|
-
return
|
|
1698
|
+
return {
|
|
1699
|
+
getOnlineCount: () => connections.size,
|
|
1700
|
+
getOnlineOwners: () => Array.from(connections.keys()),
|
|
1701
|
+
shutdown: () => {
|
|
1702
|
+
for (const [, ws] of connections) {
|
|
1703
|
+
try {
|
|
1704
|
+
ws.close(1001, "Server shutting down");
|
|
1705
|
+
} catch {
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1708
|
+
connections.clear();
|
|
1709
|
+
for (const [, pending] of pendingRequests) {
|
|
1710
|
+
clearTimeout(pending.timeout);
|
|
1711
|
+
}
|
|
1712
|
+
pendingRequests.clear();
|
|
1713
|
+
rateLimits.clear();
|
|
1714
|
+
}
|
|
1715
|
+
};
|
|
2724
1716
|
}
|
|
2725
1717
|
|
|
2726
1718
|
// src/registry/server.ts
|
|
2727
|
-
import Fastify2 from "fastify";
|
|
2728
|
-
import cors from "@fastify/cors";
|
|
2729
|
-
import fastifyStatic from "@fastify/static";
|
|
2730
|
-
import { join as join4, dirname } from "path";
|
|
2731
|
-
import { fileURLToPath } from "url";
|
|
2732
|
-
import { existsSync as existsSync5 } from "fs";
|
|
2733
1719
|
function stripInternal(card) {
|
|
2734
1720
|
const { _internal: _, ...publicCard } = card;
|
|
2735
1721
|
return publicCard;
|
|
@@ -2742,15 +1728,20 @@ function createRegistryServer(opts) {
|
|
|
2742
1728
|
methods: ["GET", "POST", "PATCH", "OPTIONS"],
|
|
2743
1729
|
allowedHeaders: ["Content-Type", "Authorization"]
|
|
2744
1730
|
});
|
|
1731
|
+
void server.register(fastifyWebsocket);
|
|
1732
|
+
let relayState = null;
|
|
1733
|
+
if (opts.creditDb) {
|
|
1734
|
+
relayState = registerWebSocketRelay(server, db);
|
|
1735
|
+
}
|
|
2745
1736
|
const __filename = fileURLToPath(import.meta.url);
|
|
2746
1737
|
const __dirname = dirname(__filename);
|
|
2747
1738
|
const hubDistCandidates = [
|
|
2748
|
-
|
|
1739
|
+
join(__dirname, "../../hub/dist"),
|
|
2749
1740
|
// When running from dist/registry/server.js
|
|
2750
|
-
|
|
1741
|
+
join(__dirname, "../../../hub/dist")
|
|
2751
1742
|
// Fallback for alternative layouts
|
|
2752
1743
|
];
|
|
2753
|
-
const hubDistDir = hubDistCandidates.find((p) =>
|
|
1744
|
+
const hubDistDir = hubDistCandidates.find((p) => existsSync2(p));
|
|
2754
1745
|
if (hubDistDir) {
|
|
2755
1746
|
void server.register(fastifyStatic, {
|
|
2756
1747
|
root: hubDistDir,
|
|
@@ -2920,6 +1911,29 @@ function createRegistryServer(opts) {
|
|
|
2920
1911
|
const items = getActivityFeed(db, limit, since);
|
|
2921
1912
|
return reply.send({ items, total: items.length, limit });
|
|
2922
1913
|
});
|
|
1914
|
+
server.get("/api/stats", async (_request, reply) => {
|
|
1915
|
+
const allCards = listCards(db);
|
|
1916
|
+
const onlineOwners = /* @__PURE__ */ new Set();
|
|
1917
|
+
if (relayState) {
|
|
1918
|
+
for (const owner of relayState.getOnlineOwners()) {
|
|
1919
|
+
onlineOwners.add(owner);
|
|
1920
|
+
}
|
|
1921
|
+
}
|
|
1922
|
+
for (const card of allCards) {
|
|
1923
|
+
if (card.availability.online) {
|
|
1924
|
+
onlineOwners.add(card.owner);
|
|
1925
|
+
}
|
|
1926
|
+
}
|
|
1927
|
+
const exchangeStmt = db.prepare(
|
|
1928
|
+
"SELECT COUNT(*) as count FROM request_log WHERE status = 'success' AND (action_type IS NULL OR action_type = 'auto_share')"
|
|
1929
|
+
);
|
|
1930
|
+
const exchangeRow = exchangeStmt.get();
|
|
1931
|
+
return reply.send({
|
|
1932
|
+
agents_online: onlineOwners.size,
|
|
1933
|
+
total_capabilities: allCards.length,
|
|
1934
|
+
total_exchanges: exchangeRow.count
|
|
1935
|
+
});
|
|
1936
|
+
});
|
|
2923
1937
|
if (opts.ownerApiKey && opts.ownerName) {
|
|
2924
1938
|
const ownerApiKey = opts.ownerApiKey;
|
|
2925
1939
|
const ownerName = opts.ownerName;
|
|
@@ -3026,7 +2040,7 @@ function createRegistryServer(opts) {
|
|
|
3026
2040
|
});
|
|
3027
2041
|
});
|
|
3028
2042
|
}
|
|
3029
|
-
return server;
|
|
2043
|
+
return { server, relayState };
|
|
3030
2044
|
}
|
|
3031
2045
|
|
|
3032
2046
|
// src/discovery/mdns.ts
|
|
@@ -3100,10 +2114,10 @@ async function stopAnnouncement() {
|
|
|
3100
2114
|
}
|
|
3101
2115
|
|
|
3102
2116
|
// src/openclaw/soul-sync.ts
|
|
3103
|
-
import { randomUUID as
|
|
2117
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
3104
2118
|
|
|
3105
2119
|
// src/skills/publish-capability.ts
|
|
3106
|
-
import { randomUUID as
|
|
2120
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
3107
2121
|
function parseSoulMd(content) {
|
|
3108
2122
|
const lines = content.split("\n");
|
|
3109
2123
|
let name = "";
|
|
@@ -3174,7 +2188,7 @@ function parseSoulMdV2(content) {
|
|
|
3174
2188
|
const parsed = parseSoulMd(content);
|
|
3175
2189
|
const skills = parsed.capabilities.map((cap) => {
|
|
3176
2190
|
const sanitizedId = cap.name.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
|
|
3177
|
-
const id = sanitizedId.length > 0 ? sanitizedId :
|
|
2191
|
+
const id = sanitizedId.length > 0 ? sanitizedId : randomUUID5();
|
|
3178
2192
|
return {
|
|
3179
2193
|
id,
|
|
3180
2194
|
name: cap.name,
|
|
@@ -3216,7 +2230,7 @@ function publishFromSoulV2(db, soulContent, owner) {
|
|
|
3216
2230
|
(c) => c.spec_version === "2.0"
|
|
3217
2231
|
);
|
|
3218
2232
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3219
|
-
const cardId = existingV2?.id ??
|
|
2233
|
+
const cardId = existingV2?.id ?? randomUUID5();
|
|
3220
2234
|
const card = {
|
|
3221
2235
|
spec_version: "2.0",
|
|
3222
2236
|
id: cardId,
|
|
@@ -3241,7 +2255,7 @@ function publishFromSoulV2(db, soulContent, owner) {
|
|
|
3241
2255
|
}
|
|
3242
2256
|
|
|
3243
2257
|
// src/openclaw/heartbeat-writer.ts
|
|
3244
|
-
import { readFileSync as
|
|
2258
|
+
import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync3 } from "fs";
|
|
3245
2259
|
var HEARTBEAT_MARKER_START = "<!-- agentbnb:start -->";
|
|
3246
2260
|
var HEARTBEAT_MARKER_END = "<!-- agentbnb:end -->";
|
|
3247
2261
|
function generateHeartbeatSection(autonomy, budget) {
|
|
@@ -3277,11 +2291,11 @@ function generateHeartbeatSection(autonomy, budget) {
|
|
|
3277
2291
|
].join("\n");
|
|
3278
2292
|
}
|
|
3279
2293
|
function injectHeartbeatSection(heartbeatPath, section) {
|
|
3280
|
-
if (!
|
|
3281
|
-
|
|
2294
|
+
if (!existsSync3(heartbeatPath)) {
|
|
2295
|
+
writeFileSync(heartbeatPath, section + "\n", "utf-8");
|
|
3282
2296
|
return;
|
|
3283
2297
|
}
|
|
3284
|
-
let content =
|
|
2298
|
+
let content = readFileSync2(heartbeatPath, "utf-8");
|
|
3285
2299
|
const startIdx = content.indexOf(HEARTBEAT_MARKER_START);
|
|
3286
2300
|
const endIdx = content.indexOf(HEARTBEAT_MARKER_END);
|
|
3287
2301
|
if (startIdx !== -1 && endIdx !== -1) {
|
|
@@ -3289,7 +2303,7 @@ function injectHeartbeatSection(heartbeatPath, section) {
|
|
|
3289
2303
|
} else {
|
|
3290
2304
|
content = content + "\n" + section + "\n";
|
|
3291
2305
|
}
|
|
3292
|
-
|
|
2306
|
+
writeFileSync(heartbeatPath, content, "utf-8");
|
|
3293
2307
|
}
|
|
3294
2308
|
|
|
3295
2309
|
// src/openclaw/skill.ts
|
|
@@ -3356,8 +2370,8 @@ program.command("init").description("Initialize AgentBnB config and create agent
|
|
|
3356
2370
|
const owner = opts.owner ?? `agent-${randomBytes(4).toString("hex")}`;
|
|
3357
2371
|
const token = randomBytes(32).toString("hex");
|
|
3358
2372
|
const configDir = getConfigDir();
|
|
3359
|
-
const dbPath =
|
|
3360
|
-
const creditDbPath =
|
|
2373
|
+
const dbPath = join2(configDir, "registry.db");
|
|
2374
|
+
const creditDbPath = join2(configDir, "credit.db");
|
|
3361
2375
|
const port = parseInt(opts.port, 10);
|
|
3362
2376
|
const ip = opts.host ?? getLanIp();
|
|
3363
2377
|
const existingConfig = loadConfig();
|
|
@@ -3471,7 +2485,7 @@ program.command("publish <card.json>").description("Publish a Capability Card to
|
|
|
3471
2485
|
}
|
|
3472
2486
|
let raw;
|
|
3473
2487
|
try {
|
|
3474
|
-
raw =
|
|
2488
|
+
raw = readFileSync3(cardPath, "utf-8");
|
|
3475
2489
|
} catch {
|
|
3476
2490
|
console.error(`Error: cannot read file: ${cardPath}`);
|
|
3477
2491
|
process.exit(1);
|
|
@@ -3622,7 +2636,7 @@ ${discovered.length} agent(s) found on local network`);
|
|
|
3622
2636
|
console.log(`
|
|
3623
2637
|
${outputCards.length} result(s)`);
|
|
3624
2638
|
});
|
|
3625
|
-
program.command("request [card-id]").description("Request a capability from another agent \u2014 direct (card-id) or auto (--query)").option("--params <json>", "Input parameters as JSON string", "{}").option("--peer <name>", "Peer name to send request to (resolves URL+token from peer registry)").option("--query <text>", "Search query for capability gap (triggers auto-request flow)").option("--max-cost <credits>", "Maximum credits to spend on auto-request (default: 50)").option("--json", "Output as JSON").action(async (cardId, opts) => {
|
|
2639
|
+
program.command("request [card-id]").description("Request a capability from another agent \u2014 direct (card-id) or auto (--query)").option("--params <json>", "Input parameters as JSON string", "{}").option("--peer <name>", "Peer name to send request to (resolves URL+token from peer registry)").option("--skill <id>", "Skill ID within a v2.0 card").option("--cost <credits>", "Credits to commit (required for cross-machine peer requests)").option("--query <text>", "Search query for capability gap (triggers auto-request flow)").option("--max-cost <credits>", "Maximum credits to spend on auto-request (default: 50)").option("--no-receipt", "Skip signed escrow receipt (local-only mode)").option("--json", "Output as JSON").action(async (cardId, opts) => {
|
|
3626
2640
|
const config = loadConfig();
|
|
3627
2641
|
if (!config) {
|
|
3628
2642
|
console.error("Error: not initialized. Run `agentbnb init` first.");
|
|
@@ -3638,8 +2652,8 @@ program.command("request [card-id]").description("Request a capability from anot
|
|
|
3638
2652
|
process.exit(1);
|
|
3639
2653
|
}
|
|
3640
2654
|
}
|
|
3641
|
-
const registryDb = openDatabase(
|
|
3642
|
-
const creditDb = openCreditDb(
|
|
2655
|
+
const registryDb = openDatabase(join2(getConfigDir(), "registry.db"));
|
|
2656
|
+
const creditDb = openCreditDb(join2(getConfigDir(), "credit.db"));
|
|
3643
2657
|
registryDb.pragma("busy_timeout = 5000");
|
|
3644
2658
|
creditDb.pragma("busy_timeout = 5000");
|
|
3645
2659
|
try {
|
|
@@ -3676,6 +2690,7 @@ program.command("request [card-id]").description("Request a capability from anot
|
|
|
3676
2690
|
}
|
|
3677
2691
|
let gatewayUrl;
|
|
3678
2692
|
let token;
|
|
2693
|
+
const isPeerRequest = !!opts.peer;
|
|
3679
2694
|
if (opts.peer) {
|
|
3680
2695
|
const peer = findPeer(opts.peer);
|
|
3681
2696
|
if (!peer) {
|
|
@@ -3688,20 +2703,87 @@ program.command("request [card-id]").description("Request a capability from anot
|
|
|
3688
2703
|
gatewayUrl = config.gateway_url;
|
|
3689
2704
|
token = config.token;
|
|
3690
2705
|
}
|
|
2706
|
+
const useReceipt = isPeerRequest && opts.receipt !== false;
|
|
2707
|
+
if (useReceipt && !opts.cost) {
|
|
2708
|
+
console.error("Error: --cost <credits> is required for peer requests. Specify the credits to commit.");
|
|
2709
|
+
process.exit(1);
|
|
2710
|
+
}
|
|
2711
|
+
let escrowId;
|
|
2712
|
+
let escrowReceipt;
|
|
2713
|
+
if (useReceipt) {
|
|
2714
|
+
const configDir = getConfigDir();
|
|
2715
|
+
const creditDb = openCreditDb(join2(configDir, "credit.db"));
|
|
2716
|
+
creditDb.pragma("busy_timeout = 5000");
|
|
2717
|
+
try {
|
|
2718
|
+
const keys = loadKeyPair(configDir);
|
|
2719
|
+
const amount = Number(opts.cost);
|
|
2720
|
+
if (isNaN(amount) || amount <= 0) {
|
|
2721
|
+
console.error("Error: --cost must be a positive number.");
|
|
2722
|
+
process.exit(1);
|
|
2723
|
+
}
|
|
2724
|
+
const receiptResult = createSignedEscrowReceipt(creditDb, keys.privateKey, keys.publicKey, {
|
|
2725
|
+
owner: config.owner,
|
|
2726
|
+
amount,
|
|
2727
|
+
cardId,
|
|
2728
|
+
skillId: opts.skill
|
|
2729
|
+
});
|
|
2730
|
+
escrowId = receiptResult.escrowId;
|
|
2731
|
+
escrowReceipt = receiptResult.receipt;
|
|
2732
|
+
if (!opts.json) {
|
|
2733
|
+
console.log(`Escrow: ${amount} credits held (ID: ${escrowId.slice(0, 8)}...)`);
|
|
2734
|
+
}
|
|
2735
|
+
} catch (err) {
|
|
2736
|
+
creditDb.close();
|
|
2737
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2738
|
+
if (opts.json) {
|
|
2739
|
+
console.log(JSON.stringify({ success: false, error: msg }, null, 2));
|
|
2740
|
+
} else {
|
|
2741
|
+
console.error(`Error creating escrow receipt: ${msg}`);
|
|
2742
|
+
}
|
|
2743
|
+
process.exit(1);
|
|
2744
|
+
}
|
|
2745
|
+
}
|
|
3691
2746
|
try {
|
|
3692
2747
|
const result = await requestCapability({
|
|
3693
2748
|
gatewayUrl,
|
|
3694
2749
|
token,
|
|
3695
2750
|
cardId,
|
|
3696
|
-
params
|
|
2751
|
+
params: { ...params, ...opts.skill ? { skill_id: opts.skill } : {} },
|
|
2752
|
+
escrowReceipt
|
|
3697
2753
|
});
|
|
2754
|
+
if (useReceipt && escrowId) {
|
|
2755
|
+
const configDir = getConfigDir();
|
|
2756
|
+
const creditDb = openCreditDb(join2(configDir, "credit.db"));
|
|
2757
|
+
creditDb.pragma("busy_timeout = 5000");
|
|
2758
|
+
try {
|
|
2759
|
+
settleRequesterEscrow(creditDb, escrowId);
|
|
2760
|
+
if (!opts.json) {
|
|
2761
|
+
console.log(`Escrow settled: ${opts.cost} credits deducted.`);
|
|
2762
|
+
}
|
|
2763
|
+
} finally {
|
|
2764
|
+
creditDb.close();
|
|
2765
|
+
}
|
|
2766
|
+
}
|
|
3698
2767
|
if (opts.json) {
|
|
3699
2768
|
console.log(JSON.stringify({ success: true, result }, null, 2));
|
|
3700
2769
|
} else {
|
|
3701
2770
|
console.log("Result:");
|
|
3702
|
-
console.log(JSON.stringify(result, null, 2));
|
|
2771
|
+
console.log(typeof result === "string" ? result : JSON.stringify(result, null, 2));
|
|
3703
2772
|
}
|
|
3704
2773
|
} catch (err) {
|
|
2774
|
+
if (useReceipt && escrowId) {
|
|
2775
|
+
const configDir = getConfigDir();
|
|
2776
|
+
const creditDb = openCreditDb(join2(configDir, "credit.db"));
|
|
2777
|
+
creditDb.pragma("busy_timeout = 5000");
|
|
2778
|
+
try {
|
|
2779
|
+
releaseRequesterEscrow(creditDb, escrowId);
|
|
2780
|
+
if (!opts.json) {
|
|
2781
|
+
console.log("Escrow released: credits refunded.");
|
|
2782
|
+
}
|
|
2783
|
+
} finally {
|
|
2784
|
+
creditDb.close();
|
|
2785
|
+
}
|
|
2786
|
+
}
|
|
3705
2787
|
const msg = err instanceof Error ? err.message : String(err);
|
|
3706
2788
|
if (opts.json) {
|
|
3707
2789
|
console.log(JSON.stringify({ success: false, error: msg }, null, 2));
|
|
@@ -3746,12 +2828,12 @@ Active Escrows (${heldEscrows.length}):`);
|
|
|
3746
2828
|
if (transactions.length > 0) {
|
|
3747
2829
|
console.log("\nRecent Transactions:");
|
|
3748
2830
|
for (const tx of transactions) {
|
|
3749
|
-
const
|
|
3750
|
-
console.log(` ${tx.created_at.slice(0, 19)} ${
|
|
2831
|
+
const sign = tx.amount > 0 ? "+" : "";
|
|
2832
|
+
console.log(` ${tx.created_at.slice(0, 19)} ${sign}${tx.amount} ${tx.reason}`);
|
|
3751
2833
|
}
|
|
3752
2834
|
}
|
|
3753
2835
|
});
|
|
3754
|
-
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("--announce", "Announce this gateway on the local network via mDNS").action(async (opts) => {
|
|
2836
|
+
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) => {
|
|
3755
2837
|
const config = loadConfig();
|
|
3756
2838
|
if (!config) {
|
|
3757
2839
|
console.error("Error: not initialized. Run `agentbnb init` first.");
|
|
@@ -3759,17 +2841,22 @@ program.command("serve").description("Start the AgentBnB gateway server").option
|
|
|
3759
2841
|
}
|
|
3760
2842
|
const port = opts.port ? parseInt(opts.port, 10) : config.gateway_port;
|
|
3761
2843
|
const registryPort = parseInt(opts.registryPort, 10);
|
|
3762
|
-
const skillsYamlPath = opts.skillsYaml ??
|
|
2844
|
+
const skillsYamlPath = opts.skillsYaml ?? join2(homedir(), ".agentbnb", "skills.yaml");
|
|
3763
2845
|
const runtime = new AgentRuntime({
|
|
3764
2846
|
registryDbPath: config.db_path,
|
|
3765
2847
|
creditDbPath: config.credit_db_path,
|
|
3766
2848
|
owner: config.owner,
|
|
3767
|
-
skillsYamlPath
|
|
2849
|
+
skillsYamlPath,
|
|
2850
|
+
conductorEnabled: opts.conductor ?? false,
|
|
2851
|
+
conductorToken: config.token
|
|
3768
2852
|
});
|
|
3769
2853
|
await runtime.start();
|
|
3770
2854
|
if (runtime.skillExecutor) {
|
|
3771
2855
|
console.log(`SkillExecutor initialized from ${skillsYamlPath}`);
|
|
3772
2856
|
}
|
|
2857
|
+
if (opts.conductor) {
|
|
2858
|
+
console.log("Conductor mode enabled \u2014 orchestrate/plan skills available via gateway");
|
|
2859
|
+
}
|
|
3773
2860
|
const autonomyConfig = config.autonomy ?? DEFAULT_AUTONOMY_CONFIG;
|
|
3774
2861
|
const idleMonitor = new IdleMonitor({
|
|
3775
2862
|
owner: config.owner,
|
|
@@ -3787,14 +2874,18 @@ program.command("serve").description("Start the AgentBnB gateway server").option
|
|
|
3787
2874
|
handlerUrl: opts.handlerUrl,
|
|
3788
2875
|
skillExecutor: runtime.skillExecutor
|
|
3789
2876
|
});
|
|
3790
|
-
let
|
|
2877
|
+
let registryFastify = null;
|
|
2878
|
+
let relayClient = null;
|
|
3791
2879
|
const gracefulShutdown = async () => {
|
|
3792
2880
|
console.log("\nShutting down...");
|
|
2881
|
+
if (relayClient) {
|
|
2882
|
+
relayClient.disconnect();
|
|
2883
|
+
}
|
|
3793
2884
|
if (opts.announce) {
|
|
3794
2885
|
await stopAnnouncement();
|
|
3795
2886
|
}
|
|
3796
|
-
if (
|
|
3797
|
-
await
|
|
2887
|
+
if (registryFastify) {
|
|
2888
|
+
await registryFastify.close();
|
|
3798
2889
|
}
|
|
3799
2890
|
await server.close();
|
|
3800
2891
|
await runtime.shutdown();
|
|
@@ -3813,15 +2904,66 @@ program.command("serve").description("Start the AgentBnB gateway server").option
|
|
|
3813
2904
|
if (!config.api_key) {
|
|
3814
2905
|
console.warn("No API key found. Run `agentbnb init` to enable dashboard features.");
|
|
3815
2906
|
}
|
|
3816
|
-
|
|
2907
|
+
const { server: regServer, relayState } = createRegistryServer({
|
|
3817
2908
|
registryDb: runtime.registryDb,
|
|
3818
2909
|
silent: false,
|
|
3819
2910
|
ownerName: config.owner,
|
|
3820
2911
|
ownerApiKey: config.api_key,
|
|
3821
2912
|
creditDb: runtime.creditDb
|
|
3822
2913
|
});
|
|
3823
|
-
|
|
2914
|
+
registryFastify = regServer;
|
|
2915
|
+
await registryFastify.listen({ port: registryPort, host: "0.0.0.0" });
|
|
3824
2916
|
console.log(`Registry API: http://0.0.0.0:${registryPort}/cards`);
|
|
2917
|
+
if (relayState) {
|
|
2918
|
+
console.log(`WebSocket relay active on /ws`);
|
|
2919
|
+
}
|
|
2920
|
+
}
|
|
2921
|
+
if (opts.registry) {
|
|
2922
|
+
const { RelayClient } = await import("../websocket-client-5TIQDYQ4.js");
|
|
2923
|
+
const { executeCapabilityRequest: executeCapabilityRequest2 } = await import("../execute-3T5RF3DP.js");
|
|
2924
|
+
const cards = listCards(runtime.registryDb, config.owner);
|
|
2925
|
+
const card = cards[0] ?? {
|
|
2926
|
+
id: config.owner,
|
|
2927
|
+
owner: config.owner,
|
|
2928
|
+
name: config.owner,
|
|
2929
|
+
description: "Agent registered via CLI",
|
|
2930
|
+
spec_version: "1.0",
|
|
2931
|
+
level: 1,
|
|
2932
|
+
inputs: [],
|
|
2933
|
+
outputs: [],
|
|
2934
|
+
pricing: { credits_per_call: 0 },
|
|
2935
|
+
availability: { online: true }
|
|
2936
|
+
};
|
|
2937
|
+
relayClient = new RelayClient({
|
|
2938
|
+
registryUrl: opts.registry,
|
|
2939
|
+
owner: config.owner,
|
|
2940
|
+
token: config.token,
|
|
2941
|
+
card,
|
|
2942
|
+
onRequest: async (req) => {
|
|
2943
|
+
const result = await executeCapabilityRequest2({
|
|
2944
|
+
registryDb: runtime.registryDb,
|
|
2945
|
+
creditDb: runtime.creditDb,
|
|
2946
|
+
cardId: req.card_id,
|
|
2947
|
+
skillId: req.skill_id,
|
|
2948
|
+
params: req.params,
|
|
2949
|
+
requester: req.requester ?? req.from_owner,
|
|
2950
|
+
escrowReceipt: req.escrow_receipt,
|
|
2951
|
+
skillExecutor: runtime.skillExecutor,
|
|
2952
|
+
handlerUrl: opts.handlerUrl
|
|
2953
|
+
});
|
|
2954
|
+
if (result.success) {
|
|
2955
|
+
return { result: result.result };
|
|
2956
|
+
}
|
|
2957
|
+
return { error: { code: result.error.code, message: result.error.message } };
|
|
2958
|
+
}
|
|
2959
|
+
});
|
|
2960
|
+
try {
|
|
2961
|
+
await relayClient.connect();
|
|
2962
|
+
console.log(`Connected to registry: ${opts.registry}`);
|
|
2963
|
+
} catch (err) {
|
|
2964
|
+
console.warn(`Warning: could not connect to registry ${opts.registry}: ${err instanceof Error ? err.message : err}`);
|
|
2965
|
+
console.warn("Will auto-reconnect in background...");
|
|
2966
|
+
}
|
|
3825
2967
|
}
|
|
3826
2968
|
if (opts.announce) {
|
|
3827
2969
|
announceGateway(config.owner, port);
|
|
@@ -3829,8 +2971,9 @@ program.command("serve").description("Start the AgentBnB gateway server").option
|
|
|
3829
2971
|
}
|
|
3830
2972
|
} catch (err) {
|
|
3831
2973
|
console.error("Failed to start:", err);
|
|
3832
|
-
if (
|
|
3833
|
-
|
|
2974
|
+
if (relayClient) relayClient.disconnect();
|
|
2975
|
+
if (registryFastify) {
|
|
2976
|
+
await registryFastify.close().catch(() => {
|
|
3834
2977
|
});
|
|
3835
2978
|
}
|
|
3836
2979
|
await runtime.shutdown();
|
|
@@ -3985,7 +3128,7 @@ openclaw.command("sync").description("Read SOUL.md and publish/update a v2.0 cap
|
|
|
3985
3128
|
}
|
|
3986
3129
|
let content;
|
|
3987
3130
|
try {
|
|
3988
|
-
content =
|
|
3131
|
+
content = readFileSync3(opts.soulPath, "utf-8");
|
|
3989
3132
|
} catch {
|
|
3990
3133
|
console.error(`Error: cannot read SOUL.md at ${opts.soulPath}`);
|
|
3991
3134
|
process.exit(1);
|
|
@@ -4045,4 +3188,38 @@ openclaw.command("rules").description("Print HEARTBEAT.md rules block (or inject
|
|
|
4045
3188
|
console.log(section);
|
|
4046
3189
|
}
|
|
4047
3190
|
});
|
|
3191
|
+
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) => {
|
|
3192
|
+
const { conductAction } = await import("../conduct-M57F72RK.js");
|
|
3193
|
+
const result = await conductAction(task, opts);
|
|
3194
|
+
if (opts.json) {
|
|
3195
|
+
console.log(JSON.stringify(result, null, 2));
|
|
3196
|
+
if (!result.success) process.exit(1);
|
|
3197
|
+
return;
|
|
3198
|
+
}
|
|
3199
|
+
if (!result.success) {
|
|
3200
|
+
console.error(`Error: ${result.error}`);
|
|
3201
|
+
process.exit(1);
|
|
3202
|
+
}
|
|
3203
|
+
const plan = result.plan;
|
|
3204
|
+
console.log("\nExecution Plan:");
|
|
3205
|
+
for (const step of plan.steps) {
|
|
3206
|
+
const deps = step.depends_on.length > 0 ? ` [depends on prior steps]` : "";
|
|
3207
|
+
console.log(` Step ${step.step}: ${step.description} (${step.capability}) -> @${step.agent} (${step.credits} cr)${deps}`);
|
|
3208
|
+
}
|
|
3209
|
+
console.log(` Orchestration fee: ${plan.orchestration_fee} cr`);
|
|
3210
|
+
console.log(` Total estimated: ${plan.estimated_total} cr`);
|
|
3211
|
+
if (result.execution) {
|
|
3212
|
+
console.log("\nResults:");
|
|
3213
|
+
console.log(JSON.stringify(result.execution, null, 2));
|
|
3214
|
+
console.log(`
|
|
3215
|
+
Total credits spent: ${result.total_credits ?? 0} cr`);
|
|
3216
|
+
console.log(`Latency: ${result.latency_ms ?? 0} ms`);
|
|
3217
|
+
}
|
|
3218
|
+
if (result.errors && result.errors.length > 0) {
|
|
3219
|
+
console.log("\nErrors:");
|
|
3220
|
+
for (const err of result.errors) {
|
|
3221
|
+
console.log(` - ${err}`);
|
|
3222
|
+
}
|
|
3223
|
+
}
|
|
3224
|
+
});
|
|
4048
3225
|
await program.parseAsync(process.argv);
|