agentbnb 3.1.6 → 4.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +117 -86
- package/dist/{card-IE5UV5QX.js → card-4XH4AOTE.js} +11 -4
- package/dist/chunk-3MJT4PZG.js +50 -0
- package/dist/{conduct-IEQ567ET.js → chunk-3UKAVIMC.js} +70 -31
- package/dist/chunk-5AH3CMOX.js +62 -0
- package/dist/{chunk-IZZ4FP45.js → chunk-6K5WUVF3.js} +33 -166
- package/dist/chunk-75OC6E4F.js +33 -0
- package/dist/chunk-DVAS2443.js +63 -0
- package/dist/{chunk-XA63SD4T.js → chunk-FNKBHBYK.js} +3 -0
- package/dist/{websocket-client-5TIQDYQ4.js → chunk-JOY533UH.js} +38 -4
- package/dist/chunk-KJG2UJV5.js +83 -0
- package/dist/chunk-M3G5NR2Z.js +90 -0
- package/dist/{chunk-7OACGAFD.js → chunk-MQKYGY5I.js} +63 -24
- package/dist/chunk-ODBGCCEH.js +358 -0
- package/dist/{chunk-QSPWE5AE.js → chunk-Q7HRI666.js} +9 -6
- package/dist/chunk-QJEOCKVF.js +148 -0
- package/dist/{chunk-3Y36WQDV.js → chunk-QT7TEVNV.js} +14 -2
- package/dist/{chunk-UOGDK2S2.js → chunk-TLU7ALCZ.js} +1 -1
- package/dist/{chunk-QHQPXO67.js → chunk-XQHN6ITI.js} +1 -58
- package/dist/cli/index.js +2734 -850
- package/dist/client-BTPIFY7E.js +10 -0
- package/dist/conduct-CW62HBPT.js +52 -0
- package/dist/conduct-FXLVGKD5.js +19 -0
- package/dist/{conductor-mode-IO45PWMI.js → conductor-mode-3JS4VWCR.js} +16 -7
- package/dist/execute-EXOITLHN.js +10 -0
- package/dist/index.d.ts +1005 -916
- package/dist/index.js +516 -120
- package/dist/{peers-G36URZYB.js → peers-K7FSHPN3.js} +2 -1
- package/dist/request-CNZ3XIVX.js +196 -0
- package/dist/serve-skill-SUOGUM7N.js +104 -0
- package/dist/server-2LWHL24P.js +295 -0
- package/dist/types-FGBUZ3QV.js +18 -0
- package/dist/websocket-client-6IIDGXKB.js +7 -0
- package/package.json +4 -1
- package/dist/chunk-BEI5MTNZ.js +0 -91
- package/dist/cli/index.d.ts +0 -1
- package/dist/execute-SWWEHV2K.js +0 -9
package/dist/index.js
CHANGED
|
@@ -436,30 +436,6 @@ function getCard(db, id) {
|
|
|
436
436
|
if (!row) return null;
|
|
437
437
|
return JSON.parse(row.data);
|
|
438
438
|
}
|
|
439
|
-
function updateCard(db, id, owner, updates) {
|
|
440
|
-
const existing = getCard(db, id);
|
|
441
|
-
if (!existing) {
|
|
442
|
-
throw new AgentBnBError(`Card not found: ${id}`, "NOT_FOUND");
|
|
443
|
-
}
|
|
444
|
-
if (existing.owner !== owner) {
|
|
445
|
-
throw new AgentBnBError("Forbidden: you do not own this card", "FORBIDDEN");
|
|
446
|
-
}
|
|
447
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
448
|
-
const merged = { ...existing, ...updates, updated_at: now };
|
|
449
|
-
const parsed = CapabilityCardSchema.safeParse(merged);
|
|
450
|
-
if (!parsed.success) {
|
|
451
|
-
throw new AgentBnBError(
|
|
452
|
-
`Card validation failed: ${parsed.error.message}`,
|
|
453
|
-
"VALIDATION_ERROR"
|
|
454
|
-
);
|
|
455
|
-
}
|
|
456
|
-
const stmt = db.prepare(`
|
|
457
|
-
UPDATE capability_cards
|
|
458
|
-
SET data = ?, updated_at = ?
|
|
459
|
-
WHERE id = ?
|
|
460
|
-
`);
|
|
461
|
-
stmt.run(JSON.stringify(parsed.data), now, id);
|
|
462
|
-
}
|
|
463
439
|
function updateReputation(db, cardId, success, latencyMs) {
|
|
464
440
|
const existing = getCard(db, cardId);
|
|
465
441
|
if (!existing) return;
|
|
@@ -783,7 +759,8 @@ async function executeCapabilityRequest(opts) {
|
|
|
783
759
|
escrowReceipt: receipt,
|
|
784
760
|
skillExecutor,
|
|
785
761
|
handlerUrl,
|
|
786
|
-
timeoutMs =
|
|
762
|
+
timeoutMs = 3e5,
|
|
763
|
+
onProgress
|
|
787
764
|
} = opts;
|
|
788
765
|
const card = getCard(registryDb, cardId);
|
|
789
766
|
if (!card) {
|
|
@@ -890,7 +867,7 @@ async function executeCapabilityRequest(opts) {
|
|
|
890
867
|
if (skillExecutor) {
|
|
891
868
|
const targetSkillId = resolvedSkillId ?? skillId ?? cardId;
|
|
892
869
|
try {
|
|
893
|
-
const execResult = await skillExecutor.execute(targetSkillId, params);
|
|
870
|
+
const execResult = await skillExecutor.execute(targetSkillId, params, onProgress);
|
|
894
871
|
if (!execResult.success) {
|
|
895
872
|
return handleFailure("failure", execResult.latency_ms, execResult.error ?? "Execution failed");
|
|
896
873
|
}
|
|
@@ -937,7 +914,7 @@ function createGatewayServer(opts) {
|
|
|
937
914
|
creditDb,
|
|
938
915
|
tokens,
|
|
939
916
|
handlerUrl,
|
|
940
|
-
timeoutMs =
|
|
917
|
+
timeoutMs = 3e5,
|
|
941
918
|
silent = false,
|
|
942
919
|
skillExecutor
|
|
943
920
|
} = opts;
|
|
@@ -1054,7 +1031,7 @@ var SkillExecutor = class {
|
|
|
1054
1031
|
* @param params - Input parameters for the skill.
|
|
1055
1032
|
* @returns ExecutionResult including success, result/error, and latency_ms.
|
|
1056
1033
|
*/
|
|
1057
|
-
async execute(skillId, params) {
|
|
1034
|
+
async execute(skillId, params, onProgress) {
|
|
1058
1035
|
const startTime = Date.now();
|
|
1059
1036
|
const config = this.skillMap.get(skillId);
|
|
1060
1037
|
if (!config) {
|
|
@@ -1073,7 +1050,7 @@ var SkillExecutor = class {
|
|
|
1073
1050
|
};
|
|
1074
1051
|
}
|
|
1075
1052
|
try {
|
|
1076
|
-
const modeResult = await mode.execute(config, params);
|
|
1053
|
+
const modeResult = await mode.execute(config, params, onProgress);
|
|
1077
1054
|
return {
|
|
1078
1055
|
...modeResult,
|
|
1079
1056
|
latency_ms: Date.now() - startTime
|
|
@@ -1493,7 +1470,7 @@ var PipelineExecutor = class {
|
|
|
1493
1470
|
* @param params - Input parameters from the caller.
|
|
1494
1471
|
* @returns Partial ExecutionResult (without latency_ms — added by SkillExecutor wrapper).
|
|
1495
1472
|
*/
|
|
1496
|
-
async execute(config, params) {
|
|
1473
|
+
async execute(config, params, onProgress) {
|
|
1497
1474
|
const pipelineConfig = config;
|
|
1498
1475
|
const steps = pipelineConfig.steps ?? [];
|
|
1499
1476
|
if (steps.length === 0) {
|
|
@@ -1552,6 +1529,13 @@ var PipelineExecutor = class {
|
|
|
1552
1529
|
}
|
|
1553
1530
|
context.steps.push({ result: stepResult });
|
|
1554
1531
|
context.prev = { result: stepResult };
|
|
1532
|
+
if (onProgress && i < steps.length - 1) {
|
|
1533
|
+
onProgress({
|
|
1534
|
+
step: i + 1,
|
|
1535
|
+
total: steps.length,
|
|
1536
|
+
message: `Completed step ${i + 1}/${steps.length}`
|
|
1537
|
+
});
|
|
1538
|
+
}
|
|
1555
1539
|
}
|
|
1556
1540
|
const lastStep = context.steps[context.steps.length - 1];
|
|
1557
1541
|
return {
|
|
@@ -1930,7 +1914,7 @@ function decompose(task, _availableCapabilities) {
|
|
|
1930
1914
|
// src/gateway/client.ts
|
|
1931
1915
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
1932
1916
|
async function requestCapability(opts) {
|
|
1933
|
-
const { gatewayUrl, token, cardId, params = {}, timeoutMs =
|
|
1917
|
+
const { gatewayUrl, token, cardId, params = {}, timeoutMs = 3e5, escrowReceipt, identity } = opts;
|
|
1934
1918
|
const id = randomUUID5();
|
|
1935
1919
|
const payload = {
|
|
1936
1920
|
jsonrpc: "2.0",
|
|
@@ -2030,6 +2014,73 @@ function loadConfig() {
|
|
|
2030
2014
|
}
|
|
2031
2015
|
}
|
|
2032
2016
|
|
|
2017
|
+
// src/cli/remote-registry.ts
|
|
2018
|
+
var RegistryTimeoutError = class extends AgentBnBError {
|
|
2019
|
+
constructor(url) {
|
|
2020
|
+
super(
|
|
2021
|
+
`Registry at ${url} did not respond within 5s. Showing local results only.`,
|
|
2022
|
+
"REGISTRY_TIMEOUT"
|
|
2023
|
+
);
|
|
2024
|
+
this.name = "RegistryTimeoutError";
|
|
2025
|
+
}
|
|
2026
|
+
};
|
|
2027
|
+
var RegistryConnectionError = class extends AgentBnBError {
|
|
2028
|
+
constructor(url) {
|
|
2029
|
+
super(
|
|
2030
|
+
`Cannot reach ${url}. Is the registry running? Showing local results only.`,
|
|
2031
|
+
"REGISTRY_CONNECTION"
|
|
2032
|
+
);
|
|
2033
|
+
this.name = "RegistryConnectionError";
|
|
2034
|
+
}
|
|
2035
|
+
};
|
|
2036
|
+
var RegistryAuthError = class extends AgentBnBError {
|
|
2037
|
+
constructor(url) {
|
|
2038
|
+
super(
|
|
2039
|
+
`Authentication failed for ${url}. Run \`agentbnb config set token <your-token>\`.`,
|
|
2040
|
+
"REGISTRY_AUTH"
|
|
2041
|
+
);
|
|
2042
|
+
this.name = "RegistryAuthError";
|
|
2043
|
+
}
|
|
2044
|
+
};
|
|
2045
|
+
async function fetchRemoteCards(registryUrl, params, timeoutMs = 5e3) {
|
|
2046
|
+
let cardsUrl;
|
|
2047
|
+
try {
|
|
2048
|
+
cardsUrl = new URL("/cards", registryUrl);
|
|
2049
|
+
} catch {
|
|
2050
|
+
throw new AgentBnBError(`Invalid registry URL: ${registryUrl}`, "INVALID_REGISTRY_URL");
|
|
2051
|
+
}
|
|
2052
|
+
const searchParams = new URLSearchParams();
|
|
2053
|
+
if (params.q !== void 0) searchParams.set("q", params.q);
|
|
2054
|
+
if (params.level !== void 0) searchParams.set("level", String(params.level));
|
|
2055
|
+
if (params.online !== void 0) searchParams.set("online", String(params.online));
|
|
2056
|
+
if (params.tag !== void 0) searchParams.set("tag", params.tag);
|
|
2057
|
+
searchParams.set("limit", "100");
|
|
2058
|
+
cardsUrl.search = searchParams.toString();
|
|
2059
|
+
const controller = new AbortController();
|
|
2060
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
2061
|
+
let response;
|
|
2062
|
+
try {
|
|
2063
|
+
response = await fetch(cardsUrl.toString(), { signal: controller.signal });
|
|
2064
|
+
} catch (err) {
|
|
2065
|
+
clearTimeout(timer);
|
|
2066
|
+
const isTimeout = err instanceof Error && err.name === "AbortError";
|
|
2067
|
+
if (isTimeout) {
|
|
2068
|
+
throw new RegistryTimeoutError(registryUrl);
|
|
2069
|
+
}
|
|
2070
|
+
throw new RegistryConnectionError(registryUrl);
|
|
2071
|
+
} finally {
|
|
2072
|
+
clearTimeout(timer);
|
|
2073
|
+
}
|
|
2074
|
+
if (response.status === 401 || response.status === 403) {
|
|
2075
|
+
throw new RegistryAuthError(registryUrl);
|
|
2076
|
+
}
|
|
2077
|
+
if (!response.ok) {
|
|
2078
|
+
throw new RegistryConnectionError(registryUrl);
|
|
2079
|
+
}
|
|
2080
|
+
const body = await response.json();
|
|
2081
|
+
return body.items;
|
|
2082
|
+
}
|
|
2083
|
+
|
|
2033
2084
|
// src/autonomy/auto-request.ts
|
|
2034
2085
|
function minMaxNormalize(values) {
|
|
2035
2086
|
if (values.length === 0) return [];
|
|
@@ -2064,10 +2115,17 @@ function scorePeers(candidates, selfOwner) {
|
|
|
2064
2115
|
|
|
2065
2116
|
// src/conductor/capability-matcher.ts
|
|
2066
2117
|
var MAX_ALTERNATIVES = 2;
|
|
2067
|
-
function matchSubTasks(opts) {
|
|
2068
|
-
const { db, subtasks, conductorOwner } = opts;
|
|
2069
|
-
return subtasks.map((subtask) => {
|
|
2070
|
-
|
|
2118
|
+
async function matchSubTasks(opts) {
|
|
2119
|
+
const { db, subtasks, conductorOwner, registryUrl } = opts;
|
|
2120
|
+
return Promise.all(subtasks.map(async (subtask) => {
|
|
2121
|
+
let cards = searchCards(db, subtask.required_capability, { online: true });
|
|
2122
|
+
if (cards.length === 0 && registryUrl) {
|
|
2123
|
+
try {
|
|
2124
|
+
cards = await fetchRemoteCards(registryUrl, { q: subtask.required_capability, online: true });
|
|
2125
|
+
} catch {
|
|
2126
|
+
cards = [];
|
|
2127
|
+
}
|
|
2128
|
+
}
|
|
2071
2129
|
const candidates = [];
|
|
2072
2130
|
for (const card of cards) {
|
|
2073
2131
|
const cardAsV2 = card;
|
|
@@ -2109,11 +2167,12 @@ function matchSubTasks(opts) {
|
|
|
2109
2167
|
subtask_id: subtask.id,
|
|
2110
2168
|
selected_agent: top.card.owner,
|
|
2111
2169
|
selected_skill: top.skillId ?? "",
|
|
2170
|
+
selected_card_id: top.card.id,
|
|
2112
2171
|
score: top.rawScore,
|
|
2113
2172
|
credits: top.cost,
|
|
2114
2173
|
alternatives
|
|
2115
2174
|
};
|
|
2116
|
-
});
|
|
2175
|
+
}));
|
|
2117
2176
|
}
|
|
2118
2177
|
|
|
2119
2178
|
// src/conductor/budget-controller.ts
|
|
@@ -2184,13 +2243,20 @@ var BudgetController = class {
|
|
|
2184
2243
|
};
|
|
2185
2244
|
|
|
2186
2245
|
// src/conductor/card.ts
|
|
2246
|
+
import { createHash } from "crypto";
|
|
2187
2247
|
var CONDUCTOR_OWNER = "agentbnb-conductor";
|
|
2188
2248
|
var CONDUCTOR_CARD_ID = "00000000-0000-4000-8000-000000000001";
|
|
2189
|
-
function
|
|
2249
|
+
function ownerToCardId(owner) {
|
|
2250
|
+
const hash = createHash("sha256").update(owner).digest("hex").slice(0, 32);
|
|
2251
|
+
return `${hash.slice(0, 8)}-${hash.slice(8, 12)}-4${hash.slice(13, 16)}-8${hash.slice(17, 20)}-${hash.slice(20, 32)}`;
|
|
2252
|
+
}
|
|
2253
|
+
function buildConductorCard(owner) {
|
|
2254
|
+
const cardOwner = owner ?? CONDUCTOR_OWNER;
|
|
2255
|
+
const cardId = owner ? ownerToCardId(owner) : CONDUCTOR_CARD_ID;
|
|
2190
2256
|
const card = {
|
|
2191
2257
|
spec_version: "2.0",
|
|
2192
|
-
id:
|
|
2193
|
-
owner:
|
|
2258
|
+
id: cardId,
|
|
2259
|
+
owner: cardOwner,
|
|
2194
2260
|
agent_name: "AgentBnB Conductor",
|
|
2195
2261
|
skills: [
|
|
2196
2262
|
{
|
|
@@ -2281,7 +2347,7 @@ function computeWaves(subtasks) {
|
|
|
2281
2347
|
return waves;
|
|
2282
2348
|
}
|
|
2283
2349
|
async function orchestrate(opts) {
|
|
2284
|
-
const { subtasks, matches, gatewayToken, resolveAgentUrl, timeoutMs =
|
|
2350
|
+
const { subtasks, matches, gatewayToken, resolveAgentUrl, timeoutMs = 3e5, maxBudget, relayClient, requesterOwner } = opts;
|
|
2285
2351
|
const startTime = Date.now();
|
|
2286
2352
|
if (subtasks.length === 0) {
|
|
2287
2353
|
return {
|
|
@@ -2332,26 +2398,50 @@ async function orchestrate(opts) {
|
|
|
2332
2398
|
);
|
|
2333
2399
|
const primary = resolveAgentUrl(m.selected_agent);
|
|
2334
2400
|
try {
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2401
|
+
let res;
|
|
2402
|
+
if (primary.url.startsWith("relay://") && relayClient) {
|
|
2403
|
+
const targetOwner = primary.url.replace("relay://", "");
|
|
2404
|
+
res = await relayClient.request({
|
|
2405
|
+
targetOwner,
|
|
2406
|
+
cardId: primary.cardId,
|
|
2407
|
+
params: interpolatedParams,
|
|
2408
|
+
requester: requesterOwner,
|
|
2409
|
+
timeoutMs
|
|
2410
|
+
});
|
|
2411
|
+
} else {
|
|
2412
|
+
res = await requestCapability({
|
|
2413
|
+
gatewayUrl: primary.url,
|
|
2414
|
+
token: gatewayToken,
|
|
2415
|
+
cardId: primary.cardId,
|
|
2416
|
+
params: interpolatedParams,
|
|
2417
|
+
timeoutMs
|
|
2418
|
+
});
|
|
2419
|
+
}
|
|
2342
2420
|
return { taskId, result: res, credits: m.credits };
|
|
2343
2421
|
} catch (primaryErr) {
|
|
2344
2422
|
if (m.alternatives.length > 0) {
|
|
2345
2423
|
const alt = m.alternatives[0];
|
|
2346
2424
|
const altAgent = resolveAgentUrl(alt.agent);
|
|
2347
2425
|
try {
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2426
|
+
let altRes;
|
|
2427
|
+
if (altAgent.url.startsWith("relay://") && relayClient) {
|
|
2428
|
+
const targetOwner = altAgent.url.replace("relay://", "");
|
|
2429
|
+
altRes = await relayClient.request({
|
|
2430
|
+
targetOwner,
|
|
2431
|
+
cardId: altAgent.cardId,
|
|
2432
|
+
params: interpolatedParams,
|
|
2433
|
+
requester: requesterOwner,
|
|
2434
|
+
timeoutMs
|
|
2435
|
+
});
|
|
2436
|
+
} else {
|
|
2437
|
+
altRes = await requestCapability({
|
|
2438
|
+
gatewayUrl: altAgent.url,
|
|
2439
|
+
token: gatewayToken,
|
|
2440
|
+
cardId: altAgent.cardId,
|
|
2441
|
+
params: interpolatedParams,
|
|
2442
|
+
timeoutMs
|
|
2443
|
+
});
|
|
2444
|
+
}
|
|
2355
2445
|
return { taskId, result: altRes, credits: alt.credits };
|
|
2356
2446
|
} catch (altErr) {
|
|
2357
2447
|
throw new Error(
|
|
@@ -2452,7 +2542,7 @@ var ConductorMode = class {
|
|
|
2452
2542
|
* @param params - Must include `task` string.
|
|
2453
2543
|
* @returns Execution result without latency_ms (added by SkillExecutor).
|
|
2454
2544
|
*/
|
|
2455
|
-
async execute(config, params) {
|
|
2545
|
+
async execute(config, params, onProgress) {
|
|
2456
2546
|
const conductorSkill = config.conductor_skill;
|
|
2457
2547
|
if (conductorSkill !== "orchestrate" && conductorSkill !== "plan") {
|
|
2458
2548
|
return {
|
|
@@ -2474,11 +2564,13 @@ var ConductorMode = class {
|
|
|
2474
2564
|
error: "No template matches task"
|
|
2475
2565
|
};
|
|
2476
2566
|
}
|
|
2477
|
-
|
|
2567
|
+
onProgress?.({ step: 1, total: 5, message: `Decomposed into ${subtasks.length} sub-tasks` });
|
|
2568
|
+
const matchResults = await matchSubTasks({
|
|
2478
2569
|
db: this.db,
|
|
2479
2570
|
subtasks,
|
|
2480
2571
|
conductorOwner: this.conductorOwner
|
|
2481
2572
|
});
|
|
2573
|
+
onProgress?.({ step: 2, total: 5, message: `Matched ${matchResults.length} sub-tasks to agents` });
|
|
2482
2574
|
const budgetManager = new BudgetManager(this.creditDb, this.conductorOwner);
|
|
2483
2575
|
const budgetController = new BudgetController(budgetManager, this.maxBudget);
|
|
2484
2576
|
const executionBudget = budgetController.calculateBudget(matchResults);
|
|
@@ -2488,6 +2580,7 @@ var ConductorMode = class {
|
|
|
2488
2580
|
error: `Budget exceeded: estimated ${executionBudget.estimated_total} cr, max ${this.maxBudget} cr`
|
|
2489
2581
|
};
|
|
2490
2582
|
}
|
|
2583
|
+
onProgress?.({ step: 3, total: 5, message: `Budget approved: ${executionBudget.estimated_total} cr` });
|
|
2491
2584
|
if (conductorSkill === "plan") {
|
|
2492
2585
|
return {
|
|
2493
2586
|
success: true,
|
|
@@ -2508,6 +2601,7 @@ var ConductorMode = class {
|
|
|
2508
2601
|
resolveAgentUrl: this.resolveAgentUrl,
|
|
2509
2602
|
maxBudget: this.maxBudget
|
|
2510
2603
|
});
|
|
2604
|
+
onProgress?.({ step: 4, total: 5, message: "Pipeline execution complete" });
|
|
2511
2605
|
const resultObj = {};
|
|
2512
2606
|
for (const [key, value] of orchResult.results) {
|
|
2513
2607
|
resultObj[key] = value;
|
|
@@ -2560,7 +2654,7 @@ function createSignedEscrowReceipt(db, privateKey, publicKey, opts) {
|
|
|
2560
2654
|
|
|
2561
2655
|
// src/identity/identity.ts
|
|
2562
2656
|
import { z as z4 } from "zod";
|
|
2563
|
-
import { createHash } from "crypto";
|
|
2657
|
+
import { createHash as createHash2 } from "crypto";
|
|
2564
2658
|
import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, existsSync as existsSync4, mkdirSync as mkdirSync3 } from "fs";
|
|
2565
2659
|
import { join as join4 } from "path";
|
|
2566
2660
|
var AgentIdentitySchema = z4.object({
|
|
@@ -2591,7 +2685,7 @@ var AgentCertificateSchema = z4.object({
|
|
|
2591
2685
|
});
|
|
2592
2686
|
var IDENTITY_FILENAME = "identity.json";
|
|
2593
2687
|
function deriveAgentId(publicKeyHex) {
|
|
2594
|
-
return
|
|
2688
|
+
return createHash2("sha256").update(publicKeyHex, "hex").digest("hex").slice(0, 16);
|
|
2595
2689
|
}
|
|
2596
2690
|
function createIdentity(configDir, owner) {
|
|
2597
2691
|
if (!existsSync4(configDir)) {
|
|
@@ -3036,8 +3130,10 @@ var RegisterMessageSchema = z6.object({
|
|
|
3036
3130
|
type: z6.literal("register"),
|
|
3037
3131
|
owner: z6.string().min(1),
|
|
3038
3132
|
token: z6.string().min(1),
|
|
3039
|
-
card: z6.record(z6.unknown())
|
|
3133
|
+
card: z6.record(z6.unknown()),
|
|
3040
3134
|
// CapabilityCard (validated separately)
|
|
3135
|
+
cards: z6.array(z6.record(z6.unknown())).optional()
|
|
3136
|
+
// Additional cards (e.g., conductor card)
|
|
3041
3137
|
});
|
|
3042
3138
|
var RegisteredMessageSchema = z6.object({
|
|
3043
3139
|
type: z6.literal("registered"),
|
|
@@ -3087,6 +3183,15 @@ var ErrorMessageSchema = z6.object({
|
|
|
3087
3183
|
message: z6.string(),
|
|
3088
3184
|
request_id: z6.string().optional()
|
|
3089
3185
|
});
|
|
3186
|
+
var RelayProgressMessageSchema = z6.object({
|
|
3187
|
+
type: z6.literal("relay_progress"),
|
|
3188
|
+
id: z6.string().uuid(),
|
|
3189
|
+
// request ID this progress relates to
|
|
3190
|
+
progress: z6.number().min(0).max(100).optional(),
|
|
3191
|
+
// optional percentage
|
|
3192
|
+
message: z6.string().optional()
|
|
3193
|
+
// optional status message
|
|
3194
|
+
});
|
|
3090
3195
|
var RelayMessageSchema = z6.discriminatedUnion("type", [
|
|
3091
3196
|
RegisterMessageSchema,
|
|
3092
3197
|
RegisteredMessageSchema,
|
|
@@ -3094,18 +3199,105 @@ var RelayMessageSchema = z6.discriminatedUnion("type", [
|
|
|
3094
3199
|
IncomingRequestMessageSchema,
|
|
3095
3200
|
RelayResponseMessageSchema,
|
|
3096
3201
|
ResponseMessageSchema,
|
|
3097
|
-
ErrorMessageSchema
|
|
3202
|
+
ErrorMessageSchema,
|
|
3203
|
+
RelayProgressMessageSchema
|
|
3098
3204
|
]);
|
|
3099
3205
|
|
|
3100
3206
|
// src/relay/websocket-relay.ts
|
|
3207
|
+
import { randomUUID as randomUUID12 } from "crypto";
|
|
3208
|
+
|
|
3209
|
+
// src/relay/relay-credit.ts
|
|
3210
|
+
function lookupCardPrice(registryDb, cardId, skillId) {
|
|
3211
|
+
const row = registryDb.prepare("SELECT data FROM capability_cards WHERE id = ?").get(cardId);
|
|
3212
|
+
if (!row) return null;
|
|
3213
|
+
let card;
|
|
3214
|
+
try {
|
|
3215
|
+
card = JSON.parse(row.data);
|
|
3216
|
+
} catch {
|
|
3217
|
+
return null;
|
|
3218
|
+
}
|
|
3219
|
+
if (skillId && Array.isArray(card.skills)) {
|
|
3220
|
+
const skills = card.skills;
|
|
3221
|
+
const skill = skills.find((s) => s.id === skillId);
|
|
3222
|
+
if (skill) {
|
|
3223
|
+
const skillPricing = skill.pricing;
|
|
3224
|
+
if (skillPricing && typeof skillPricing.credits_per_call === "number") {
|
|
3225
|
+
return skillPricing.credits_per_call;
|
|
3226
|
+
}
|
|
3227
|
+
}
|
|
3228
|
+
}
|
|
3229
|
+
const pricing = card.pricing;
|
|
3230
|
+
if (!pricing || typeof pricing.credits_per_call !== "number") {
|
|
3231
|
+
return null;
|
|
3232
|
+
}
|
|
3233
|
+
return pricing.credits_per_call;
|
|
3234
|
+
}
|
|
3235
|
+
function holdForRelay(creditDb, owner, amount, cardId) {
|
|
3236
|
+
return holdEscrow(creditDb, owner, amount, cardId);
|
|
3237
|
+
}
|
|
3238
|
+
function settleForRelay(creditDb, escrowId, recipientOwner) {
|
|
3239
|
+
settleEscrow(creditDb, escrowId, recipientOwner);
|
|
3240
|
+
}
|
|
3241
|
+
function calculateConductorFee(totalSubTaskCost) {
|
|
3242
|
+
if (totalSubTaskCost <= 0) return 0;
|
|
3243
|
+
const fee = Math.ceil(totalSubTaskCost * 0.1);
|
|
3244
|
+
return Math.max(1, Math.min(20, fee));
|
|
3245
|
+
}
|
|
3246
|
+
function releaseForRelay(creditDb, escrowId) {
|
|
3247
|
+
if (escrowId === void 0) return;
|
|
3248
|
+
releaseEscrow(creditDb, escrowId);
|
|
3249
|
+
}
|
|
3250
|
+
|
|
3251
|
+
// src/hub-agent/relay-bridge.ts
|
|
3252
|
+
import { randomUUID as randomUUID11 } from "crypto";
|
|
3253
|
+
|
|
3254
|
+
// src/hub-agent/job-queue.ts
|
|
3101
3255
|
import { randomUUID as randomUUID10 } from "crypto";
|
|
3256
|
+
function updateJobStatus(db, jobId, status, result) {
|
|
3257
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3258
|
+
if (result !== void 0) {
|
|
3259
|
+
db.prepare("UPDATE hub_agent_jobs SET status = ?, result = ?, updated_at = ? WHERE id = ?").run(status, result, now, jobId);
|
|
3260
|
+
} else {
|
|
3261
|
+
db.prepare("UPDATE hub_agent_jobs SET status = ?, updated_at = ? WHERE id = ?").run(status, now, jobId);
|
|
3262
|
+
}
|
|
3263
|
+
}
|
|
3264
|
+
|
|
3265
|
+
// src/hub-agent/crypto.ts
|
|
3266
|
+
import { createCipheriv, createDecipheriv, randomBytes } from "crypto";
|
|
3267
|
+
|
|
3268
|
+
// src/hub-agent/relay-bridge.ts
|
|
3269
|
+
function handleJobRelayResponse(opts) {
|
|
3270
|
+
const { registryDb, creditDb, jobId, escrowId, relayOwner, result, error } = opts;
|
|
3271
|
+
if (error) {
|
|
3272
|
+
updateJobStatus(registryDb, jobId, "failed", JSON.stringify(error));
|
|
3273
|
+
if (escrowId) {
|
|
3274
|
+
try {
|
|
3275
|
+
releaseForRelay(creditDb, escrowId);
|
|
3276
|
+
} catch (e) {
|
|
3277
|
+
console.error("[relay-bridge] escrow release on error failed:", e);
|
|
3278
|
+
}
|
|
3279
|
+
}
|
|
3280
|
+
} else {
|
|
3281
|
+
updateJobStatus(registryDb, jobId, "completed", JSON.stringify(result));
|
|
3282
|
+
if (escrowId) {
|
|
3283
|
+
try {
|
|
3284
|
+
settleForRelay(creditDb, escrowId, relayOwner);
|
|
3285
|
+
} catch (e) {
|
|
3286
|
+
console.error("[relay-bridge] escrow settle failed:", e);
|
|
3287
|
+
}
|
|
3288
|
+
}
|
|
3289
|
+
}
|
|
3290
|
+
}
|
|
3291
|
+
|
|
3292
|
+
// src/relay/websocket-relay.ts
|
|
3102
3293
|
var RATE_LIMIT_MAX = 60;
|
|
3103
3294
|
var RATE_LIMIT_WINDOW_MS = 6e4;
|
|
3104
|
-
var RELAY_TIMEOUT_MS =
|
|
3105
|
-
function registerWebSocketRelay(server, db) {
|
|
3295
|
+
var RELAY_TIMEOUT_MS = 3e5;
|
|
3296
|
+
function registerWebSocketRelay(server, db, creditDb) {
|
|
3106
3297
|
const connections = /* @__PURE__ */ new Map();
|
|
3107
3298
|
const pendingRequests = /* @__PURE__ */ new Map();
|
|
3108
3299
|
const rateLimits = /* @__PURE__ */ new Map();
|
|
3300
|
+
let onAgentOnlineCallback;
|
|
3109
3301
|
function checkRateLimit(owner) {
|
|
3110
3302
|
const now = Date.now();
|
|
3111
3303
|
const entry = rateLimits.get(owner);
|
|
@@ -3156,21 +3348,32 @@ function registerWebSocketRelay(server, db) {
|
|
|
3156
3348
|
}
|
|
3157
3349
|
}
|
|
3158
3350
|
function upsertCard(cardData, owner) {
|
|
3159
|
-
const
|
|
3160
|
-
|
|
3351
|
+
const parsed = AnyCardSchema.safeParse(cardData);
|
|
3352
|
+
if (!parsed.success) {
|
|
3353
|
+
throw new AgentBnBError(
|
|
3354
|
+
`Card validation failed: ${parsed.error.message}`,
|
|
3355
|
+
"VALIDATION_ERROR"
|
|
3356
|
+
);
|
|
3357
|
+
}
|
|
3358
|
+
const card = { ...parsed.data, availability: { ...parsed.data.availability, online: true } };
|
|
3359
|
+
const cardId = card.id;
|
|
3360
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3361
|
+
const existing = db.prepare("SELECT id FROM capability_cards WHERE id = ?").get(cardId);
|
|
3161
3362
|
if (existing) {
|
|
3162
|
-
|
|
3163
|
-
|
|
3363
|
+
db.prepare(
|
|
3364
|
+
"UPDATE capability_cards SET data = ?, updated_at = ? WHERE id = ?"
|
|
3365
|
+
).run(JSON.stringify(card), now, cardId);
|
|
3164
3366
|
} else {
|
|
3165
|
-
|
|
3166
|
-
|
|
3367
|
+
db.prepare(
|
|
3368
|
+
"INSERT INTO capability_cards (id, owner, data, created_at, updated_at) VALUES (?, ?, ?, ?, ?)"
|
|
3369
|
+
).run(cardId, owner, JSON.stringify(card), now, now);
|
|
3167
3370
|
}
|
|
3168
3371
|
return cardId;
|
|
3169
3372
|
}
|
|
3170
3373
|
function logAgentJoined(owner, cardName, cardId) {
|
|
3171
3374
|
try {
|
|
3172
3375
|
insertRequestLog(db, {
|
|
3173
|
-
id:
|
|
3376
|
+
id: randomUUID12(),
|
|
3174
3377
|
card_id: cardId,
|
|
3175
3378
|
card_name: cardName,
|
|
3176
3379
|
requester: owner,
|
|
@@ -3201,10 +3404,25 @@ function registerWebSocketRelay(server, db) {
|
|
|
3201
3404
|
const cardId = upsertCard(card, owner);
|
|
3202
3405
|
const cardName = card.name ?? card.agent_name ?? owner;
|
|
3203
3406
|
logAgentJoined(owner, cardName, cardId);
|
|
3407
|
+
if (msg.cards && msg.cards.length > 0) {
|
|
3408
|
+
for (const extraCard of msg.cards) {
|
|
3409
|
+
try {
|
|
3410
|
+
upsertCard(extraCard, owner);
|
|
3411
|
+
} catch {
|
|
3412
|
+
}
|
|
3413
|
+
}
|
|
3414
|
+
}
|
|
3204
3415
|
markOwnerOnline(owner);
|
|
3416
|
+
if (onAgentOnlineCallback) {
|
|
3417
|
+
try {
|
|
3418
|
+
onAgentOnlineCallback(owner);
|
|
3419
|
+
} catch (e) {
|
|
3420
|
+
console.error("[relay] onAgentOnline callback error:", e);
|
|
3421
|
+
}
|
|
3422
|
+
}
|
|
3205
3423
|
sendMessage(ws, { type: "registered", agent_id: cardId });
|
|
3206
3424
|
}
|
|
3207
|
-
function handleRelayRequest(ws, msg, fromOwner) {
|
|
3425
|
+
async function handleRelayRequest(ws, msg, fromOwner) {
|
|
3208
3426
|
if (!checkRateLimit(fromOwner)) {
|
|
3209
3427
|
sendMessage(ws, {
|
|
3210
3428
|
type: "error",
|
|
@@ -3223,15 +3441,42 @@ function registerWebSocketRelay(server, db) {
|
|
|
3223
3441
|
});
|
|
3224
3442
|
return;
|
|
3225
3443
|
}
|
|
3444
|
+
let escrowId;
|
|
3445
|
+
if (creditDb) {
|
|
3446
|
+
try {
|
|
3447
|
+
const price = lookupCardPrice(db, msg.card_id, msg.skill_id);
|
|
3448
|
+
if (price !== null && price > 0) {
|
|
3449
|
+
escrowId = holdForRelay(creditDb, fromOwner, price, msg.card_id);
|
|
3450
|
+
}
|
|
3451
|
+
} catch (err) {
|
|
3452
|
+
if (err instanceof AgentBnBError && err.code === "INSUFFICIENT_CREDITS") {
|
|
3453
|
+
sendMessage(ws, {
|
|
3454
|
+
type: "response",
|
|
3455
|
+
id: msg.id,
|
|
3456
|
+
error: { code: -32603, message: "Insufficient credits" }
|
|
3457
|
+
});
|
|
3458
|
+
return;
|
|
3459
|
+
}
|
|
3460
|
+
console.error("[relay] credit hold error (non-fatal):", err);
|
|
3461
|
+
}
|
|
3462
|
+
}
|
|
3226
3463
|
const timeout = setTimeout(() => {
|
|
3464
|
+
const pending = pendingRequests.get(msg.id);
|
|
3227
3465
|
pendingRequests.delete(msg.id);
|
|
3466
|
+
if (pending?.escrowId && creditDb) {
|
|
3467
|
+
try {
|
|
3468
|
+
releaseForRelay(creditDb, pending.escrowId);
|
|
3469
|
+
} catch (e) {
|
|
3470
|
+
console.error("[relay] escrow release on timeout failed:", e);
|
|
3471
|
+
}
|
|
3472
|
+
}
|
|
3228
3473
|
sendMessage(ws, {
|
|
3229
3474
|
type: "response",
|
|
3230
3475
|
id: msg.id,
|
|
3231
3476
|
error: { code: -32603, message: "Relay request timeout" }
|
|
3232
3477
|
});
|
|
3233
3478
|
}, RELAY_TIMEOUT_MS);
|
|
3234
|
-
pendingRequests.set(msg.id, { originOwner: fromOwner, timeout });
|
|
3479
|
+
pendingRequests.set(msg.id, { originOwner: fromOwner, timeout, escrowId, targetOwner: msg.target_owner });
|
|
3235
3480
|
sendMessage(targetWs, {
|
|
3236
3481
|
type: "incoming_request",
|
|
3237
3482
|
id: msg.id,
|
|
@@ -3243,18 +3488,98 @@ function registerWebSocketRelay(server, db) {
|
|
|
3243
3488
|
escrow_receipt: msg.escrow_receipt
|
|
3244
3489
|
});
|
|
3245
3490
|
}
|
|
3491
|
+
function handleRelayProgress(msg) {
|
|
3492
|
+
const pending = pendingRequests.get(msg.id);
|
|
3493
|
+
if (!pending) return;
|
|
3494
|
+
clearTimeout(pending.timeout);
|
|
3495
|
+
const newTimeout = setTimeout(() => {
|
|
3496
|
+
const p = pendingRequests.get(msg.id);
|
|
3497
|
+
pendingRequests.delete(msg.id);
|
|
3498
|
+
if (p?.escrowId && creditDb) {
|
|
3499
|
+
try {
|
|
3500
|
+
releaseForRelay(creditDb, p.escrowId);
|
|
3501
|
+
} catch (e) {
|
|
3502
|
+
console.error("[relay] escrow release on progress timeout failed:", e);
|
|
3503
|
+
}
|
|
3504
|
+
}
|
|
3505
|
+
const originWs2 = connections.get(pending.originOwner);
|
|
3506
|
+
if (originWs2 && originWs2.readyState === 1) {
|
|
3507
|
+
sendMessage(originWs2, {
|
|
3508
|
+
type: "response",
|
|
3509
|
+
id: msg.id,
|
|
3510
|
+
error: { code: -32603, message: "Relay request timeout" }
|
|
3511
|
+
});
|
|
3512
|
+
}
|
|
3513
|
+
}, RELAY_TIMEOUT_MS);
|
|
3514
|
+
pending.timeout = newTimeout;
|
|
3515
|
+
const originWs = connections.get(pending.originOwner);
|
|
3516
|
+
if (originWs && originWs.readyState === 1) {
|
|
3517
|
+
sendMessage(originWs, {
|
|
3518
|
+
type: "relay_progress",
|
|
3519
|
+
id: msg.id,
|
|
3520
|
+
progress: msg.progress,
|
|
3521
|
+
message: msg.message
|
|
3522
|
+
});
|
|
3523
|
+
}
|
|
3524
|
+
}
|
|
3246
3525
|
function handleRelayResponse(msg) {
|
|
3247
3526
|
const pending = pendingRequests.get(msg.id);
|
|
3248
3527
|
if (!pending) return;
|
|
3249
3528
|
clearTimeout(pending.timeout);
|
|
3250
3529
|
pendingRequests.delete(msg.id);
|
|
3530
|
+
if (pending.jobId && creditDb) {
|
|
3531
|
+
try {
|
|
3532
|
+
handleJobRelayResponse({
|
|
3533
|
+
registryDb: db,
|
|
3534
|
+
creditDb,
|
|
3535
|
+
jobId: pending.jobId,
|
|
3536
|
+
escrowId: pending.escrowId,
|
|
3537
|
+
relayOwner: pending.targetOwner ?? "",
|
|
3538
|
+
result: msg.error === void 0 ? msg.result : void 0,
|
|
3539
|
+
error: msg.error
|
|
3540
|
+
});
|
|
3541
|
+
} catch (e) {
|
|
3542
|
+
console.error("[relay] job relay response handling failed:", e);
|
|
3543
|
+
}
|
|
3544
|
+
const originWs2 = connections.get(pending.originOwner);
|
|
3545
|
+
if (originWs2 && originWs2.readyState === 1) {
|
|
3546
|
+
sendMessage(originWs2, { type: "response", id: msg.id, result: msg.result, error: msg.error });
|
|
3547
|
+
}
|
|
3548
|
+
return;
|
|
3549
|
+
}
|
|
3550
|
+
if (pending.escrowId && creditDb) {
|
|
3551
|
+
try {
|
|
3552
|
+
if (msg.error === void 0) {
|
|
3553
|
+
settleForRelay(creditDb, pending.escrowId, pending.targetOwner);
|
|
3554
|
+
} else {
|
|
3555
|
+
releaseForRelay(creditDb, pending.escrowId);
|
|
3556
|
+
}
|
|
3557
|
+
} catch (e) {
|
|
3558
|
+
console.error("[relay] escrow settle/release on response failed:", e);
|
|
3559
|
+
}
|
|
3560
|
+
}
|
|
3561
|
+
let conductorFee = 0;
|
|
3562
|
+
if (creditDb && msg.error === void 0 && typeof msg.result === "object" && msg.result !== null && "total_credits" in msg.result && typeof msg.result.total_credits === "number") {
|
|
3563
|
+
const totalCredits = msg.result.total_credits;
|
|
3564
|
+
conductorFee = calculateConductorFee(totalCredits);
|
|
3565
|
+
if (conductorFee > 0) {
|
|
3566
|
+
try {
|
|
3567
|
+
const feeEscrowId = holdForRelay(creditDb, pending.originOwner, conductorFee, msg.id);
|
|
3568
|
+
settleForRelay(creditDb, feeEscrowId, pending.targetOwner);
|
|
3569
|
+
} catch (e) {
|
|
3570
|
+
console.error("[relay] conductor fee settlement failed (non-fatal):", e);
|
|
3571
|
+
conductorFee = 0;
|
|
3572
|
+
}
|
|
3573
|
+
}
|
|
3574
|
+
}
|
|
3251
3575
|
const originWs = connections.get(pending.originOwner);
|
|
3252
3576
|
if (originWs && originWs.readyState === 1) {
|
|
3253
3577
|
sendMessage(originWs, {
|
|
3254
3578
|
type: "response",
|
|
3255
3579
|
id: msg.id,
|
|
3256
3580
|
result: msg.result,
|
|
3257
|
-
error: msg.error
|
|
3581
|
+
error: msg.error,
|
|
3582
|
+
...conductorFee > 0 ? { conductor_fee: conductorFee } : {}
|
|
3258
3583
|
});
|
|
3259
3584
|
}
|
|
3260
3585
|
}
|
|
@@ -3264,9 +3589,34 @@ function registerWebSocketRelay(server, db) {
|
|
|
3264
3589
|
rateLimits.delete(owner);
|
|
3265
3590
|
markOwnerOffline(owner);
|
|
3266
3591
|
for (const [reqId, pending] of pendingRequests) {
|
|
3267
|
-
if (pending.
|
|
3592
|
+
if (pending.targetOwner === owner) {
|
|
3593
|
+
clearTimeout(pending.timeout);
|
|
3594
|
+
pendingRequests.delete(reqId);
|
|
3595
|
+
if (pending.escrowId && creditDb) {
|
|
3596
|
+
try {
|
|
3597
|
+
releaseForRelay(creditDb, pending.escrowId);
|
|
3598
|
+
} catch (e) {
|
|
3599
|
+
console.error("[relay] escrow release on disconnect failed:", e);
|
|
3600
|
+
}
|
|
3601
|
+
}
|
|
3602
|
+
const originWs = connections.get(pending.originOwner);
|
|
3603
|
+
if (originWs && originWs.readyState === 1) {
|
|
3604
|
+
sendMessage(originWs, {
|
|
3605
|
+
type: "response",
|
|
3606
|
+
id: reqId,
|
|
3607
|
+
error: { code: -32603, message: "Provider disconnected" }
|
|
3608
|
+
});
|
|
3609
|
+
}
|
|
3610
|
+
} else if (pending.originOwner === owner) {
|
|
3268
3611
|
clearTimeout(pending.timeout);
|
|
3269
3612
|
pendingRequests.delete(reqId);
|
|
3613
|
+
if (pending.escrowId && creditDb) {
|
|
3614
|
+
try {
|
|
3615
|
+
releaseForRelay(creditDb, pending.escrowId);
|
|
3616
|
+
} catch (e) {
|
|
3617
|
+
console.error("[relay] escrow release on requester disconnect failed:", e);
|
|
3618
|
+
}
|
|
3619
|
+
}
|
|
3270
3620
|
}
|
|
3271
3621
|
}
|
|
3272
3622
|
}
|
|
@@ -3274,45 +3624,50 @@ function registerWebSocketRelay(server, db) {
|
|
|
3274
3624
|
const socket = rawSocket;
|
|
3275
3625
|
let registeredOwner;
|
|
3276
3626
|
socket.on("message", (raw) => {
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3627
|
+
void (async () => {
|
|
3628
|
+
let data;
|
|
3629
|
+
try {
|
|
3630
|
+
data = JSON.parse(typeof raw === "string" ? raw : raw.toString("utf-8"));
|
|
3631
|
+
} catch {
|
|
3632
|
+
sendMessage(socket, { type: "error", code: "invalid_json", message: "Invalid JSON" });
|
|
3633
|
+
return;
|
|
3634
|
+
}
|
|
3635
|
+
const parsed = RelayMessageSchema.safeParse(data);
|
|
3636
|
+
if (!parsed.success) {
|
|
3637
|
+
sendMessage(socket, {
|
|
3638
|
+
type: "error",
|
|
3639
|
+
code: "invalid_message",
|
|
3640
|
+
message: `Invalid message: ${parsed.error.issues[0]?.message ?? "unknown error"}`
|
|
3641
|
+
});
|
|
3642
|
+
return;
|
|
3643
|
+
}
|
|
3644
|
+
const msg = parsed.data;
|
|
3645
|
+
switch (msg.type) {
|
|
3646
|
+
case "register":
|
|
3647
|
+
registeredOwner = msg.owner;
|
|
3648
|
+
handleRegister(socket, msg);
|
|
3649
|
+
break;
|
|
3650
|
+
case "relay_request":
|
|
3651
|
+
if (!registeredOwner) {
|
|
3652
|
+
sendMessage(socket, {
|
|
3653
|
+
type: "error",
|
|
3654
|
+
code: "not_registered",
|
|
3655
|
+
message: "Must send register message before relay requests"
|
|
3656
|
+
});
|
|
3657
|
+
return;
|
|
3658
|
+
}
|
|
3659
|
+
await handleRelayRequest(socket, msg, registeredOwner);
|
|
3660
|
+
break;
|
|
3661
|
+
case "relay_response":
|
|
3662
|
+
handleRelayResponse(msg);
|
|
3663
|
+
break;
|
|
3664
|
+
case "relay_progress":
|
|
3665
|
+
handleRelayProgress(msg);
|
|
3666
|
+
break;
|
|
3667
|
+
default:
|
|
3668
|
+
break;
|
|
3669
|
+
}
|
|
3670
|
+
})();
|
|
3316
3671
|
});
|
|
3317
3672
|
socket.on("close", () => {
|
|
3318
3673
|
handleDisconnect(registeredOwner);
|
|
@@ -3337,13 +3692,21 @@ function registerWebSocketRelay(server, db) {
|
|
|
3337
3692
|
}
|
|
3338
3693
|
pendingRequests.clear();
|
|
3339
3694
|
rateLimits.clear();
|
|
3695
|
+
},
|
|
3696
|
+
setOnAgentOnline: (cb) => {
|
|
3697
|
+
onAgentOnlineCallback = cb;
|
|
3698
|
+
},
|
|
3699
|
+
getConnections: () => connections,
|
|
3700
|
+
getPendingRequests: () => pendingRequests,
|
|
3701
|
+
sendMessage: (ws, msg) => {
|
|
3702
|
+
sendMessage(ws, msg);
|
|
3340
3703
|
}
|
|
3341
3704
|
};
|
|
3342
3705
|
}
|
|
3343
3706
|
|
|
3344
3707
|
// src/relay/websocket-client.ts
|
|
3345
3708
|
import WebSocket from "ws";
|
|
3346
|
-
import { randomUUID as
|
|
3709
|
+
import { randomUUID as randomUUID13 } from "crypto";
|
|
3347
3710
|
var RelayClient = class {
|
|
3348
3711
|
ws = null;
|
|
3349
3712
|
opts;
|
|
@@ -3375,7 +3738,8 @@ var RelayClient = class {
|
|
|
3375
3738
|
type: "register",
|
|
3376
3739
|
owner: this.opts.owner,
|
|
3377
3740
|
token: this.opts.token,
|
|
3378
|
-
card: this.opts.card
|
|
3741
|
+
card: this.opts.card,
|
|
3742
|
+
...this.opts.cards && this.opts.cards.length > 0 ? { cards: this.opts.cards } : {}
|
|
3379
3743
|
});
|
|
3380
3744
|
});
|
|
3381
3745
|
this.ws.on("message", (raw) => {
|
|
@@ -3439,14 +3803,14 @@ var RelayClient = class {
|
|
|
3439
3803
|
if (!this.ws || this.ws.readyState !== WebSocket.OPEN || !this.registered) {
|
|
3440
3804
|
throw new Error("Not connected to registry relay");
|
|
3441
3805
|
}
|
|
3442
|
-
const id =
|
|
3443
|
-
const timeoutMs = opts.timeoutMs ??
|
|
3806
|
+
const id = randomUUID13();
|
|
3807
|
+
const timeoutMs = opts.timeoutMs ?? 3e5;
|
|
3444
3808
|
return new Promise((resolve, reject) => {
|
|
3445
3809
|
const timeout = setTimeout(() => {
|
|
3446
3810
|
this.pendingRequests.delete(id);
|
|
3447
3811
|
reject(new Error("Relay request timeout"));
|
|
3448
3812
|
}, timeoutMs);
|
|
3449
|
-
this.pendingRequests.set(id, { resolve, reject, timeout });
|
|
3813
|
+
this.pendingRequests.set(id, { resolve, reject, timeout, timeoutMs, onProgress: opts.onProgress });
|
|
3450
3814
|
this.send({
|
|
3451
3815
|
type: "relay_request",
|
|
3452
3816
|
id,
|
|
@@ -3459,6 +3823,22 @@ var RelayClient = class {
|
|
|
3459
3823
|
});
|
|
3460
3824
|
});
|
|
3461
3825
|
}
|
|
3826
|
+
/**
|
|
3827
|
+
* Send a relay_progress message to the relay server for a given request.
|
|
3828
|
+
* Used by the onRequest handler to forward SkillExecutor progress updates
|
|
3829
|
+
* to the requesting agent so it can reset its timeout window.
|
|
3830
|
+
*
|
|
3831
|
+
* @param requestId - The relay request ID to associate progress with.
|
|
3832
|
+
* @param info - Progress details (step, total, message).
|
|
3833
|
+
*/
|
|
3834
|
+
sendProgress(requestId, info) {
|
|
3835
|
+
this.send({
|
|
3836
|
+
type: "relay_progress",
|
|
3837
|
+
id: requestId,
|
|
3838
|
+
progress: Math.round(info.step / info.total * 100),
|
|
3839
|
+
message: info.message
|
|
3840
|
+
});
|
|
3841
|
+
}
|
|
3462
3842
|
/** Whether the client is connected and registered */
|
|
3463
3843
|
get isConnected() {
|
|
3464
3844
|
return this.ws !== null && this.ws.readyState === WebSocket.OPEN && this.registered;
|
|
@@ -3505,6 +3885,9 @@ var RelayClient = class {
|
|
|
3505
3885
|
case "error":
|
|
3506
3886
|
this.handleError(msg);
|
|
3507
3887
|
break;
|
|
3888
|
+
case "relay_progress":
|
|
3889
|
+
this.handleProgress(msg);
|
|
3890
|
+
break;
|
|
3508
3891
|
default:
|
|
3509
3892
|
break;
|
|
3510
3893
|
}
|
|
@@ -3550,6 +3933,19 @@ var RelayClient = class {
|
|
|
3550
3933
|
}
|
|
3551
3934
|
}
|
|
3552
3935
|
}
|
|
3936
|
+
handleProgress(msg) {
|
|
3937
|
+
const pending = this.pendingRequests.get(msg.id);
|
|
3938
|
+
if (!pending) return;
|
|
3939
|
+
clearTimeout(pending.timeout);
|
|
3940
|
+
const newTimeout = setTimeout(() => {
|
|
3941
|
+
this.pendingRequests.delete(msg.id);
|
|
3942
|
+
pending.reject(new Error("Relay request timeout"));
|
|
3943
|
+
}, pending.timeoutMs);
|
|
3944
|
+
pending.timeout = newTimeout;
|
|
3945
|
+
if (pending.onProgress) {
|
|
3946
|
+
pending.onProgress({ id: msg.id, progress: msg.progress, message: msg.message });
|
|
3947
|
+
}
|
|
3948
|
+
}
|
|
3553
3949
|
send(msg) {
|
|
3554
3950
|
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
3555
3951
|
this.ws.send(JSON.stringify(msg));
|
|
@@ -3611,12 +4007,12 @@ var RelayClient = class {
|
|
|
3611
4007
|
};
|
|
3612
4008
|
|
|
3613
4009
|
// src/onboarding/index.ts
|
|
3614
|
-
import { randomUUID as
|
|
4010
|
+
import { randomUUID as randomUUID15 } from "crypto";
|
|
3615
4011
|
import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
|
|
3616
4012
|
import { join as join5 } from "path";
|
|
3617
4013
|
|
|
3618
4014
|
// src/cli/onboarding.ts
|
|
3619
|
-
import { randomUUID as
|
|
4015
|
+
import { randomUUID as randomUUID14 } from "crypto";
|
|
3620
4016
|
import { createConnection } from "net";
|
|
3621
4017
|
var KNOWN_API_KEYS = [
|
|
3622
4018
|
"OPENAI_API_KEY",
|
|
@@ -3758,7 +4154,7 @@ function capabilitiesToV2Card(capabilities, owner, agentName) {
|
|
|
3758
4154
|
}));
|
|
3759
4155
|
const card = {
|
|
3760
4156
|
spec_version: "2.0",
|
|
3761
|
-
id:
|
|
4157
|
+
id: randomUUID15(),
|
|
3762
4158
|
owner,
|
|
3763
4159
|
agent_name: agentName ?? owner,
|
|
3764
4160
|
skills,
|