agentbnb 2.2.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/card-P5C36VBD.js +81 -0
- package/dist/chunk-2LLXUKMY.js +489 -0
- package/dist/chunk-3Y36WQDV.js +70 -0
- package/dist/chunk-4Q7D24DP.js +257 -0
- package/dist/chunk-BEI5MTNZ.js +91 -0
- package/dist/chunk-QVIGMCHA.js +486 -0
- package/dist/chunk-T7ZJPQHD.js +372 -0
- package/dist/chunk-TQMI73LL.js +125 -0
- package/dist/chunk-ZJCIBK6O.js +192 -0
- package/dist/cli/index.js +715 -1538
- package/dist/conduct-M57F72RK.js +117 -0
- package/dist/conductor-mode-CF6PSRRA.js +112 -0
- package/dist/execute-3T5RF3DP.js +9 -0
- package/dist/index.js +2432 -183
- package/dist/peers-G36URZYB.js +12 -0
- package/dist/websocket-client-5TIQDYQ4.js +275 -0
- package/package.json +5 -2
- package/dist/index.d.ts +0 -676
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
import {
|
|
2
|
+
interpolateObject,
|
|
3
|
+
requestCapability,
|
|
4
|
+
scorePeers,
|
|
5
|
+
searchCards
|
|
6
|
+
} from "./chunk-QVIGMCHA.js";
|
|
7
|
+
|
|
8
|
+
// src/conductor/task-decomposer.ts
|
|
9
|
+
import { randomUUID } from "crypto";
|
|
10
|
+
var TEMPLATES = {
|
|
11
|
+
"video-production": {
|
|
12
|
+
keywords: ["video", "demo", "clip", "animation"],
|
|
13
|
+
steps: [
|
|
14
|
+
{
|
|
15
|
+
description: "Generate script from task description",
|
|
16
|
+
required_capability: "text_gen",
|
|
17
|
+
estimated_credits: 2,
|
|
18
|
+
depends_on_indices: []
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
description: "Generate voiceover from script",
|
|
22
|
+
required_capability: "tts",
|
|
23
|
+
estimated_credits: 3,
|
|
24
|
+
depends_on_indices: [0]
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
description: "Generate video visuals from script",
|
|
28
|
+
required_capability: "video_gen",
|
|
29
|
+
estimated_credits: 5,
|
|
30
|
+
depends_on_indices: [0]
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
description: "Composite voiceover and video into final output",
|
|
34
|
+
required_capability: "video_edit",
|
|
35
|
+
estimated_credits: 3,
|
|
36
|
+
depends_on_indices: [1, 2]
|
|
37
|
+
}
|
|
38
|
+
]
|
|
39
|
+
},
|
|
40
|
+
"deep-analysis": {
|
|
41
|
+
keywords: ["analyze", "analysis", "research", "report", "evaluate"],
|
|
42
|
+
steps: [
|
|
43
|
+
{
|
|
44
|
+
description: "Research and gather relevant data",
|
|
45
|
+
required_capability: "web_search",
|
|
46
|
+
estimated_credits: 2,
|
|
47
|
+
depends_on_indices: []
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
description: "Analyze gathered data",
|
|
51
|
+
required_capability: "text_gen",
|
|
52
|
+
estimated_credits: 3,
|
|
53
|
+
depends_on_indices: [0]
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
description: "Summarize analysis findings",
|
|
57
|
+
required_capability: "text_gen",
|
|
58
|
+
estimated_credits: 2,
|
|
59
|
+
depends_on_indices: [1]
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
description: "Format into final report",
|
|
63
|
+
required_capability: "text_gen",
|
|
64
|
+
estimated_credits: 1,
|
|
65
|
+
depends_on_indices: [2]
|
|
66
|
+
}
|
|
67
|
+
]
|
|
68
|
+
},
|
|
69
|
+
"content-generation": {
|
|
70
|
+
keywords: ["write", "blog", "article", "content", "post", "essay"],
|
|
71
|
+
steps: [
|
|
72
|
+
{
|
|
73
|
+
description: "Create content outline",
|
|
74
|
+
required_capability: "text_gen",
|
|
75
|
+
estimated_credits: 1,
|
|
76
|
+
depends_on_indices: []
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
description: "Draft content from outline",
|
|
80
|
+
required_capability: "text_gen",
|
|
81
|
+
estimated_credits: 3,
|
|
82
|
+
depends_on_indices: [0]
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
description: "Review and refine draft",
|
|
86
|
+
required_capability: "text_gen",
|
|
87
|
+
estimated_credits: 2,
|
|
88
|
+
depends_on_indices: [1]
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
description: "Finalize and polish content",
|
|
92
|
+
required_capability: "text_gen",
|
|
93
|
+
estimated_credits: 1,
|
|
94
|
+
depends_on_indices: [2]
|
|
95
|
+
}
|
|
96
|
+
]
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
function decompose(task, _availableCapabilities) {
|
|
100
|
+
const lower = task.toLowerCase();
|
|
101
|
+
for (const template of Object.values(TEMPLATES)) {
|
|
102
|
+
const matched = template.keywords.some((kw) => lower.includes(kw));
|
|
103
|
+
if (!matched) continue;
|
|
104
|
+
const ids = template.steps.map(() => randomUUID());
|
|
105
|
+
return template.steps.map((step, i) => ({
|
|
106
|
+
id: ids[i],
|
|
107
|
+
description: step.description,
|
|
108
|
+
required_capability: step.required_capability,
|
|
109
|
+
params: {},
|
|
110
|
+
depends_on: step.depends_on_indices.map((idx) => ids[idx]),
|
|
111
|
+
estimated_credits: step.estimated_credits
|
|
112
|
+
}));
|
|
113
|
+
}
|
|
114
|
+
return [];
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// src/conductor/capability-matcher.ts
|
|
118
|
+
var MAX_ALTERNATIVES = 2;
|
|
119
|
+
function matchSubTasks(opts) {
|
|
120
|
+
const { db, subtasks, conductorOwner } = opts;
|
|
121
|
+
return subtasks.map((subtask) => {
|
|
122
|
+
const cards = searchCards(db, subtask.required_capability, { online: true });
|
|
123
|
+
const candidates = [];
|
|
124
|
+
for (const card of cards) {
|
|
125
|
+
const cardAsV2 = card;
|
|
126
|
+
if (Array.isArray(cardAsV2.skills)) {
|
|
127
|
+
for (const skill of cardAsV2.skills) {
|
|
128
|
+
candidates.push({
|
|
129
|
+
card,
|
|
130
|
+
cost: skill.pricing.credits_per_call,
|
|
131
|
+
skillId: skill.id
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
} else {
|
|
135
|
+
candidates.push({
|
|
136
|
+
card,
|
|
137
|
+
cost: card.pricing.credits_per_call,
|
|
138
|
+
skillId: void 0
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
const scored = scorePeers(candidates, conductorOwner);
|
|
143
|
+
if (scored.length === 0) {
|
|
144
|
+
return {
|
|
145
|
+
subtask_id: subtask.id,
|
|
146
|
+
selected_agent: "",
|
|
147
|
+
selected_skill: "",
|
|
148
|
+
score: 0,
|
|
149
|
+
credits: 0,
|
|
150
|
+
alternatives: []
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
const top = scored[0];
|
|
154
|
+
const alternatives = scored.slice(1, 1 + MAX_ALTERNATIVES).map((s) => ({
|
|
155
|
+
agent: s.card.owner,
|
|
156
|
+
skill: s.skillId ?? "",
|
|
157
|
+
score: s.rawScore,
|
|
158
|
+
credits: s.cost
|
|
159
|
+
}));
|
|
160
|
+
return {
|
|
161
|
+
subtask_id: subtask.id,
|
|
162
|
+
selected_agent: top.card.owner,
|
|
163
|
+
selected_skill: top.skillId ?? "",
|
|
164
|
+
score: top.rawScore,
|
|
165
|
+
credits: top.cost,
|
|
166
|
+
alternatives
|
|
167
|
+
};
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// src/conductor/budget-controller.ts
|
|
172
|
+
var ORCHESTRATION_FEE = 5;
|
|
173
|
+
var BudgetController = class {
|
|
174
|
+
/**
|
|
175
|
+
* Creates a new BudgetController.
|
|
176
|
+
*
|
|
177
|
+
* @param budgetManager - Underlying BudgetManager for reserve floor enforcement.
|
|
178
|
+
* @param maxBudget - Hard ceiling for the orchestration run.
|
|
179
|
+
*/
|
|
180
|
+
constructor(budgetManager, maxBudget) {
|
|
181
|
+
this.budgetManager = budgetManager;
|
|
182
|
+
this.maxBudget = maxBudget;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Pre-calculates the total budget for an orchestration run.
|
|
186
|
+
*
|
|
187
|
+
* Sums all matched sub-task credits, adds the orchestration fee,
|
|
188
|
+
* and determines whether approval is required (estimated > max).
|
|
189
|
+
*
|
|
190
|
+
* @param matches - MatchResult[] from the CapabilityMatcher.
|
|
191
|
+
* @returns An ExecutionBudget with cost breakdown and approval status.
|
|
192
|
+
*/
|
|
193
|
+
calculateBudget(matches) {
|
|
194
|
+
const perTaskSpending = /* @__PURE__ */ new Map();
|
|
195
|
+
let subTotal = 0;
|
|
196
|
+
for (const match of matches) {
|
|
197
|
+
perTaskSpending.set(match.subtask_id, match.credits);
|
|
198
|
+
subTotal += match.credits;
|
|
199
|
+
}
|
|
200
|
+
const estimatedTotal = subTotal + ORCHESTRATION_FEE;
|
|
201
|
+
return {
|
|
202
|
+
estimated_total: estimatedTotal,
|
|
203
|
+
max_budget: this.maxBudget,
|
|
204
|
+
orchestration_fee: ORCHESTRATION_FEE,
|
|
205
|
+
per_task_spending: perTaskSpending,
|
|
206
|
+
requires_approval: estimatedTotal > this.maxBudget
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Checks whether orchestration can proceed without explicit approval.
|
|
211
|
+
*
|
|
212
|
+
* Returns true only when:
|
|
213
|
+
* 1. The budget does NOT require approval (estimated_total <= max_budget)
|
|
214
|
+
* 2. The BudgetManager confirms sufficient credits (respecting reserve floor)
|
|
215
|
+
*
|
|
216
|
+
* @param budget - ExecutionBudget from calculateBudget().
|
|
217
|
+
* @returns true if execution can proceed autonomously.
|
|
218
|
+
*/
|
|
219
|
+
canExecute(budget) {
|
|
220
|
+
if (budget.requires_approval) return false;
|
|
221
|
+
return this.budgetManager.canSpend(budget.estimated_total);
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Checks budget after explicit user/agent approval.
|
|
225
|
+
*
|
|
226
|
+
* Ignores the requires_approval flag — used when the caller has already
|
|
227
|
+
* obtained explicit approval for the over-budget orchestration.
|
|
228
|
+
* Still enforces the reserve floor via BudgetManager.canSpend().
|
|
229
|
+
*
|
|
230
|
+
* @param budget - ExecutionBudget from calculateBudget().
|
|
231
|
+
* @returns true if the agent has sufficient credits (reserve floor check only).
|
|
232
|
+
*/
|
|
233
|
+
approveAndCheck(budget) {
|
|
234
|
+
return this.budgetManager.canSpend(budget.estimated_total);
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
// src/conductor/pipeline-orchestrator.ts
|
|
239
|
+
function computeWaves(subtasks) {
|
|
240
|
+
const waves = [];
|
|
241
|
+
const completed = /* @__PURE__ */ new Set();
|
|
242
|
+
const remaining = new Map(subtasks.map((s) => [s.id, s]));
|
|
243
|
+
while (remaining.size > 0) {
|
|
244
|
+
const wave = [];
|
|
245
|
+
for (const [id, task] of remaining) {
|
|
246
|
+
const depsResolved = task.depends_on.every((dep) => completed.has(dep));
|
|
247
|
+
if (depsResolved) {
|
|
248
|
+
wave.push(id);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
if (wave.length === 0) {
|
|
252
|
+
break;
|
|
253
|
+
}
|
|
254
|
+
for (const id of wave) {
|
|
255
|
+
remaining.delete(id);
|
|
256
|
+
completed.add(id);
|
|
257
|
+
}
|
|
258
|
+
waves.push(wave);
|
|
259
|
+
}
|
|
260
|
+
return waves;
|
|
261
|
+
}
|
|
262
|
+
async function orchestrate(opts) {
|
|
263
|
+
const { subtasks, matches, gatewayToken, resolveAgentUrl, timeoutMs = 3e4, maxBudget } = opts;
|
|
264
|
+
const startTime = Date.now();
|
|
265
|
+
if (subtasks.length === 0) {
|
|
266
|
+
return {
|
|
267
|
+
success: true,
|
|
268
|
+
results: /* @__PURE__ */ new Map(),
|
|
269
|
+
total_credits: 0,
|
|
270
|
+
latency_ms: Date.now() - startTime
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
const results = /* @__PURE__ */ new Map();
|
|
274
|
+
const errors = [];
|
|
275
|
+
let totalCredits = 0;
|
|
276
|
+
const waves = computeWaves(subtasks);
|
|
277
|
+
const subtaskMap = new Map(subtasks.map((s) => [s.id, s]));
|
|
278
|
+
for (const wave of waves) {
|
|
279
|
+
if (maxBudget !== void 0 && totalCredits >= maxBudget) {
|
|
280
|
+
errors.push(`Budget exceeded: spent ${totalCredits} cr, max ${maxBudget} cr`);
|
|
281
|
+
break;
|
|
282
|
+
}
|
|
283
|
+
const executableIds = [];
|
|
284
|
+
for (const taskId of wave) {
|
|
285
|
+
const m = matches.get(taskId);
|
|
286
|
+
if (maxBudget !== void 0 && m && totalCredits + m.credits > maxBudget) {
|
|
287
|
+
errors.push(`Skipping task ${taskId}: would exceed budget (${totalCredits} + ${m.credits} > ${maxBudget})`);
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
executableIds.push(taskId);
|
|
291
|
+
}
|
|
292
|
+
const waveResults = await Promise.allSettled(
|
|
293
|
+
executableIds.map(async (taskId) => {
|
|
294
|
+
const subtask = subtaskMap.get(taskId);
|
|
295
|
+
const m = matches.get(taskId);
|
|
296
|
+
if (!m) {
|
|
297
|
+
throw new Error(`No match found for subtask ${taskId}`);
|
|
298
|
+
}
|
|
299
|
+
const stepsContext = {};
|
|
300
|
+
for (const [id, val] of results) {
|
|
301
|
+
stepsContext[id] = val;
|
|
302
|
+
}
|
|
303
|
+
const interpContext = { steps: stepsContext, prev: void 0 };
|
|
304
|
+
if (subtask.depends_on.length > 0) {
|
|
305
|
+
const lastDep = subtask.depends_on[subtask.depends_on.length - 1];
|
|
306
|
+
interpContext.prev = results.get(lastDep);
|
|
307
|
+
}
|
|
308
|
+
const interpolatedParams = interpolateObject(
|
|
309
|
+
subtask.params,
|
|
310
|
+
interpContext
|
|
311
|
+
);
|
|
312
|
+
const primary = resolveAgentUrl(m.selected_agent);
|
|
313
|
+
try {
|
|
314
|
+
const res = await requestCapability({
|
|
315
|
+
gatewayUrl: primary.url,
|
|
316
|
+
token: gatewayToken,
|
|
317
|
+
cardId: primary.cardId,
|
|
318
|
+
params: interpolatedParams,
|
|
319
|
+
timeoutMs
|
|
320
|
+
});
|
|
321
|
+
return { taskId, result: res, credits: m.credits };
|
|
322
|
+
} catch (primaryErr) {
|
|
323
|
+
if (m.alternatives.length > 0) {
|
|
324
|
+
const alt = m.alternatives[0];
|
|
325
|
+
const altAgent = resolveAgentUrl(alt.agent);
|
|
326
|
+
try {
|
|
327
|
+
const altRes = await requestCapability({
|
|
328
|
+
gatewayUrl: altAgent.url,
|
|
329
|
+
token: gatewayToken,
|
|
330
|
+
cardId: altAgent.cardId,
|
|
331
|
+
params: interpolatedParams,
|
|
332
|
+
timeoutMs
|
|
333
|
+
});
|
|
334
|
+
return { taskId, result: altRes, credits: alt.credits };
|
|
335
|
+
} catch (altErr) {
|
|
336
|
+
throw new Error(
|
|
337
|
+
`Task ${taskId}: primary (${m.selected_agent}) failed: ${primaryErr instanceof Error ? primaryErr.message : String(primaryErr)}; alternative (${alt.agent}) failed: ${altErr instanceof Error ? altErr.message : String(altErr)}`
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
throw new Error(
|
|
342
|
+
`Task ${taskId}: ${primaryErr instanceof Error ? primaryErr.message : String(primaryErr)}`
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
})
|
|
346
|
+
);
|
|
347
|
+
for (const settlement of waveResults) {
|
|
348
|
+
if (settlement.status === "fulfilled") {
|
|
349
|
+
const { taskId, result, credits } = settlement.value;
|
|
350
|
+
results.set(taskId, result);
|
|
351
|
+
totalCredits += credits;
|
|
352
|
+
} else {
|
|
353
|
+
errors.push(settlement.reason instanceof Error ? settlement.reason.message : String(settlement.reason));
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
return {
|
|
358
|
+
success: errors.length === 0,
|
|
359
|
+
results,
|
|
360
|
+
total_credits: totalCredits,
|
|
361
|
+
latency_ms: Date.now() - startTime,
|
|
362
|
+
errors: errors.length > 0 ? errors : void 0
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
export {
|
|
367
|
+
decompose,
|
|
368
|
+
matchSubTasks,
|
|
369
|
+
ORCHESTRATION_FEE,
|
|
370
|
+
BudgetController,
|
|
371
|
+
orchestrate
|
|
372
|
+
};
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// src/types/index.ts
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
var IOSchemaSchema = z.object({
|
|
4
|
+
name: z.string(),
|
|
5
|
+
type: z.enum(["text", "json", "file", "audio", "image", "video", "stream"]),
|
|
6
|
+
description: z.string().optional(),
|
|
7
|
+
required: z.boolean().default(true),
|
|
8
|
+
schema: z.record(z.unknown()).optional()
|
|
9
|
+
// JSON Schema
|
|
10
|
+
});
|
|
11
|
+
var PoweredBySchema = z.object({
|
|
12
|
+
provider: z.string().min(1),
|
|
13
|
+
model: z.string().optional(),
|
|
14
|
+
tier: z.string().optional()
|
|
15
|
+
});
|
|
16
|
+
var CapabilityCardSchema = z.object({
|
|
17
|
+
spec_version: z.literal("1.0").default("1.0"),
|
|
18
|
+
id: z.string().uuid(),
|
|
19
|
+
owner: z.string().min(1),
|
|
20
|
+
name: z.string().min(1).max(100),
|
|
21
|
+
description: z.string().max(500),
|
|
22
|
+
level: z.union([z.literal(1), z.literal(2), z.literal(3)]),
|
|
23
|
+
inputs: z.array(IOSchemaSchema),
|
|
24
|
+
outputs: z.array(IOSchemaSchema),
|
|
25
|
+
pricing: z.object({
|
|
26
|
+
credits_per_call: z.number().nonnegative(),
|
|
27
|
+
credits_per_minute: z.number().nonnegative().optional(),
|
|
28
|
+
/** Number of free monthly calls. Shown as a "N free/mo" badge in the Hub. */
|
|
29
|
+
free_tier: z.number().nonnegative().optional()
|
|
30
|
+
}),
|
|
31
|
+
availability: z.object({
|
|
32
|
+
online: z.boolean(),
|
|
33
|
+
schedule: z.string().optional()
|
|
34
|
+
// cron expression
|
|
35
|
+
}),
|
|
36
|
+
powered_by: z.array(PoweredBySchema).optional(),
|
|
37
|
+
/**
|
|
38
|
+
* Private per-card metadata. Stripped from all API and CLI responses —
|
|
39
|
+
* never transmitted beyond the local store.
|
|
40
|
+
*/
|
|
41
|
+
_internal: z.record(z.unknown()).optional(),
|
|
42
|
+
metadata: z.object({
|
|
43
|
+
apis_used: z.array(z.string()).optional(),
|
|
44
|
+
avg_latency_ms: z.number().nonnegative().optional(),
|
|
45
|
+
success_rate: z.number().min(0).max(1).optional(),
|
|
46
|
+
tags: z.array(z.string()).optional()
|
|
47
|
+
}).optional(),
|
|
48
|
+
created_at: z.string().datetime().optional(),
|
|
49
|
+
updated_at: z.string().datetime().optional()
|
|
50
|
+
});
|
|
51
|
+
var SkillSchema = z.object({
|
|
52
|
+
/** Stable skill identifier, e.g. 'tts-elevenlabs'. Used for gateway routing and idle tracking. */
|
|
53
|
+
id: z.string().min(1),
|
|
54
|
+
name: z.string().min(1).max(100),
|
|
55
|
+
description: z.string().max(500),
|
|
56
|
+
level: z.union([z.literal(1), z.literal(2), z.literal(3)]),
|
|
57
|
+
/** Optional grouping category, e.g. 'tts' | 'video_gen' | 'code_review'. */
|
|
58
|
+
category: z.string().optional(),
|
|
59
|
+
inputs: z.array(IOSchemaSchema),
|
|
60
|
+
outputs: z.array(IOSchemaSchema),
|
|
61
|
+
pricing: z.object({
|
|
62
|
+
credits_per_call: z.number().nonnegative(),
|
|
63
|
+
credits_per_minute: z.number().nonnegative().optional(),
|
|
64
|
+
free_tier: z.number().nonnegative().optional()
|
|
65
|
+
}),
|
|
66
|
+
/** Per-skill online flag — overrides card-level availability for this skill. */
|
|
67
|
+
availability: z.object({ online: z.boolean() }).optional(),
|
|
68
|
+
powered_by: z.array(PoweredBySchema).optional(),
|
|
69
|
+
metadata: z.object({
|
|
70
|
+
apis_used: z.array(z.string()).optional(),
|
|
71
|
+
avg_latency_ms: z.number().nonnegative().optional(),
|
|
72
|
+
success_rate: z.number().min(0).max(1).optional(),
|
|
73
|
+
tags: z.array(z.string()).optional(),
|
|
74
|
+
capacity: z.object({
|
|
75
|
+
calls_per_hour: z.number().positive().default(60)
|
|
76
|
+
}).optional()
|
|
77
|
+
}).optional(),
|
|
78
|
+
/**
|
|
79
|
+
* Private per-skill metadata. Stripped from all API and CLI responses —
|
|
80
|
+
* never transmitted beyond the local store.
|
|
81
|
+
*/
|
|
82
|
+
_internal: z.record(z.unknown()).optional()
|
|
83
|
+
});
|
|
84
|
+
var CapabilityCardV2Schema = z.object({
|
|
85
|
+
spec_version: z.literal("2.0"),
|
|
86
|
+
id: z.string().uuid(),
|
|
87
|
+
owner: z.string().min(1),
|
|
88
|
+
/** Agent display name — was 'name' in v1.0. */
|
|
89
|
+
agent_name: z.string().min(1).max(100),
|
|
90
|
+
/** At least one skill is required. */
|
|
91
|
+
skills: z.array(SkillSchema).min(1),
|
|
92
|
+
availability: z.object({
|
|
93
|
+
online: z.boolean(),
|
|
94
|
+
schedule: z.string().optional()
|
|
95
|
+
}),
|
|
96
|
+
/** Optional deployment environment metadata. */
|
|
97
|
+
environment: z.object({
|
|
98
|
+
runtime: z.string(),
|
|
99
|
+
region: z.string().optional()
|
|
100
|
+
}).optional(),
|
|
101
|
+
/**
|
|
102
|
+
* Private per-card metadata. Stripped from all API and CLI responses —
|
|
103
|
+
* never transmitted beyond the local store.
|
|
104
|
+
*/
|
|
105
|
+
_internal: z.record(z.unknown()).optional(),
|
|
106
|
+
created_at: z.string().datetime().optional(),
|
|
107
|
+
updated_at: z.string().datetime().optional()
|
|
108
|
+
});
|
|
109
|
+
var AnyCardSchema = z.discriminatedUnion("spec_version", [
|
|
110
|
+
CapabilityCardSchema,
|
|
111
|
+
CapabilityCardV2Schema
|
|
112
|
+
]);
|
|
113
|
+
var AgentBnBError = class extends Error {
|
|
114
|
+
constructor(message, code) {
|
|
115
|
+
super(message);
|
|
116
|
+
this.code = code;
|
|
117
|
+
this.name = "AgentBnBError";
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
export {
|
|
122
|
+
CapabilityCardSchema,
|
|
123
|
+
CapabilityCardV2Schema,
|
|
124
|
+
AgentBnBError
|
|
125
|
+
};
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AgentBnBError
|
|
3
|
+
} from "./chunk-TQMI73LL.js";
|
|
4
|
+
|
|
5
|
+
// src/credit/escrow.ts
|
|
6
|
+
import { randomUUID } from "crypto";
|
|
7
|
+
function holdEscrow(db, owner, amount, cardId) {
|
|
8
|
+
const escrowId = randomUUID();
|
|
9
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
10
|
+
const hold = db.transaction(() => {
|
|
11
|
+
const row = db.prepare("SELECT balance FROM credit_balances WHERE owner = ?").get(owner);
|
|
12
|
+
if (!row || row.balance < amount) {
|
|
13
|
+
throw new AgentBnBError("Insufficient credits", "INSUFFICIENT_CREDITS");
|
|
14
|
+
}
|
|
15
|
+
db.prepare(
|
|
16
|
+
"UPDATE credit_balances SET balance = balance - ?, updated_at = ? WHERE owner = ? AND balance >= ?"
|
|
17
|
+
).run(amount, now, owner, amount);
|
|
18
|
+
db.prepare(
|
|
19
|
+
"INSERT INTO credit_escrow (id, owner, amount, card_id, status, created_at) VALUES (?, ?, ?, ?, ?, ?)"
|
|
20
|
+
).run(escrowId, owner, amount, cardId, "held", now);
|
|
21
|
+
db.prepare(
|
|
22
|
+
"INSERT INTO credit_transactions (id, owner, amount, reason, reference_id, created_at) VALUES (?, ?, ?, ?, ?, ?)"
|
|
23
|
+
).run(randomUUID(), owner, -amount, "escrow_hold", escrowId, now);
|
|
24
|
+
});
|
|
25
|
+
hold();
|
|
26
|
+
return escrowId;
|
|
27
|
+
}
|
|
28
|
+
function settleEscrow(db, escrowId, recipientOwner) {
|
|
29
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
30
|
+
const settle = db.transaction(() => {
|
|
31
|
+
const escrow = db.prepare("SELECT id, owner, amount, status FROM credit_escrow WHERE id = ?").get(escrowId);
|
|
32
|
+
if (!escrow) {
|
|
33
|
+
throw new AgentBnBError(`Escrow not found: ${escrowId}`, "ESCROW_NOT_FOUND");
|
|
34
|
+
}
|
|
35
|
+
if (escrow.status !== "held") {
|
|
36
|
+
throw new AgentBnBError(
|
|
37
|
+
`Escrow ${escrowId} is already ${escrow.status}`,
|
|
38
|
+
"ESCROW_ALREADY_SETTLED"
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
db.prepare(
|
|
42
|
+
"INSERT OR IGNORE INTO credit_balances (owner, balance, updated_at) VALUES (?, 0, ?)"
|
|
43
|
+
).run(recipientOwner, now);
|
|
44
|
+
db.prepare(
|
|
45
|
+
"UPDATE credit_balances SET balance = balance + ?, updated_at = ? WHERE owner = ?"
|
|
46
|
+
).run(escrow.amount, now, recipientOwner);
|
|
47
|
+
db.prepare(
|
|
48
|
+
"UPDATE credit_escrow SET status = ?, settled_at = ? WHERE id = ?"
|
|
49
|
+
).run("settled", now, escrowId);
|
|
50
|
+
db.prepare(
|
|
51
|
+
"INSERT INTO credit_transactions (id, owner, amount, reason, reference_id, created_at) VALUES (?, ?, ?, ?, ?, ?)"
|
|
52
|
+
).run(randomUUID(), recipientOwner, escrow.amount, "settlement", escrowId, now);
|
|
53
|
+
});
|
|
54
|
+
settle();
|
|
55
|
+
}
|
|
56
|
+
function releaseEscrow(db, escrowId) {
|
|
57
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
58
|
+
const release = db.transaction(() => {
|
|
59
|
+
const escrow = db.prepare("SELECT id, owner, amount, status FROM credit_escrow WHERE id = ?").get(escrowId);
|
|
60
|
+
if (!escrow) {
|
|
61
|
+
throw new AgentBnBError(`Escrow not found: ${escrowId}`, "ESCROW_NOT_FOUND");
|
|
62
|
+
}
|
|
63
|
+
if (escrow.status !== "held") {
|
|
64
|
+
throw new AgentBnBError(
|
|
65
|
+
`Escrow ${escrowId} is already ${escrow.status}`,
|
|
66
|
+
"ESCROW_ALREADY_SETTLED"
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
db.prepare(
|
|
70
|
+
"UPDATE credit_balances SET balance = balance + ?, updated_at = ? WHERE owner = ?"
|
|
71
|
+
).run(escrow.amount, now, escrow.owner);
|
|
72
|
+
db.prepare(
|
|
73
|
+
"UPDATE credit_escrow SET status = ?, settled_at = ? WHERE id = ?"
|
|
74
|
+
).run("released", now, escrowId);
|
|
75
|
+
db.prepare(
|
|
76
|
+
"INSERT INTO credit_transactions (id, owner, amount, reason, reference_id, created_at) VALUES (?, ?, ?, ?, ?, ?)"
|
|
77
|
+
).run(randomUUID(), escrow.owner, escrow.amount, "refund", escrowId, now);
|
|
78
|
+
});
|
|
79
|
+
release();
|
|
80
|
+
}
|
|
81
|
+
function confirmEscrowDebit(db, escrowId) {
|
|
82
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
83
|
+
const confirm = db.transaction(() => {
|
|
84
|
+
const escrow = db.prepare("SELECT id, owner, amount, status FROM credit_escrow WHERE id = ?").get(escrowId);
|
|
85
|
+
if (!escrow) {
|
|
86
|
+
throw new AgentBnBError(`Escrow not found: ${escrowId}`, "ESCROW_NOT_FOUND");
|
|
87
|
+
}
|
|
88
|
+
if (escrow.status !== "held") {
|
|
89
|
+
throw new AgentBnBError(
|
|
90
|
+
`Escrow ${escrowId} is already ${escrow.status}`,
|
|
91
|
+
"ESCROW_ALREADY_SETTLED"
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
db.prepare(
|
|
95
|
+
"UPDATE credit_escrow SET status = ?, settled_at = ? WHERE id = ?"
|
|
96
|
+
).run("settled", now, escrowId);
|
|
97
|
+
db.prepare(
|
|
98
|
+
"INSERT INTO credit_transactions (id, owner, amount, reason, reference_id, created_at) VALUES (?, ?, ?, ?, ?, ?)"
|
|
99
|
+
).run(randomUUID(), escrow.owner, 0, "remote_settlement_confirmed", escrowId, now);
|
|
100
|
+
});
|
|
101
|
+
confirm();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// src/credit/ledger.ts
|
|
105
|
+
import Database from "better-sqlite3";
|
|
106
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
107
|
+
var CREDIT_SCHEMA = `
|
|
108
|
+
CREATE TABLE IF NOT EXISTS credit_balances (
|
|
109
|
+
owner TEXT PRIMARY KEY,
|
|
110
|
+
balance INTEGER NOT NULL DEFAULT 0,
|
|
111
|
+
updated_at TEXT NOT NULL
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
CREATE TABLE IF NOT EXISTS credit_transactions (
|
|
115
|
+
id TEXT PRIMARY KEY,
|
|
116
|
+
owner TEXT NOT NULL,
|
|
117
|
+
amount INTEGER NOT NULL,
|
|
118
|
+
reason TEXT NOT NULL,
|
|
119
|
+
reference_id TEXT,
|
|
120
|
+
created_at TEXT NOT NULL
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
CREATE TABLE IF NOT EXISTS credit_escrow (
|
|
124
|
+
id TEXT PRIMARY KEY,
|
|
125
|
+
owner TEXT NOT NULL,
|
|
126
|
+
amount INTEGER NOT NULL,
|
|
127
|
+
card_id TEXT NOT NULL,
|
|
128
|
+
status TEXT NOT NULL DEFAULT 'held',
|
|
129
|
+
created_at TEXT NOT NULL,
|
|
130
|
+
settled_at TEXT
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
CREATE INDEX IF NOT EXISTS idx_transactions_owner ON credit_transactions(owner, created_at);
|
|
134
|
+
CREATE INDEX IF NOT EXISTS idx_escrow_owner ON credit_escrow(owner);
|
|
135
|
+
`;
|
|
136
|
+
function openCreditDb(path = ":memory:") {
|
|
137
|
+
const db = new Database(path);
|
|
138
|
+
db.pragma("journal_mode = WAL");
|
|
139
|
+
db.pragma("foreign_keys = ON");
|
|
140
|
+
db.exec(CREDIT_SCHEMA);
|
|
141
|
+
return db;
|
|
142
|
+
}
|
|
143
|
+
function bootstrapAgent(db, owner, amount = 100) {
|
|
144
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
145
|
+
db.transaction(() => {
|
|
146
|
+
const result = db.prepare("INSERT OR IGNORE INTO credit_balances (owner, balance, updated_at) VALUES (?, ?, ?)").run(owner, amount, now);
|
|
147
|
+
if (result.changes > 0) {
|
|
148
|
+
db.prepare(
|
|
149
|
+
"INSERT INTO credit_transactions (id, owner, amount, reason, reference_id, created_at) VALUES (?, ?, ?, ?, ?, ?)"
|
|
150
|
+
).run(randomUUID2(), owner, amount, "bootstrap", null, now);
|
|
151
|
+
}
|
|
152
|
+
})();
|
|
153
|
+
}
|
|
154
|
+
function getBalance(db, owner) {
|
|
155
|
+
const row = db.prepare("SELECT balance FROM credit_balances WHERE owner = ?").get(owner);
|
|
156
|
+
return row?.balance ?? 0;
|
|
157
|
+
}
|
|
158
|
+
function getTransactions(db, owner, limit = 100) {
|
|
159
|
+
return db.prepare(
|
|
160
|
+
"SELECT id, owner, amount, reason, reference_id, created_at FROM credit_transactions WHERE owner = ? ORDER BY created_at DESC LIMIT ?"
|
|
161
|
+
).all(owner, limit);
|
|
162
|
+
}
|
|
163
|
+
function recordEarning(db, owner, amount, _cardId, receiptNonce) {
|
|
164
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
165
|
+
db.transaction(() => {
|
|
166
|
+
const existing = db.prepare(
|
|
167
|
+
"SELECT id FROM credit_transactions WHERE reference_id = ? AND reason = 'remote_earning'"
|
|
168
|
+
).get(receiptNonce);
|
|
169
|
+
if (existing) return;
|
|
170
|
+
db.prepare(
|
|
171
|
+
"INSERT OR IGNORE INTO credit_balances (owner, balance, updated_at) VALUES (?, 0, ?)"
|
|
172
|
+
).run(owner, now);
|
|
173
|
+
db.prepare(
|
|
174
|
+
"UPDATE credit_balances SET balance = balance + ?, updated_at = ? WHERE owner = ?"
|
|
175
|
+
).run(amount, now, owner);
|
|
176
|
+
db.prepare(
|
|
177
|
+
"INSERT INTO credit_transactions (id, owner, amount, reason, reference_id, created_at) VALUES (?, ?, ?, ?, ?, ?)"
|
|
178
|
+
).run(randomUUID2(), owner, amount, "remote_earning", receiptNonce, now);
|
|
179
|
+
})();
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export {
|
|
183
|
+
holdEscrow,
|
|
184
|
+
settleEscrow,
|
|
185
|
+
releaseEscrow,
|
|
186
|
+
confirmEscrowDebit,
|
|
187
|
+
openCreditDb,
|
|
188
|
+
bootstrapAgent,
|
|
189
|
+
getBalance,
|
|
190
|
+
getTransactions,
|
|
191
|
+
recordEarning
|
|
192
|
+
};
|