agentbnb 4.0.0 → 4.0.2
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 +2 -0
- package/dist/{card-IE5UV5QX.js → card-RSGDCHCV.js} +11 -4
- package/dist/chunk-3MJT4PZG.js +50 -0
- package/dist/{chunk-HEVXCYCY.js → chunk-4P3EMGL4.js} +61 -24
- package/dist/chunk-5AH3CMOX.js +62 -0
- package/dist/{chunk-QO67IGCW.js → chunk-5KFI5X7B.js} +1 -1
- package/dist/chunk-75OC6E4F.js +33 -0
- package/dist/{chunk-CUVIWPQO.js → chunk-7NA43XCG.js} +7 -6
- package/dist/{conduct-IQYAT6ZU.js → chunk-BH6WGYFB.js} +70 -33
- package/dist/{chunk-QVV2P3FN.js → chunk-DNWT5FZQ.js} +22 -2
- package/dist/chunk-FF226TIV.js +148 -0
- package/dist/{chunk-UJWYE7VL.js → chunk-GGYC5U2Z.js} +28 -111
- package/dist/chunk-HH24WMFN.js +373 -0
- package/dist/{websocket-client-5TIQDYQ4.js → chunk-JOY533UH.js} +38 -4
- package/dist/chunk-QITOPASZ.js +96 -0
- package/dist/{chunk-3Y36WQDV.js → chunk-QT7TEVNV.js} +14 -2
- package/dist/{chunk-UOGDK2S2.js → chunk-T7NS2J2B.js} +1 -1
- package/dist/{chunk-XA63SD4T.js → chunk-WGZ5AGOX.js} +37 -0
- package/dist/{chunk-RSX4SCPN.js → chunk-XND2DWTZ.js} +4 -3
- package/dist/cli/index.js +2924 -835
- package/dist/{client-IOTK6GOS.js → client-T5MTY3CS.js} +3 -3
- package/dist/conduct-GZQNFTRP.js +19 -0
- package/dist/conduct-N52JX7RT.js +52 -0
- package/dist/{conductor-mode-XU7ONJWC.js → conductor-mode-XUWGR4ZE.js} +16 -9
- package/dist/execute-PNGQOMYO.js +10 -0
- package/dist/index.d.ts +1148 -915
- package/dist/index.js +589 -127
- package/dist/{peers-G36URZYB.js → peers-K7FSHPN3.js} +2 -1
- package/dist/request-4GQSSM4B.js +196 -0
- package/dist/serve-skill-TPHZH6BS.js +104 -0
- package/dist/server-365V3GYD.js +295 -0
- package/dist/websocket-client-6IIDGXKB.js +7 -0
- package/package.json +3 -6
- package/skills/agentbnb/HEARTBEAT.rules.md +47 -0
- package/skills/agentbnb/SKILL.md +166 -0
- package/skills/agentbnb/auto-request.ts +14 -0
- package/skills/agentbnb/auto-share.ts +10 -0
- package/skills/agentbnb/bootstrap.test.ts +323 -0
- package/skills/agentbnb/bootstrap.ts +126 -0
- package/skills/agentbnb/credit-mgr.ts +11 -0
- package/skills/agentbnb/gateway.ts +12 -0
- package/skills/agentbnb/install.sh +210 -0
- package/dist/chunk-BEI5MTNZ.js +0 -91
- package/dist/execute-GDGBU6DJ.js +0 -10
package/dist/index.js
CHANGED
|
@@ -83,12 +83,45 @@ var SkillSchema = z.object({
|
|
|
83
83
|
*/
|
|
84
84
|
_internal: z.record(z.unknown()).optional()
|
|
85
85
|
});
|
|
86
|
+
var SuitabilitySchema = z.object({
|
|
87
|
+
/** Use cases this agent/skill is optimised for. */
|
|
88
|
+
ideal_for: z.array(z.string()).optional(),
|
|
89
|
+
/** Scenarios this agent/skill cannot reliably handle. */
|
|
90
|
+
not_suitable_for: z.array(z.string()).optional(),
|
|
91
|
+
/** Domains explicitly excluded (used for routing exclusions in later phases). */
|
|
92
|
+
excluded_domains: z.array(z.string()).optional(),
|
|
93
|
+
/** Conditions that increase failure risk, shown as warnings in the Hub. */
|
|
94
|
+
risk_conditions: z.array(z.string()).optional(),
|
|
95
|
+
/** Recommended alternative when this agent is unsuitable. */
|
|
96
|
+
fallback_recommendation: z.string().optional()
|
|
97
|
+
});
|
|
98
|
+
var LearningSchema = z.object({
|
|
99
|
+
/** Known limitations that may affect reliability (self-declared). */
|
|
100
|
+
known_limitations: z.array(z.string()).optional(),
|
|
101
|
+
/** Common failure patterns observed by the provider. */
|
|
102
|
+
common_failure_patterns: z.array(z.string()).optional(),
|
|
103
|
+
/** Version-tagged improvements the provider has shipped. */
|
|
104
|
+
recent_improvements: z.array(z.object({
|
|
105
|
+
version: z.string(),
|
|
106
|
+
summary: z.string(),
|
|
107
|
+
timestamp: z.string()
|
|
108
|
+
})).optional(),
|
|
109
|
+
/** Structured critiques from external sources (phase 2+). */
|
|
110
|
+
critiques: z.array(z.object({
|
|
111
|
+
type: z.literal("structured"),
|
|
112
|
+
summary: z.string(),
|
|
113
|
+
source_tier: z.string(),
|
|
114
|
+
timestamp: z.string()
|
|
115
|
+
})).optional()
|
|
116
|
+
});
|
|
86
117
|
var CapabilityCardV2Schema = z.object({
|
|
87
118
|
spec_version: z.literal("2.0"),
|
|
88
119
|
id: z.string().uuid(),
|
|
89
120
|
owner: z.string().min(1),
|
|
90
121
|
/** Agent display name — was 'name' in v1.0. */
|
|
91
122
|
agent_name: z.string().min(1).max(100),
|
|
123
|
+
/** Short one-liner shown in Hub v2 Identity Header. */
|
|
124
|
+
short_description: z.string().max(200).optional(),
|
|
92
125
|
/** At least one skill is required. */
|
|
93
126
|
skills: z.array(SkillSchema).min(1),
|
|
94
127
|
availability: z.object({
|
|
@@ -100,6 +133,10 @@ var CapabilityCardV2Schema = z.object({
|
|
|
100
133
|
runtime: z.string(),
|
|
101
134
|
region: z.string().optional()
|
|
102
135
|
}).optional(),
|
|
136
|
+
/** Suitability metadata for Hub v2 profile and future routing warnings. */
|
|
137
|
+
suitability: SuitabilitySchema.optional(),
|
|
138
|
+
/** Learning signals — self-declared limitations, improvements, critiques. */
|
|
139
|
+
learning: LearningSchema.optional(),
|
|
103
140
|
/**
|
|
104
141
|
* Private per-card metadata. Stripped from all API and CLI responses —
|
|
105
142
|
* never transmitted beyond the local store.
|
|
@@ -436,30 +473,6 @@ function getCard(db, id) {
|
|
|
436
473
|
if (!row) return null;
|
|
437
474
|
return JSON.parse(row.data);
|
|
438
475
|
}
|
|
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
476
|
function updateReputation(db, cardId, success, latencyMs) {
|
|
464
477
|
const existing = getCard(db, cardId);
|
|
465
478
|
if (!existing) return;
|
|
@@ -783,7 +796,8 @@ async function executeCapabilityRequest(opts) {
|
|
|
783
796
|
escrowReceipt: receipt,
|
|
784
797
|
skillExecutor,
|
|
785
798
|
handlerUrl,
|
|
786
|
-
timeoutMs =
|
|
799
|
+
timeoutMs = 3e5,
|
|
800
|
+
onProgress
|
|
787
801
|
} = opts;
|
|
788
802
|
const card = getCard(registryDb, cardId);
|
|
789
803
|
if (!card) {
|
|
@@ -890,7 +904,7 @@ async function executeCapabilityRequest(opts) {
|
|
|
890
904
|
if (skillExecutor) {
|
|
891
905
|
const targetSkillId = resolvedSkillId ?? skillId ?? cardId;
|
|
892
906
|
try {
|
|
893
|
-
const execResult = await skillExecutor.execute(targetSkillId, params);
|
|
907
|
+
const execResult = await skillExecutor.execute(targetSkillId, params, onProgress);
|
|
894
908
|
if (!execResult.success) {
|
|
895
909
|
return handleFailure("failure", execResult.latency_ms, execResult.error ?? "Execution failed");
|
|
896
910
|
}
|
|
@@ -937,7 +951,7 @@ function createGatewayServer(opts) {
|
|
|
937
951
|
creditDb,
|
|
938
952
|
tokens,
|
|
939
953
|
handlerUrl,
|
|
940
|
-
timeoutMs =
|
|
954
|
+
timeoutMs = 3e5,
|
|
941
955
|
silent = false,
|
|
942
956
|
skillExecutor
|
|
943
957
|
} = opts;
|
|
@@ -1054,7 +1068,7 @@ var SkillExecutor = class {
|
|
|
1054
1068
|
* @param params - Input parameters for the skill.
|
|
1055
1069
|
* @returns ExecutionResult including success, result/error, and latency_ms.
|
|
1056
1070
|
*/
|
|
1057
|
-
async execute(skillId, params) {
|
|
1071
|
+
async execute(skillId, params, onProgress) {
|
|
1058
1072
|
const startTime = Date.now();
|
|
1059
1073
|
const config = this.skillMap.get(skillId);
|
|
1060
1074
|
if (!config) {
|
|
@@ -1073,7 +1087,7 @@ var SkillExecutor = class {
|
|
|
1073
1087
|
};
|
|
1074
1088
|
}
|
|
1075
1089
|
try {
|
|
1076
|
-
const modeResult = await mode.execute(config, params);
|
|
1090
|
+
const modeResult = await mode.execute(config, params, onProgress);
|
|
1077
1091
|
return {
|
|
1078
1092
|
...modeResult,
|
|
1079
1093
|
latency_ms: Date.now() - startTime
|
|
@@ -1493,7 +1507,7 @@ var PipelineExecutor = class {
|
|
|
1493
1507
|
* @param params - Input parameters from the caller.
|
|
1494
1508
|
* @returns Partial ExecutionResult (without latency_ms — added by SkillExecutor wrapper).
|
|
1495
1509
|
*/
|
|
1496
|
-
async execute(config, params) {
|
|
1510
|
+
async execute(config, params, onProgress) {
|
|
1497
1511
|
const pipelineConfig = config;
|
|
1498
1512
|
const steps = pipelineConfig.steps ?? [];
|
|
1499
1513
|
if (steps.length === 0) {
|
|
@@ -1552,6 +1566,13 @@ var PipelineExecutor = class {
|
|
|
1552
1566
|
}
|
|
1553
1567
|
context.steps.push({ result: stepResult });
|
|
1554
1568
|
context.prev = { result: stepResult };
|
|
1569
|
+
if (onProgress && i < steps.length - 1) {
|
|
1570
|
+
onProgress({
|
|
1571
|
+
step: i + 1,
|
|
1572
|
+
total: steps.length,
|
|
1573
|
+
message: `Completed step ${i + 1}/${steps.length}`
|
|
1574
|
+
});
|
|
1575
|
+
}
|
|
1555
1576
|
}
|
|
1556
1577
|
const lastStep = context.steps[context.steps.length - 1];
|
|
1557
1578
|
return {
|
|
@@ -1930,7 +1951,7 @@ function decompose(task, _availableCapabilities) {
|
|
|
1930
1951
|
// src/gateway/client.ts
|
|
1931
1952
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
1932
1953
|
async function requestCapability(opts) {
|
|
1933
|
-
const { gatewayUrl, token, cardId, params = {}, timeoutMs =
|
|
1954
|
+
const { gatewayUrl, token, cardId, params = {}, timeoutMs = 3e5, escrowReceipt, identity } = opts;
|
|
1934
1955
|
const id = randomUUID5();
|
|
1935
1956
|
const payload = {
|
|
1936
1957
|
jsonrpc: "2.0",
|
|
@@ -1984,6 +2005,7 @@ async function requestViaRelay(relay, opts) {
|
|
|
1984
2005
|
cardId: opts.cardId,
|
|
1985
2006
|
skillId: opts.skillId,
|
|
1986
2007
|
params: opts.params ?? {},
|
|
2008
|
+
requester: opts.requester,
|
|
1987
2009
|
escrowReceipt: opts.escrowReceipt,
|
|
1988
2010
|
timeoutMs: opts.timeoutMs
|
|
1989
2011
|
});
|
|
@@ -2030,6 +2052,73 @@ function loadConfig() {
|
|
|
2030
2052
|
}
|
|
2031
2053
|
}
|
|
2032
2054
|
|
|
2055
|
+
// src/cli/remote-registry.ts
|
|
2056
|
+
var RegistryTimeoutError = class extends AgentBnBError {
|
|
2057
|
+
constructor(url) {
|
|
2058
|
+
super(
|
|
2059
|
+
`Registry at ${url} did not respond within 5s. Showing local results only.`,
|
|
2060
|
+
"REGISTRY_TIMEOUT"
|
|
2061
|
+
);
|
|
2062
|
+
this.name = "RegistryTimeoutError";
|
|
2063
|
+
}
|
|
2064
|
+
};
|
|
2065
|
+
var RegistryConnectionError = class extends AgentBnBError {
|
|
2066
|
+
constructor(url) {
|
|
2067
|
+
super(
|
|
2068
|
+
`Cannot reach ${url}. Is the registry running? Showing local results only.`,
|
|
2069
|
+
"REGISTRY_CONNECTION"
|
|
2070
|
+
);
|
|
2071
|
+
this.name = "RegistryConnectionError";
|
|
2072
|
+
}
|
|
2073
|
+
};
|
|
2074
|
+
var RegistryAuthError = class extends AgentBnBError {
|
|
2075
|
+
constructor(url) {
|
|
2076
|
+
super(
|
|
2077
|
+
`Authentication failed for ${url}. Run \`agentbnb config set token <your-token>\`.`,
|
|
2078
|
+
"REGISTRY_AUTH"
|
|
2079
|
+
);
|
|
2080
|
+
this.name = "RegistryAuthError";
|
|
2081
|
+
}
|
|
2082
|
+
};
|
|
2083
|
+
async function fetchRemoteCards(registryUrl, params, timeoutMs = 5e3) {
|
|
2084
|
+
let cardsUrl;
|
|
2085
|
+
try {
|
|
2086
|
+
cardsUrl = new URL("/cards", registryUrl);
|
|
2087
|
+
} catch {
|
|
2088
|
+
throw new AgentBnBError(`Invalid registry URL: ${registryUrl}`, "INVALID_REGISTRY_URL");
|
|
2089
|
+
}
|
|
2090
|
+
const searchParams = new URLSearchParams();
|
|
2091
|
+
if (params.q !== void 0) searchParams.set("q", params.q);
|
|
2092
|
+
if (params.level !== void 0) searchParams.set("level", String(params.level));
|
|
2093
|
+
if (params.online !== void 0) searchParams.set("online", String(params.online));
|
|
2094
|
+
if (params.tag !== void 0) searchParams.set("tag", params.tag);
|
|
2095
|
+
searchParams.set("limit", "100");
|
|
2096
|
+
cardsUrl.search = searchParams.toString();
|
|
2097
|
+
const controller = new AbortController();
|
|
2098
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
2099
|
+
let response;
|
|
2100
|
+
try {
|
|
2101
|
+
response = await fetch(cardsUrl.toString(), { signal: controller.signal });
|
|
2102
|
+
} catch (err) {
|
|
2103
|
+
clearTimeout(timer);
|
|
2104
|
+
const isTimeout = err instanceof Error && err.name === "AbortError";
|
|
2105
|
+
if (isTimeout) {
|
|
2106
|
+
throw new RegistryTimeoutError(registryUrl);
|
|
2107
|
+
}
|
|
2108
|
+
throw new RegistryConnectionError(registryUrl);
|
|
2109
|
+
} finally {
|
|
2110
|
+
clearTimeout(timer);
|
|
2111
|
+
}
|
|
2112
|
+
if (response.status === 401 || response.status === 403) {
|
|
2113
|
+
throw new RegistryAuthError(registryUrl);
|
|
2114
|
+
}
|
|
2115
|
+
if (!response.ok) {
|
|
2116
|
+
throw new RegistryConnectionError(registryUrl);
|
|
2117
|
+
}
|
|
2118
|
+
const body = await response.json();
|
|
2119
|
+
return body.items;
|
|
2120
|
+
}
|
|
2121
|
+
|
|
2033
2122
|
// src/autonomy/auto-request.ts
|
|
2034
2123
|
function minMaxNormalize(values) {
|
|
2035
2124
|
if (values.length === 0) return [];
|
|
@@ -2064,10 +2153,17 @@ function scorePeers(candidates, selfOwner) {
|
|
|
2064
2153
|
|
|
2065
2154
|
// src/conductor/capability-matcher.ts
|
|
2066
2155
|
var MAX_ALTERNATIVES = 2;
|
|
2067
|
-
function matchSubTasks(opts) {
|
|
2068
|
-
const { db, subtasks, conductorOwner } = opts;
|
|
2069
|
-
return subtasks.map((subtask) => {
|
|
2070
|
-
|
|
2156
|
+
async function matchSubTasks(opts) {
|
|
2157
|
+
const { db, subtasks, conductorOwner, registryUrl } = opts;
|
|
2158
|
+
return Promise.all(subtasks.map(async (subtask) => {
|
|
2159
|
+
let cards = searchCards(db, subtask.required_capability, { online: true });
|
|
2160
|
+
if (cards.length === 0 && registryUrl) {
|
|
2161
|
+
try {
|
|
2162
|
+
cards = await fetchRemoteCards(registryUrl, { q: subtask.required_capability, online: true });
|
|
2163
|
+
} catch {
|
|
2164
|
+
cards = [];
|
|
2165
|
+
}
|
|
2166
|
+
}
|
|
2071
2167
|
const candidates = [];
|
|
2072
2168
|
for (const card of cards) {
|
|
2073
2169
|
const cardAsV2 = card;
|
|
@@ -2109,11 +2205,12 @@ function matchSubTasks(opts) {
|
|
|
2109
2205
|
subtask_id: subtask.id,
|
|
2110
2206
|
selected_agent: top.card.owner,
|
|
2111
2207
|
selected_skill: top.skillId ?? "",
|
|
2208
|
+
selected_card_id: top.card.id,
|
|
2112
2209
|
score: top.rawScore,
|
|
2113
2210
|
credits: top.cost,
|
|
2114
2211
|
alternatives
|
|
2115
2212
|
};
|
|
2116
|
-
});
|
|
2213
|
+
}));
|
|
2117
2214
|
}
|
|
2118
2215
|
|
|
2119
2216
|
// src/conductor/budget-controller.ts
|
|
@@ -2184,13 +2281,20 @@ var BudgetController = class {
|
|
|
2184
2281
|
};
|
|
2185
2282
|
|
|
2186
2283
|
// src/conductor/card.ts
|
|
2284
|
+
import { createHash } from "crypto";
|
|
2187
2285
|
var CONDUCTOR_OWNER = "agentbnb-conductor";
|
|
2188
2286
|
var CONDUCTOR_CARD_ID = "00000000-0000-4000-8000-000000000001";
|
|
2189
|
-
function
|
|
2287
|
+
function ownerToCardId(owner) {
|
|
2288
|
+
const hash = createHash("sha256").update(owner).digest("hex").slice(0, 32);
|
|
2289
|
+
return `${hash.slice(0, 8)}-${hash.slice(8, 12)}-4${hash.slice(13, 16)}-8${hash.slice(17, 20)}-${hash.slice(20, 32)}`;
|
|
2290
|
+
}
|
|
2291
|
+
function buildConductorCard(owner) {
|
|
2292
|
+
const cardOwner = owner ?? CONDUCTOR_OWNER;
|
|
2293
|
+
const cardId = owner ? ownerToCardId(owner) : CONDUCTOR_CARD_ID;
|
|
2190
2294
|
const card = {
|
|
2191
2295
|
spec_version: "2.0",
|
|
2192
|
-
id:
|
|
2193
|
-
owner:
|
|
2296
|
+
id: cardId,
|
|
2297
|
+
owner: cardOwner,
|
|
2194
2298
|
agent_name: "AgentBnB Conductor",
|
|
2195
2299
|
skills: [
|
|
2196
2300
|
{
|
|
@@ -2281,7 +2385,7 @@ function computeWaves(subtasks) {
|
|
|
2281
2385
|
return waves;
|
|
2282
2386
|
}
|
|
2283
2387
|
async function orchestrate(opts) {
|
|
2284
|
-
const { subtasks, matches, gatewayToken, resolveAgentUrl, timeoutMs =
|
|
2388
|
+
const { subtasks, matches, gatewayToken, resolveAgentUrl, timeoutMs = 3e5, maxBudget, relayClient, requesterOwner } = opts;
|
|
2285
2389
|
const startTime = Date.now();
|
|
2286
2390
|
if (subtasks.length === 0) {
|
|
2287
2391
|
return {
|
|
@@ -2332,26 +2436,50 @@ async function orchestrate(opts) {
|
|
|
2332
2436
|
);
|
|
2333
2437
|
const primary = resolveAgentUrl(m.selected_agent);
|
|
2334
2438
|
try {
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2439
|
+
let res;
|
|
2440
|
+
if (primary.url.startsWith("relay://") && relayClient) {
|
|
2441
|
+
const targetOwner = primary.url.replace("relay://", "");
|
|
2442
|
+
res = await relayClient.request({
|
|
2443
|
+
targetOwner,
|
|
2444
|
+
cardId: primary.cardId,
|
|
2445
|
+
params: interpolatedParams,
|
|
2446
|
+
requester: requesterOwner,
|
|
2447
|
+
timeoutMs
|
|
2448
|
+
});
|
|
2449
|
+
} else {
|
|
2450
|
+
res = await requestCapability({
|
|
2451
|
+
gatewayUrl: primary.url,
|
|
2452
|
+
token: gatewayToken,
|
|
2453
|
+
cardId: primary.cardId,
|
|
2454
|
+
params: interpolatedParams,
|
|
2455
|
+
timeoutMs
|
|
2456
|
+
});
|
|
2457
|
+
}
|
|
2342
2458
|
return { taskId, result: res, credits: m.credits };
|
|
2343
2459
|
} catch (primaryErr) {
|
|
2344
2460
|
if (m.alternatives.length > 0) {
|
|
2345
2461
|
const alt = m.alternatives[0];
|
|
2346
2462
|
const altAgent = resolveAgentUrl(alt.agent);
|
|
2347
2463
|
try {
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2464
|
+
let altRes;
|
|
2465
|
+
if (altAgent.url.startsWith("relay://") && relayClient) {
|
|
2466
|
+
const targetOwner = altAgent.url.replace("relay://", "");
|
|
2467
|
+
altRes = await relayClient.request({
|
|
2468
|
+
targetOwner,
|
|
2469
|
+
cardId: altAgent.cardId,
|
|
2470
|
+
params: interpolatedParams,
|
|
2471
|
+
requester: requesterOwner,
|
|
2472
|
+
timeoutMs
|
|
2473
|
+
});
|
|
2474
|
+
} else {
|
|
2475
|
+
altRes = await requestCapability({
|
|
2476
|
+
gatewayUrl: altAgent.url,
|
|
2477
|
+
token: gatewayToken,
|
|
2478
|
+
cardId: altAgent.cardId,
|
|
2479
|
+
params: interpolatedParams,
|
|
2480
|
+
timeoutMs
|
|
2481
|
+
});
|
|
2482
|
+
}
|
|
2355
2483
|
return { taskId, result: altRes, credits: alt.credits };
|
|
2356
2484
|
} catch (altErr) {
|
|
2357
2485
|
throw new Error(
|
|
@@ -2452,7 +2580,7 @@ var ConductorMode = class {
|
|
|
2452
2580
|
* @param params - Must include `task` string.
|
|
2453
2581
|
* @returns Execution result without latency_ms (added by SkillExecutor).
|
|
2454
2582
|
*/
|
|
2455
|
-
async execute(config, params) {
|
|
2583
|
+
async execute(config, params, onProgress) {
|
|
2456
2584
|
const conductorSkill = config.conductor_skill;
|
|
2457
2585
|
if (conductorSkill !== "orchestrate" && conductorSkill !== "plan") {
|
|
2458
2586
|
return {
|
|
@@ -2474,11 +2602,13 @@ var ConductorMode = class {
|
|
|
2474
2602
|
error: "No template matches task"
|
|
2475
2603
|
};
|
|
2476
2604
|
}
|
|
2477
|
-
|
|
2605
|
+
onProgress?.({ step: 1, total: 5, message: `Decomposed into ${subtasks.length} sub-tasks` });
|
|
2606
|
+
const matchResults = await matchSubTasks({
|
|
2478
2607
|
db: this.db,
|
|
2479
2608
|
subtasks,
|
|
2480
2609
|
conductorOwner: this.conductorOwner
|
|
2481
2610
|
});
|
|
2611
|
+
onProgress?.({ step: 2, total: 5, message: `Matched ${matchResults.length} sub-tasks to agents` });
|
|
2482
2612
|
const budgetManager = new BudgetManager(this.creditDb, this.conductorOwner);
|
|
2483
2613
|
const budgetController = new BudgetController(budgetManager, this.maxBudget);
|
|
2484
2614
|
const executionBudget = budgetController.calculateBudget(matchResults);
|
|
@@ -2488,6 +2618,7 @@ var ConductorMode = class {
|
|
|
2488
2618
|
error: `Budget exceeded: estimated ${executionBudget.estimated_total} cr, max ${this.maxBudget} cr`
|
|
2489
2619
|
};
|
|
2490
2620
|
}
|
|
2621
|
+
onProgress?.({ step: 3, total: 5, message: `Budget approved: ${executionBudget.estimated_total} cr` });
|
|
2491
2622
|
if (conductorSkill === "plan") {
|
|
2492
2623
|
return {
|
|
2493
2624
|
success: true,
|
|
@@ -2508,6 +2639,7 @@ var ConductorMode = class {
|
|
|
2508
2639
|
resolveAgentUrl: this.resolveAgentUrl,
|
|
2509
2640
|
maxBudget: this.maxBudget
|
|
2510
2641
|
});
|
|
2642
|
+
onProgress?.({ step: 4, total: 5, message: "Pipeline execution complete" });
|
|
2511
2643
|
const resultObj = {};
|
|
2512
2644
|
for (const [key, value] of orchResult.results) {
|
|
2513
2645
|
resultObj[key] = value;
|
|
@@ -2560,7 +2692,7 @@ function createSignedEscrowReceipt(db, privateKey, publicKey, opts) {
|
|
|
2560
2692
|
|
|
2561
2693
|
// src/identity/identity.ts
|
|
2562
2694
|
import { z as z4 } from "zod";
|
|
2563
|
-
import { createHash } from "crypto";
|
|
2695
|
+
import { createHash as createHash2 } from "crypto";
|
|
2564
2696
|
import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, existsSync as existsSync4, mkdirSync as mkdirSync3 } from "fs";
|
|
2565
2697
|
import { join as join4 } from "path";
|
|
2566
2698
|
var AgentIdentitySchema = z4.object({
|
|
@@ -2591,7 +2723,7 @@ var AgentCertificateSchema = z4.object({
|
|
|
2591
2723
|
});
|
|
2592
2724
|
var IDENTITY_FILENAME = "identity.json";
|
|
2593
2725
|
function deriveAgentId(publicKeyHex) {
|
|
2594
|
-
return
|
|
2726
|
+
return createHash2("sha256").update(publicKeyHex, "hex").digest("hex").slice(0, 16);
|
|
2595
2727
|
}
|
|
2596
2728
|
function createIdentity(configDir, owner) {
|
|
2597
2729
|
if (!existsSync4(configDir)) {
|
|
@@ -2666,7 +2798,13 @@ function verifyAgentCertificate(cert) {
|
|
|
2666
2798
|
}
|
|
2667
2799
|
function ensureIdentity(configDir, owner) {
|
|
2668
2800
|
const existing = loadIdentity(configDir);
|
|
2669
|
-
if (existing)
|
|
2801
|
+
if (existing) {
|
|
2802
|
+
if (existing.owner !== owner) {
|
|
2803
|
+
existing.owner = owner;
|
|
2804
|
+
saveIdentity(configDir, existing);
|
|
2805
|
+
}
|
|
2806
|
+
return existing;
|
|
2807
|
+
}
|
|
2670
2808
|
return createIdentity(configDir, owner);
|
|
2671
2809
|
}
|
|
2672
2810
|
|
|
@@ -3036,8 +3174,10 @@ var RegisterMessageSchema = z6.object({
|
|
|
3036
3174
|
type: z6.literal("register"),
|
|
3037
3175
|
owner: z6.string().min(1),
|
|
3038
3176
|
token: z6.string().min(1),
|
|
3039
|
-
card: z6.record(z6.unknown())
|
|
3177
|
+
card: z6.record(z6.unknown()),
|
|
3040
3178
|
// CapabilityCard (validated separately)
|
|
3179
|
+
cards: z6.array(z6.record(z6.unknown())).optional()
|
|
3180
|
+
// Additional cards (e.g., conductor card)
|
|
3041
3181
|
});
|
|
3042
3182
|
var RegisteredMessageSchema = z6.object({
|
|
3043
3183
|
type: z6.literal("registered"),
|
|
@@ -3087,6 +3227,15 @@ var ErrorMessageSchema = z6.object({
|
|
|
3087
3227
|
message: z6.string(),
|
|
3088
3228
|
request_id: z6.string().optional()
|
|
3089
3229
|
});
|
|
3230
|
+
var RelayProgressMessageSchema = z6.object({
|
|
3231
|
+
type: z6.literal("relay_progress"),
|
|
3232
|
+
id: z6.string().uuid(),
|
|
3233
|
+
// request ID this progress relates to
|
|
3234
|
+
progress: z6.number().min(0).max(100).optional(),
|
|
3235
|
+
// optional percentage
|
|
3236
|
+
message: z6.string().optional()
|
|
3237
|
+
// optional status message
|
|
3238
|
+
});
|
|
3090
3239
|
var RelayMessageSchema = z6.discriminatedUnion("type", [
|
|
3091
3240
|
RegisterMessageSchema,
|
|
3092
3241
|
RegisteredMessageSchema,
|
|
@@ -3094,18 +3243,118 @@ var RelayMessageSchema = z6.discriminatedUnion("type", [
|
|
|
3094
3243
|
IncomingRequestMessageSchema,
|
|
3095
3244
|
RelayResponseMessageSchema,
|
|
3096
3245
|
ResponseMessageSchema,
|
|
3097
|
-
ErrorMessageSchema
|
|
3246
|
+
ErrorMessageSchema,
|
|
3247
|
+
RelayProgressMessageSchema
|
|
3098
3248
|
]);
|
|
3099
3249
|
|
|
3100
3250
|
// src/relay/websocket-relay.ts
|
|
3251
|
+
import { randomUUID as randomUUID12 } from "crypto";
|
|
3252
|
+
|
|
3253
|
+
// src/relay/relay-credit.ts
|
|
3254
|
+
function lookupCardPrice(registryDb, cardId, skillId) {
|
|
3255
|
+
const row = registryDb.prepare("SELECT data FROM capability_cards WHERE id = ?").get(cardId);
|
|
3256
|
+
if (!row) return null;
|
|
3257
|
+
let card;
|
|
3258
|
+
try {
|
|
3259
|
+
card = JSON.parse(row.data);
|
|
3260
|
+
} catch {
|
|
3261
|
+
return null;
|
|
3262
|
+
}
|
|
3263
|
+
if (Array.isArray(card.skills) && card.skills.length > 0) {
|
|
3264
|
+
const skills = card.skills;
|
|
3265
|
+
if (skillId) {
|
|
3266
|
+
const skill = skills.find((s) => s.id === skillId);
|
|
3267
|
+
if (skill) {
|
|
3268
|
+
const skillPricing = skill.pricing;
|
|
3269
|
+
if (skillPricing && typeof skillPricing.credits_per_call === "number") {
|
|
3270
|
+
return skillPricing.credits_per_call;
|
|
3271
|
+
}
|
|
3272
|
+
}
|
|
3273
|
+
} else {
|
|
3274
|
+
let minPrice = null;
|
|
3275
|
+
for (const s of skills) {
|
|
3276
|
+
const sp = s.pricing;
|
|
3277
|
+
if (sp && typeof sp.credits_per_call === "number" && sp.credits_per_call > 0) {
|
|
3278
|
+
if (minPrice === null || sp.credits_per_call < minPrice) {
|
|
3279
|
+
minPrice = sp.credits_per_call;
|
|
3280
|
+
}
|
|
3281
|
+
}
|
|
3282
|
+
}
|
|
3283
|
+
if (minPrice !== null) return minPrice;
|
|
3284
|
+
}
|
|
3285
|
+
}
|
|
3286
|
+
const pricing = card.pricing;
|
|
3287
|
+
if (!pricing || typeof pricing.credits_per_call !== "number") {
|
|
3288
|
+
return null;
|
|
3289
|
+
}
|
|
3290
|
+
return pricing.credits_per_call;
|
|
3291
|
+
}
|
|
3292
|
+
function holdForRelay(creditDb, owner, amount, cardId) {
|
|
3293
|
+
return holdEscrow(creditDb, owner, amount, cardId);
|
|
3294
|
+
}
|
|
3295
|
+
function settleForRelay(creditDb, escrowId, recipientOwner) {
|
|
3296
|
+
settleEscrow(creditDb, escrowId, recipientOwner);
|
|
3297
|
+
}
|
|
3298
|
+
function calculateConductorFee(totalSubTaskCost) {
|
|
3299
|
+
if (totalSubTaskCost <= 0) return 0;
|
|
3300
|
+
const fee = Math.ceil(totalSubTaskCost * 0.1);
|
|
3301
|
+
return Math.max(1, Math.min(20, fee));
|
|
3302
|
+
}
|
|
3303
|
+
function releaseForRelay(creditDb, escrowId) {
|
|
3304
|
+
if (escrowId === void 0) return;
|
|
3305
|
+
releaseEscrow(creditDb, escrowId);
|
|
3306
|
+
}
|
|
3307
|
+
|
|
3308
|
+
// src/hub-agent/relay-bridge.ts
|
|
3309
|
+
import { randomUUID as randomUUID11 } from "crypto";
|
|
3310
|
+
|
|
3311
|
+
// src/hub-agent/job-queue.ts
|
|
3101
3312
|
import { randomUUID as randomUUID10 } from "crypto";
|
|
3313
|
+
function updateJobStatus(db, jobId, status, result) {
|
|
3314
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3315
|
+
if (result !== void 0) {
|
|
3316
|
+
db.prepare("UPDATE hub_agent_jobs SET status = ?, result = ?, updated_at = ? WHERE id = ?").run(status, result, now, jobId);
|
|
3317
|
+
} else {
|
|
3318
|
+
db.prepare("UPDATE hub_agent_jobs SET status = ?, updated_at = ? WHERE id = ?").run(status, now, jobId);
|
|
3319
|
+
}
|
|
3320
|
+
}
|
|
3321
|
+
|
|
3322
|
+
// src/hub-agent/crypto.ts
|
|
3323
|
+
import { createCipheriv, createDecipheriv, randomBytes } from "crypto";
|
|
3324
|
+
|
|
3325
|
+
// src/hub-agent/relay-bridge.ts
|
|
3326
|
+
function handleJobRelayResponse(opts) {
|
|
3327
|
+
const { registryDb, creditDb, jobId, escrowId, relayOwner, result, error } = opts;
|
|
3328
|
+
if (error) {
|
|
3329
|
+
updateJobStatus(registryDb, jobId, "failed", JSON.stringify(error));
|
|
3330
|
+
if (escrowId) {
|
|
3331
|
+
try {
|
|
3332
|
+
releaseForRelay(creditDb, escrowId);
|
|
3333
|
+
} catch (e) {
|
|
3334
|
+
console.error("[relay-bridge] escrow release on error failed:", e);
|
|
3335
|
+
}
|
|
3336
|
+
}
|
|
3337
|
+
} else {
|
|
3338
|
+
updateJobStatus(registryDb, jobId, "completed", JSON.stringify(result));
|
|
3339
|
+
if (escrowId) {
|
|
3340
|
+
try {
|
|
3341
|
+
settleForRelay(creditDb, escrowId, relayOwner);
|
|
3342
|
+
} catch (e) {
|
|
3343
|
+
console.error("[relay-bridge] escrow settle failed:", e);
|
|
3344
|
+
}
|
|
3345
|
+
}
|
|
3346
|
+
}
|
|
3347
|
+
}
|
|
3348
|
+
|
|
3349
|
+
// src/relay/websocket-relay.ts
|
|
3102
3350
|
var RATE_LIMIT_MAX = 60;
|
|
3103
3351
|
var RATE_LIMIT_WINDOW_MS = 6e4;
|
|
3104
|
-
var RELAY_TIMEOUT_MS =
|
|
3105
|
-
function registerWebSocketRelay(server, db) {
|
|
3352
|
+
var RELAY_TIMEOUT_MS = 3e5;
|
|
3353
|
+
function registerWebSocketRelay(server, db, creditDb) {
|
|
3106
3354
|
const connections = /* @__PURE__ */ new Map();
|
|
3107
3355
|
const pendingRequests = /* @__PURE__ */ new Map();
|
|
3108
3356
|
const rateLimits = /* @__PURE__ */ new Map();
|
|
3357
|
+
let onAgentOnlineCallback;
|
|
3109
3358
|
function checkRateLimit(owner) {
|
|
3110
3359
|
const now = Date.now();
|
|
3111
3360
|
const entry = rateLimits.get(owner);
|
|
@@ -3156,21 +3405,32 @@ function registerWebSocketRelay(server, db) {
|
|
|
3156
3405
|
}
|
|
3157
3406
|
}
|
|
3158
3407
|
function upsertCard(cardData, owner) {
|
|
3159
|
-
const
|
|
3160
|
-
|
|
3408
|
+
const parsed = AnyCardSchema.safeParse(cardData);
|
|
3409
|
+
if (!parsed.success) {
|
|
3410
|
+
throw new AgentBnBError(
|
|
3411
|
+
`Card validation failed: ${parsed.error.message}`,
|
|
3412
|
+
"VALIDATION_ERROR"
|
|
3413
|
+
);
|
|
3414
|
+
}
|
|
3415
|
+
const card = { ...parsed.data, availability: { ...parsed.data.availability, online: true } };
|
|
3416
|
+
const cardId = card.id;
|
|
3417
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3418
|
+
const existing = db.prepare("SELECT id FROM capability_cards WHERE id = ?").get(cardId);
|
|
3161
3419
|
if (existing) {
|
|
3162
|
-
|
|
3163
|
-
|
|
3420
|
+
db.prepare(
|
|
3421
|
+
"UPDATE capability_cards SET data = ?, updated_at = ? WHERE id = ?"
|
|
3422
|
+
).run(JSON.stringify(card), now, cardId);
|
|
3164
3423
|
} else {
|
|
3165
|
-
|
|
3166
|
-
|
|
3424
|
+
db.prepare(
|
|
3425
|
+
"INSERT INTO capability_cards (id, owner, data, created_at, updated_at) VALUES (?, ?, ?, ?, ?)"
|
|
3426
|
+
).run(cardId, owner, JSON.stringify(card), now, now);
|
|
3167
3427
|
}
|
|
3168
3428
|
return cardId;
|
|
3169
3429
|
}
|
|
3170
3430
|
function logAgentJoined(owner, cardName, cardId) {
|
|
3171
3431
|
try {
|
|
3172
3432
|
insertRequestLog(db, {
|
|
3173
|
-
id:
|
|
3433
|
+
id: randomUUID12(),
|
|
3174
3434
|
card_id: cardId,
|
|
3175
3435
|
card_name: cardName,
|
|
3176
3436
|
requester: owner,
|
|
@@ -3198,13 +3458,34 @@ function registerWebSocketRelay(server, db) {
|
|
|
3198
3458
|
}
|
|
3199
3459
|
}
|
|
3200
3460
|
connections.set(owner, ws);
|
|
3201
|
-
|
|
3461
|
+
let cardId;
|
|
3462
|
+
try {
|
|
3463
|
+
cardId = upsertCard(card, owner);
|
|
3464
|
+
} catch (err) {
|
|
3465
|
+
console.error(`[relay] card validation failed for ${owner}:`, err instanceof Error ? err.message : err);
|
|
3466
|
+
cardId = card.id ?? owner;
|
|
3467
|
+
}
|
|
3202
3468
|
const cardName = card.name ?? card.agent_name ?? owner;
|
|
3203
3469
|
logAgentJoined(owner, cardName, cardId);
|
|
3470
|
+
if (msg.cards && msg.cards.length > 0) {
|
|
3471
|
+
for (const extraCard of msg.cards) {
|
|
3472
|
+
try {
|
|
3473
|
+
upsertCard(extraCard, owner);
|
|
3474
|
+
} catch {
|
|
3475
|
+
}
|
|
3476
|
+
}
|
|
3477
|
+
}
|
|
3204
3478
|
markOwnerOnline(owner);
|
|
3479
|
+
if (onAgentOnlineCallback) {
|
|
3480
|
+
try {
|
|
3481
|
+
onAgentOnlineCallback(owner);
|
|
3482
|
+
} catch (e) {
|
|
3483
|
+
console.error("[relay] onAgentOnline callback error:", e);
|
|
3484
|
+
}
|
|
3485
|
+
}
|
|
3205
3486
|
sendMessage(ws, { type: "registered", agent_id: cardId });
|
|
3206
3487
|
}
|
|
3207
|
-
function handleRelayRequest(ws, msg, fromOwner) {
|
|
3488
|
+
async function handleRelayRequest(ws, msg, fromOwner) {
|
|
3208
3489
|
if (!checkRateLimit(fromOwner)) {
|
|
3209
3490
|
sendMessage(ws, {
|
|
3210
3491
|
type: "error",
|
|
@@ -3223,15 +3504,43 @@ function registerWebSocketRelay(server, db) {
|
|
|
3223
3504
|
});
|
|
3224
3505
|
return;
|
|
3225
3506
|
}
|
|
3507
|
+
const creditOwner = msg.requester ?? fromOwner;
|
|
3508
|
+
let escrowId;
|
|
3509
|
+
if (creditDb) {
|
|
3510
|
+
try {
|
|
3511
|
+
const price = lookupCardPrice(db, msg.card_id, msg.skill_id);
|
|
3512
|
+
if (price !== null && price > 0) {
|
|
3513
|
+
escrowId = holdForRelay(creditDb, creditOwner, price, msg.card_id);
|
|
3514
|
+
}
|
|
3515
|
+
} catch (err) {
|
|
3516
|
+
if (err instanceof AgentBnBError && err.code === "INSUFFICIENT_CREDITS") {
|
|
3517
|
+
sendMessage(ws, {
|
|
3518
|
+
type: "response",
|
|
3519
|
+
id: msg.id,
|
|
3520
|
+
error: { code: -32603, message: "Insufficient credits" }
|
|
3521
|
+
});
|
|
3522
|
+
return;
|
|
3523
|
+
}
|
|
3524
|
+
console.error("[relay] credit hold error (non-fatal):", err);
|
|
3525
|
+
}
|
|
3526
|
+
}
|
|
3226
3527
|
const timeout = setTimeout(() => {
|
|
3528
|
+
const pending = pendingRequests.get(msg.id);
|
|
3227
3529
|
pendingRequests.delete(msg.id);
|
|
3530
|
+
if (pending?.escrowId && creditDb) {
|
|
3531
|
+
try {
|
|
3532
|
+
releaseForRelay(creditDb, pending.escrowId);
|
|
3533
|
+
} catch (e) {
|
|
3534
|
+
console.error("[relay] escrow release on timeout failed:", e);
|
|
3535
|
+
}
|
|
3536
|
+
}
|
|
3228
3537
|
sendMessage(ws, {
|
|
3229
3538
|
type: "response",
|
|
3230
3539
|
id: msg.id,
|
|
3231
3540
|
error: { code: -32603, message: "Relay request timeout" }
|
|
3232
3541
|
});
|
|
3233
3542
|
}, RELAY_TIMEOUT_MS);
|
|
3234
|
-
pendingRequests.set(msg.id, { originOwner: fromOwner, timeout });
|
|
3543
|
+
pendingRequests.set(msg.id, { originOwner: fromOwner, creditOwner, timeout, escrowId, targetOwner: msg.target_owner });
|
|
3235
3544
|
sendMessage(targetWs, {
|
|
3236
3545
|
type: "incoming_request",
|
|
3237
3546
|
id: msg.id,
|
|
@@ -3243,18 +3552,98 @@ function registerWebSocketRelay(server, db) {
|
|
|
3243
3552
|
escrow_receipt: msg.escrow_receipt
|
|
3244
3553
|
});
|
|
3245
3554
|
}
|
|
3555
|
+
function handleRelayProgress(msg) {
|
|
3556
|
+
const pending = pendingRequests.get(msg.id);
|
|
3557
|
+
if (!pending) return;
|
|
3558
|
+
clearTimeout(pending.timeout);
|
|
3559
|
+
const newTimeout = setTimeout(() => {
|
|
3560
|
+
const p = pendingRequests.get(msg.id);
|
|
3561
|
+
pendingRequests.delete(msg.id);
|
|
3562
|
+
if (p?.escrowId && creditDb) {
|
|
3563
|
+
try {
|
|
3564
|
+
releaseForRelay(creditDb, p.escrowId);
|
|
3565
|
+
} catch (e) {
|
|
3566
|
+
console.error("[relay] escrow release on progress timeout failed:", e);
|
|
3567
|
+
}
|
|
3568
|
+
}
|
|
3569
|
+
const originWs2 = connections.get(pending.originOwner);
|
|
3570
|
+
if (originWs2 && originWs2.readyState === 1) {
|
|
3571
|
+
sendMessage(originWs2, {
|
|
3572
|
+
type: "response",
|
|
3573
|
+
id: msg.id,
|
|
3574
|
+
error: { code: -32603, message: "Relay request timeout" }
|
|
3575
|
+
});
|
|
3576
|
+
}
|
|
3577
|
+
}, RELAY_TIMEOUT_MS);
|
|
3578
|
+
pending.timeout = newTimeout;
|
|
3579
|
+
const originWs = connections.get(pending.originOwner);
|
|
3580
|
+
if (originWs && originWs.readyState === 1) {
|
|
3581
|
+
sendMessage(originWs, {
|
|
3582
|
+
type: "relay_progress",
|
|
3583
|
+
id: msg.id,
|
|
3584
|
+
progress: msg.progress,
|
|
3585
|
+
message: msg.message
|
|
3586
|
+
});
|
|
3587
|
+
}
|
|
3588
|
+
}
|
|
3246
3589
|
function handleRelayResponse(msg) {
|
|
3247
3590
|
const pending = pendingRequests.get(msg.id);
|
|
3248
3591
|
if (!pending) return;
|
|
3249
3592
|
clearTimeout(pending.timeout);
|
|
3250
3593
|
pendingRequests.delete(msg.id);
|
|
3594
|
+
if (pending.jobId && creditDb) {
|
|
3595
|
+
try {
|
|
3596
|
+
handleJobRelayResponse({
|
|
3597
|
+
registryDb: db,
|
|
3598
|
+
creditDb,
|
|
3599
|
+
jobId: pending.jobId,
|
|
3600
|
+
escrowId: pending.escrowId,
|
|
3601
|
+
relayOwner: pending.targetOwner ?? "",
|
|
3602
|
+
result: msg.error === void 0 ? msg.result : void 0,
|
|
3603
|
+
error: msg.error
|
|
3604
|
+
});
|
|
3605
|
+
} catch (e) {
|
|
3606
|
+
console.error("[relay] job relay response handling failed:", e);
|
|
3607
|
+
}
|
|
3608
|
+
const originWs2 = connections.get(pending.originOwner);
|
|
3609
|
+
if (originWs2 && originWs2.readyState === 1) {
|
|
3610
|
+
sendMessage(originWs2, { type: "response", id: msg.id, result: msg.result, error: msg.error });
|
|
3611
|
+
}
|
|
3612
|
+
return;
|
|
3613
|
+
}
|
|
3614
|
+
if (pending.escrowId && creditDb) {
|
|
3615
|
+
try {
|
|
3616
|
+
if (msg.error === void 0) {
|
|
3617
|
+
settleForRelay(creditDb, pending.escrowId, pending.targetOwner);
|
|
3618
|
+
} else {
|
|
3619
|
+
releaseForRelay(creditDb, pending.escrowId);
|
|
3620
|
+
}
|
|
3621
|
+
} catch (e) {
|
|
3622
|
+
console.error("[relay] escrow settle/release on response failed:", e);
|
|
3623
|
+
}
|
|
3624
|
+
}
|
|
3625
|
+
let conductorFee = 0;
|
|
3626
|
+
if (creditDb && msg.error === void 0 && typeof msg.result === "object" && msg.result !== null && "total_credits" in msg.result && typeof msg.result.total_credits === "number") {
|
|
3627
|
+
const totalCredits = msg.result.total_credits;
|
|
3628
|
+
conductorFee = calculateConductorFee(totalCredits);
|
|
3629
|
+
if (conductorFee > 0) {
|
|
3630
|
+
try {
|
|
3631
|
+
const feeEscrowId = holdForRelay(creditDb, pending.creditOwner ?? pending.originOwner, conductorFee, msg.id);
|
|
3632
|
+
settleForRelay(creditDb, feeEscrowId, pending.targetOwner);
|
|
3633
|
+
} catch (e) {
|
|
3634
|
+
console.error("[relay] conductor fee settlement failed (non-fatal):", e);
|
|
3635
|
+
conductorFee = 0;
|
|
3636
|
+
}
|
|
3637
|
+
}
|
|
3638
|
+
}
|
|
3251
3639
|
const originWs = connections.get(pending.originOwner);
|
|
3252
3640
|
if (originWs && originWs.readyState === 1) {
|
|
3253
3641
|
sendMessage(originWs, {
|
|
3254
3642
|
type: "response",
|
|
3255
3643
|
id: msg.id,
|
|
3256
3644
|
result: msg.result,
|
|
3257
|
-
error: msg.error
|
|
3645
|
+
error: msg.error,
|
|
3646
|
+
...conductorFee > 0 ? { conductor_fee: conductorFee } : {}
|
|
3258
3647
|
});
|
|
3259
3648
|
}
|
|
3260
3649
|
}
|
|
@@ -3264,61 +3653,93 @@ function registerWebSocketRelay(server, db) {
|
|
|
3264
3653
|
rateLimits.delete(owner);
|
|
3265
3654
|
markOwnerOffline(owner);
|
|
3266
3655
|
for (const [reqId, pending] of pendingRequests) {
|
|
3267
|
-
if (pending.
|
|
3656
|
+
if (pending.targetOwner === owner) {
|
|
3268
3657
|
clearTimeout(pending.timeout);
|
|
3269
3658
|
pendingRequests.delete(reqId);
|
|
3659
|
+
if (pending.escrowId && creditDb) {
|
|
3660
|
+
try {
|
|
3661
|
+
releaseForRelay(creditDb, pending.escrowId);
|
|
3662
|
+
} catch (e) {
|
|
3663
|
+
console.error("[relay] escrow release on disconnect failed:", e);
|
|
3664
|
+
}
|
|
3665
|
+
}
|
|
3666
|
+
const originWs = connections.get(pending.originOwner);
|
|
3667
|
+
if (originWs && originWs.readyState === 1) {
|
|
3668
|
+
sendMessage(originWs, {
|
|
3669
|
+
type: "response",
|
|
3670
|
+
id: reqId,
|
|
3671
|
+
error: { code: -32603, message: "Provider disconnected" }
|
|
3672
|
+
});
|
|
3673
|
+
}
|
|
3674
|
+
} else if (pending.originOwner === owner) {
|
|
3675
|
+
clearTimeout(pending.timeout);
|
|
3676
|
+
pendingRequests.delete(reqId);
|
|
3677
|
+
if (pending.escrowId && creditDb) {
|
|
3678
|
+
try {
|
|
3679
|
+
releaseForRelay(creditDb, pending.escrowId);
|
|
3680
|
+
} catch (e) {
|
|
3681
|
+
console.error("[relay] escrow release on requester disconnect failed:", e);
|
|
3682
|
+
}
|
|
3683
|
+
}
|
|
3270
3684
|
}
|
|
3271
3685
|
}
|
|
3272
3686
|
}
|
|
3273
|
-
server.
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
code: "invalid_message",
|
|
3289
|
-
message: `Invalid message: ${parsed.error.issues[0]?.message ?? "unknown error"}`
|
|
3290
|
-
});
|
|
3291
|
-
return;
|
|
3292
|
-
}
|
|
3293
|
-
const msg = parsed.data;
|
|
3294
|
-
switch (msg.type) {
|
|
3295
|
-
case "register":
|
|
3296
|
-
registeredOwner = msg.owner;
|
|
3297
|
-
handleRegister(socket, msg);
|
|
3298
|
-
break;
|
|
3299
|
-
case "relay_request":
|
|
3300
|
-
if (!registeredOwner) {
|
|
3687
|
+
void server.register(async (app) => {
|
|
3688
|
+
app.get("/ws", { websocket: true }, (rawSocket, _request) => {
|
|
3689
|
+
const socket = rawSocket;
|
|
3690
|
+
let registeredOwner;
|
|
3691
|
+
socket.on("message", (raw) => {
|
|
3692
|
+
void (async () => {
|
|
3693
|
+
let data;
|
|
3694
|
+
try {
|
|
3695
|
+
data = JSON.parse(typeof raw === "string" ? raw : raw.toString("utf-8"));
|
|
3696
|
+
} catch {
|
|
3697
|
+
sendMessage(socket, { type: "error", code: "invalid_json", message: "Invalid JSON" });
|
|
3698
|
+
return;
|
|
3699
|
+
}
|
|
3700
|
+
const parsed = RelayMessageSchema.safeParse(data);
|
|
3701
|
+
if (!parsed.success) {
|
|
3301
3702
|
sendMessage(socket, {
|
|
3302
3703
|
type: "error",
|
|
3303
|
-
code: "
|
|
3304
|
-
message:
|
|
3704
|
+
code: "invalid_message",
|
|
3705
|
+
message: `Invalid message: ${parsed.error.issues[0]?.message ?? "unknown error"}`
|
|
3305
3706
|
});
|
|
3306
3707
|
return;
|
|
3307
3708
|
}
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3709
|
+
const msg = parsed.data;
|
|
3710
|
+
switch (msg.type) {
|
|
3711
|
+
case "register":
|
|
3712
|
+
registeredOwner = msg.owner;
|
|
3713
|
+
handleRegister(socket, msg);
|
|
3714
|
+
break;
|
|
3715
|
+
case "relay_request":
|
|
3716
|
+
if (!registeredOwner) {
|
|
3717
|
+
sendMessage(socket, {
|
|
3718
|
+
type: "error",
|
|
3719
|
+
code: "not_registered",
|
|
3720
|
+
message: "Must send register message before relay requests"
|
|
3721
|
+
});
|
|
3722
|
+
return;
|
|
3723
|
+
}
|
|
3724
|
+
await handleRelayRequest(socket, msg, registeredOwner);
|
|
3725
|
+
break;
|
|
3726
|
+
case "relay_response":
|
|
3727
|
+
handleRelayResponse(msg);
|
|
3728
|
+
break;
|
|
3729
|
+
case "relay_progress":
|
|
3730
|
+
handleRelayProgress(msg);
|
|
3731
|
+
break;
|
|
3732
|
+
default:
|
|
3733
|
+
break;
|
|
3734
|
+
}
|
|
3735
|
+
})();
|
|
3736
|
+
});
|
|
3737
|
+
socket.on("close", () => {
|
|
3738
|
+
handleDisconnect(registeredOwner);
|
|
3739
|
+
});
|
|
3740
|
+
socket.on("error", () => {
|
|
3741
|
+
handleDisconnect(registeredOwner);
|
|
3742
|
+
});
|
|
3322
3743
|
});
|
|
3323
3744
|
});
|
|
3324
3745
|
return {
|
|
@@ -3337,13 +3758,21 @@ function registerWebSocketRelay(server, db) {
|
|
|
3337
3758
|
}
|
|
3338
3759
|
pendingRequests.clear();
|
|
3339
3760
|
rateLimits.clear();
|
|
3761
|
+
},
|
|
3762
|
+
setOnAgentOnline: (cb) => {
|
|
3763
|
+
onAgentOnlineCallback = cb;
|
|
3764
|
+
},
|
|
3765
|
+
getConnections: () => connections,
|
|
3766
|
+
getPendingRequests: () => pendingRequests,
|
|
3767
|
+
sendMessage: (ws, msg) => {
|
|
3768
|
+
sendMessage(ws, msg);
|
|
3340
3769
|
}
|
|
3341
3770
|
};
|
|
3342
3771
|
}
|
|
3343
3772
|
|
|
3344
3773
|
// src/relay/websocket-client.ts
|
|
3345
3774
|
import WebSocket from "ws";
|
|
3346
|
-
import { randomUUID as
|
|
3775
|
+
import { randomUUID as randomUUID13 } from "crypto";
|
|
3347
3776
|
var RelayClient = class {
|
|
3348
3777
|
ws = null;
|
|
3349
3778
|
opts;
|
|
@@ -3375,7 +3804,8 @@ var RelayClient = class {
|
|
|
3375
3804
|
type: "register",
|
|
3376
3805
|
owner: this.opts.owner,
|
|
3377
3806
|
token: this.opts.token,
|
|
3378
|
-
card: this.opts.card
|
|
3807
|
+
card: this.opts.card,
|
|
3808
|
+
...this.opts.cards && this.opts.cards.length > 0 ? { cards: this.opts.cards } : {}
|
|
3379
3809
|
});
|
|
3380
3810
|
});
|
|
3381
3811
|
this.ws.on("message", (raw) => {
|
|
@@ -3439,14 +3869,14 @@ var RelayClient = class {
|
|
|
3439
3869
|
if (!this.ws || this.ws.readyState !== WebSocket.OPEN || !this.registered) {
|
|
3440
3870
|
throw new Error("Not connected to registry relay");
|
|
3441
3871
|
}
|
|
3442
|
-
const id =
|
|
3443
|
-
const timeoutMs = opts.timeoutMs ??
|
|
3872
|
+
const id = randomUUID13();
|
|
3873
|
+
const timeoutMs = opts.timeoutMs ?? 3e5;
|
|
3444
3874
|
return new Promise((resolve, reject) => {
|
|
3445
3875
|
const timeout = setTimeout(() => {
|
|
3446
3876
|
this.pendingRequests.delete(id);
|
|
3447
3877
|
reject(new Error("Relay request timeout"));
|
|
3448
3878
|
}, timeoutMs);
|
|
3449
|
-
this.pendingRequests.set(id, { resolve, reject, timeout });
|
|
3879
|
+
this.pendingRequests.set(id, { resolve, reject, timeout, timeoutMs, onProgress: opts.onProgress });
|
|
3450
3880
|
this.send({
|
|
3451
3881
|
type: "relay_request",
|
|
3452
3882
|
id,
|
|
@@ -3459,6 +3889,22 @@ var RelayClient = class {
|
|
|
3459
3889
|
});
|
|
3460
3890
|
});
|
|
3461
3891
|
}
|
|
3892
|
+
/**
|
|
3893
|
+
* Send a relay_progress message to the relay server for a given request.
|
|
3894
|
+
* Used by the onRequest handler to forward SkillExecutor progress updates
|
|
3895
|
+
* to the requesting agent so it can reset its timeout window.
|
|
3896
|
+
*
|
|
3897
|
+
* @param requestId - The relay request ID to associate progress with.
|
|
3898
|
+
* @param info - Progress details (step, total, message).
|
|
3899
|
+
*/
|
|
3900
|
+
sendProgress(requestId, info) {
|
|
3901
|
+
this.send({
|
|
3902
|
+
type: "relay_progress",
|
|
3903
|
+
id: requestId,
|
|
3904
|
+
progress: Math.round(info.step / info.total * 100),
|
|
3905
|
+
message: info.message
|
|
3906
|
+
});
|
|
3907
|
+
}
|
|
3462
3908
|
/** Whether the client is connected and registered */
|
|
3463
3909
|
get isConnected() {
|
|
3464
3910
|
return this.ws !== null && this.ws.readyState === WebSocket.OPEN && this.registered;
|
|
@@ -3505,6 +3951,9 @@ var RelayClient = class {
|
|
|
3505
3951
|
case "error":
|
|
3506
3952
|
this.handleError(msg);
|
|
3507
3953
|
break;
|
|
3954
|
+
case "relay_progress":
|
|
3955
|
+
this.handleProgress(msg);
|
|
3956
|
+
break;
|
|
3508
3957
|
default:
|
|
3509
3958
|
break;
|
|
3510
3959
|
}
|
|
@@ -3550,6 +3999,19 @@ var RelayClient = class {
|
|
|
3550
3999
|
}
|
|
3551
4000
|
}
|
|
3552
4001
|
}
|
|
4002
|
+
handleProgress(msg) {
|
|
4003
|
+
const pending = this.pendingRequests.get(msg.id);
|
|
4004
|
+
if (!pending) return;
|
|
4005
|
+
clearTimeout(pending.timeout);
|
|
4006
|
+
const newTimeout = setTimeout(() => {
|
|
4007
|
+
this.pendingRequests.delete(msg.id);
|
|
4008
|
+
pending.reject(new Error("Relay request timeout"));
|
|
4009
|
+
}, pending.timeoutMs);
|
|
4010
|
+
pending.timeout = newTimeout;
|
|
4011
|
+
if (pending.onProgress) {
|
|
4012
|
+
pending.onProgress({ id: msg.id, progress: msg.progress, message: msg.message });
|
|
4013
|
+
}
|
|
4014
|
+
}
|
|
3553
4015
|
send(msg) {
|
|
3554
4016
|
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
3555
4017
|
this.ws.send(JSON.stringify(msg));
|
|
@@ -3611,12 +4073,12 @@ var RelayClient = class {
|
|
|
3611
4073
|
};
|
|
3612
4074
|
|
|
3613
4075
|
// src/onboarding/index.ts
|
|
3614
|
-
import { randomUUID as
|
|
4076
|
+
import { randomUUID as randomUUID15 } from "crypto";
|
|
3615
4077
|
import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
|
|
3616
4078
|
import { join as join5 } from "path";
|
|
3617
4079
|
|
|
3618
4080
|
// src/cli/onboarding.ts
|
|
3619
|
-
import { randomUUID as
|
|
4081
|
+
import { randomUUID as randomUUID14 } from "crypto";
|
|
3620
4082
|
import { createConnection } from "net";
|
|
3621
4083
|
var KNOWN_API_KEYS = [
|
|
3622
4084
|
"OPENAI_API_KEY",
|
|
@@ -3758,7 +4220,7 @@ function capabilitiesToV2Card(capabilities, owner, agentName) {
|
|
|
3758
4220
|
}));
|
|
3759
4221
|
const card = {
|
|
3760
4222
|
spec_version: "2.0",
|
|
3761
|
-
id:
|
|
4223
|
+
id: randomUUID15(),
|
|
3762
4224
|
owner,
|
|
3763
4225
|
agent_name: agentName ?? owner,
|
|
3764
4226
|
skills,
|