agentbnb 2.2.0 → 3.0.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-BEI5MTNZ.js +91 -0
- package/dist/chunk-EZB4RUIG.js +372 -0
- package/dist/chunk-PJSYSVKN.js +491 -0
- package/dist/chunk-TQMI73LL.js +125 -0
- package/dist/chunk-V7M6GIJZ.js +666 -0
- package/dist/cli/index.js +495 -1353
- package/dist/conduct-JZJS2ZHA.js +115 -0
- package/dist/conductor-mode-DJ3RIJ5T.js +111 -0
- package/dist/index.d.ts +2174 -120
- package/dist/index.js +1721 -27
- package/dist/peers-G36URZYB.js +12 -0
- package/package.json +2 -2
package/dist/cli/index.js
CHANGED
|
@@ -1,168 +1,69 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
getActivityFeed,
|
|
4
|
+
getCard,
|
|
5
|
+
getRequestLog,
|
|
6
|
+
getSkillRequestCount,
|
|
7
|
+
insertCard,
|
|
8
|
+
insertRequestLog,
|
|
9
|
+
listCards,
|
|
10
|
+
openDatabase,
|
|
11
|
+
updateCard,
|
|
12
|
+
updateReputation,
|
|
13
|
+
updateSkillAvailability,
|
|
14
|
+
updateSkillIdleRate
|
|
15
|
+
} from "../chunk-PJSYSVKN.js";
|
|
16
|
+
import {
|
|
17
|
+
AutoRequestor,
|
|
18
|
+
BudgetManager,
|
|
19
|
+
DEFAULT_AUTONOMY_CONFIG,
|
|
20
|
+
DEFAULT_BUDGET_CONFIG,
|
|
21
|
+
bootstrapAgent,
|
|
22
|
+
confirmEscrowDebit,
|
|
23
|
+
filterCards,
|
|
24
|
+
getAutonomyTier,
|
|
25
|
+
getBalance,
|
|
26
|
+
getTransactions,
|
|
27
|
+
holdEscrow,
|
|
28
|
+
insertAuditEvent,
|
|
29
|
+
interpolateObject,
|
|
30
|
+
listPendingRequests,
|
|
31
|
+
openCreditDb,
|
|
32
|
+
recordEarning,
|
|
33
|
+
releaseEscrow,
|
|
34
|
+
requestCapability,
|
|
35
|
+
resolvePendingRequest,
|
|
36
|
+
searchCards,
|
|
37
|
+
settleEscrow
|
|
38
|
+
} from "../chunk-V7M6GIJZ.js";
|
|
39
|
+
import {
|
|
40
|
+
findPeer,
|
|
41
|
+
getConfigDir,
|
|
42
|
+
loadConfig,
|
|
43
|
+
loadPeers,
|
|
44
|
+
removePeer,
|
|
45
|
+
saveConfig,
|
|
46
|
+
savePeer
|
|
47
|
+
} from "../chunk-BEI5MTNZ.js";
|
|
48
|
+
import {
|
|
49
|
+
AgentBnBError,
|
|
50
|
+
CapabilityCardSchema,
|
|
51
|
+
CapabilityCardV2Schema
|
|
52
|
+
} from "../chunk-TQMI73LL.js";
|
|
2
53
|
|
|
3
54
|
// src/cli/index.ts
|
|
4
55
|
import { Command } from "commander";
|
|
5
|
-
import { readFileSync as
|
|
56
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
6
57
|
import { createRequire } from "module";
|
|
7
58
|
import { randomBytes } from "crypto";
|
|
8
|
-
import { join as
|
|
9
|
-
import { networkInterfaces, homedir
|
|
59
|
+
import { join as join3 } from "path";
|
|
60
|
+
import { networkInterfaces, homedir } from "os";
|
|
10
61
|
import { createInterface } from "readline";
|
|
11
62
|
|
|
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
63
|
// src/credit/signing.ts
|
|
41
64
|
import { generateKeyPairSync, sign, verify, createPublicKey, createPrivateKey } from "crypto";
|
|
42
|
-
import { writeFileSync
|
|
43
|
-
import { join
|
|
44
|
-
|
|
45
|
-
// src/types/index.ts
|
|
46
|
-
import { z } from "zod";
|
|
47
|
-
var IOSchemaSchema = z.object({
|
|
48
|
-
name: z.string(),
|
|
49
|
-
type: z.enum(["text", "json", "file", "audio", "image", "video", "stream"]),
|
|
50
|
-
description: z.string().optional(),
|
|
51
|
-
required: z.boolean().default(true),
|
|
52
|
-
schema: z.record(z.unknown()).optional()
|
|
53
|
-
// JSON Schema
|
|
54
|
-
});
|
|
55
|
-
var PoweredBySchema = z.object({
|
|
56
|
-
provider: z.string().min(1),
|
|
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()
|
|
152
|
-
});
|
|
153
|
-
var AnyCardSchema = z.discriminatedUnion("spec_version", [
|
|
154
|
-
CapabilityCardSchema,
|
|
155
|
-
CapabilityCardV2Schema
|
|
156
|
-
]);
|
|
157
|
-
var AgentBnBError = class extends Error {
|
|
158
|
-
constructor(message, code) {
|
|
159
|
-
super(message);
|
|
160
|
-
this.code = code;
|
|
161
|
-
this.name = "AgentBnBError";
|
|
162
|
-
}
|
|
163
|
-
};
|
|
164
|
-
|
|
165
|
-
// src/credit/signing.ts
|
|
65
|
+
import { writeFileSync, readFileSync, existsSync, chmodSync } from "fs";
|
|
66
|
+
import { join } from "path";
|
|
166
67
|
function generateKeyPair() {
|
|
167
68
|
const { publicKey, privateKey } = generateKeyPairSync("ed25519", {
|
|
168
69
|
publicKeyEncoding: { type: "spki", format: "der" },
|
|
@@ -174,536 +75,95 @@ function generateKeyPair() {
|
|
|
174
75
|
};
|
|
175
76
|
}
|
|
176
77
|
function saveKeyPair(configDir, keys) {
|
|
177
|
-
const privatePath =
|
|
178
|
-
const publicPath =
|
|
179
|
-
|
|
78
|
+
const privatePath = join(configDir, "private.key");
|
|
79
|
+
const publicPath = join(configDir, "public.key");
|
|
80
|
+
writeFileSync(privatePath, keys.privateKey);
|
|
180
81
|
chmodSync(privatePath, 384);
|
|
181
|
-
|
|
82
|
+
writeFileSync(publicPath, keys.publicKey);
|
|
182
83
|
}
|
|
183
84
|
function loadKeyPair(configDir) {
|
|
184
|
-
const privatePath =
|
|
185
|
-
const publicPath =
|
|
186
|
-
if (!
|
|
85
|
+
const privatePath = join(configDir, "private.key");
|
|
86
|
+
const publicPath = join(configDir, "public.key");
|
|
87
|
+
if (!existsSync(privatePath) || !existsSync(publicPath)) {
|
|
187
88
|
throw new AgentBnBError("Keypair not found. Run `agentbnb init` to generate one.", "KEYPAIR_NOT_FOUND");
|
|
188
89
|
}
|
|
189
90
|
return {
|
|
190
|
-
publicKey:
|
|
191
|
-
privateKey:
|
|
91
|
+
publicKey: readFileSync(publicPath),
|
|
92
|
+
privateKey: readFileSync(privatePath)
|
|
192
93
|
};
|
|
193
94
|
}
|
|
194
|
-
|
|
195
|
-
|
|
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;
|
|
95
|
+
function canonicalJson(data) {
|
|
96
|
+
return JSON.stringify(data, Object.keys(data).sort());
|
|
205
97
|
}
|
|
206
|
-
function
|
|
207
|
-
const
|
|
208
|
-
const
|
|
209
|
-
const
|
|
210
|
-
|
|
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
|
-
);
|
|
98
|
+
function signEscrowReceipt(data, privateKey) {
|
|
99
|
+
const message = Buffer.from(canonicalJson(data), "utf-8");
|
|
100
|
+
const keyObject = createPrivateKey({ key: privateKey, format: "der", type: "pkcs8" });
|
|
101
|
+
const signature = sign(null, message, keyObject);
|
|
102
|
+
return signature.toString("base64url");
|
|
229
103
|
}
|
|
230
|
-
|
|
231
|
-
// src/autonomy/idle-monitor.ts
|
|
232
|
-
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
|
-
`);
|
|
104
|
+
function verifyEscrowReceipt(data, signature, publicKey) {
|
|
259
105
|
try {
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
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");
|
|
106
|
+
const message = Buffer.from(canonicalJson(data), "utf-8");
|
|
107
|
+
const keyObject = createPublicKey({ key: publicKey, format: "der", type: "spki" });
|
|
108
|
+
const sigBuffer = Buffer.from(signature, "base64url");
|
|
109
|
+
return verify(null, message, keyObject, sigBuffer);
|
|
269
110
|
} catch {
|
|
111
|
+
return false;
|
|
270
112
|
}
|
|
271
113
|
}
|
|
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
114
|
|
|
347
|
-
// src/
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
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)
|
|
115
|
+
// src/credit/escrow-receipt.ts
|
|
116
|
+
import { z } from "zod";
|
|
117
|
+
import { randomUUID } from "crypto";
|
|
118
|
+
var EscrowReceiptSchema = z.object({
|
|
119
|
+
requester_owner: z.string().min(1),
|
|
120
|
+
requester_public_key: z.string().min(1),
|
|
121
|
+
amount: z.number().positive(),
|
|
122
|
+
card_id: z.string().min(1),
|
|
123
|
+
skill_id: z.string().optional(),
|
|
124
|
+
timestamp: z.string(),
|
|
125
|
+
nonce: z.string().uuid(),
|
|
126
|
+
signature: z.string().min(1)
|
|
127
|
+
});
|
|
128
|
+
function createSignedEscrowReceipt(db, privateKey, publicKey, opts) {
|
|
129
|
+
const escrowId = holdEscrow(db, opts.owner, opts.amount, opts.cardId);
|
|
130
|
+
const receiptData = {
|
|
131
|
+
requester_owner: opts.owner,
|
|
132
|
+
requester_public_key: publicKey.toString("hex"),
|
|
133
|
+
amount: opts.amount,
|
|
134
|
+
card_id: opts.cardId,
|
|
135
|
+
...opts.skillId ? { skill_id: opts.skillId } : {},
|
|
136
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
137
|
+
nonce: randomUUID()
|
|
646
138
|
};
|
|
647
|
-
const
|
|
648
|
-
const
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
stmt.run(JSON.stringify(updatedCard), now, cardId);
|
|
139
|
+
const signature = signEscrowReceipt(receiptData, privateKey);
|
|
140
|
+
const receipt = {
|
|
141
|
+
...receiptData,
|
|
142
|
+
signature
|
|
143
|
+
};
|
|
144
|
+
return { escrowId, receipt };
|
|
654
145
|
}
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
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
|
|
146
|
+
|
|
147
|
+
// src/credit/settlement.ts
|
|
148
|
+
function settleProviderEarning(providerDb, providerOwner, receipt) {
|
|
149
|
+
recordEarning(
|
|
150
|
+
providerDb,
|
|
151
|
+
providerOwner,
|
|
152
|
+
receipt.amount,
|
|
153
|
+
receipt.card_id,
|
|
154
|
+
receipt.nonce
|
|
670
155
|
);
|
|
156
|
+
return { settled: true };
|
|
671
157
|
}
|
|
672
|
-
function
|
|
673
|
-
|
|
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
|
-
);
|
|
158
|
+
function settleRequesterEscrow(requesterDb, escrowId) {
|
|
159
|
+
confirmEscrowDebit(requesterDb, escrowId);
|
|
692
160
|
}
|
|
693
|
-
function
|
|
694
|
-
|
|
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));
|
|
161
|
+
function releaseRequesterEscrow(requesterDb, escrowId) {
|
|
162
|
+
releaseEscrow(requesterDb, escrowId);
|
|
704
163
|
}
|
|
705
164
|
|
|
706
165
|
// src/autonomy/idle-monitor.ts
|
|
166
|
+
import { Cron } from "croner";
|
|
707
167
|
var IdleMonitor = class {
|
|
708
168
|
job;
|
|
709
169
|
owner;
|
|
@@ -795,566 +255,6 @@ var IdleMonitor = class {
|
|
|
795
255
|
}
|
|
796
256
|
};
|
|
797
257
|
|
|
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
258
|
// src/cli/remote-registry.ts
|
|
1359
259
|
var RegistryTimeoutError = class extends AgentBnBError {
|
|
1360
260
|
constructor(url) {
|
|
@@ -1439,7 +339,7 @@ function mergeResults(localCards, remoteCards, hasQuery) {
|
|
|
1439
339
|
}
|
|
1440
340
|
|
|
1441
341
|
// src/cli/onboarding.ts
|
|
1442
|
-
import { randomUUID as
|
|
342
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
1443
343
|
import { createConnection } from "net";
|
|
1444
344
|
var KNOWN_API_KEYS = [
|
|
1445
345
|
"OPENAI_API_KEY",
|
|
@@ -1588,7 +488,7 @@ function buildDraftCard(apiKey, owner) {
|
|
|
1588
488
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1589
489
|
return {
|
|
1590
490
|
spec_version: "1.0",
|
|
1591
|
-
id:
|
|
491
|
+
id: randomUUID2(),
|
|
1592
492
|
owner,
|
|
1593
493
|
name: template.name,
|
|
1594
494
|
description: template.description,
|
|
@@ -1608,7 +508,7 @@ function buildDraftCard(apiKey, owner) {
|
|
|
1608
508
|
}
|
|
1609
509
|
|
|
1610
510
|
// src/runtime/agent-runtime.ts
|
|
1611
|
-
import { readFileSync as
|
|
511
|
+
import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
|
|
1612
512
|
|
|
1613
513
|
// src/skills/executor.ts
|
|
1614
514
|
var SkillExecutor = class {
|
|
@@ -1766,17 +666,29 @@ var CommandSkillConfigSchema = z2.object({
|
|
|
1766
666
|
timeout_ms: z2.number().positive().default(3e4),
|
|
1767
667
|
pricing: PricingSchema
|
|
1768
668
|
});
|
|
669
|
+
var ConductorSkillConfigSchema = z2.object({
|
|
670
|
+
id: z2.string().min(1),
|
|
671
|
+
type: z2.literal("conductor"),
|
|
672
|
+
name: z2.string().min(1),
|
|
673
|
+
conductor_skill: z2.enum(["orchestrate", "plan"]),
|
|
674
|
+
pricing: PricingSchema,
|
|
675
|
+
timeout_ms: z2.number().positive().optional()
|
|
676
|
+
});
|
|
1769
677
|
var SkillConfigSchema = z2.discriminatedUnion("type", [
|
|
1770
678
|
ApiSkillConfigSchema,
|
|
1771
679
|
PipelineSkillConfigSchema,
|
|
1772
680
|
OpenClawSkillConfigSchema,
|
|
1773
|
-
CommandSkillConfigSchema
|
|
681
|
+
CommandSkillConfigSchema,
|
|
682
|
+
ConductorSkillConfigSchema
|
|
1774
683
|
]);
|
|
1775
684
|
var SkillsFileSchema = z2.object({
|
|
1776
685
|
skills: z2.array(SkillConfigSchema)
|
|
1777
686
|
});
|
|
1778
687
|
function expandEnvVars(value) {
|
|
1779
688
|
return value.replace(/\$\{([^}]+)\}/g, (_match, varName) => {
|
|
689
|
+
if (/[.a-z]/.test(varName)) {
|
|
690
|
+
return _match;
|
|
691
|
+
}
|
|
1780
692
|
const envValue = process.env[varName];
|
|
1781
693
|
if (envValue === void 0) {
|
|
1782
694
|
throw new Error(`Environment variable "${varName}" is not defined`);
|
|
@@ -1867,7 +779,7 @@ function applyInputMapping(params, mapping) {
|
|
|
1867
779
|
pathParams[key] = String(value);
|
|
1868
780
|
break;
|
|
1869
781
|
case "header":
|
|
1870
|
-
headers[key] = String(value);
|
|
782
|
+
headers[key] = String(value).replace(/[\r\n]/g, "");
|
|
1871
783
|
break;
|
|
1872
784
|
}
|
|
1873
785
|
}
|
|
@@ -1962,58 +874,34 @@ var ApiExecutor = class {
|
|
|
1962
874
|
};
|
|
1963
875
|
|
|
1964
876
|
// src/skills/pipeline-executor.ts
|
|
1965
|
-
import {
|
|
877
|
+
import { execFile } from "child_process";
|
|
1966
878
|
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);
|
|
879
|
+
var execFileAsync = promisify(execFile);
|
|
880
|
+
function shellEscape(value) {
|
|
881
|
+
return "'" + value.replace(/'/g, "'\\''") + "'";
|
|
882
|
+
}
|
|
883
|
+
function safeInterpolateCommand(template, context) {
|
|
884
|
+
return template.replace(/\$\{([^}]+)\}/g, (_match, expr) => {
|
|
885
|
+
const parts = expr.split(".");
|
|
886
|
+
let current = context;
|
|
887
|
+
for (const part of parts) {
|
|
888
|
+
if (current === null || typeof current !== "object") return "";
|
|
889
|
+
const bracketMatch = part.match(/^(\w+)\[(\d+)\]$/);
|
|
890
|
+
if (bracketMatch) {
|
|
891
|
+
current = current[bracketMatch[1]];
|
|
892
|
+
if (Array.isArray(current)) {
|
|
893
|
+
current = current[parseInt(bracketMatch[2], 10)];
|
|
894
|
+
} else {
|
|
895
|
+
return "";
|
|
896
|
+
}
|
|
897
|
+
} else {
|
|
898
|
+
current = current[part];
|
|
899
|
+
}
|
|
1991
900
|
}
|
|
1992
|
-
|
|
901
|
+
if (current === void 0 || current === null) return "";
|
|
902
|
+
return shellEscape(String(current));
|
|
1993
903
|
});
|
|
1994
904
|
}
|
|
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
905
|
var PipelineExecutor = class {
|
|
2018
906
|
/**
|
|
2019
907
|
* @param skillExecutor - The parent SkillExecutor used to dispatch sub-skill calls.
|
|
@@ -2074,12 +962,12 @@ var PipelineExecutor = class {
|
|
|
2074
962
|
}
|
|
2075
963
|
stepResult = subResult.result;
|
|
2076
964
|
} else if ("command" in step && step.command) {
|
|
2077
|
-
const interpolatedCommand =
|
|
965
|
+
const interpolatedCommand = safeInterpolateCommand(
|
|
2078
966
|
step.command,
|
|
2079
967
|
context
|
|
2080
968
|
);
|
|
2081
969
|
try {
|
|
2082
|
-
const { stdout } = await
|
|
970
|
+
const { stdout } = await execFileAsync("/bin/sh", ["-c", interpolatedCommand], { timeout: 3e4 });
|
|
2083
971
|
stepResult = stdout.trim();
|
|
2084
972
|
} catch (err) {
|
|
2085
973
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -2106,7 +994,7 @@ var PipelineExecutor = class {
|
|
|
2106
994
|
};
|
|
2107
995
|
|
|
2108
996
|
// src/skills/openclaw-bridge.ts
|
|
2109
|
-
import {
|
|
997
|
+
import { execFileSync } from "child_process";
|
|
2110
998
|
var DEFAULT_BASE_URL = "http://localhost:3000";
|
|
2111
999
|
var DEFAULT_TIMEOUT_MS = 6e4;
|
|
2112
1000
|
function buildPayload(config, params) {
|
|
@@ -2151,12 +1039,22 @@ async function executeWebhook(config, payload) {
|
|
|
2151
1039
|
clearTimeout(timer);
|
|
2152
1040
|
}
|
|
2153
1041
|
}
|
|
1042
|
+
function validateAgentName(name) {
|
|
1043
|
+
return /^[a-zA-Z0-9._-]+$/.test(name);
|
|
1044
|
+
}
|
|
2154
1045
|
function executeProcess(config, payload) {
|
|
2155
1046
|
const timeoutMs = config.timeout_ms ?? DEFAULT_TIMEOUT_MS;
|
|
1047
|
+
if (!validateAgentName(config.agent_name)) {
|
|
1048
|
+
return {
|
|
1049
|
+
success: false,
|
|
1050
|
+
error: `Invalid agent name: "${config.agent_name}" (only alphanumeric, hyphens, underscores, dots allowed)`
|
|
1051
|
+
};
|
|
1052
|
+
}
|
|
2156
1053
|
const inputJson = JSON.stringify(payload);
|
|
2157
|
-
const cmd = `openclaw run ${config.agent_name} --input '${inputJson}'`;
|
|
2158
1054
|
try {
|
|
2159
|
-
const stdout =
|
|
1055
|
+
const stdout = execFileSync("openclaw", ["run", config.agent_name, "--input", inputJson], {
|
|
1056
|
+
timeout: timeoutMs
|
|
1057
|
+
});
|
|
2160
1058
|
const text = stdout.toString().trim();
|
|
2161
1059
|
const result = JSON.parse(text);
|
|
2162
1060
|
return { success: true, result };
|
|
@@ -2229,10 +1127,35 @@ var OpenClawBridge = class {
|
|
|
2229
1127
|
};
|
|
2230
1128
|
|
|
2231
1129
|
// src/skills/command-executor.ts
|
|
2232
|
-
import {
|
|
2233
|
-
function
|
|
1130
|
+
import { execFile as execFile2 } from "child_process";
|
|
1131
|
+
function shellEscape2(value) {
|
|
1132
|
+
return "'" + value.replace(/'/g, "'\\''") + "'";
|
|
1133
|
+
}
|
|
1134
|
+
function safeInterpolateCommand2(template, context) {
|
|
1135
|
+
return template.replace(/\$\{([^}]+)\}/g, (_match, expr) => {
|
|
1136
|
+
const parts = expr.split(".");
|
|
1137
|
+
let current = context;
|
|
1138
|
+
for (const part of parts) {
|
|
1139
|
+
if (current === null || typeof current !== "object") return "";
|
|
1140
|
+
const bracketMatch = part.match(/^(\w+)\[(\d+)\]$/);
|
|
1141
|
+
if (bracketMatch) {
|
|
1142
|
+
current = current[bracketMatch[1]];
|
|
1143
|
+
if (Array.isArray(current)) {
|
|
1144
|
+
current = current[parseInt(bracketMatch[2], 10)];
|
|
1145
|
+
} else {
|
|
1146
|
+
return "";
|
|
1147
|
+
}
|
|
1148
|
+
} else {
|
|
1149
|
+
current = current[part];
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
if (current === void 0 || current === null) return "";
|
|
1153
|
+
return shellEscape2(String(current));
|
|
1154
|
+
});
|
|
1155
|
+
}
|
|
1156
|
+
function execFileAsync2(file, args, options) {
|
|
2234
1157
|
return new Promise((resolve, reject) => {
|
|
2235
|
-
|
|
1158
|
+
execFile2(file, args, options, (error, stdout, stderr) => {
|
|
2236
1159
|
const stdoutStr = typeof stdout === "string" ? stdout : stdout.toString();
|
|
2237
1160
|
const stderrStr = typeof stderr === "string" ? stderr : stderr.toString();
|
|
2238
1161
|
if (error) {
|
|
@@ -2269,17 +1192,16 @@ var CommandExecutor = class {
|
|
|
2269
1192
|
};
|
|
2270
1193
|
}
|
|
2271
1194
|
}
|
|
2272
|
-
const interpolatedCommand =
|
|
1195
|
+
const interpolatedCommand = safeInterpolateCommand2(cmdConfig.command, { params });
|
|
2273
1196
|
const timeout = cmdConfig.timeout_ms ?? 3e4;
|
|
2274
1197
|
const cwd = cmdConfig.working_dir ?? process.cwd();
|
|
2275
1198
|
let stdout;
|
|
2276
1199
|
try {
|
|
2277
|
-
const result = await
|
|
1200
|
+
const result = await execFileAsync2("/bin/sh", ["-c", interpolatedCommand], {
|
|
2278
1201
|
timeout,
|
|
2279
1202
|
cwd,
|
|
2280
|
-
maxBuffer: 10 * 1024 * 1024
|
|
1203
|
+
maxBuffer: 10 * 1024 * 1024
|
|
2281
1204
|
// 10 MB
|
|
2282
|
-
shell: "/bin/sh"
|
|
2283
1205
|
});
|
|
2284
1206
|
stdout = result.stdout;
|
|
2285
1207
|
} catch (err) {
|
|
@@ -2346,6 +1268,8 @@ var AgentRuntime = class {
|
|
|
2346
1268
|
draining = false;
|
|
2347
1269
|
orphanedEscrowAgeMinutes;
|
|
2348
1270
|
skillsYamlPath;
|
|
1271
|
+
conductorEnabled;
|
|
1272
|
+
conductorToken;
|
|
2349
1273
|
/**
|
|
2350
1274
|
* Creates a new AgentRuntime instance.
|
|
2351
1275
|
* Opens both databases with WAL mode, foreign_keys=ON, and busy_timeout=5000.
|
|
@@ -2357,6 +1281,8 @@ var AgentRuntime = class {
|
|
|
2357
1281
|
this.owner = options.owner;
|
|
2358
1282
|
this.orphanedEscrowAgeMinutes = options.orphanedEscrowAgeMinutes ?? 10;
|
|
2359
1283
|
this.skillsYamlPath = options.skillsYamlPath;
|
|
1284
|
+
this.conductorEnabled = options.conductorEnabled ?? false;
|
|
1285
|
+
this.conductorToken = options.conductorToken ?? "";
|
|
2360
1286
|
this.registryDb = openDatabase(options.registryDbPath);
|
|
2361
1287
|
this.creditDb = openCreditDb(options.creditDbPath);
|
|
2362
1288
|
this.registryDb.pragma("busy_timeout = 5000");
|
|
@@ -2393,18 +1319,70 @@ var AgentRuntime = class {
|
|
|
2393
1319
|
* 3. Populate the Map with all 4 modes — SkillExecutor sees them via reference.
|
|
2394
1320
|
*/
|
|
2395
1321
|
async initSkillExecutor() {
|
|
2396
|
-
|
|
1322
|
+
const hasSkillsYaml = this.skillsYamlPath && existsSync2(this.skillsYamlPath);
|
|
1323
|
+
if (!hasSkillsYaml && !this.conductorEnabled) {
|
|
2397
1324
|
return;
|
|
2398
1325
|
}
|
|
2399
|
-
|
|
2400
|
-
|
|
1326
|
+
let configs = [];
|
|
1327
|
+
if (hasSkillsYaml) {
|
|
1328
|
+
const yamlContent = readFileSync2(this.skillsYamlPath, "utf8");
|
|
1329
|
+
configs = parseSkillsFile(yamlContent);
|
|
1330
|
+
}
|
|
2401
1331
|
const modes = /* @__PURE__ */ new Map();
|
|
1332
|
+
if (this.conductorEnabled) {
|
|
1333
|
+
const { ConductorMode } = await import("../conductor-mode-DJ3RIJ5T.js");
|
|
1334
|
+
const { registerConductorCard, CONDUCTOR_OWNER } = await import("../card-P5C36VBD.js");
|
|
1335
|
+
const { loadPeers: loadPeers2 } = await import("../peers-G36URZYB.js");
|
|
1336
|
+
registerConductorCard(this.registryDb);
|
|
1337
|
+
const resolveAgentUrl = (owner) => {
|
|
1338
|
+
const peers = loadPeers2();
|
|
1339
|
+
const peer = peers.find((p) => p.name.toLowerCase() === owner.toLowerCase());
|
|
1340
|
+
if (!peer) {
|
|
1341
|
+
throw new Error(
|
|
1342
|
+
`No peer found for agent owner "${owner}". Add with: agentbnb peers add ${owner} <url> <token>`
|
|
1343
|
+
);
|
|
1344
|
+
}
|
|
1345
|
+
const stmt = this.registryDb.prepare(
|
|
1346
|
+
"SELECT id FROM capability_cards WHERE owner = ? LIMIT 1"
|
|
1347
|
+
);
|
|
1348
|
+
const row = stmt.get(owner);
|
|
1349
|
+
const cardId = row?.id ?? owner;
|
|
1350
|
+
return { url: peer.url, cardId };
|
|
1351
|
+
};
|
|
1352
|
+
const conductorMode = new ConductorMode({
|
|
1353
|
+
db: this.registryDb,
|
|
1354
|
+
creditDb: this.creditDb,
|
|
1355
|
+
conductorOwner: CONDUCTOR_OWNER,
|
|
1356
|
+
gatewayToken: this.conductorToken,
|
|
1357
|
+
resolveAgentUrl,
|
|
1358
|
+
maxBudget: 100
|
|
1359
|
+
});
|
|
1360
|
+
modes.set("conductor", conductorMode);
|
|
1361
|
+
configs.push(
|
|
1362
|
+
{
|
|
1363
|
+
id: "orchestrate",
|
|
1364
|
+
type: "conductor",
|
|
1365
|
+
name: "Orchestrate",
|
|
1366
|
+
conductor_skill: "orchestrate",
|
|
1367
|
+
pricing: { credits_per_call: 5 }
|
|
1368
|
+
},
|
|
1369
|
+
{
|
|
1370
|
+
id: "plan",
|
|
1371
|
+
type: "conductor",
|
|
1372
|
+
name: "Plan",
|
|
1373
|
+
conductor_skill: "plan",
|
|
1374
|
+
pricing: { credits_per_call: 1 }
|
|
1375
|
+
}
|
|
1376
|
+
);
|
|
1377
|
+
}
|
|
2402
1378
|
const executor = createSkillExecutor(configs, modes);
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
1379
|
+
if (hasSkillsYaml) {
|
|
1380
|
+
const pipelineExecutor = new PipelineExecutor(executor);
|
|
1381
|
+
modes.set("api", new ApiExecutor());
|
|
1382
|
+
modes.set("pipeline", pipelineExecutor);
|
|
1383
|
+
modes.set("openclaw", new OpenClawBridge());
|
|
1384
|
+
modes.set("command", new CommandExecutor());
|
|
1385
|
+
}
|
|
2408
1386
|
this.skillExecutor = executor;
|
|
2409
1387
|
}
|
|
2410
1388
|
/**
|
|
@@ -2459,7 +1437,7 @@ var AgentRuntime = class {
|
|
|
2459
1437
|
|
|
2460
1438
|
// src/gateway/server.ts
|
|
2461
1439
|
import Fastify from "fastify";
|
|
2462
|
-
import { randomUUID as
|
|
1440
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
2463
1441
|
var VERSION = "0.0.1";
|
|
2464
1442
|
function createGatewayServer(opts) {
|
|
2465
1443
|
const {
|
|
@@ -2553,24 +1531,55 @@ function createGatewayServer(opts) {
|
|
|
2553
1531
|
creditsNeeded = card.pricing.credits_per_call;
|
|
2554
1532
|
cardName = card.name;
|
|
2555
1533
|
}
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
1534
|
+
const receipt = params.escrow_receipt;
|
|
1535
|
+
let escrowId = null;
|
|
1536
|
+
let isRemoteEscrow = false;
|
|
1537
|
+
if (receipt) {
|
|
1538
|
+
const { signature, ...receiptData } = receipt;
|
|
1539
|
+
const publicKeyBuf = Buffer.from(receipt.requester_public_key, "hex");
|
|
1540
|
+
const valid = verifyEscrowReceipt(receiptData, signature, publicKeyBuf);
|
|
1541
|
+
if (!valid) {
|
|
2560
1542
|
return reply.send({
|
|
2561
1543
|
jsonrpc: "2.0",
|
|
2562
1544
|
id,
|
|
2563
|
-
error: { code: -32603, message: "
|
|
1545
|
+
error: { code: -32603, message: "Invalid escrow receipt signature" }
|
|
1546
|
+
});
|
|
1547
|
+
}
|
|
1548
|
+
if (receipt.amount < creditsNeeded) {
|
|
1549
|
+
return reply.send({
|
|
1550
|
+
jsonrpc: "2.0",
|
|
1551
|
+
id,
|
|
1552
|
+
error: { code: -32603, message: "Insufficient escrow amount" }
|
|
1553
|
+
});
|
|
1554
|
+
}
|
|
1555
|
+
const receiptAge = Date.now() - new Date(receipt.timestamp).getTime();
|
|
1556
|
+
if (receiptAge > 5 * 60 * 1e3) {
|
|
1557
|
+
return reply.send({
|
|
1558
|
+
jsonrpc: "2.0",
|
|
1559
|
+
id,
|
|
1560
|
+
error: { code: -32603, message: "Escrow receipt expired" }
|
|
1561
|
+
});
|
|
1562
|
+
}
|
|
1563
|
+
isRemoteEscrow = true;
|
|
1564
|
+
} else {
|
|
1565
|
+
try {
|
|
1566
|
+
const balance = getBalance(creditDb, requester);
|
|
1567
|
+
if (balance < creditsNeeded) {
|
|
1568
|
+
return reply.send({
|
|
1569
|
+
jsonrpc: "2.0",
|
|
1570
|
+
id,
|
|
1571
|
+
error: { code: -32603, message: "Insufficient credits" }
|
|
1572
|
+
});
|
|
1573
|
+
}
|
|
1574
|
+
escrowId = holdEscrow(creditDb, requester, creditsNeeded, cardId);
|
|
1575
|
+
} catch (err) {
|
|
1576
|
+
const msg = err instanceof AgentBnBError ? err.message : "Failed to hold escrow";
|
|
1577
|
+
return reply.send({
|
|
1578
|
+
jsonrpc: "2.0",
|
|
1579
|
+
id,
|
|
1580
|
+
error: { code: -32603, message: msg }
|
|
2564
1581
|
});
|
|
2565
1582
|
}
|
|
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
1583
|
}
|
|
2575
1584
|
const startMs = Date.now();
|
|
2576
1585
|
if (skillExecutor) {
|
|
@@ -2579,11 +1588,11 @@ function createGatewayServer(opts) {
|
|
|
2579
1588
|
try {
|
|
2580
1589
|
execResult = await skillExecutor.execute(targetSkillId, params);
|
|
2581
1590
|
} catch (err) {
|
|
2582
|
-
releaseEscrow(creditDb, escrowId);
|
|
1591
|
+
if (!isRemoteEscrow && escrowId) releaseEscrow(creditDb, escrowId);
|
|
2583
1592
|
updateReputation(registryDb, cardId, false, Date.now() - startMs);
|
|
2584
1593
|
try {
|
|
2585
1594
|
insertRequestLog(registryDb, {
|
|
2586
|
-
id:
|
|
1595
|
+
id: randomUUID3(),
|
|
2587
1596
|
card_id: cardId,
|
|
2588
1597
|
card_name: cardName,
|
|
2589
1598
|
skill_id: resolvedSkillId,
|
|
@@ -2599,15 +1608,19 @@ function createGatewayServer(opts) {
|
|
|
2599
1608
|
return reply.send({
|
|
2600
1609
|
jsonrpc: "2.0",
|
|
2601
1610
|
id,
|
|
2602
|
-
error: {
|
|
1611
|
+
error: {
|
|
1612
|
+
code: -32603,
|
|
1613
|
+
message,
|
|
1614
|
+
...isRemoteEscrow ? { data: { receipt_released: true } } : {}
|
|
1615
|
+
}
|
|
2603
1616
|
});
|
|
2604
1617
|
}
|
|
2605
1618
|
if (!execResult.success) {
|
|
2606
|
-
releaseEscrow(creditDb, escrowId);
|
|
1619
|
+
if (!isRemoteEscrow && escrowId) releaseEscrow(creditDb, escrowId);
|
|
2607
1620
|
updateReputation(registryDb, cardId, false, execResult.latency_ms);
|
|
2608
1621
|
try {
|
|
2609
1622
|
insertRequestLog(registryDb, {
|
|
2610
|
-
id:
|
|
1623
|
+
id: randomUUID3(),
|
|
2611
1624
|
card_id: cardId,
|
|
2612
1625
|
card_name: cardName,
|
|
2613
1626
|
skill_id: resolvedSkillId,
|
|
@@ -2622,14 +1635,22 @@ function createGatewayServer(opts) {
|
|
|
2622
1635
|
return reply.send({
|
|
2623
1636
|
jsonrpc: "2.0",
|
|
2624
1637
|
id,
|
|
2625
|
-
error: {
|
|
1638
|
+
error: {
|
|
1639
|
+
code: -32603,
|
|
1640
|
+
message: execResult.error ?? "Execution failed",
|
|
1641
|
+
...isRemoteEscrow ? { data: { receipt_released: true } } : {}
|
|
1642
|
+
}
|
|
2626
1643
|
});
|
|
2627
1644
|
}
|
|
2628
|
-
|
|
1645
|
+
if (isRemoteEscrow && receipt) {
|
|
1646
|
+
settleProviderEarning(creditDb, card.owner, receipt);
|
|
1647
|
+
} else if (escrowId) {
|
|
1648
|
+
settleEscrow(creditDb, escrowId, card.owner);
|
|
1649
|
+
}
|
|
2629
1650
|
updateReputation(registryDb, cardId, true, execResult.latency_ms);
|
|
2630
1651
|
try {
|
|
2631
1652
|
insertRequestLog(registryDb, {
|
|
2632
|
-
id:
|
|
1653
|
+
id: randomUUID3(),
|
|
2633
1654
|
card_id: cardId,
|
|
2634
1655
|
card_name: cardName,
|
|
2635
1656
|
skill_id: resolvedSkillId,
|
|
@@ -2641,7 +1662,8 @@ function createGatewayServer(opts) {
|
|
|
2641
1662
|
});
|
|
2642
1663
|
} catch {
|
|
2643
1664
|
}
|
|
2644
|
-
|
|
1665
|
+
const successResult = isRemoteEscrow ? { ...typeof execResult.result === "object" && execResult.result !== null ? execResult.result : { data: execResult.result }, receipt_settled: true, receipt_nonce: receipt.nonce } : execResult.result;
|
|
1666
|
+
return reply.send({ jsonrpc: "2.0", id, result: successResult });
|
|
2645
1667
|
}
|
|
2646
1668
|
const controller = new AbortController();
|
|
2647
1669
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
@@ -2654,11 +1676,11 @@ function createGatewayServer(opts) {
|
|
|
2654
1676
|
});
|
|
2655
1677
|
clearTimeout(timer);
|
|
2656
1678
|
if (!response.ok) {
|
|
2657
|
-
releaseEscrow(creditDb, escrowId);
|
|
1679
|
+
if (!isRemoteEscrow && escrowId) releaseEscrow(creditDb, escrowId);
|
|
2658
1680
|
updateReputation(registryDb, cardId, false, Date.now() - startMs);
|
|
2659
1681
|
try {
|
|
2660
1682
|
insertRequestLog(registryDb, {
|
|
2661
|
-
id:
|
|
1683
|
+
id: randomUUID3(),
|
|
2662
1684
|
card_id: cardId,
|
|
2663
1685
|
card_name: cardName,
|
|
2664
1686
|
skill_id: resolvedSkillId,
|
|
@@ -2673,15 +1695,23 @@ function createGatewayServer(opts) {
|
|
|
2673
1695
|
return reply.send({
|
|
2674
1696
|
jsonrpc: "2.0",
|
|
2675
1697
|
id,
|
|
2676
|
-
error: {
|
|
1698
|
+
error: {
|
|
1699
|
+
code: -32603,
|
|
1700
|
+
message: `Handler returned ${response.status}`,
|
|
1701
|
+
...isRemoteEscrow ? { data: { receipt_released: true } } : {}
|
|
1702
|
+
}
|
|
2677
1703
|
});
|
|
2678
1704
|
}
|
|
2679
1705
|
const result = await response.json();
|
|
2680
|
-
|
|
1706
|
+
if (isRemoteEscrow && receipt) {
|
|
1707
|
+
settleProviderEarning(creditDb, card.owner, receipt);
|
|
1708
|
+
} else if (escrowId) {
|
|
1709
|
+
settleEscrow(creditDb, escrowId, card.owner);
|
|
1710
|
+
}
|
|
2681
1711
|
updateReputation(registryDb, cardId, true, Date.now() - startMs);
|
|
2682
1712
|
try {
|
|
2683
1713
|
insertRequestLog(registryDb, {
|
|
2684
|
-
id:
|
|
1714
|
+
id: randomUUID3(),
|
|
2685
1715
|
card_id: cardId,
|
|
2686
1716
|
card_name: cardName,
|
|
2687
1717
|
skill_id: resolvedSkillId,
|
|
@@ -2693,15 +1723,16 @@ function createGatewayServer(opts) {
|
|
|
2693
1723
|
});
|
|
2694
1724
|
} catch {
|
|
2695
1725
|
}
|
|
2696
|
-
|
|
1726
|
+
const successResult = isRemoteEscrow ? { ...typeof result === "object" && result !== null ? result : { data: result }, receipt_settled: true, receipt_nonce: receipt.nonce } : result;
|
|
1727
|
+
return reply.send({ jsonrpc: "2.0", id, result: successResult });
|
|
2697
1728
|
} catch (err) {
|
|
2698
1729
|
clearTimeout(timer);
|
|
2699
|
-
releaseEscrow(creditDb, escrowId);
|
|
1730
|
+
if (!isRemoteEscrow && escrowId) releaseEscrow(creditDb, escrowId);
|
|
2700
1731
|
updateReputation(registryDb, cardId, false, Date.now() - startMs);
|
|
2701
1732
|
const isTimeout = err instanceof Error && err.name === "AbortError";
|
|
2702
1733
|
try {
|
|
2703
1734
|
insertRequestLog(registryDb, {
|
|
2704
|
-
id:
|
|
1735
|
+
id: randomUUID3(),
|
|
2705
1736
|
card_id: cardId,
|
|
2706
1737
|
card_name: cardName,
|
|
2707
1738
|
skill_id: resolvedSkillId,
|
|
@@ -2716,7 +1747,11 @@ function createGatewayServer(opts) {
|
|
|
2716
1747
|
return reply.send({
|
|
2717
1748
|
jsonrpc: "2.0",
|
|
2718
1749
|
id,
|
|
2719
|
-
error: {
|
|
1750
|
+
error: {
|
|
1751
|
+
code: -32603,
|
|
1752
|
+
message: isTimeout ? "Execution timeout" : "Handler error",
|
|
1753
|
+
...isRemoteEscrow ? { data: { receipt_released: true } } : {}
|
|
1754
|
+
}
|
|
2720
1755
|
});
|
|
2721
1756
|
}
|
|
2722
1757
|
});
|
|
@@ -2727,9 +1762,9 @@ function createGatewayServer(opts) {
|
|
|
2727
1762
|
import Fastify2 from "fastify";
|
|
2728
1763
|
import cors from "@fastify/cors";
|
|
2729
1764
|
import fastifyStatic from "@fastify/static";
|
|
2730
|
-
import { join as
|
|
1765
|
+
import { join as join2, dirname } from "path";
|
|
2731
1766
|
import { fileURLToPath } from "url";
|
|
2732
|
-
import { existsSync as
|
|
1767
|
+
import { existsSync as existsSync3 } from "fs";
|
|
2733
1768
|
function stripInternal(card) {
|
|
2734
1769
|
const { _internal: _, ...publicCard } = card;
|
|
2735
1770
|
return publicCard;
|
|
@@ -2745,12 +1780,12 @@ function createRegistryServer(opts) {
|
|
|
2745
1780
|
const __filename = fileURLToPath(import.meta.url);
|
|
2746
1781
|
const __dirname = dirname(__filename);
|
|
2747
1782
|
const hubDistCandidates = [
|
|
2748
|
-
|
|
1783
|
+
join2(__dirname, "../../hub/dist"),
|
|
2749
1784
|
// When running from dist/registry/server.js
|
|
2750
|
-
|
|
1785
|
+
join2(__dirname, "../../../hub/dist")
|
|
2751
1786
|
// Fallback for alternative layouts
|
|
2752
1787
|
];
|
|
2753
|
-
const hubDistDir = hubDistCandidates.find((p) =>
|
|
1788
|
+
const hubDistDir = hubDistCandidates.find((p) => existsSync3(p));
|
|
2754
1789
|
if (hubDistDir) {
|
|
2755
1790
|
void server.register(fastifyStatic, {
|
|
2756
1791
|
root: hubDistDir,
|
|
@@ -3100,10 +2135,10 @@ async function stopAnnouncement() {
|
|
|
3100
2135
|
}
|
|
3101
2136
|
|
|
3102
2137
|
// src/openclaw/soul-sync.ts
|
|
3103
|
-
import { randomUUID as
|
|
2138
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
3104
2139
|
|
|
3105
2140
|
// src/skills/publish-capability.ts
|
|
3106
|
-
import { randomUUID as
|
|
2141
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
3107
2142
|
function parseSoulMd(content) {
|
|
3108
2143
|
const lines = content.split("\n");
|
|
3109
2144
|
let name = "";
|
|
@@ -3174,7 +2209,7 @@ function parseSoulMdV2(content) {
|
|
|
3174
2209
|
const parsed = parseSoulMd(content);
|
|
3175
2210
|
const skills = parsed.capabilities.map((cap) => {
|
|
3176
2211
|
const sanitizedId = cap.name.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
|
|
3177
|
-
const id = sanitizedId.length > 0 ? sanitizedId :
|
|
2212
|
+
const id = sanitizedId.length > 0 ? sanitizedId : randomUUID5();
|
|
3178
2213
|
return {
|
|
3179
2214
|
id,
|
|
3180
2215
|
name: cap.name,
|
|
@@ -3216,7 +2251,7 @@ function publishFromSoulV2(db, soulContent, owner) {
|
|
|
3216
2251
|
(c) => c.spec_version === "2.0"
|
|
3217
2252
|
);
|
|
3218
2253
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3219
|
-
const cardId = existingV2?.id ??
|
|
2254
|
+
const cardId = existingV2?.id ?? randomUUID5();
|
|
3220
2255
|
const card = {
|
|
3221
2256
|
spec_version: "2.0",
|
|
3222
2257
|
id: cardId,
|
|
@@ -3241,7 +2276,7 @@ function publishFromSoulV2(db, soulContent, owner) {
|
|
|
3241
2276
|
}
|
|
3242
2277
|
|
|
3243
2278
|
// src/openclaw/heartbeat-writer.ts
|
|
3244
|
-
import { readFileSync as
|
|
2279
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync4 } from "fs";
|
|
3245
2280
|
var HEARTBEAT_MARKER_START = "<!-- agentbnb:start -->";
|
|
3246
2281
|
var HEARTBEAT_MARKER_END = "<!-- agentbnb:end -->";
|
|
3247
2282
|
function generateHeartbeatSection(autonomy, budget) {
|
|
@@ -3277,11 +2312,11 @@ function generateHeartbeatSection(autonomy, budget) {
|
|
|
3277
2312
|
].join("\n");
|
|
3278
2313
|
}
|
|
3279
2314
|
function injectHeartbeatSection(heartbeatPath, section) {
|
|
3280
|
-
if (!
|
|
3281
|
-
|
|
2315
|
+
if (!existsSync4(heartbeatPath)) {
|
|
2316
|
+
writeFileSync2(heartbeatPath, section + "\n", "utf-8");
|
|
3282
2317
|
return;
|
|
3283
2318
|
}
|
|
3284
|
-
let content =
|
|
2319
|
+
let content = readFileSync3(heartbeatPath, "utf-8");
|
|
3285
2320
|
const startIdx = content.indexOf(HEARTBEAT_MARKER_START);
|
|
3286
2321
|
const endIdx = content.indexOf(HEARTBEAT_MARKER_END);
|
|
3287
2322
|
if (startIdx !== -1 && endIdx !== -1) {
|
|
@@ -3289,7 +2324,7 @@ function injectHeartbeatSection(heartbeatPath, section) {
|
|
|
3289
2324
|
} else {
|
|
3290
2325
|
content = content + "\n" + section + "\n";
|
|
3291
2326
|
}
|
|
3292
|
-
|
|
2327
|
+
writeFileSync2(heartbeatPath, content, "utf-8");
|
|
3293
2328
|
}
|
|
3294
2329
|
|
|
3295
2330
|
// src/openclaw/skill.ts
|
|
@@ -3356,8 +2391,8 @@ program.command("init").description("Initialize AgentBnB config and create agent
|
|
|
3356
2391
|
const owner = opts.owner ?? `agent-${randomBytes(4).toString("hex")}`;
|
|
3357
2392
|
const token = randomBytes(32).toString("hex");
|
|
3358
2393
|
const configDir = getConfigDir();
|
|
3359
|
-
const dbPath =
|
|
3360
|
-
const creditDbPath =
|
|
2394
|
+
const dbPath = join3(configDir, "registry.db");
|
|
2395
|
+
const creditDbPath = join3(configDir, "credit.db");
|
|
3361
2396
|
const port = parseInt(opts.port, 10);
|
|
3362
2397
|
const ip = opts.host ?? getLanIp();
|
|
3363
2398
|
const existingConfig = loadConfig();
|
|
@@ -3471,7 +2506,7 @@ program.command("publish <card.json>").description("Publish a Capability Card to
|
|
|
3471
2506
|
}
|
|
3472
2507
|
let raw;
|
|
3473
2508
|
try {
|
|
3474
|
-
raw =
|
|
2509
|
+
raw = readFileSync4(cardPath, "utf-8");
|
|
3475
2510
|
} catch {
|
|
3476
2511
|
console.error(`Error: cannot read file: ${cardPath}`);
|
|
3477
2512
|
process.exit(1);
|
|
@@ -3622,7 +2657,7 @@ ${discovered.length} agent(s) found on local network`);
|
|
|
3622
2657
|
console.log(`
|
|
3623
2658
|
${outputCards.length} result(s)`);
|
|
3624
2659
|
});
|
|
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) => {
|
|
2660
|
+
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
2661
|
const config = loadConfig();
|
|
3627
2662
|
if (!config) {
|
|
3628
2663
|
console.error("Error: not initialized. Run `agentbnb init` first.");
|
|
@@ -3638,8 +2673,8 @@ program.command("request [card-id]").description("Request a capability from anot
|
|
|
3638
2673
|
process.exit(1);
|
|
3639
2674
|
}
|
|
3640
2675
|
}
|
|
3641
|
-
const registryDb = openDatabase(
|
|
3642
|
-
const creditDb = openCreditDb(
|
|
2676
|
+
const registryDb = openDatabase(join3(getConfigDir(), "registry.db"));
|
|
2677
|
+
const creditDb = openCreditDb(join3(getConfigDir(), "credit.db"));
|
|
3643
2678
|
registryDb.pragma("busy_timeout = 5000");
|
|
3644
2679
|
creditDb.pragma("busy_timeout = 5000");
|
|
3645
2680
|
try {
|
|
@@ -3676,6 +2711,7 @@ program.command("request [card-id]").description("Request a capability from anot
|
|
|
3676
2711
|
}
|
|
3677
2712
|
let gatewayUrl;
|
|
3678
2713
|
let token;
|
|
2714
|
+
const isPeerRequest = !!opts.peer;
|
|
3679
2715
|
if (opts.peer) {
|
|
3680
2716
|
const peer = findPeer(opts.peer);
|
|
3681
2717
|
if (!peer) {
|
|
@@ -3688,20 +2724,87 @@ program.command("request [card-id]").description("Request a capability from anot
|
|
|
3688
2724
|
gatewayUrl = config.gateway_url;
|
|
3689
2725
|
token = config.token;
|
|
3690
2726
|
}
|
|
2727
|
+
const useReceipt = isPeerRequest && opts.receipt !== false;
|
|
2728
|
+
if (useReceipt && !opts.cost) {
|
|
2729
|
+
console.error("Error: --cost <credits> is required for peer requests. Specify the credits to commit.");
|
|
2730
|
+
process.exit(1);
|
|
2731
|
+
}
|
|
2732
|
+
let escrowId;
|
|
2733
|
+
let escrowReceipt;
|
|
2734
|
+
if (useReceipt) {
|
|
2735
|
+
const configDir = getConfigDir();
|
|
2736
|
+
const creditDb = openCreditDb(join3(configDir, "credit.db"));
|
|
2737
|
+
creditDb.pragma("busy_timeout = 5000");
|
|
2738
|
+
try {
|
|
2739
|
+
const keys = loadKeyPair(configDir);
|
|
2740
|
+
const amount = Number(opts.cost);
|
|
2741
|
+
if (isNaN(amount) || amount <= 0) {
|
|
2742
|
+
console.error("Error: --cost must be a positive number.");
|
|
2743
|
+
process.exit(1);
|
|
2744
|
+
}
|
|
2745
|
+
const receiptResult = createSignedEscrowReceipt(creditDb, keys.privateKey, keys.publicKey, {
|
|
2746
|
+
owner: config.owner,
|
|
2747
|
+
amount,
|
|
2748
|
+
cardId,
|
|
2749
|
+
skillId: opts.skill
|
|
2750
|
+
});
|
|
2751
|
+
escrowId = receiptResult.escrowId;
|
|
2752
|
+
escrowReceipt = receiptResult.receipt;
|
|
2753
|
+
if (!opts.json) {
|
|
2754
|
+
console.log(`Escrow: ${amount} credits held (ID: ${escrowId.slice(0, 8)}...)`);
|
|
2755
|
+
}
|
|
2756
|
+
} catch (err) {
|
|
2757
|
+
creditDb.close();
|
|
2758
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2759
|
+
if (opts.json) {
|
|
2760
|
+
console.log(JSON.stringify({ success: false, error: msg }, null, 2));
|
|
2761
|
+
} else {
|
|
2762
|
+
console.error(`Error creating escrow receipt: ${msg}`);
|
|
2763
|
+
}
|
|
2764
|
+
process.exit(1);
|
|
2765
|
+
}
|
|
2766
|
+
}
|
|
3691
2767
|
try {
|
|
3692
2768
|
const result = await requestCapability({
|
|
3693
2769
|
gatewayUrl,
|
|
3694
2770
|
token,
|
|
3695
2771
|
cardId,
|
|
3696
|
-
params
|
|
2772
|
+
params: { ...params, ...opts.skill ? { skill_id: opts.skill } : {} },
|
|
2773
|
+
escrowReceipt
|
|
3697
2774
|
});
|
|
2775
|
+
if (useReceipt && escrowId) {
|
|
2776
|
+
const configDir = getConfigDir();
|
|
2777
|
+
const creditDb = openCreditDb(join3(configDir, "credit.db"));
|
|
2778
|
+
creditDb.pragma("busy_timeout = 5000");
|
|
2779
|
+
try {
|
|
2780
|
+
settleRequesterEscrow(creditDb, escrowId);
|
|
2781
|
+
if (!opts.json) {
|
|
2782
|
+
console.log(`Escrow settled: ${opts.cost} credits deducted.`);
|
|
2783
|
+
}
|
|
2784
|
+
} finally {
|
|
2785
|
+
creditDb.close();
|
|
2786
|
+
}
|
|
2787
|
+
}
|
|
3698
2788
|
if (opts.json) {
|
|
3699
2789
|
console.log(JSON.stringify({ success: true, result }, null, 2));
|
|
3700
2790
|
} else {
|
|
3701
2791
|
console.log("Result:");
|
|
3702
|
-
console.log(JSON.stringify(result, null, 2));
|
|
2792
|
+
console.log(typeof result === "string" ? result : JSON.stringify(result, null, 2));
|
|
3703
2793
|
}
|
|
3704
2794
|
} catch (err) {
|
|
2795
|
+
if (useReceipt && escrowId) {
|
|
2796
|
+
const configDir = getConfigDir();
|
|
2797
|
+
const creditDb = openCreditDb(join3(configDir, "credit.db"));
|
|
2798
|
+
creditDb.pragma("busy_timeout = 5000");
|
|
2799
|
+
try {
|
|
2800
|
+
releaseRequesterEscrow(creditDb, escrowId);
|
|
2801
|
+
if (!opts.json) {
|
|
2802
|
+
console.log("Escrow released: credits refunded.");
|
|
2803
|
+
}
|
|
2804
|
+
} finally {
|
|
2805
|
+
creditDb.close();
|
|
2806
|
+
}
|
|
2807
|
+
}
|
|
3705
2808
|
const msg = err instanceof Error ? err.message : String(err);
|
|
3706
2809
|
if (opts.json) {
|
|
3707
2810
|
console.log(JSON.stringify({ success: false, error: msg }, null, 2));
|
|
@@ -3751,7 +2854,7 @@ Active Escrows (${heldEscrows.length}):`);
|
|
|
3751
2854
|
}
|
|
3752
2855
|
}
|
|
3753
2856
|
});
|
|
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) => {
|
|
2857
|
+
program.command("serve").description("Start the AgentBnB gateway server").option("--port <port>", "Port to listen on (overrides config)").option("--handler-url <url>", "Local capability handler URL", "http://localhost:8080").option("--skills-yaml <path>", "Path to skills.yaml (default: ~/.agentbnb/skills.yaml)").option("--registry-port <port>", "Public registry API port (0 to disable)", "7701").option("--conductor", "Enable Conductor orchestration mode").option("--announce", "Announce this gateway on the local network via mDNS").action(async (opts) => {
|
|
3755
2858
|
const config = loadConfig();
|
|
3756
2859
|
if (!config) {
|
|
3757
2860
|
console.error("Error: not initialized. Run `agentbnb init` first.");
|
|
@@ -3759,17 +2862,22 @@ program.command("serve").description("Start the AgentBnB gateway server").option
|
|
|
3759
2862
|
}
|
|
3760
2863
|
const port = opts.port ? parseInt(opts.port, 10) : config.gateway_port;
|
|
3761
2864
|
const registryPort = parseInt(opts.registryPort, 10);
|
|
3762
|
-
const skillsYamlPath = opts.skillsYaml ??
|
|
2865
|
+
const skillsYamlPath = opts.skillsYaml ?? join3(homedir(), ".agentbnb", "skills.yaml");
|
|
3763
2866
|
const runtime = new AgentRuntime({
|
|
3764
2867
|
registryDbPath: config.db_path,
|
|
3765
2868
|
creditDbPath: config.credit_db_path,
|
|
3766
2869
|
owner: config.owner,
|
|
3767
|
-
skillsYamlPath
|
|
2870
|
+
skillsYamlPath,
|
|
2871
|
+
conductorEnabled: opts.conductor ?? false,
|
|
2872
|
+
conductorToken: config.token
|
|
3768
2873
|
});
|
|
3769
2874
|
await runtime.start();
|
|
3770
2875
|
if (runtime.skillExecutor) {
|
|
3771
2876
|
console.log(`SkillExecutor initialized from ${skillsYamlPath}`);
|
|
3772
2877
|
}
|
|
2878
|
+
if (opts.conductor) {
|
|
2879
|
+
console.log("Conductor mode enabled \u2014 orchestrate/plan skills available via gateway");
|
|
2880
|
+
}
|
|
3773
2881
|
const autonomyConfig = config.autonomy ?? DEFAULT_AUTONOMY_CONFIG;
|
|
3774
2882
|
const idleMonitor = new IdleMonitor({
|
|
3775
2883
|
owner: config.owner,
|
|
@@ -3985,7 +3093,7 @@ openclaw.command("sync").description("Read SOUL.md and publish/update a v2.0 cap
|
|
|
3985
3093
|
}
|
|
3986
3094
|
let content;
|
|
3987
3095
|
try {
|
|
3988
|
-
content =
|
|
3096
|
+
content = readFileSync4(opts.soulPath, "utf-8");
|
|
3989
3097
|
} catch {
|
|
3990
3098
|
console.error(`Error: cannot read SOUL.md at ${opts.soulPath}`);
|
|
3991
3099
|
process.exit(1);
|
|
@@ -4045,4 +3153,38 @@ openclaw.command("rules").description("Print HEARTBEAT.md rules block (or inject
|
|
|
4045
3153
|
console.log(section);
|
|
4046
3154
|
}
|
|
4047
3155
|
});
|
|
3156
|
+
program.command("conduct <task>").description("Orchestrate a complex task across the AgentBnB network").option("--plan-only", "Show execution plan without executing").option("--max-budget <credits>", "Maximum credits to spend", "100").option("--json", "Output as JSON").action(async (task, opts) => {
|
|
3157
|
+
const { conductAction } = await import("../conduct-JZJS2ZHA.js");
|
|
3158
|
+
const result = await conductAction(task, opts);
|
|
3159
|
+
if (opts.json) {
|
|
3160
|
+
console.log(JSON.stringify(result, null, 2));
|
|
3161
|
+
if (!result.success) process.exit(1);
|
|
3162
|
+
return;
|
|
3163
|
+
}
|
|
3164
|
+
if (!result.success) {
|
|
3165
|
+
console.error(`Error: ${result.error}`);
|
|
3166
|
+
process.exit(1);
|
|
3167
|
+
}
|
|
3168
|
+
const plan = result.plan;
|
|
3169
|
+
console.log("\nExecution Plan:");
|
|
3170
|
+
for (const step of plan.steps) {
|
|
3171
|
+
const deps = step.depends_on.length > 0 ? ` [depends on prior steps]` : "";
|
|
3172
|
+
console.log(` Step ${step.step}: ${step.description} (${step.capability}) -> @${step.agent} (${step.credits} cr)${deps}`);
|
|
3173
|
+
}
|
|
3174
|
+
console.log(` Orchestration fee: ${plan.orchestration_fee} cr`);
|
|
3175
|
+
console.log(` Total estimated: ${plan.estimated_total} cr`);
|
|
3176
|
+
if (result.execution) {
|
|
3177
|
+
console.log("\nResults:");
|
|
3178
|
+
console.log(JSON.stringify(result.execution, null, 2));
|
|
3179
|
+
console.log(`
|
|
3180
|
+
Total credits spent: ${result.total_credits ?? 0} cr`);
|
|
3181
|
+
console.log(`Latency: ${result.latency_ms ?? 0} ms`);
|
|
3182
|
+
}
|
|
3183
|
+
if (result.errors && result.errors.length > 0) {
|
|
3184
|
+
console.log("\nErrors:");
|
|
3185
|
+
for (const err of result.errors) {
|
|
3186
|
+
console.log(` - ${err}`);
|
|
3187
|
+
}
|
|
3188
|
+
}
|
|
3189
|
+
});
|
|
4048
3190
|
await program.parseAsync(process.argv);
|