agentbnb 8.2.2 → 8.3.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-EX2EYGCZ.js → card-BN643ZOY.js} +6 -2
- package/dist/card-T2XJZA5A.js +92 -0
- package/dist/{chunk-3LWBH7P3.js → chunk-4NFJ3VYZ.js} +20 -1
- package/dist/chunk-5AIYALBX.js +857 -0
- package/dist/chunk-6QMDJVMS.js +238 -0
- package/dist/{chunk-LKLKYXLV.js → chunk-74LZDEDT.js} +6 -4
- package/dist/{chunk-GKVTD4EZ.js → chunk-77KGEDH4.js} +1 -1
- package/dist/{chunk-QCGIG7WW.js → chunk-7IQE34QK.js} +14 -7
- package/dist/{chunk-QHZGOG3O.js → chunk-D242QZCR.js} +168 -41
- package/dist/chunk-EE3V3DXK.js +60 -0
- package/dist/{chunk-RYISHSHB.js → chunk-F3KIEVJ2.js} +207 -265
- package/dist/{chunk-XBGVQMQJ.js → chunk-FELGHDCA.js} +16 -39
- package/dist/{chunk-EJKW57ZV.js → chunk-GIEJVKZZ.js} +1 -1
- package/dist/{chunk-WVY2W7AA.js → chunk-I7KWA7OB.js} +20 -0
- package/dist/{chunk-4IPJJRTP.js → chunk-IGQNP3ZO.js} +5 -2
- package/dist/chunk-NQANA6WH.js +797 -0
- package/dist/{chunk-Z4MCGKTL.js → chunk-NX27AFPA.js} +15 -2
- package/dist/{chunk-Z2GEFFDO.js → chunk-O4Q7BRG6.js} +2 -2
- package/dist/{chunk-SSK653A6.js → chunk-PQIP7EXY.js} +6 -0
- package/dist/{chunk-EG6RS4JC.js → chunk-QFPXZITP.js} +20 -65
- package/dist/chunk-R4F4XII4.js +264 -0
- package/dist/{chunk-DYQOFGGI.js → chunk-RVBW2QXU.js} +178 -49
- package/dist/{chunk-CQFBNTGT.js → chunk-S7DZHKCG.js} +25 -12
- package/dist/chunk-U6LP4KWN.js +238 -0
- package/dist/{chunk-MWOXW7JQ.js → chunk-VJ7XBEY6.js} +24 -16
- package/dist/chunk-WTHMHNKC.js +129 -0
- package/dist/{chunk-OCSU2S6W.js → chunk-WX3GZVFG.js} +2 -1
- package/dist/{chunk-CKOOVZOI.js → chunk-YKMBFQC2.js} +37 -5
- package/dist/{chunk-S3V6R3EN.js → chunk-ZU2TP7CN.js} +70 -27
- package/dist/cli/index.js +211 -278
- package/dist/client-OKJJ3UP2.js +19 -0
- package/dist/client-UQBGCIPA.js +20 -0
- package/dist/conduct-4JDMWBQD.js +22 -0
- package/dist/{conduct-AZFLNUX3.js → conduct-VYYBCPHA.js} +14 -13
- package/dist/{conductor-mode-WKB42PYM.js → conductor-mode-OPGQJFLA.js} +12 -8
- package/dist/{conductor-mode-PLTB6MS3.js → conductor-mode-SBDCRIX6.js} +16 -11
- package/dist/execute-FZLQGIXB.js +14 -0
- package/dist/execute-TEZPQ5WP.js +15 -0
- package/dist/index.d.ts +172 -11
- package/dist/index.js +1529 -433
- package/dist/{process-guard-GH5LRNWO.js → process-guard-TNSUNHSR.js} +1 -1
- package/dist/{publish-capability-QDR2QIZ2.js → publish-capability-HVYILTPR.js} +4 -3
- package/dist/{reliability-metrics-QG7WC5QK.js → reliability-metrics-G7LPUYJD.js} +3 -1
- package/dist/reliability-metrics-RRUKJ4ME.js +20 -0
- package/dist/{request-NX7GSPIG.js → request-KJNKR27T.js} +96 -43
- package/dist/{serve-skill-E6EJQYAK.js → serve-skill-GC6NIQ5T.js} +10 -11
- package/dist/{server-VBCT32FC.js → server-YV3XPTX5.js} +11 -10
- package/dist/{service-coordinator-KMSA6BST.js → service-coordinator-RY5AKUZS.js} +420 -171
- package/dist/{skill-config-FETXPNVP.js → skill-config-5O2VR546.js} +1 -1
- package/dist/skills/agentbnb/bootstrap.js +550 -231
- package/dist/websocket-client-3U27WJUU.js +7 -0
- package/dist/{websocket-client-4Z5P54RU.js → websocket-client-SNDF3B6N.js} +1 -1
- package/package.json +18 -12
- package/skills/agentbnb/install.sh +0 -0
- package/dist/chunk-MCED4GDW.js +0 -1572
- package/dist/chunk-NWIQJ2CL.js +0 -108
- package/dist/chunk-WNXXLCV5.js +0 -32
- package/dist/client-XOLP5IUZ.js +0 -12
- package/dist/conduct-VPUYTNEA.js +0 -21
- package/dist/execute-NNDCXTN4.js +0 -13
- package/dist/execute-RIRHTIBU.js +0 -16
- package/dist/websocket-client-QOVARTRN.js +0 -7
package/dist/index.js
CHANGED
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
RelayClient,
|
|
3
|
-
RelayMessageSchema
|
|
4
|
-
} from "./chunk-3LWBH7P3.js";
|
|
5
|
-
|
|
6
1
|
// src/types/index.ts
|
|
7
2
|
import { z } from "zod";
|
|
8
3
|
var IOSchemaSchema = z.object({
|
|
@@ -40,6 +35,16 @@ var CapabilityCardSchema = z.object({
|
|
|
40
35
|
schedule: z.string().optional()
|
|
41
36
|
// cron expression
|
|
42
37
|
}),
|
|
38
|
+
/**
|
|
39
|
+
* Provider-estimated typical execution duration in milliseconds.
|
|
40
|
+
* Used by requesters to derive default client-side timeouts.
|
|
41
|
+
*/
|
|
42
|
+
expected_duration_ms: z.number().positive().optional(),
|
|
43
|
+
/**
|
|
44
|
+
* Provider hard timeout in milliseconds.
|
|
45
|
+
* Used as a fallback timeout hint when expected_duration_ms is unavailable.
|
|
46
|
+
*/
|
|
47
|
+
hard_timeout_ms: z.number().positive().optional(),
|
|
43
48
|
powered_by: z.array(PoweredBySchema).optional(),
|
|
44
49
|
/**
|
|
45
50
|
* Private per-card metadata. Stripped from all API and CLI responses —
|
|
@@ -93,6 +98,16 @@ var SkillSchema = z.object({
|
|
|
93
98
|
credits_per_minute: z.number().nonnegative().optional(),
|
|
94
99
|
free_tier: z.number().nonnegative().optional()
|
|
95
100
|
}),
|
|
101
|
+
/**
|
|
102
|
+
* Provider-estimated typical execution duration in milliseconds.
|
|
103
|
+
* Used by requesters to derive default client-side timeouts.
|
|
104
|
+
*/
|
|
105
|
+
expected_duration_ms: z.number().positive().optional(),
|
|
106
|
+
/**
|
|
107
|
+
* Provider hard timeout in milliseconds.
|
|
108
|
+
* Used as a fallback timeout hint when expected_duration_ms is unavailable.
|
|
109
|
+
*/
|
|
110
|
+
hard_timeout_ms: z.number().positive().optional(),
|
|
96
111
|
/** Per-skill online flag — overrides card-level availability for this skill. */
|
|
97
112
|
availability: z.object({ online: z.boolean() }).optional(),
|
|
98
113
|
powered_by: z.array(PoweredBySchema).optional(),
|
|
@@ -343,6 +358,61 @@ function initEvolutionTable(db) {
|
|
|
343
358
|
`);
|
|
344
359
|
}
|
|
345
360
|
|
|
361
|
+
// src/identity/agent-identity.ts
|
|
362
|
+
var AGENTS_SCHEMA = `
|
|
363
|
+
CREATE TABLE IF NOT EXISTS agents (
|
|
364
|
+
agent_id TEXT PRIMARY KEY,
|
|
365
|
+
display_name TEXT NOT NULL,
|
|
366
|
+
public_key TEXT NOT NULL,
|
|
367
|
+
operator_id TEXT,
|
|
368
|
+
server_id TEXT,
|
|
369
|
+
legacy_owner TEXT,
|
|
370
|
+
created_at TEXT NOT NULL,
|
|
371
|
+
updated_at TEXT NOT NULL
|
|
372
|
+
);
|
|
373
|
+
|
|
374
|
+
CREATE INDEX IF NOT EXISTS idx_agents_operator ON agents(operator_id);
|
|
375
|
+
CREATE INDEX IF NOT EXISTS idx_agents_legacy_owner ON agents(legacy_owner);
|
|
376
|
+
`;
|
|
377
|
+
function ensureAgentsTable(db) {
|
|
378
|
+
db.exec(AGENTS_SCHEMA);
|
|
379
|
+
}
|
|
380
|
+
function lookupAgent(db, agentId) {
|
|
381
|
+
return db.prepare("SELECT * FROM agents WHERE agent_id = ?").get(agentId) ?? null;
|
|
382
|
+
}
|
|
383
|
+
function lookupAgentByOwner(db, owner) {
|
|
384
|
+
return db.prepare("SELECT * FROM agents WHERE legacy_owner = ?").get(owner) ?? null;
|
|
385
|
+
}
|
|
386
|
+
function resolveCanonicalIdentity(db, identifier) {
|
|
387
|
+
ensureAgentsTable(db);
|
|
388
|
+
if (/^[a-f0-9]{16}$/.test(identifier)) {
|
|
389
|
+
const byAgentId = lookupAgent(db, identifier);
|
|
390
|
+
if (byAgentId) {
|
|
391
|
+
return {
|
|
392
|
+
agent_id: byAgentId.agent_id,
|
|
393
|
+
legacy_owner: byAgentId.legacy_owner,
|
|
394
|
+
resolved: true,
|
|
395
|
+
source: "agent_id"
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
const byOwner = lookupAgentByOwner(db, identifier);
|
|
400
|
+
if (byOwner) {
|
|
401
|
+
return {
|
|
402
|
+
agent_id: byOwner.agent_id,
|
|
403
|
+
legacy_owner: byOwner.legacy_owner,
|
|
404
|
+
resolved: true,
|
|
405
|
+
source: "legacy_owner"
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
return {
|
|
409
|
+
agent_id: identifier,
|
|
410
|
+
legacy_owner: null,
|
|
411
|
+
resolved: false,
|
|
412
|
+
source: "unresolved"
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
|
|
346
416
|
// src/registry/store.ts
|
|
347
417
|
var V2_FTS_TRIGGERS = `
|
|
348
418
|
DROP TRIGGER IF EXISTS cards_ai;
|
|
@@ -562,6 +632,7 @@ function openDatabase(path = ":memory:") {
|
|
|
562
632
|
ON capability_cards(owner);
|
|
563
633
|
`);
|
|
564
634
|
createRequestLogTable(db);
|
|
635
|
+
ensureAgentsTable(db);
|
|
565
636
|
initFeedbackTable(db);
|
|
566
637
|
initEvolutionTable(db);
|
|
567
638
|
runMigrations(db);
|
|
@@ -667,7 +738,11 @@ function rebuildCardsFts(db) {
|
|
|
667
738
|
}
|
|
668
739
|
function insertCard(db, card) {
|
|
669
740
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
670
|
-
const withTimestamps =
|
|
741
|
+
const withTimestamps = attachCanonicalAgentId(db, {
|
|
742
|
+
...card,
|
|
743
|
+
created_at: card.created_at ?? now,
|
|
744
|
+
updated_at: now
|
|
745
|
+
});
|
|
671
746
|
const parsed = CapabilityCardSchema.safeParse(withTimestamps);
|
|
672
747
|
if (!parsed.success) {
|
|
673
748
|
throw new AgentBnBError(
|
|
@@ -693,6 +768,19 @@ function getCard(db, id) {
|
|
|
693
768
|
if (!row) return null;
|
|
694
769
|
return JSON.parse(row.data);
|
|
695
770
|
}
|
|
771
|
+
function attachCanonicalAgentId(db, card) {
|
|
772
|
+
const resolved = resolveCanonicalIdentity(db, card.owner);
|
|
773
|
+
if (!resolved.resolved) {
|
|
774
|
+
return card;
|
|
775
|
+
}
|
|
776
|
+
if (card.agent_id === resolved.agent_id) {
|
|
777
|
+
return card;
|
|
778
|
+
}
|
|
779
|
+
return {
|
|
780
|
+
...card,
|
|
781
|
+
agent_id: resolved.agent_id
|
|
782
|
+
};
|
|
783
|
+
}
|
|
696
784
|
function updateReputation(db, cardId, success, latencyMs) {
|
|
697
785
|
const existing = getCard(db, cardId);
|
|
698
786
|
if (!existing) return;
|
|
@@ -717,16 +805,35 @@ function updateReputation(db, cardId, success, latencyMs) {
|
|
|
717
805
|
stmt.run(JSON.stringify(updatedCard), now, cardId);
|
|
718
806
|
}
|
|
719
807
|
function listCards(db, owner) {
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
808
|
+
if (owner === void 0) {
|
|
809
|
+
const rows = db.prepare("SELECT data FROM capability_cards").all();
|
|
810
|
+
return rows.map((row) => JSON.parse(row.data));
|
|
811
|
+
}
|
|
812
|
+
const filtered = /* @__PURE__ */ new Map();
|
|
813
|
+
const resolved = resolveCanonicalIdentity(db, owner);
|
|
814
|
+
const ownerAliases = /* @__PURE__ */ new Set([owner]);
|
|
815
|
+
if (resolved.legacy_owner) {
|
|
816
|
+
ownerAliases.add(resolved.legacy_owner);
|
|
817
|
+
}
|
|
818
|
+
for (const alias of ownerAliases) {
|
|
819
|
+
const aliasRows = db.prepare("SELECT data FROM capability_cards WHERE owner = ?").all(alias);
|
|
820
|
+
for (const row of aliasRows) {
|
|
821
|
+
const parsed = JSON.parse(row.data);
|
|
822
|
+
filtered.set(parsed.id, parsed);
|
|
823
|
+
}
|
|
728
824
|
}
|
|
729
|
-
|
|
825
|
+
const agentIdentifiers = /* @__PURE__ */ new Set([owner]);
|
|
826
|
+
if (resolved.resolved) {
|
|
827
|
+
agentIdentifiers.add(resolved.agent_id);
|
|
828
|
+
}
|
|
829
|
+
for (const agentIdentifier of agentIdentifiers) {
|
|
830
|
+
const agentRows = db.prepare("SELECT data FROM capability_cards WHERE json_extract(data, '$.agent_id') = ?").all(agentIdentifier);
|
|
831
|
+
for (const row of agentRows) {
|
|
832
|
+
const parsed = JSON.parse(row.data);
|
|
833
|
+
filtered.set(parsed.id, parsed);
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
return [...filtered.values()];
|
|
730
837
|
}
|
|
731
838
|
function getCardsByCapabilityType(db, capabilityType) {
|
|
732
839
|
const rows = db.prepare(
|
|
@@ -915,6 +1022,122 @@ function applyReputationFilter(db, cards, minReputation) {
|
|
|
915
1022
|
import Database2 from "better-sqlite3";
|
|
916
1023
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
917
1024
|
|
|
1025
|
+
// src/credit/owner-normalization.ts
|
|
1026
|
+
var RESERVED_OWNERS = /* @__PURE__ */ new Set(["platform_treasury"]);
|
|
1027
|
+
function tableExists(db, table) {
|
|
1028
|
+
const row = db.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?").get(table);
|
|
1029
|
+
return row !== void 0;
|
|
1030
|
+
}
|
|
1031
|
+
function mergeBalanceRows(db, oldOwner, newOwner, now) {
|
|
1032
|
+
if (!tableExists(db, "credit_balances")) return;
|
|
1033
|
+
const oldRow = db.prepare("SELECT balance FROM credit_balances WHERE owner = ?").get(oldOwner);
|
|
1034
|
+
if (!oldRow) return;
|
|
1035
|
+
const newRow = db.prepare("SELECT balance FROM credit_balances WHERE owner = ?").get(newOwner);
|
|
1036
|
+
if (newRow) {
|
|
1037
|
+
db.prepare(
|
|
1038
|
+
"UPDATE credit_balances SET balance = balance + ?, updated_at = ? WHERE owner = ?"
|
|
1039
|
+
).run(oldRow.balance, now, newOwner);
|
|
1040
|
+
db.prepare("DELETE FROM credit_balances WHERE owner = ?").run(oldOwner);
|
|
1041
|
+
return;
|
|
1042
|
+
}
|
|
1043
|
+
db.prepare("UPDATE credit_balances SET owner = ?, updated_at = ? WHERE owner = ?").run(
|
|
1044
|
+
newOwner,
|
|
1045
|
+
now,
|
|
1046
|
+
oldOwner
|
|
1047
|
+
);
|
|
1048
|
+
}
|
|
1049
|
+
function mergeProviderRegistryRows(db, oldOwner, newOwner) {
|
|
1050
|
+
if (!tableExists(db, "provider_registry")) return;
|
|
1051
|
+
const oldRow = db.prepare("SELECT provider_number FROM provider_registry WHERE owner = ?").get(oldOwner);
|
|
1052
|
+
if (!oldRow) return;
|
|
1053
|
+
const newRow = db.prepare("SELECT provider_number FROM provider_registry WHERE owner = ?").get(newOwner);
|
|
1054
|
+
if (newRow) {
|
|
1055
|
+
db.prepare("DELETE FROM provider_registry WHERE owner = ?").run(oldOwner);
|
|
1056
|
+
return;
|
|
1057
|
+
}
|
|
1058
|
+
db.prepare("UPDATE provider_registry SET owner = ? WHERE owner = ?").run(newOwner, oldOwner);
|
|
1059
|
+
}
|
|
1060
|
+
function mergeReliabilityRows(db, oldOwner, newOwner, now) {
|
|
1061
|
+
if (!tableExists(db, "provider_reliability_metrics")) return;
|
|
1062
|
+
const oldRow = db.prepare("SELECT * FROM provider_reliability_metrics WHERE owner = ?").get(oldOwner);
|
|
1063
|
+
if (!oldRow) return;
|
|
1064
|
+
const newRow = db.prepare("SELECT * FROM provider_reliability_metrics WHERE owner = ?").get(newOwner);
|
|
1065
|
+
if (!newRow) {
|
|
1066
|
+
db.prepare(
|
|
1067
|
+
"UPDATE provider_reliability_metrics SET owner = ?, updated_at = ? WHERE owner = ?"
|
|
1068
|
+
).run(newOwner, now, oldOwner);
|
|
1069
|
+
return;
|
|
1070
|
+
}
|
|
1071
|
+
const cycleStartCandidates = [oldRow.cycle_start, newRow.cycle_start].filter(Boolean);
|
|
1072
|
+
const mergedCycleStart = cycleStartCandidates.length > 0 ? cycleStartCandidates.slice().sort((left, right) => left.localeCompare(right))[0] : now;
|
|
1073
|
+
db.prepare(
|
|
1074
|
+
`UPDATE provider_reliability_metrics
|
|
1075
|
+
SET current_streak = ?,
|
|
1076
|
+
longest_streak = ?,
|
|
1077
|
+
total_hires = ?,
|
|
1078
|
+
repeat_hires = ?,
|
|
1079
|
+
feedback_count = ?,
|
|
1080
|
+
feedback_sum = ?,
|
|
1081
|
+
availability_checks = ?,
|
|
1082
|
+
availability_hits = ?,
|
|
1083
|
+
cycle_start = ?,
|
|
1084
|
+
updated_at = ?
|
|
1085
|
+
WHERE owner = ?`
|
|
1086
|
+
).run(
|
|
1087
|
+
Math.max(oldRow.current_streak, newRow.current_streak),
|
|
1088
|
+
Math.max(oldRow.longest_streak, newRow.longest_streak),
|
|
1089
|
+
oldRow.total_hires + newRow.total_hires,
|
|
1090
|
+
oldRow.repeat_hires + newRow.repeat_hires,
|
|
1091
|
+
oldRow.feedback_count + newRow.feedback_count,
|
|
1092
|
+
oldRow.feedback_sum + newRow.feedback_sum,
|
|
1093
|
+
oldRow.availability_checks + newRow.availability_checks,
|
|
1094
|
+
oldRow.availability_hits + newRow.availability_hits,
|
|
1095
|
+
mergedCycleStart,
|
|
1096
|
+
now,
|
|
1097
|
+
newOwner
|
|
1098
|
+
);
|
|
1099
|
+
db.prepare("DELETE FROM provider_reliability_metrics WHERE owner = ?").run(oldOwner);
|
|
1100
|
+
}
|
|
1101
|
+
function migrateCreditOwnerData(db, oldOwner, newOwner) {
|
|
1102
|
+
if (!oldOwner || !newOwner || oldOwner === newOwner) return;
|
|
1103
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1104
|
+
db.transaction(() => {
|
|
1105
|
+
mergeBalanceRows(db, oldOwner, newOwner, now);
|
|
1106
|
+
if (tableExists(db, "credit_transactions")) {
|
|
1107
|
+
db.prepare("UPDATE credit_transactions SET owner = ? WHERE owner = ?").run(newOwner, oldOwner);
|
|
1108
|
+
}
|
|
1109
|
+
if (tableExists(db, "credit_escrow")) {
|
|
1110
|
+
db.prepare("UPDATE credit_escrow SET owner = ? WHERE owner = ?").run(newOwner, oldOwner);
|
|
1111
|
+
}
|
|
1112
|
+
mergeProviderRegistryRows(db, oldOwner, newOwner);
|
|
1113
|
+
if (tableExists(db, "demand_vouchers")) {
|
|
1114
|
+
db.prepare("UPDATE demand_vouchers SET owner = ? WHERE owner = ?").run(newOwner, oldOwner);
|
|
1115
|
+
}
|
|
1116
|
+
mergeReliabilityRows(db, oldOwner, newOwner, now);
|
|
1117
|
+
})();
|
|
1118
|
+
}
|
|
1119
|
+
function canonicalizeCreditOwner(db, owner) {
|
|
1120
|
+
if (!owner || RESERVED_OWNERS.has(owner)) {
|
|
1121
|
+
return owner;
|
|
1122
|
+
}
|
|
1123
|
+
ensureAgentsTable(db);
|
|
1124
|
+
const resolved = resolveCanonicalIdentity(db, owner);
|
|
1125
|
+
if (!resolved.resolved) {
|
|
1126
|
+
return owner;
|
|
1127
|
+
}
|
|
1128
|
+
const aliases = /* @__PURE__ */ new Set();
|
|
1129
|
+
if (owner !== resolved.agent_id) {
|
|
1130
|
+
aliases.add(owner);
|
|
1131
|
+
}
|
|
1132
|
+
if (resolved.legacy_owner && resolved.legacy_owner !== resolved.agent_id) {
|
|
1133
|
+
aliases.add(resolved.legacy_owner);
|
|
1134
|
+
}
|
|
1135
|
+
for (const alias of aliases) {
|
|
1136
|
+
migrateCreditOwnerData(db, alias, resolved.agent_id);
|
|
1137
|
+
}
|
|
1138
|
+
return resolved.agent_id;
|
|
1139
|
+
}
|
|
1140
|
+
|
|
918
1141
|
// src/credit/reliability-metrics.ts
|
|
919
1142
|
var RELIABILITY_METRICS_SCHEMA = `
|
|
920
1143
|
CREATE TABLE IF NOT EXISTS provider_reliability_metrics (
|
|
@@ -935,6 +1158,7 @@ function ensureReliabilityTable(db) {
|
|
|
935
1158
|
db.exec(RELIABILITY_METRICS_SCHEMA);
|
|
936
1159
|
}
|
|
937
1160
|
function ensureRow(db, owner) {
|
|
1161
|
+
const canonicalOwner = canonicalizeCreditOwner(db, owner);
|
|
938
1162
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
939
1163
|
db.prepare(
|
|
940
1164
|
`INSERT OR IGNORE INTO provider_reliability_metrics
|
|
@@ -942,18 +1166,20 @@ function ensureRow(db, owner) {
|
|
|
942
1166
|
feedback_count, feedback_sum, availability_checks, availability_hits,
|
|
943
1167
|
cycle_start, updated_at)
|
|
944
1168
|
VALUES (?, 0, 0, 0, 0, 0, 0, 0, 0, ?, ?)`
|
|
945
|
-
).run(
|
|
1169
|
+
).run(canonicalOwner, now, now);
|
|
946
1170
|
}
|
|
947
1171
|
function recordSuccessfulHire(db, providerOwner, consumerOwner) {
|
|
1172
|
+
const canonicalProviderOwner = canonicalizeCreditOwner(db, providerOwner);
|
|
1173
|
+
const canonicalConsumerOwner = canonicalizeCreditOwner(db, consumerOwner);
|
|
948
1174
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
949
|
-
ensureRow(db,
|
|
1175
|
+
ensureRow(db, canonicalProviderOwner);
|
|
950
1176
|
const isRepeat = db.prepare(
|
|
951
1177
|
`SELECT COUNT(*) as cnt FROM credit_transactions
|
|
952
1178
|
WHERE owner = ? AND reason = 'settlement'
|
|
953
1179
|
AND reference_id IN (
|
|
954
1180
|
SELECT id FROM credit_escrow WHERE owner = ?
|
|
955
1181
|
)`
|
|
956
|
-
).get(
|
|
1182
|
+
).get(canonicalProviderOwner, canonicalConsumerOwner);
|
|
957
1183
|
const repeatIncrement = (isRepeat?.cnt ?? 0) > 0 ? 1 : 0;
|
|
958
1184
|
db.prepare(
|
|
959
1185
|
`UPDATE provider_reliability_metrics
|
|
@@ -963,27 +1189,7 @@ function recordSuccessfulHire(db, providerOwner, consumerOwner) {
|
|
|
963
1189
|
repeat_hires = repeat_hires + ?,
|
|
964
1190
|
updated_at = ?
|
|
965
1191
|
WHERE owner = ?`
|
|
966
|
-
).run(repeatIncrement, now,
|
|
967
|
-
}
|
|
968
|
-
|
|
969
|
-
// src/identity/agent-identity.ts
|
|
970
|
-
var AGENTS_SCHEMA = `
|
|
971
|
-
CREATE TABLE IF NOT EXISTS agents (
|
|
972
|
-
agent_id TEXT PRIMARY KEY,
|
|
973
|
-
display_name TEXT NOT NULL,
|
|
974
|
-
public_key TEXT NOT NULL,
|
|
975
|
-
operator_id TEXT,
|
|
976
|
-
server_id TEXT,
|
|
977
|
-
legacy_owner TEXT,
|
|
978
|
-
created_at TEXT NOT NULL,
|
|
979
|
-
updated_at TEXT NOT NULL
|
|
980
|
-
);
|
|
981
|
-
|
|
982
|
-
CREATE INDEX IF NOT EXISTS idx_agents_operator ON agents(operator_id);
|
|
983
|
-
CREATE INDEX IF NOT EXISTS idx_agents_legacy_owner ON agents(legacy_owner);
|
|
984
|
-
`;
|
|
985
|
-
function ensureAgentsTable(db) {
|
|
986
|
-
db.exec(AGENTS_SCHEMA);
|
|
1192
|
+
).run(repeatIncrement, now, canonicalProviderOwner);
|
|
987
1193
|
}
|
|
988
1194
|
|
|
989
1195
|
// src/credit/ledger.ts
|
|
@@ -1046,19 +1252,22 @@ function openCreditDb(path = ":memory:") {
|
|
|
1046
1252
|
return db;
|
|
1047
1253
|
}
|
|
1048
1254
|
function getBalance(db, owner) {
|
|
1049
|
-
const
|
|
1255
|
+
const canonicalOwner = canonicalizeCreditOwner(db, owner);
|
|
1256
|
+
const row = db.prepare("SELECT balance FROM credit_balances WHERE owner = ?").get(canonicalOwner);
|
|
1050
1257
|
return row?.balance ?? 0;
|
|
1051
1258
|
}
|
|
1052
1259
|
function registerProvider(db, owner) {
|
|
1260
|
+
const canonicalOwner = canonicalizeCreditOwner(db, owner);
|
|
1053
1261
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1054
1262
|
const maxRow = db.prepare("SELECT MAX(provider_number) as maxNum FROM provider_registry").get();
|
|
1055
1263
|
const nextNum = (maxRow?.maxNum ?? 0) + 1;
|
|
1056
|
-
db.prepare("INSERT OR IGNORE INTO provider_registry (owner, provider_number, registered_at) VALUES (?, ?, ?)").run(
|
|
1057
|
-
const row = db.prepare("SELECT provider_number FROM provider_registry WHERE owner = ?").get(
|
|
1264
|
+
db.prepare("INSERT OR IGNORE INTO provider_registry (owner, provider_number, registered_at) VALUES (?, ?, ?)").run(canonicalOwner, nextNum, now);
|
|
1265
|
+
const row = db.prepare("SELECT provider_number FROM provider_registry WHERE owner = ?").get(canonicalOwner);
|
|
1058
1266
|
return row.provider_number;
|
|
1059
1267
|
}
|
|
1060
1268
|
function getProviderNumber(db, owner) {
|
|
1061
|
-
const
|
|
1269
|
+
const canonicalOwner = canonicalizeCreditOwner(db, owner);
|
|
1270
|
+
const row = db.prepare("SELECT provider_number FROM provider_registry WHERE owner = ?").get(canonicalOwner);
|
|
1062
1271
|
return row?.provider_number ?? null;
|
|
1063
1272
|
}
|
|
1064
1273
|
function getProviderBonus(providerNumber) {
|
|
@@ -1067,10 +1276,11 @@ function getProviderBonus(providerNumber) {
|
|
|
1067
1276
|
return 1;
|
|
1068
1277
|
}
|
|
1069
1278
|
function getActiveVoucher(db, owner) {
|
|
1279
|
+
const canonicalOwner = canonicalizeCreditOwner(db, owner);
|
|
1070
1280
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1071
1281
|
const row = db.prepare(
|
|
1072
1282
|
"SELECT id, remaining, expires_at FROM demand_vouchers WHERE owner = ? AND is_active = 1 AND remaining > 0 AND expires_at > ? ORDER BY created_at ASC LIMIT 1"
|
|
1073
|
-
).get(
|
|
1283
|
+
).get(canonicalOwner, now);
|
|
1074
1284
|
return row ?? null;
|
|
1075
1285
|
}
|
|
1076
1286
|
function consumeVoucher(db, voucherId, amount) {
|
|
@@ -1079,6 +1289,7 @@ function consumeVoucher(db, voucherId, amount) {
|
|
|
1079
1289
|
).run(amount, voucherId, amount);
|
|
1080
1290
|
}
|
|
1081
1291
|
function recordEarning(db, owner, amount, _cardId, receiptNonce) {
|
|
1292
|
+
const canonicalOwner = canonicalizeCreditOwner(db, owner);
|
|
1082
1293
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1083
1294
|
db.transaction(() => {
|
|
1084
1295
|
const existing = db.prepare(
|
|
@@ -1087,13 +1298,13 @@ function recordEarning(db, owner, amount, _cardId, receiptNonce) {
|
|
|
1087
1298
|
if (existing) return;
|
|
1088
1299
|
db.prepare(
|
|
1089
1300
|
"INSERT OR IGNORE INTO credit_balances (owner, balance, updated_at) VALUES (?, 0, ?)"
|
|
1090
|
-
).run(
|
|
1301
|
+
).run(canonicalOwner, now);
|
|
1091
1302
|
db.prepare(
|
|
1092
1303
|
"UPDATE credit_balances SET balance = balance + ?, updated_at = ? WHERE owner = ?"
|
|
1093
|
-
).run(amount, now,
|
|
1304
|
+
).run(amount, now, canonicalOwner);
|
|
1094
1305
|
db.prepare(
|
|
1095
1306
|
"INSERT INTO credit_transactions (id, owner, amount, reason, reference_id, created_at) VALUES (?, ?, ?, ?, ?, ?)"
|
|
1096
|
-
).run(randomUUID3(),
|
|
1307
|
+
).run(randomUUID3(), canonicalOwner, amount, "remote_earning", receiptNonce, now);
|
|
1097
1308
|
})();
|
|
1098
1309
|
}
|
|
1099
1310
|
|
|
@@ -1107,59 +1318,116 @@ import { randomUUID as randomUUID5 } from "crypto";
|
|
|
1107
1318
|
// src/credit/escrow.ts
|
|
1108
1319
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
1109
1320
|
var NETWORK_FEE_RATE = 0.05;
|
|
1321
|
+
var FINALIZABLE_ESCROW_STATUSES = /* @__PURE__ */ new Set([
|
|
1322
|
+
"held",
|
|
1323
|
+
"started",
|
|
1324
|
+
"progressing",
|
|
1325
|
+
"abandoned"
|
|
1326
|
+
]);
|
|
1327
|
+
var TERMINAL_ESCROW_STATUSES = /* @__PURE__ */ new Set(["settled", "released"]);
|
|
1328
|
+
function getEscrowForMutation(db, escrowId) {
|
|
1329
|
+
const escrow = db.prepare("SELECT id, owner, amount, status, funding_source FROM credit_escrow WHERE id = ?").get(escrowId);
|
|
1330
|
+
if (!escrow) {
|
|
1331
|
+
throw new AgentBnBError(`Escrow not found: ${escrowId}`, "ESCROW_NOT_FOUND");
|
|
1332
|
+
}
|
|
1333
|
+
return {
|
|
1334
|
+
...escrow,
|
|
1335
|
+
owner: canonicalizeCreditOwner(db, escrow.owner)
|
|
1336
|
+
};
|
|
1337
|
+
}
|
|
1338
|
+
function updateEscrowStatus(db, escrowId, fromStatuses, toStatus) {
|
|
1339
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1340
|
+
const transition = db.transaction(() => {
|
|
1341
|
+
const escrow = getEscrowForMutation(db, escrowId);
|
|
1342
|
+
const current = escrow.status;
|
|
1343
|
+
if (!fromStatuses.includes(current)) {
|
|
1344
|
+
throw new AgentBnBError(
|
|
1345
|
+
`Invalid escrow transition for ${escrowId}: ${current} -> ${toStatus}`,
|
|
1346
|
+
"ESCROW_INVALID_TRANSITION"
|
|
1347
|
+
);
|
|
1348
|
+
}
|
|
1349
|
+
if (current === toStatus) return;
|
|
1350
|
+
const settledAt = TERMINAL_ESCROW_STATUSES.has(toStatus) ? now : null;
|
|
1351
|
+
db.prepare("UPDATE credit_escrow SET status = ?, settled_at = ? WHERE id = ?").run(
|
|
1352
|
+
toStatus,
|
|
1353
|
+
settledAt,
|
|
1354
|
+
escrowId
|
|
1355
|
+
);
|
|
1356
|
+
});
|
|
1357
|
+
transition();
|
|
1358
|
+
}
|
|
1359
|
+
function assertEscrowCanFinalize(escrow) {
|
|
1360
|
+
const status = escrow.status;
|
|
1361
|
+
if (FINALIZABLE_ESCROW_STATUSES.has(status)) {
|
|
1362
|
+
return;
|
|
1363
|
+
}
|
|
1364
|
+
if (TERMINAL_ESCROW_STATUSES.has(status)) {
|
|
1365
|
+
throw new AgentBnBError(
|
|
1366
|
+
`Escrow ${escrow.id} is already ${status}`,
|
|
1367
|
+
"ESCROW_ALREADY_SETTLED"
|
|
1368
|
+
);
|
|
1369
|
+
}
|
|
1370
|
+
throw new AgentBnBError(
|
|
1371
|
+
`Escrow ${escrow.id} has invalid lifecycle status: ${escrow.status}`,
|
|
1372
|
+
"ESCROW_INVALID_TRANSITION"
|
|
1373
|
+
);
|
|
1374
|
+
}
|
|
1110
1375
|
function holdEscrow(db, owner, amount, cardId) {
|
|
1376
|
+
const canonicalOwner = canonicalizeCreditOwner(db, owner);
|
|
1111
1377
|
const escrowId = randomUUID4();
|
|
1112
1378
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1113
1379
|
const hold = db.transaction(() => {
|
|
1114
|
-
const voucher = getActiveVoucher(db,
|
|
1380
|
+
const voucher = getActiveVoucher(db, canonicalOwner);
|
|
1115
1381
|
if (voucher && voucher.remaining >= amount) {
|
|
1116
1382
|
consumeVoucher(db, voucher.id, amount);
|
|
1117
1383
|
db.prepare(
|
|
1118
1384
|
"INSERT INTO credit_escrow (id, owner, amount, card_id, status, created_at, funding_source) VALUES (?, ?, ?, ?, ?, ?, ?)"
|
|
1119
|
-
).run(escrowId,
|
|
1385
|
+
).run(escrowId, canonicalOwner, amount, cardId, "held", now, "voucher");
|
|
1120
1386
|
db.prepare(
|
|
1121
1387
|
"INSERT INTO credit_transactions (id, owner, amount, reason, reference_id, created_at) VALUES (?, ?, ?, ?, ?, ?)"
|
|
1122
|
-
).run(randomUUID4(),
|
|
1388
|
+
).run(randomUUID4(), canonicalOwner, -amount, "voucher_hold", escrowId, now);
|
|
1123
1389
|
} else {
|
|
1124
|
-
const row = db.prepare("SELECT balance FROM credit_balances WHERE owner = ?").get(
|
|
1390
|
+
const row = db.prepare("SELECT balance FROM credit_balances WHERE owner = ?").get(canonicalOwner);
|
|
1125
1391
|
if (!row || row.balance < amount) {
|
|
1126
1392
|
throw new AgentBnBError("Insufficient credits", "INSUFFICIENT_CREDITS");
|
|
1127
1393
|
}
|
|
1128
1394
|
db.prepare(
|
|
1129
1395
|
"UPDATE credit_balances SET balance = balance - ?, updated_at = ? WHERE owner = ? AND balance >= ?"
|
|
1130
|
-
).run(amount, now,
|
|
1396
|
+
).run(amount, now, canonicalOwner, amount);
|
|
1131
1397
|
db.prepare(
|
|
1132
1398
|
"INSERT INTO credit_escrow (id, owner, amount, card_id, status, created_at, funding_source) VALUES (?, ?, ?, ?, ?, ?, ?)"
|
|
1133
|
-
).run(escrowId,
|
|
1399
|
+
).run(escrowId, canonicalOwner, amount, cardId, "held", now, "balance");
|
|
1134
1400
|
db.prepare(
|
|
1135
1401
|
"INSERT INTO credit_transactions (id, owner, amount, reason, reference_id, created_at) VALUES (?, ?, ?, ?, ?, ?)"
|
|
1136
|
-
).run(randomUUID4(),
|
|
1402
|
+
).run(randomUUID4(), canonicalOwner, -amount, "escrow_hold", escrowId, now);
|
|
1137
1403
|
}
|
|
1138
1404
|
});
|
|
1139
1405
|
hold();
|
|
1140
1406
|
return escrowId;
|
|
1141
1407
|
}
|
|
1408
|
+
function markEscrowStarted(db, escrowId) {
|
|
1409
|
+
updateEscrowStatus(db, escrowId, ["held", "started"], "started");
|
|
1410
|
+
}
|
|
1411
|
+
function markEscrowProgressing(db, escrowId) {
|
|
1412
|
+
updateEscrowStatus(db, escrowId, ["held", "started", "progressing"], "progressing");
|
|
1413
|
+
}
|
|
1414
|
+
function markEscrowAbandoned(db, escrowId) {
|
|
1415
|
+
updateEscrowStatus(db, escrowId, ["started", "progressing", "abandoned"], "abandoned");
|
|
1416
|
+
}
|
|
1142
1417
|
function settleEscrow(db, escrowId, recipientOwner) {
|
|
1418
|
+
const canonicalRecipientOwner = canonicalizeCreditOwner(db, recipientOwner);
|
|
1143
1419
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1144
1420
|
const settle = db.transaction(() => {
|
|
1145
|
-
const escrow = db
|
|
1146
|
-
|
|
1147
|
-
throw new AgentBnBError(`Escrow not found: ${escrowId}`, "ESCROW_NOT_FOUND");
|
|
1148
|
-
}
|
|
1149
|
-
if (escrow.status !== "held") {
|
|
1150
|
-
throw new AgentBnBError(
|
|
1151
|
-
`Escrow ${escrowId} is already ${escrow.status}`,
|
|
1152
|
-
"ESCROW_ALREADY_SETTLED"
|
|
1153
|
-
);
|
|
1154
|
-
}
|
|
1421
|
+
const escrow = getEscrowForMutation(db, escrowId);
|
|
1422
|
+
assertEscrowCanFinalize(escrow);
|
|
1155
1423
|
const feeAmount = Math.floor(escrow.amount * NETWORK_FEE_RATE);
|
|
1156
1424
|
const providerAmount = escrow.amount - feeAmount;
|
|
1157
1425
|
db.prepare(
|
|
1158
1426
|
"INSERT OR IGNORE INTO credit_balances (owner, balance, updated_at) VALUES (?, 0, ?)"
|
|
1159
|
-
).run(
|
|
1427
|
+
).run(canonicalRecipientOwner, now);
|
|
1160
1428
|
db.prepare(
|
|
1161
1429
|
"UPDATE credit_balances SET balance = balance + ?, updated_at = ? WHERE owner = ?"
|
|
1162
|
-
).run(providerAmount, now,
|
|
1430
|
+
).run(providerAmount, now, canonicalRecipientOwner);
|
|
1163
1431
|
if (feeAmount > 0) {
|
|
1164
1432
|
db.prepare(
|
|
1165
1433
|
"INSERT OR IGNORE INTO credit_balances (owner, balance, updated_at) VALUES (?, 0, ?)"
|
|
@@ -1176,10 +1444,10 @@ function settleEscrow(db, escrowId, recipientOwner) {
|
|
|
1176
1444
|
).run("settled", now, escrowId);
|
|
1177
1445
|
db.prepare(
|
|
1178
1446
|
"INSERT INTO credit_transactions (id, owner, amount, reason, reference_id, created_at) VALUES (?, ?, ?, ?, ?, ?)"
|
|
1179
|
-
).run(randomUUID4(),
|
|
1180
|
-
let providerNum = getProviderNumber(db,
|
|
1447
|
+
).run(randomUUID4(), canonicalRecipientOwner, providerAmount, "settlement", escrowId, now);
|
|
1448
|
+
let providerNum = getProviderNumber(db, canonicalRecipientOwner);
|
|
1181
1449
|
if (providerNum === null) {
|
|
1182
|
-
providerNum = registerProvider(db,
|
|
1450
|
+
providerNum = registerProvider(db, canonicalRecipientOwner);
|
|
1183
1451
|
}
|
|
1184
1452
|
const bonus = getProviderBonus(providerNum);
|
|
1185
1453
|
if (bonus > 1) {
|
|
@@ -1190,14 +1458,14 @@ function settleEscrow(db, escrowId, recipientOwner) {
|
|
|
1190
1458
|
).run("platform_treasury", now);
|
|
1191
1459
|
db.prepare(
|
|
1192
1460
|
"UPDATE credit_balances SET balance = balance + ?, updated_at = ? WHERE owner = ?"
|
|
1193
|
-
).run(bonusAmount, now,
|
|
1461
|
+
).run(bonusAmount, now, canonicalRecipientOwner);
|
|
1194
1462
|
db.prepare(
|
|
1195
1463
|
"INSERT INTO credit_transactions (id, owner, amount, reason, reference_id, created_at) VALUES (?, ?, ?, ?, ?, ?)"
|
|
1196
|
-
).run(randomUUID4(),
|
|
1464
|
+
).run(randomUUID4(), canonicalRecipientOwner, bonusAmount, "provider_bonus", escrowId, now);
|
|
1197
1465
|
}
|
|
1198
1466
|
}
|
|
1199
1467
|
try {
|
|
1200
|
-
recordSuccessfulHire(db,
|
|
1468
|
+
recordSuccessfulHire(db, canonicalRecipientOwner, escrow.owner);
|
|
1201
1469
|
} catch {
|
|
1202
1470
|
}
|
|
1203
1471
|
});
|
|
@@ -1206,16 +1474,8 @@ function settleEscrow(db, escrowId, recipientOwner) {
|
|
|
1206
1474
|
function releaseEscrow(db, escrowId) {
|
|
1207
1475
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1208
1476
|
const release = db.transaction(() => {
|
|
1209
|
-
const escrow = db
|
|
1210
|
-
|
|
1211
|
-
throw new AgentBnBError(`Escrow not found: ${escrowId}`, "ESCROW_NOT_FOUND");
|
|
1212
|
-
}
|
|
1213
|
-
if (escrow.status !== "held") {
|
|
1214
|
-
throw new AgentBnBError(
|
|
1215
|
-
`Escrow ${escrowId} is already ${escrow.status}`,
|
|
1216
|
-
"ESCROW_ALREADY_SETTLED"
|
|
1217
|
-
);
|
|
1218
|
-
}
|
|
1477
|
+
const escrow = getEscrowForMutation(db, escrowId);
|
|
1478
|
+
assertEscrowCanFinalize(escrow);
|
|
1219
1479
|
db.prepare(
|
|
1220
1480
|
"UPDATE credit_balances SET balance = balance + ?, updated_at = ? WHERE owner = ?"
|
|
1221
1481
|
).run(escrow.amount, now, escrow.owner);
|
|
@@ -1231,16 +1491,8 @@ function releaseEscrow(db, escrowId) {
|
|
|
1231
1491
|
function confirmEscrowDebit(db, escrowId) {
|
|
1232
1492
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1233
1493
|
const confirm = db.transaction(() => {
|
|
1234
|
-
const escrow = db
|
|
1235
|
-
|
|
1236
|
-
throw new AgentBnBError(`Escrow not found: ${escrowId}`, "ESCROW_NOT_FOUND");
|
|
1237
|
-
}
|
|
1238
|
-
if (escrow.status !== "held") {
|
|
1239
|
-
throw new AgentBnBError(
|
|
1240
|
-
`Escrow ${escrowId} is already ${escrow.status}`,
|
|
1241
|
-
"ESCROW_ALREADY_SETTLED"
|
|
1242
|
-
);
|
|
1243
|
-
}
|
|
1494
|
+
const escrow = getEscrowForMutation(db, escrowId);
|
|
1495
|
+
assertEscrowCanFinalize(escrow);
|
|
1244
1496
|
db.prepare(
|
|
1245
1497
|
"UPDATE credit_escrow SET status = ?, settled_at = ? WHERE id = ?"
|
|
1246
1498
|
).run("settled", now, escrowId);
|
|
@@ -1251,111 +1503,21 @@ function confirmEscrowDebit(db, escrowId) {
|
|
|
1251
1503
|
confirm();
|
|
1252
1504
|
}
|
|
1253
1505
|
|
|
1254
|
-
// src/credit/signing.ts
|
|
1255
|
-
import { generateKeyPairSync, sign, verify, createPublicKey, createPrivateKey } from "crypto";
|
|
1256
|
-
import { writeFileSync, readFileSync, existsSync, chmodSync } from "fs";
|
|
1257
|
-
import { join } from "path";
|
|
1258
|
-
function generateKeyPair() {
|
|
1259
|
-
const { publicKey, privateKey } = generateKeyPairSync("ed25519", {
|
|
1260
|
-
publicKeyEncoding: { type: "spki", format: "der" },
|
|
1261
|
-
privateKeyEncoding: { type: "pkcs8", format: "der" }
|
|
1262
|
-
});
|
|
1263
|
-
return {
|
|
1264
|
-
publicKey: Buffer.from(publicKey),
|
|
1265
|
-
privateKey: Buffer.from(privateKey)
|
|
1266
|
-
};
|
|
1267
|
-
}
|
|
1268
|
-
function saveKeyPair(configDir, keys) {
|
|
1269
|
-
const privatePath = join(configDir, "private.key");
|
|
1270
|
-
const publicPath = join(configDir, "public.key");
|
|
1271
|
-
writeFileSync(privatePath, keys.privateKey);
|
|
1272
|
-
chmodSync(privatePath, 384);
|
|
1273
|
-
writeFileSync(publicPath, keys.publicKey);
|
|
1274
|
-
}
|
|
1275
|
-
function loadKeyPair(configDir) {
|
|
1276
|
-
const privatePath = join(configDir, "private.key");
|
|
1277
|
-
const publicPath = join(configDir, "public.key");
|
|
1278
|
-
if (!existsSync(privatePath) || !existsSync(publicPath)) {
|
|
1279
|
-
throw new AgentBnBError("Keypair not found. Run `agentbnb init` to generate one.", "KEYPAIR_NOT_FOUND");
|
|
1280
|
-
}
|
|
1281
|
-
return {
|
|
1282
|
-
publicKey: readFileSync(publicPath),
|
|
1283
|
-
privateKey: readFileSync(privatePath)
|
|
1284
|
-
};
|
|
1285
|
-
}
|
|
1286
|
-
function canonicalJson(data) {
|
|
1287
|
-
return JSON.stringify(sortForCanonicalJson(data));
|
|
1288
|
-
}
|
|
1289
|
-
function sortForCanonicalJson(value) {
|
|
1290
|
-
if (Array.isArray(value)) {
|
|
1291
|
-
return value.map((item) => sortForCanonicalJson(item));
|
|
1292
|
-
}
|
|
1293
|
-
if (value !== null && typeof value === "object") {
|
|
1294
|
-
const proto = Object.getPrototypeOf(value);
|
|
1295
|
-
if (proto === Object.prototype || proto === null) {
|
|
1296
|
-
const input = value;
|
|
1297
|
-
const output = {};
|
|
1298
|
-
const sortedKeys = Object.keys(input).sort();
|
|
1299
|
-
for (const key of sortedKeys) {
|
|
1300
|
-
output[key] = sortForCanonicalJson(input[key]);
|
|
1301
|
-
}
|
|
1302
|
-
return output;
|
|
1303
|
-
}
|
|
1304
|
-
}
|
|
1305
|
-
return value;
|
|
1306
|
-
}
|
|
1307
|
-
function signEscrowReceipt(data, privateKey) {
|
|
1308
|
-
const message = Buffer.from(canonicalJson(data), "utf-8");
|
|
1309
|
-
const keyObject = createPrivateKey({ key: privateKey, format: "der", type: "pkcs8" });
|
|
1310
|
-
const signature = sign(null, message, keyObject);
|
|
1311
|
-
return signature.toString("base64url");
|
|
1312
|
-
}
|
|
1313
|
-
function verifyEscrowReceipt(data, signature, publicKey) {
|
|
1314
|
-
try {
|
|
1315
|
-
const message = Buffer.from(canonicalJson(data), "utf-8");
|
|
1316
|
-
const keyObject = createPublicKey({ key: publicKey, format: "der", type: "spki" });
|
|
1317
|
-
const sigBuffer = Buffer.from(signature, "base64url");
|
|
1318
|
-
return verify(null, message, keyObject, sigBuffer);
|
|
1319
|
-
} catch {
|
|
1320
|
-
return false;
|
|
1321
|
-
}
|
|
1322
|
-
}
|
|
1323
|
-
|
|
1324
|
-
// src/credit/settlement.ts
|
|
1325
|
-
function settleProviderEarning(providerDb, providerOwner, receipt) {
|
|
1326
|
-
const feeAmount = Math.floor(receipt.amount * NETWORK_FEE_RATE);
|
|
1327
|
-
const providerAmount = receipt.amount - feeAmount;
|
|
1328
|
-
recordEarning(
|
|
1329
|
-
providerDb,
|
|
1330
|
-
providerOwner,
|
|
1331
|
-
providerAmount,
|
|
1332
|
-
receipt.card_id,
|
|
1333
|
-
receipt.nonce
|
|
1334
|
-
);
|
|
1335
|
-
return { settled: true };
|
|
1336
|
-
}
|
|
1337
|
-
function settleRequesterEscrow(requesterDb, escrowId) {
|
|
1338
|
-
confirmEscrowDebit(requesterDb, escrowId);
|
|
1339
|
-
}
|
|
1340
|
-
function releaseRequesterEscrow(requesterDb, escrowId) {
|
|
1341
|
-
releaseEscrow(requesterDb, escrowId);
|
|
1342
|
-
}
|
|
1343
|
-
|
|
1344
1506
|
// src/cli/config.ts
|
|
1345
|
-
import { readFileSync
|
|
1507
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
|
|
1346
1508
|
import { homedir } from "os";
|
|
1347
|
-
import { join
|
|
1509
|
+
import { join } from "path";
|
|
1348
1510
|
function getConfigDir() {
|
|
1349
|
-
return process.env["AGENTBNB_DIR"] ??
|
|
1511
|
+
return process.env["AGENTBNB_DIR"] ?? join(homedir(), ".agentbnb");
|
|
1350
1512
|
}
|
|
1351
1513
|
function getConfigPath() {
|
|
1352
|
-
return
|
|
1514
|
+
return join(getConfigDir(), "config.json");
|
|
1353
1515
|
}
|
|
1354
1516
|
function loadConfig() {
|
|
1355
1517
|
const configPath = getConfigPath();
|
|
1356
|
-
if (!
|
|
1518
|
+
if (!existsSync(configPath)) return null;
|
|
1357
1519
|
try {
|
|
1358
|
-
const raw =
|
|
1520
|
+
const raw = readFileSync(configPath, "utf-8");
|
|
1359
1521
|
return JSON.parse(raw);
|
|
1360
1522
|
} catch {
|
|
1361
1523
|
return null;
|
|
@@ -1508,23 +1670,17 @@ async function executeCapabilityRequest(opts) {
|
|
|
1508
1670
|
cardName = card.name;
|
|
1509
1671
|
}
|
|
1510
1672
|
let escrowId = null;
|
|
1511
|
-
let isRemoteEscrow = false;
|
|
1512
1673
|
if (relayAuthorized) {
|
|
1513
1674
|
} else if (receipt) {
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
}
|
|
1523
|
-
const receiptAge = Date.now() - new Date(receipt.timestamp).getTime();
|
|
1524
|
-
if (receiptAge > 5 * 60 * 1e3) {
|
|
1525
|
-
return { success: false, error: { code: -32603, message: "Escrow receipt expired" } };
|
|
1675
|
+
if (creditsNeeded > 0) {
|
|
1676
|
+
return {
|
|
1677
|
+
success: false,
|
|
1678
|
+
error: {
|
|
1679
|
+
code: -32603,
|
|
1680
|
+
message: "Direct HTTP paid remote settlement is disabled. Route paid requests through relay."
|
|
1681
|
+
}
|
|
1682
|
+
};
|
|
1526
1683
|
}
|
|
1527
|
-
isRemoteEscrow = true;
|
|
1528
1684
|
} else {
|
|
1529
1685
|
try {
|
|
1530
1686
|
const balance = getBalance(creditDb, requester);
|
|
@@ -1538,9 +1694,8 @@ async function executeCapabilityRequest(opts) {
|
|
|
1538
1694
|
}
|
|
1539
1695
|
}
|
|
1540
1696
|
const startMs = Date.now();
|
|
1541
|
-
const receiptData = isRemoteEscrow ? { receipt_released: true } : void 0;
|
|
1542
1697
|
const handleFailure = (status, latencyMs, message, failureReason = "bad_execution") => {
|
|
1543
|
-
if (
|
|
1698
|
+
if (escrowId) releaseEscrow(creditDb, escrowId);
|
|
1544
1699
|
if (failureReason === "bad_execution" || failureReason === "auth_error") {
|
|
1545
1700
|
updateReputation(registryDb, cardId, false, latencyMs);
|
|
1546
1701
|
}
|
|
@@ -1559,15 +1714,10 @@ async function executeCapabilityRequest(opts) {
|
|
|
1559
1714
|
});
|
|
1560
1715
|
} catch {
|
|
1561
1716
|
}
|
|
1562
|
-
return {
|
|
1563
|
-
success: false,
|
|
1564
|
-
error: { code: -32603, message, ...receiptData ? { data: receiptData } : {} }
|
|
1565
|
-
};
|
|
1717
|
+
return { success: false, error: { code: -32603, message } };
|
|
1566
1718
|
};
|
|
1567
1719
|
const handleSuccess = (result, latencyMs) => {
|
|
1568
|
-
if (
|
|
1569
|
-
settleProviderEarning(creditDb, card.owner, receipt);
|
|
1570
|
-
} else if (escrowId) {
|
|
1720
|
+
if (escrowId) {
|
|
1571
1721
|
settleEscrow(creditDb, escrowId, card.owner);
|
|
1572
1722
|
}
|
|
1573
1723
|
updateReputation(registryDb, cardId, true, latencyMs);
|
|
@@ -1595,12 +1745,7 @@ async function executeCapabilityRequest(opts) {
|
|
|
1595
1745
|
latencyMs
|
|
1596
1746
|
}).catch(() => {
|
|
1597
1747
|
});
|
|
1598
|
-
|
|
1599
|
-
...typeof result === "object" && result !== null ? result : { data: result },
|
|
1600
|
-
receipt_settled: true,
|
|
1601
|
-
receipt_nonce: receipt.nonce
|
|
1602
|
-
} : result;
|
|
1603
|
-
return { success: true, result: successResult };
|
|
1748
|
+
return { success: true, result };
|
|
1604
1749
|
};
|
|
1605
1750
|
if (skillExecutor) {
|
|
1606
1751
|
let targetSkillId = resolvedSkillId ?? skillId;
|
|
@@ -1628,33 +1773,103 @@ async function executeCapabilityRequest(opts) {
|
|
|
1628
1773
|
return handleFailure("failure", Date.now() - startMs, message, "bad_execution");
|
|
1629
1774
|
}
|
|
1630
1775
|
}
|
|
1631
|
-
if (!handlerUrl) {
|
|
1632
|
-
return handleFailure("failure", Date.now() - startMs, "No skill executor or handler URL configured", "bad_execution");
|
|
1633
|
-
}
|
|
1634
|
-
const controller = new AbortController();
|
|
1635
|
-
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
1776
|
+
if (!handlerUrl) {
|
|
1777
|
+
return handleFailure("failure", Date.now() - startMs, "No skill executor or handler URL configured", "bad_execution");
|
|
1778
|
+
}
|
|
1779
|
+
const controller = new AbortController();
|
|
1780
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
1781
|
+
try {
|
|
1782
|
+
const response = await fetch(handlerUrl, {
|
|
1783
|
+
method: "POST",
|
|
1784
|
+
headers: { "Content-Type": "application/json" },
|
|
1785
|
+
body: JSON.stringify({ card_id: cardId, skill_id: resolvedSkillId, params }),
|
|
1786
|
+
signal: controller.signal
|
|
1787
|
+
});
|
|
1788
|
+
clearTimeout(timer);
|
|
1789
|
+
if (!response.ok) {
|
|
1790
|
+
return handleFailure("failure", Date.now() - startMs, `Handler returned ${response.status}`, "bad_execution");
|
|
1791
|
+
}
|
|
1792
|
+
const result = await response.json();
|
|
1793
|
+
return handleSuccess(result, Date.now() - startMs);
|
|
1794
|
+
} catch (err) {
|
|
1795
|
+
clearTimeout(timer);
|
|
1796
|
+
const isTimeout = err instanceof Error && err.name === "AbortError";
|
|
1797
|
+
return handleFailure(
|
|
1798
|
+
isTimeout ? "timeout" : "failure",
|
|
1799
|
+
Date.now() - startMs,
|
|
1800
|
+
isTimeout ? "Execution timeout" : "Handler error",
|
|
1801
|
+
isTimeout ? "timeout" : "bad_execution"
|
|
1802
|
+
);
|
|
1803
|
+
}
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1806
|
+
// src/credit/signing.ts
|
|
1807
|
+
import { generateKeyPairSync, sign, verify, createPublicKey, createPrivateKey } from "crypto";
|
|
1808
|
+
import { writeFileSync as writeFileSync2, readFileSync as readFileSync2, existsSync as existsSync2, chmodSync } from "fs";
|
|
1809
|
+
import { join as join2 } from "path";
|
|
1810
|
+
function generateKeyPair() {
|
|
1811
|
+
const { publicKey, privateKey } = generateKeyPairSync("ed25519", {
|
|
1812
|
+
publicKeyEncoding: { type: "spki", format: "der" },
|
|
1813
|
+
privateKeyEncoding: { type: "pkcs8", format: "der" }
|
|
1814
|
+
});
|
|
1815
|
+
return {
|
|
1816
|
+
publicKey: Buffer.from(publicKey),
|
|
1817
|
+
privateKey: Buffer.from(privateKey)
|
|
1818
|
+
};
|
|
1819
|
+
}
|
|
1820
|
+
function saveKeyPair(configDir, keys) {
|
|
1821
|
+
const privatePath = join2(configDir, "private.key");
|
|
1822
|
+
const publicPath = join2(configDir, "public.key");
|
|
1823
|
+
writeFileSync2(privatePath, keys.privateKey);
|
|
1824
|
+
chmodSync(privatePath, 384);
|
|
1825
|
+
writeFileSync2(publicPath, keys.publicKey);
|
|
1826
|
+
}
|
|
1827
|
+
function loadKeyPair(configDir) {
|
|
1828
|
+
const privatePath = join2(configDir, "private.key");
|
|
1829
|
+
const publicPath = join2(configDir, "public.key");
|
|
1830
|
+
if (!existsSync2(privatePath) || !existsSync2(publicPath)) {
|
|
1831
|
+
throw new AgentBnBError("Keypair not found. Run `agentbnb init` to generate one.", "KEYPAIR_NOT_FOUND");
|
|
1832
|
+
}
|
|
1833
|
+
return {
|
|
1834
|
+
publicKey: readFileSync2(publicPath),
|
|
1835
|
+
privateKey: readFileSync2(privatePath)
|
|
1836
|
+
};
|
|
1837
|
+
}
|
|
1838
|
+
function canonicalJson(data) {
|
|
1839
|
+
return JSON.stringify(sortForCanonicalJson(data));
|
|
1840
|
+
}
|
|
1841
|
+
function sortForCanonicalJson(value) {
|
|
1842
|
+
if (Array.isArray(value)) {
|
|
1843
|
+
return value.map((item) => sortForCanonicalJson(item));
|
|
1844
|
+
}
|
|
1845
|
+
if (value !== null && typeof value === "object") {
|
|
1846
|
+
const proto = Object.getPrototypeOf(value);
|
|
1847
|
+
if (proto === Object.prototype || proto === null) {
|
|
1848
|
+
const input = value;
|
|
1849
|
+
const output = {};
|
|
1850
|
+
const sortedKeys = Object.keys(input).sort();
|
|
1851
|
+
for (const key of sortedKeys) {
|
|
1852
|
+
output[key] = sortForCanonicalJson(input[key]);
|
|
1853
|
+
}
|
|
1854
|
+
return output;
|
|
1855
|
+
}
|
|
1856
|
+
}
|
|
1857
|
+
return value;
|
|
1858
|
+
}
|
|
1859
|
+
function signEscrowReceipt(data, privateKey) {
|
|
1860
|
+
const message = Buffer.from(canonicalJson(data), "utf-8");
|
|
1861
|
+
const keyObject = createPrivateKey({ key: privateKey, format: "der", type: "pkcs8" });
|
|
1862
|
+
const signature = sign(null, message, keyObject);
|
|
1863
|
+
return signature.toString("base64url");
|
|
1864
|
+
}
|
|
1865
|
+
function verifyEscrowReceipt(data, signature, publicKey) {
|
|
1636
1866
|
try {
|
|
1637
|
-
const
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
clearTimeout(timer);
|
|
1644
|
-
if (!response.ok) {
|
|
1645
|
-
return handleFailure("failure", Date.now() - startMs, `Handler returned ${response.status}`, "bad_execution");
|
|
1646
|
-
}
|
|
1647
|
-
const result = await response.json();
|
|
1648
|
-
return handleSuccess(result, Date.now() - startMs);
|
|
1649
|
-
} catch (err) {
|
|
1650
|
-
clearTimeout(timer);
|
|
1651
|
-
const isTimeout = err instanceof Error && err.name === "AbortError";
|
|
1652
|
-
return handleFailure(
|
|
1653
|
-
isTimeout ? "timeout" : "failure",
|
|
1654
|
-
Date.now() - startMs,
|
|
1655
|
-
isTimeout ? "Execution timeout" : "Handler error",
|
|
1656
|
-
isTimeout ? "timeout" : "bad_execution"
|
|
1657
|
-
);
|
|
1867
|
+
const message = Buffer.from(canonicalJson(data), "utf-8");
|
|
1868
|
+
const keyObject = createPublicKey({ key: publicKey, format: "der", type: "spki" });
|
|
1869
|
+
const sigBuffer = Buffer.from(signature, "base64url");
|
|
1870
|
+
return verify(null, message, keyObject, sigBuffer);
|
|
1871
|
+
} catch {
|
|
1872
|
+
return false;
|
|
1658
1873
|
}
|
|
1659
1874
|
}
|
|
1660
1875
|
|
|
@@ -1853,6 +2068,9 @@ function createGatewayServer(opts) {
|
|
|
1853
2068
|
}
|
|
1854
2069
|
|
|
1855
2070
|
// src/skills/executor.ts
|
|
2071
|
+
function buildTimeoutError(skillId, timeoutMs) {
|
|
2072
|
+
return `Skill "${skillId}" timed out after ${timeoutMs}ms`;
|
|
2073
|
+
}
|
|
1856
2074
|
var SkillExecutor = class {
|
|
1857
2075
|
skillMap;
|
|
1858
2076
|
modeMap;
|
|
@@ -1911,7 +2129,20 @@ var SkillExecutor = class {
|
|
|
1911
2129
|
this.concurrencyGuard.acquire(skillId);
|
|
1912
2130
|
}
|
|
1913
2131
|
try {
|
|
1914
|
-
const
|
|
2132
|
+
const configuredTimeoutMs = typeof config.timeout_ms === "number" ? config.timeout_ms : void 0;
|
|
2133
|
+
const modeExecution = mode.execute(config, params, onProgress);
|
|
2134
|
+
const modeResult = configuredTimeoutMs === void 0 ? await modeExecution : await new Promise((resolve, reject) => {
|
|
2135
|
+
const timeout = setTimeout(() => {
|
|
2136
|
+
reject(new Error(buildTimeoutError(skillId, configuredTimeoutMs)));
|
|
2137
|
+
}, configuredTimeoutMs);
|
|
2138
|
+
modeExecution.then((value) => {
|
|
2139
|
+
clearTimeout(timeout);
|
|
2140
|
+
resolve(value);
|
|
2141
|
+
}).catch((err) => {
|
|
2142
|
+
clearTimeout(timeout);
|
|
2143
|
+
reject(err);
|
|
2144
|
+
});
|
|
2145
|
+
});
|
|
1915
2146
|
return {
|
|
1916
2147
|
...modeResult,
|
|
1917
2148
|
latency_ms: Date.now() - startTime
|
|
@@ -1982,7 +2213,8 @@ var CapabilityDeclarationSchema = {
|
|
|
1982
2213
|
description: z2.string().optional(),
|
|
1983
2214
|
capability_types: z2.array(z2.string()).optional(),
|
|
1984
2215
|
requires_capabilities: z2.array(z2.string()).optional(),
|
|
1985
|
-
visibility: z2.enum(["public", "private"]).optional()
|
|
2216
|
+
visibility: z2.enum(["public", "private"]).optional(),
|
|
2217
|
+
expected_duration_ms: z2.number().positive().optional()
|
|
1986
2218
|
};
|
|
1987
2219
|
var ApiSkillConfigSchema = z2.object({
|
|
1988
2220
|
id: z2.string().min(1),
|
|
@@ -2315,6 +2547,15 @@ function interpolateValue(value, context) {
|
|
|
2315
2547
|
|
|
2316
2548
|
// src/skills/pipeline-executor.ts
|
|
2317
2549
|
var execFileAsync = promisify(execFile);
|
|
2550
|
+
function isTimedOutCommandError(err) {
|
|
2551
|
+
if (!(err instanceof Error)) {
|
|
2552
|
+
return false;
|
|
2553
|
+
}
|
|
2554
|
+
const timeoutCode = err.code;
|
|
2555
|
+
const signal = err.signal;
|
|
2556
|
+
const killed = err.killed;
|
|
2557
|
+
return timeoutCode === "ETIMEDOUT" || err.message.includes("timed out") || killed === true && typeof signal === "string";
|
|
2558
|
+
}
|
|
2318
2559
|
function shellEscape(value) {
|
|
2319
2560
|
return "'" + value.replace(/'/g, "'\\''") + "'";
|
|
2320
2561
|
}
|
|
@@ -2366,6 +2607,8 @@ var PipelineExecutor = class {
|
|
|
2366
2607
|
async execute(config, params, onProgress) {
|
|
2367
2608
|
const pipelineConfig = config;
|
|
2368
2609
|
const steps = pipelineConfig.steps ?? [];
|
|
2610
|
+
const pipelineTimeoutMs = pipelineConfig.timeout_ms;
|
|
2611
|
+
const deadline = typeof pipelineTimeoutMs === "number" ? Date.now() + pipelineTimeoutMs : void 0;
|
|
2369
2612
|
if (steps.length === 0) {
|
|
2370
2613
|
return { success: true, result: null };
|
|
2371
2614
|
}
|
|
@@ -2387,11 +2630,41 @@ var PipelineExecutor = class {
|
|
|
2387
2630
|
context
|
|
2388
2631
|
);
|
|
2389
2632
|
let stepResult;
|
|
2633
|
+
const runWithRemainingDeadline = async (operation) => {
|
|
2634
|
+
if (deadline === void 0) {
|
|
2635
|
+
return operation();
|
|
2636
|
+
}
|
|
2637
|
+
const remainingMs = deadline - Date.now();
|
|
2638
|
+
if (remainingMs <= 0) {
|
|
2639
|
+
throw new Error(`pipeline timed out after ${pipelineTimeoutMs}ms`);
|
|
2640
|
+
}
|
|
2641
|
+
return new Promise((resolve, reject) => {
|
|
2642
|
+
const timeout = setTimeout(() => {
|
|
2643
|
+
reject(new Error(`pipeline timed out after ${pipelineTimeoutMs}ms`));
|
|
2644
|
+
}, remainingMs);
|
|
2645
|
+
operation().then((value) => {
|
|
2646
|
+
clearTimeout(timeout);
|
|
2647
|
+
resolve(value);
|
|
2648
|
+
}).catch((err) => {
|
|
2649
|
+
clearTimeout(timeout);
|
|
2650
|
+
reject(err);
|
|
2651
|
+
});
|
|
2652
|
+
});
|
|
2653
|
+
};
|
|
2390
2654
|
if ("skill_id" in step && step.skill_id) {
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2655
|
+
let subResult;
|
|
2656
|
+
try {
|
|
2657
|
+
subResult = await runWithRemainingDeadline(() => this.skillExecutor.execute(
|
|
2658
|
+
step.skill_id,
|
|
2659
|
+
resolvedInputs
|
|
2660
|
+
));
|
|
2661
|
+
} catch (err) {
|
|
2662
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2663
|
+
return {
|
|
2664
|
+
success: false,
|
|
2665
|
+
error: `Step ${i} failed: ${message}`
|
|
2666
|
+
};
|
|
2667
|
+
}
|
|
2395
2668
|
if (!subResult.success) {
|
|
2396
2669
|
return {
|
|
2397
2670
|
success: false,
|
|
@@ -2405,10 +2678,16 @@ var PipelineExecutor = class {
|
|
|
2405
2678
|
context
|
|
2406
2679
|
);
|
|
2407
2680
|
try {
|
|
2408
|
-
const
|
|
2681
|
+
const remainingMs = deadline === void 0 ? void 0 : deadline - Date.now();
|
|
2682
|
+
if (remainingMs !== void 0 && remainingMs <= 0) {
|
|
2683
|
+
throw new Error(`pipeline timed out after ${pipelineTimeoutMs}ms`);
|
|
2684
|
+
}
|
|
2685
|
+
const { stdout } = await execFileAsync("/bin/sh", ["-c", interpolatedCommand], {
|
|
2686
|
+
timeout: remainingMs !== void 0 ? remainingMs : 3e4
|
|
2687
|
+
});
|
|
2409
2688
|
stepResult = stdout.trim();
|
|
2410
2689
|
} catch (err) {
|
|
2411
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
2690
|
+
const message = pipelineTimeoutMs !== void 0 && isTimedOutCommandError(err) ? `pipeline timed out after ${pipelineTimeoutMs}ms` : err instanceof Error ? err.message : String(err);
|
|
2412
2691
|
return {
|
|
2413
2692
|
success: false,
|
|
2414
2693
|
error: `Step ${i} failed: ${message}`
|
|
@@ -2495,17 +2774,43 @@ function executeProcess(config, payload) {
|
|
|
2495
2774
|
error: `Invalid agent name: "${config.agent_name}" (only alphanumeric, hyphens, underscores, dots allowed)`
|
|
2496
2775
|
};
|
|
2497
2776
|
}
|
|
2498
|
-
const
|
|
2777
|
+
const skillId = config.id;
|
|
2778
|
+
const message = `[AgentBnB Rental Request]
|
|
2779
|
+
You are executing the "${skillId}" skill for an AgentBnB network rental.
|
|
2780
|
+
Read your skills/${skillId}/SKILL.md for detailed instructions.
|
|
2781
|
+
|
|
2782
|
+
Input parameters:
|
|
2783
|
+
${JSON.stringify(payload.params ?? {}, null, 2)}
|
|
2784
|
+
|
|
2785
|
+
IMPORTANT: Return ONLY a JSON object as your response.
|
|
2786
|
+
Do NOT include explanations, markdown formatting, or code blocks.
|
|
2787
|
+
The JSON should contain the output fields specified in your SKILL.md.
|
|
2788
|
+
If you cannot complete the task, return: {"error": "reason"}`;
|
|
2499
2789
|
try {
|
|
2500
|
-
const stdout = execFileSync("openclaw", [
|
|
2790
|
+
const stdout = execFileSync("openclaw", [
|
|
2791
|
+
"agent",
|
|
2792
|
+
"--agent",
|
|
2793
|
+
config.agent_name,
|
|
2794
|
+
"--message",
|
|
2795
|
+
message,
|
|
2796
|
+
"--json",
|
|
2797
|
+
"--local"
|
|
2798
|
+
], {
|
|
2501
2799
|
timeout: timeoutMs
|
|
2502
2800
|
});
|
|
2503
2801
|
const text = stdout.toString().trim();
|
|
2504
|
-
|
|
2505
|
-
|
|
2802
|
+
try {
|
|
2803
|
+
const parsed = JSON.parse(text);
|
|
2804
|
+
return { success: true, result: parsed };
|
|
2805
|
+
} catch {
|
|
2806
|
+
return {
|
|
2807
|
+
success: false,
|
|
2808
|
+
error: `OpenClaw process channel returned invalid JSON: ${text}`
|
|
2809
|
+
};
|
|
2810
|
+
}
|
|
2506
2811
|
} catch (err) {
|
|
2507
|
-
const
|
|
2508
|
-
return { success: false, error:
|
|
2812
|
+
const message2 = err instanceof Error ? err.message : String(err);
|
|
2813
|
+
return { success: false, error: message2 };
|
|
2509
2814
|
}
|
|
2510
2815
|
}
|
|
2511
2816
|
async function executeTelegram(config, payload) {
|
|
@@ -3077,18 +3382,29 @@ function decompose(task, _availableCapabilities) {
|
|
|
3077
3382
|
return [];
|
|
3078
3383
|
}
|
|
3079
3384
|
|
|
3080
|
-
// src/autonomy/auto-request.ts
|
|
3081
|
-
import { randomUUID as randomUUID11 } from "crypto";
|
|
3082
|
-
|
|
3083
3385
|
// src/gateway/client.ts
|
|
3084
3386
|
import { randomUUID as randomUUID8 } from "crypto";
|
|
3085
3387
|
import { Agent } from "undici";
|
|
3388
|
+
var REQUEST_TIMEOUT_FALLBACK_MS = 3e5;
|
|
3389
|
+
var REQUEST_TIMEOUT_GRACE_MS = 3e4;
|
|
3390
|
+
var REQUEST_TIMEOUT_EXPECTED_MULTIPLIER = 1.5;
|
|
3086
3391
|
var gatewayAgent = new Agent({
|
|
3087
3392
|
keepAliveTimeout: 3e4,
|
|
3088
3393
|
keepAliveMaxTimeout: 6e4,
|
|
3089
3394
|
connections: 10,
|
|
3090
3395
|
pipelining: 1
|
|
3091
3396
|
});
|
|
3397
|
+
function deriveRequestTimeoutMs(hint) {
|
|
3398
|
+
const expectedDurationMs = hint?.expected_duration_ms;
|
|
3399
|
+
if (typeof expectedDurationMs === "number" && expectedDurationMs > 0) {
|
|
3400
|
+
return Math.ceil(expectedDurationMs * REQUEST_TIMEOUT_EXPECTED_MULTIPLIER) + REQUEST_TIMEOUT_GRACE_MS;
|
|
3401
|
+
}
|
|
3402
|
+
const hardTimeoutMs = hint?.hard_timeout_ms;
|
|
3403
|
+
if (typeof hardTimeoutMs === "number" && hardTimeoutMs > 0) {
|
|
3404
|
+
return Math.ceil(hardTimeoutMs) + REQUEST_TIMEOUT_GRACE_MS;
|
|
3405
|
+
}
|
|
3406
|
+
return REQUEST_TIMEOUT_FALLBACK_MS;
|
|
3407
|
+
}
|
|
3092
3408
|
function buildGatewayAuthHeaders(payload, token, identity) {
|
|
3093
3409
|
const headers = { "Content-Type": "application/json" };
|
|
3094
3410
|
if (identity) {
|
|
@@ -3103,7 +3419,17 @@ function buildGatewayAuthHeaders(payload, token, identity) {
|
|
|
3103
3419
|
return headers;
|
|
3104
3420
|
}
|
|
3105
3421
|
async function requestCapability(opts) {
|
|
3106
|
-
const {
|
|
3422
|
+
const {
|
|
3423
|
+
gatewayUrl,
|
|
3424
|
+
token,
|
|
3425
|
+
cardId,
|
|
3426
|
+
params = {},
|
|
3427
|
+
timeoutMs: timeoutOverrideMs,
|
|
3428
|
+
timeoutHint,
|
|
3429
|
+
escrowReceipt,
|
|
3430
|
+
identity
|
|
3431
|
+
} = opts;
|
|
3432
|
+
const timeoutMs = timeoutOverrideMs ?? deriveRequestTimeoutMs(timeoutHint);
|
|
3107
3433
|
const id = randomUUID8();
|
|
3108
3434
|
const payload = {
|
|
3109
3435
|
jsonrpc: "2.0",
|
|
@@ -3155,82 +3481,640 @@ async function requestCapabilityBatch(gatewayUrl, token, items, opts = {}) {
|
|
|
3155
3481
|
params: item.params,
|
|
3156
3482
|
escrowReceipt: item.escrowReceipt,
|
|
3157
3483
|
timeoutMs: opts.timeoutMs,
|
|
3484
|
+
timeoutHint: opts.timeoutHint,
|
|
3158
3485
|
identity: opts.identity
|
|
3159
3486
|
});
|
|
3160
3487
|
return /* @__PURE__ */ new Map([[item.id, result]]);
|
|
3161
3488
|
}
|
|
3162
|
-
const { timeoutMs
|
|
3163
|
-
const
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
...item.
|
|
3489
|
+
const { timeoutMs: timeoutOverrideMs, timeoutHint, identity } = opts;
|
|
3490
|
+
const timeoutMs = timeoutOverrideMs ?? deriveRequestTimeoutMs(timeoutHint);
|
|
3491
|
+
const batchPayload = items.map((item) => ({
|
|
3492
|
+
jsonrpc: "2.0",
|
|
3493
|
+
id: item.id,
|
|
3494
|
+
method: "capability.execute",
|
|
3495
|
+
params: {
|
|
3496
|
+
card_id: item.cardId,
|
|
3497
|
+
...item.params,
|
|
3498
|
+
...item.escrowReceipt ? { escrow_receipt: item.escrowReceipt } : {}
|
|
3499
|
+
}
|
|
3500
|
+
}));
|
|
3501
|
+
const headers = buildGatewayAuthHeaders(batchPayload, token, identity);
|
|
3502
|
+
const controller = new AbortController();
|
|
3503
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
3504
|
+
let response;
|
|
3505
|
+
try {
|
|
3506
|
+
response = await fetch(`${gatewayUrl}/rpc`, {
|
|
3507
|
+
method: "POST",
|
|
3508
|
+
headers,
|
|
3509
|
+
body: JSON.stringify(batchPayload),
|
|
3510
|
+
signal: controller.signal,
|
|
3511
|
+
dispatcher: gatewayAgent
|
|
3512
|
+
});
|
|
3513
|
+
} catch (err) {
|
|
3514
|
+
clearTimeout(timer);
|
|
3515
|
+
const isTimeout = err instanceof Error && err.name === "AbortError";
|
|
3516
|
+
throw new AgentBnBError(
|
|
3517
|
+
isTimeout ? "Batch request timed out" : `Network error: ${String(err)}`,
|
|
3518
|
+
isTimeout ? "TIMEOUT" : "NETWORK_ERROR"
|
|
3519
|
+
);
|
|
3520
|
+
} finally {
|
|
3521
|
+
clearTimeout(timer);
|
|
3522
|
+
}
|
|
3523
|
+
const body = await response.json();
|
|
3524
|
+
const results = /* @__PURE__ */ new Map();
|
|
3525
|
+
for (const resp of body) {
|
|
3526
|
+
if (resp.error) {
|
|
3527
|
+
results.set(resp.id, new AgentBnBError(resp.error.message, `RPC_ERROR_${resp.error.code}`));
|
|
3528
|
+
} else {
|
|
3529
|
+
results.set(resp.id, resp.result);
|
|
3530
|
+
}
|
|
3531
|
+
}
|
|
3532
|
+
return results;
|
|
3533
|
+
}
|
|
3534
|
+
async function requestViaRelay(relay, opts) {
|
|
3535
|
+
const timeoutMs = opts.timeoutMs ?? deriveRequestTimeoutMs(opts.timeoutHint);
|
|
3536
|
+
try {
|
|
3537
|
+
return await relay.request({
|
|
3538
|
+
targetOwner: opts.targetOwner,
|
|
3539
|
+
targetAgentId: opts.targetAgentId,
|
|
3540
|
+
cardId: opts.cardId,
|
|
3541
|
+
skillId: opts.skillId,
|
|
3542
|
+
params: opts.params ?? {},
|
|
3543
|
+
requester: opts.requester,
|
|
3544
|
+
escrowReceipt: opts.escrowReceipt,
|
|
3545
|
+
timeoutMs
|
|
3546
|
+
});
|
|
3547
|
+
} catch (err) {
|
|
3548
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3549
|
+
if (message.includes("timeout")) {
|
|
3550
|
+
throw new AgentBnBError(message, "TIMEOUT");
|
|
3551
|
+
}
|
|
3552
|
+
if (message.includes("offline")) {
|
|
3553
|
+
throw new AgentBnBError(message, "AGENT_OFFLINE");
|
|
3554
|
+
}
|
|
3555
|
+
throw new AgentBnBError(message, "RELAY_ERROR");
|
|
3556
|
+
}
|
|
3557
|
+
}
|
|
3558
|
+
|
|
3559
|
+
// src/gateway/relay-dispatch.ts
|
|
3560
|
+
import { randomUUID as randomUUID10 } from "crypto";
|
|
3561
|
+
|
|
3562
|
+
// src/relay/websocket-client.ts
|
|
3563
|
+
import WebSocket from "ws";
|
|
3564
|
+
import { randomUUID as randomUUID9 } from "crypto";
|
|
3565
|
+
|
|
3566
|
+
// src/relay/types.ts
|
|
3567
|
+
import { z as z3 } from "zod";
|
|
3568
|
+
var RegisterMessageSchema = z3.object({
|
|
3569
|
+
type: z3.literal("register"),
|
|
3570
|
+
owner: z3.string().min(1),
|
|
3571
|
+
/** V8: Cryptographic agent identity. When present, used as the canonical key. */
|
|
3572
|
+
agent_id: z3.string().optional(),
|
|
3573
|
+
/** V8 Phase 3: Server identifier for multi-agent delegation. */
|
|
3574
|
+
server_id: z3.string().optional(),
|
|
3575
|
+
token: z3.string().min(1),
|
|
3576
|
+
card: z3.record(z3.unknown()),
|
|
3577
|
+
// CapabilityCard (validated separately)
|
|
3578
|
+
cards: z3.array(z3.record(z3.unknown())).optional(),
|
|
3579
|
+
// Additional cards (e.g., conductor card)
|
|
3580
|
+
/** V8 Phase 3: Additional agents served by this server (multi-agent registration). */
|
|
3581
|
+
agents: z3.array(z3.object({
|
|
3582
|
+
agent_id: z3.string().min(1),
|
|
3583
|
+
display_name: z3.string().min(1),
|
|
3584
|
+
cards: z3.array(z3.record(z3.unknown())),
|
|
3585
|
+
delegation_token: z3.record(z3.unknown()).optional()
|
|
3586
|
+
})).optional()
|
|
3587
|
+
});
|
|
3588
|
+
var RegisteredMessageSchema = z3.object({
|
|
3589
|
+
type: z3.literal("registered"),
|
|
3590
|
+
agent_id: z3.string()
|
|
3591
|
+
});
|
|
3592
|
+
var RelayRequestMessageSchema = z3.object({
|
|
3593
|
+
type: z3.literal("relay_request"),
|
|
3594
|
+
id: z3.string().uuid(),
|
|
3595
|
+
target_owner: z3.string().min(1),
|
|
3596
|
+
/** V8: Target agent's cryptographic identity. Preferred over target_owner. */
|
|
3597
|
+
target_agent_id: z3.string().optional(),
|
|
3598
|
+
card_id: z3.string(),
|
|
3599
|
+
skill_id: z3.string().optional(),
|
|
3600
|
+
params: z3.record(z3.unknown()).default({}),
|
|
3601
|
+
requester: z3.string().optional(),
|
|
3602
|
+
escrow_receipt: z3.record(z3.unknown()).optional()
|
|
3603
|
+
});
|
|
3604
|
+
var IncomingRequestMessageSchema = z3.object({
|
|
3605
|
+
type: z3.literal("incoming_request"),
|
|
3606
|
+
id: z3.string().uuid(),
|
|
3607
|
+
from_owner: z3.string().min(1),
|
|
3608
|
+
card_id: z3.string(),
|
|
3609
|
+
skill_id: z3.string().optional(),
|
|
3610
|
+
params: z3.record(z3.unknown()).default({}),
|
|
3611
|
+
requester: z3.string().optional(),
|
|
3612
|
+
escrow_receipt: z3.record(z3.unknown()).optional()
|
|
3613
|
+
});
|
|
3614
|
+
var RelayResponseMessageSchema = z3.object({
|
|
3615
|
+
type: z3.literal("relay_response"),
|
|
3616
|
+
id: z3.string().uuid(),
|
|
3617
|
+
result: z3.unknown().optional(),
|
|
3618
|
+
error: z3.object({
|
|
3619
|
+
code: z3.number(),
|
|
3620
|
+
message: z3.string()
|
|
3621
|
+
}).optional()
|
|
3622
|
+
});
|
|
3623
|
+
var ResponseMessageSchema = z3.object({
|
|
3624
|
+
type: z3.literal("response"),
|
|
3625
|
+
id: z3.string().uuid(),
|
|
3626
|
+
result: z3.unknown().optional(),
|
|
3627
|
+
error: z3.object({
|
|
3628
|
+
code: z3.number(),
|
|
3629
|
+
message: z3.string()
|
|
3630
|
+
}).optional()
|
|
3631
|
+
});
|
|
3632
|
+
var ErrorMessageSchema = z3.object({
|
|
3633
|
+
type: z3.literal("error"),
|
|
3634
|
+
code: z3.string(),
|
|
3635
|
+
message: z3.string(),
|
|
3636
|
+
request_id: z3.string().optional()
|
|
3637
|
+
});
|
|
3638
|
+
var RelayProgressMessageSchema = z3.object({
|
|
3639
|
+
type: z3.literal("relay_progress"),
|
|
3640
|
+
id: z3.string().uuid(),
|
|
3641
|
+
// request ID this progress relates to
|
|
3642
|
+
progress: z3.number().min(0).max(100).optional(),
|
|
3643
|
+
// optional percentage
|
|
3644
|
+
message: z3.string().optional()
|
|
3645
|
+
// optional status message
|
|
3646
|
+
});
|
|
3647
|
+
var RelayStartedMessageSchema = z3.object({
|
|
3648
|
+
type: z3.literal("relay_started"),
|
|
3649
|
+
id: z3.string().uuid(),
|
|
3650
|
+
message: z3.string().optional()
|
|
3651
|
+
});
|
|
3652
|
+
var HeartbeatMessageSchema = z3.object({
|
|
3653
|
+
type: z3.literal("heartbeat"),
|
|
3654
|
+
owner: z3.string().min(1),
|
|
3655
|
+
capacity: z3.object({
|
|
3656
|
+
current_load: z3.number(),
|
|
3657
|
+
max_concurrent: z3.number(),
|
|
3658
|
+
queue_depth: z3.number()
|
|
3659
|
+
}),
|
|
3660
|
+
self_summary: z3.object({
|
|
3661
|
+
capabilities: z3.array(z3.string()),
|
|
3662
|
+
success_rate: z3.number(),
|
|
3663
|
+
credit_balance: z3.number(),
|
|
3664
|
+
total_completed: z3.number(),
|
|
3665
|
+
provider_number: z3.number(),
|
|
3666
|
+
reliability: z3.object({
|
|
3667
|
+
current_streak: z3.number(),
|
|
3668
|
+
repeat_hire_rate: z3.number(),
|
|
3669
|
+
avg_feedback: z3.number()
|
|
3670
|
+
})
|
|
3671
|
+
})
|
|
3672
|
+
});
|
|
3673
|
+
var EscrowHoldMessageSchema = z3.object({
|
|
3674
|
+
type: z3.literal("escrow_hold"),
|
|
3675
|
+
consumer_agent_id: z3.string().min(1),
|
|
3676
|
+
provider_agent_id: z3.string().min(1),
|
|
3677
|
+
skill_id: z3.string().min(1),
|
|
3678
|
+
amount: z3.number().positive(),
|
|
3679
|
+
request_id: z3.string().uuid(),
|
|
3680
|
+
signature: z3.string().optional(),
|
|
3681
|
+
public_key: z3.string().optional()
|
|
3682
|
+
});
|
|
3683
|
+
var EscrowHoldConfirmedMessageSchema = z3.object({
|
|
3684
|
+
type: z3.literal("escrow_hold_confirmed"),
|
|
3685
|
+
request_id: z3.string(),
|
|
3686
|
+
escrow_id: z3.string(),
|
|
3687
|
+
hold_amount: z3.number(),
|
|
3688
|
+
consumer_remaining: z3.number()
|
|
3689
|
+
});
|
|
3690
|
+
var EscrowSettleMessageSchema = z3.object({
|
|
3691
|
+
type: z3.literal("escrow_settle"),
|
|
3692
|
+
escrow_id: z3.string().min(1),
|
|
3693
|
+
request_id: z3.string().uuid(),
|
|
3694
|
+
success: z3.boolean(),
|
|
3695
|
+
failure_reason: z3.enum(["bad_execution", "overload", "timeout", "auth_error", "not_found"]).optional(),
|
|
3696
|
+
result_hash: z3.string().optional(),
|
|
3697
|
+
signature: z3.string().optional(),
|
|
3698
|
+
public_key: z3.string().optional(),
|
|
3699
|
+
consumer_agent_id: z3.string().optional()
|
|
3700
|
+
});
|
|
3701
|
+
var EscrowSettledMessageSchema = z3.object({
|
|
3702
|
+
type: z3.literal("escrow_settled"),
|
|
3703
|
+
escrow_id: z3.string(),
|
|
3704
|
+
request_id: z3.string(),
|
|
3705
|
+
provider_earned: z3.number(),
|
|
3706
|
+
network_fee: z3.number(),
|
|
3707
|
+
consumer_remaining: z3.number(),
|
|
3708
|
+
provider_balance: z3.number()
|
|
3709
|
+
});
|
|
3710
|
+
var BalanceSyncMessageSchema = z3.object({
|
|
3711
|
+
type: z3.literal("balance_sync"),
|
|
3712
|
+
agent_id: z3.string().min(1)
|
|
3713
|
+
});
|
|
3714
|
+
var BalanceSyncResponseMessageSchema = z3.object({
|
|
3715
|
+
type: z3.literal("balance_sync_response"),
|
|
3716
|
+
agent_id: z3.string(),
|
|
3717
|
+
balance: z3.number()
|
|
3718
|
+
});
|
|
3719
|
+
var RelayMessageSchema = z3.discriminatedUnion("type", [
|
|
3720
|
+
RegisterMessageSchema,
|
|
3721
|
+
RegisteredMessageSchema,
|
|
3722
|
+
RelayRequestMessageSchema,
|
|
3723
|
+
IncomingRequestMessageSchema,
|
|
3724
|
+
RelayResponseMessageSchema,
|
|
3725
|
+
ResponseMessageSchema,
|
|
3726
|
+
ErrorMessageSchema,
|
|
3727
|
+
RelayProgressMessageSchema,
|
|
3728
|
+
RelayStartedMessageSchema,
|
|
3729
|
+
HeartbeatMessageSchema,
|
|
3730
|
+
EscrowHoldMessageSchema,
|
|
3731
|
+
EscrowHoldConfirmedMessageSchema,
|
|
3732
|
+
EscrowSettleMessageSchema,
|
|
3733
|
+
EscrowSettledMessageSchema,
|
|
3734
|
+
BalanceSyncMessageSchema,
|
|
3735
|
+
BalanceSyncResponseMessageSchema
|
|
3736
|
+
]);
|
|
3737
|
+
|
|
3738
|
+
// src/relay/websocket-client.ts
|
|
3739
|
+
var RelayClient = class {
|
|
3740
|
+
ws = null;
|
|
3741
|
+
opts;
|
|
3742
|
+
pendingRequests = /* @__PURE__ */ new Map();
|
|
3743
|
+
reconnectAttempts = 0;
|
|
3744
|
+
reconnectTimer = null;
|
|
3745
|
+
intentionalClose = false;
|
|
3746
|
+
registered = false;
|
|
3747
|
+
pongTimeout = null;
|
|
3748
|
+
pingInterval = null;
|
|
3749
|
+
constructor(opts) {
|
|
3750
|
+
this.opts = opts;
|
|
3751
|
+
}
|
|
3752
|
+
/**
|
|
3753
|
+
* Connect to the registry relay and register.
|
|
3754
|
+
* Resolves when registration is acknowledged.
|
|
3755
|
+
*/
|
|
3756
|
+
async connect() {
|
|
3757
|
+
return new Promise((resolve, reject) => {
|
|
3758
|
+
this.intentionalClose = false;
|
|
3759
|
+
this.registered = false;
|
|
3760
|
+
const wsUrl = this.buildWsUrl();
|
|
3761
|
+
this.ws = new WebSocket(wsUrl);
|
|
3762
|
+
let resolved = false;
|
|
3763
|
+
this.ws.on("open", () => {
|
|
3764
|
+
this.reconnectAttempts = 0;
|
|
3765
|
+
this.startPingInterval();
|
|
3766
|
+
this.send({
|
|
3767
|
+
type: "register",
|
|
3768
|
+
owner: this.opts.owner,
|
|
3769
|
+
...this.opts.agent_id ? { agent_id: this.opts.agent_id } : {},
|
|
3770
|
+
...this.opts.server_id ? { server_id: this.opts.server_id } : {},
|
|
3771
|
+
token: this.opts.token,
|
|
3772
|
+
card: this.opts.card,
|
|
3773
|
+
...this.opts.cards && this.opts.cards.length > 0 ? { cards: this.opts.cards } : {},
|
|
3774
|
+
...this.opts.agents && this.opts.agents.length > 0 ? { agents: this.opts.agents } : {}
|
|
3775
|
+
});
|
|
3776
|
+
});
|
|
3777
|
+
this.ws.on("message", (raw) => {
|
|
3778
|
+
this.handleMessage(raw, (err) => {
|
|
3779
|
+
if (!resolved) {
|
|
3780
|
+
resolved = true;
|
|
3781
|
+
if (err) reject(err);
|
|
3782
|
+
else resolve();
|
|
3783
|
+
}
|
|
3784
|
+
});
|
|
3785
|
+
});
|
|
3786
|
+
this.ws.on("close", () => {
|
|
3787
|
+
this.cleanup();
|
|
3788
|
+
if (!this.intentionalClose) {
|
|
3789
|
+
if (!resolved) {
|
|
3790
|
+
resolved = true;
|
|
3791
|
+
reject(new Error("WebSocket closed before registration"));
|
|
3792
|
+
}
|
|
3793
|
+
this.scheduleReconnect();
|
|
3794
|
+
}
|
|
3795
|
+
});
|
|
3796
|
+
this.ws.on("error", (err) => {
|
|
3797
|
+
if (!resolved) {
|
|
3798
|
+
resolved = true;
|
|
3799
|
+
reject(err);
|
|
3800
|
+
}
|
|
3801
|
+
});
|
|
3802
|
+
setTimeout(() => {
|
|
3803
|
+
if (!resolved) {
|
|
3804
|
+
resolved = true;
|
|
3805
|
+
reject(new Error("Connection timeout"));
|
|
3806
|
+
this.ws?.close();
|
|
3807
|
+
}
|
|
3808
|
+
}, 1e4);
|
|
3809
|
+
});
|
|
3810
|
+
}
|
|
3811
|
+
/**
|
|
3812
|
+
* Disconnect from the registry relay.
|
|
3813
|
+
*/
|
|
3814
|
+
disconnect() {
|
|
3815
|
+
this.intentionalClose = true;
|
|
3816
|
+
this.cleanup();
|
|
3817
|
+
if (this.ws) {
|
|
3818
|
+
try {
|
|
3819
|
+
this.ws.close(1e3, "Client disconnect");
|
|
3820
|
+
} catch {
|
|
3821
|
+
}
|
|
3822
|
+
this.ws = null;
|
|
3823
|
+
}
|
|
3824
|
+
for (const [id, pending] of this.pendingRequests) {
|
|
3825
|
+
clearTimeout(pending.timeout);
|
|
3826
|
+
pending.reject(new Error("Client disconnected"));
|
|
3827
|
+
this.pendingRequests.delete(id);
|
|
3828
|
+
}
|
|
3829
|
+
}
|
|
3830
|
+
/**
|
|
3831
|
+
* Send a relay request to another agent via the registry.
|
|
3832
|
+
* @returns The result from the target agent.
|
|
3833
|
+
*/
|
|
3834
|
+
async request(opts) {
|
|
3835
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN || !this.registered) {
|
|
3836
|
+
throw new Error("Not connected to registry relay");
|
|
3837
|
+
}
|
|
3838
|
+
const id = randomUUID9();
|
|
3839
|
+
const timeoutMs = opts.timeoutMs ?? 3e5;
|
|
3840
|
+
return new Promise((resolve, reject) => {
|
|
3841
|
+
const timeout = setTimeout(() => {
|
|
3842
|
+
this.pendingRequests.delete(id);
|
|
3843
|
+
reject(new Error("Relay request timeout"));
|
|
3844
|
+
}, timeoutMs);
|
|
3845
|
+
this.pendingRequests.set(id, { resolve, reject, timeout, timeoutMs, onProgress: opts.onProgress });
|
|
3846
|
+
this.send({
|
|
3847
|
+
type: "relay_request",
|
|
3848
|
+
id,
|
|
3849
|
+
target_owner: opts.targetOwner,
|
|
3850
|
+
...opts.targetAgentId ? { target_agent_id: opts.targetAgentId } : {},
|
|
3851
|
+
card_id: opts.cardId,
|
|
3852
|
+
skill_id: opts.skillId,
|
|
3853
|
+
params: opts.params,
|
|
3854
|
+
requester: opts.requester ?? this.opts.owner,
|
|
3855
|
+
escrow_receipt: opts.escrowReceipt
|
|
3856
|
+
});
|
|
3857
|
+
});
|
|
3858
|
+
}
|
|
3859
|
+
/**
|
|
3860
|
+
* Send a relay_started message to acknowledge that provider has started work.
|
|
3861
|
+
*
|
|
3862
|
+
* @param requestId - The relay request ID being processed.
|
|
3863
|
+
* @param message - Optional status message.
|
|
3864
|
+
*/
|
|
3865
|
+
sendStarted(requestId, message) {
|
|
3866
|
+
this.send({
|
|
3867
|
+
type: "relay_started",
|
|
3868
|
+
id: requestId,
|
|
3869
|
+
...message ? { message } : {}
|
|
3870
|
+
});
|
|
3871
|
+
}
|
|
3872
|
+
/**
|
|
3873
|
+
* Send a relay_progress message to the relay server for a given request.
|
|
3874
|
+
* Used by the onRequest handler to forward SkillExecutor progress updates
|
|
3875
|
+
* to the requesting agent while work is in-flight.
|
|
3876
|
+
*
|
|
3877
|
+
* @param requestId - The relay request ID to associate progress with.
|
|
3878
|
+
* @param info - Progress details (step, total, message).
|
|
3879
|
+
*/
|
|
3880
|
+
sendProgress(requestId, info) {
|
|
3881
|
+
this.send({
|
|
3882
|
+
type: "relay_progress",
|
|
3883
|
+
id: requestId,
|
|
3884
|
+
progress: Math.round(info.step / info.total * 100),
|
|
3885
|
+
message: info.message
|
|
3886
|
+
});
|
|
3887
|
+
}
|
|
3888
|
+
/** Whether the client is connected and registered */
|
|
3889
|
+
get isConnected() {
|
|
3890
|
+
return this.ws !== null && this.ws.readyState === WebSocket.OPEN && this.registered;
|
|
3891
|
+
}
|
|
3892
|
+
// ── Private methods ─────────────────────────────────────────────────────────
|
|
3893
|
+
buildWsUrl() {
|
|
3894
|
+
let url = this.opts.registryUrl;
|
|
3895
|
+
if (url.startsWith("http://")) {
|
|
3896
|
+
url = "ws://" + url.slice(7);
|
|
3897
|
+
} else if (url.startsWith("https://")) {
|
|
3898
|
+
url = "wss://" + url.slice(8);
|
|
3899
|
+
} else if (!url.startsWith("ws://") && !url.startsWith("wss://")) {
|
|
3900
|
+
url = "wss://" + url;
|
|
3901
|
+
}
|
|
3902
|
+
if (!url.endsWith("/ws")) {
|
|
3903
|
+
url = url.replace(/\/$/, "") + "/ws";
|
|
3904
|
+
}
|
|
3905
|
+
return url;
|
|
3906
|
+
}
|
|
3907
|
+
handleMessage(raw, onRegistered) {
|
|
3908
|
+
let data;
|
|
3909
|
+
try {
|
|
3910
|
+
data = JSON.parse(typeof raw === "string" ? raw : raw.toString("utf-8"));
|
|
3911
|
+
} catch {
|
|
3912
|
+
return;
|
|
3913
|
+
}
|
|
3914
|
+
const parsed = RelayMessageSchema.safeParse(data);
|
|
3915
|
+
if (!parsed.success) return;
|
|
3916
|
+
const msg = parsed.data;
|
|
3917
|
+
switch (msg.type) {
|
|
3918
|
+
case "registered":
|
|
3919
|
+
this.registered = true;
|
|
3920
|
+
if (!this.opts.silent) {
|
|
3921
|
+
console.log(` \u2713 Registered with registry (agent_id: ${msg.agent_id})`);
|
|
3922
|
+
}
|
|
3923
|
+
onRegistered?.();
|
|
3924
|
+
break;
|
|
3925
|
+
case "incoming_request":
|
|
3926
|
+
this.handleIncomingRequest(msg);
|
|
3927
|
+
break;
|
|
3928
|
+
case "response":
|
|
3929
|
+
this.handleResponse(msg);
|
|
3930
|
+
break;
|
|
3931
|
+
case "error":
|
|
3932
|
+
this.handleError(msg);
|
|
3933
|
+
break;
|
|
3934
|
+
case "relay_progress":
|
|
3935
|
+
this.handleProgress(msg);
|
|
3936
|
+
break;
|
|
3937
|
+
default:
|
|
3938
|
+
break;
|
|
3939
|
+
}
|
|
3940
|
+
}
|
|
3941
|
+
async handleIncomingRequest(msg) {
|
|
3942
|
+
try {
|
|
3943
|
+
const result = await this.opts.onRequest(msg);
|
|
3944
|
+
this.send({
|
|
3945
|
+
type: "relay_response",
|
|
3946
|
+
id: msg.id,
|
|
3947
|
+
result: result.result,
|
|
3948
|
+
error: result.error
|
|
3949
|
+
});
|
|
3950
|
+
} catch (err) {
|
|
3951
|
+
this.send({
|
|
3952
|
+
type: "relay_response",
|
|
3953
|
+
id: msg.id,
|
|
3954
|
+
error: {
|
|
3955
|
+
code: -32603,
|
|
3956
|
+
message: err instanceof Error ? err.message : "Internal error"
|
|
3957
|
+
}
|
|
3958
|
+
});
|
|
3959
|
+
}
|
|
3960
|
+
}
|
|
3961
|
+
handleResponse(msg) {
|
|
3962
|
+
const pending = this.pendingRequests.get(msg.id);
|
|
3963
|
+
if (!pending) return;
|
|
3964
|
+
clearTimeout(pending.timeout);
|
|
3965
|
+
this.pendingRequests.delete(msg.id);
|
|
3966
|
+
if (msg.error) {
|
|
3967
|
+
pending.reject(new Error(msg.error.message));
|
|
3968
|
+
} else {
|
|
3969
|
+
pending.resolve(msg.result);
|
|
3970
|
+
}
|
|
3971
|
+
}
|
|
3972
|
+
handleError(msg) {
|
|
3973
|
+
if (msg.request_id) {
|
|
3974
|
+
const pending = this.pendingRequests.get(msg.request_id);
|
|
3975
|
+
if (pending) {
|
|
3976
|
+
clearTimeout(pending.timeout);
|
|
3977
|
+
this.pendingRequests.delete(msg.request_id);
|
|
3978
|
+
pending.reject(new Error(`${msg.code}: ${msg.message}`));
|
|
3979
|
+
}
|
|
3980
|
+
}
|
|
3981
|
+
}
|
|
3982
|
+
handleProgress(msg) {
|
|
3983
|
+
const pending = this.pendingRequests.get(msg.id);
|
|
3984
|
+
if (!pending) return;
|
|
3985
|
+
clearTimeout(pending.timeout);
|
|
3986
|
+
const newTimeout = setTimeout(() => {
|
|
3987
|
+
this.pendingRequests.delete(msg.id);
|
|
3988
|
+
pending.reject(new Error("Relay request timeout"));
|
|
3989
|
+
}, pending.timeoutMs);
|
|
3990
|
+
pending.timeout = newTimeout;
|
|
3991
|
+
if (pending.onProgress) {
|
|
3992
|
+
pending.onProgress({ id: msg.id, progress: msg.progress, message: msg.message });
|
|
3171
3993
|
}
|
|
3172
|
-
}
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3994
|
+
}
|
|
3995
|
+
send(msg) {
|
|
3996
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
3997
|
+
this.ws.send(JSON.stringify(msg));
|
|
3998
|
+
}
|
|
3999
|
+
}
|
|
4000
|
+
startPingInterval() {
|
|
4001
|
+
this.stopPingInterval();
|
|
4002
|
+
this.pingInterval = setInterval(() => {
|
|
4003
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
4004
|
+
this.ws.ping();
|
|
4005
|
+
this.pongTimeout = setTimeout(() => {
|
|
4006
|
+
if (!this.opts.silent) {
|
|
4007
|
+
console.log(" \u26A0 Registry pong timeout, reconnecting...");
|
|
4008
|
+
}
|
|
4009
|
+
this.ws?.terminate();
|
|
4010
|
+
}, 15e3);
|
|
4011
|
+
}
|
|
4012
|
+
}, 3e4);
|
|
4013
|
+
this.ws?.on("pong", () => {
|
|
4014
|
+
if (this.pongTimeout) {
|
|
4015
|
+
clearTimeout(this.pongTimeout);
|
|
4016
|
+
this.pongTimeout = null;
|
|
4017
|
+
}
|
|
3184
4018
|
});
|
|
3185
|
-
} catch (err) {
|
|
3186
|
-
clearTimeout(timer);
|
|
3187
|
-
const isTimeout = err instanceof Error && err.name === "AbortError";
|
|
3188
|
-
throw new AgentBnBError(
|
|
3189
|
-
isTimeout ? "Batch request timed out" : `Network error: ${String(err)}`,
|
|
3190
|
-
isTimeout ? "TIMEOUT" : "NETWORK_ERROR"
|
|
3191
|
-
);
|
|
3192
|
-
} finally {
|
|
3193
|
-
clearTimeout(timer);
|
|
3194
4019
|
}
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
4020
|
+
stopPingInterval() {
|
|
4021
|
+
if (this.pingInterval) {
|
|
4022
|
+
clearInterval(this.pingInterval);
|
|
4023
|
+
this.pingInterval = null;
|
|
4024
|
+
}
|
|
4025
|
+
if (this.pongTimeout) {
|
|
4026
|
+
clearTimeout(this.pongTimeout);
|
|
4027
|
+
this.pongTimeout = null;
|
|
3202
4028
|
}
|
|
3203
4029
|
}
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
4030
|
+
cleanup() {
|
|
4031
|
+
this.stopPingInterval();
|
|
4032
|
+
this.registered = false;
|
|
4033
|
+
}
|
|
4034
|
+
scheduleReconnect() {
|
|
4035
|
+
if (this.intentionalClose) return;
|
|
4036
|
+
if (this.reconnectTimer) return;
|
|
4037
|
+
const delay = Math.min(1e3 * Math.pow(2, this.reconnectAttempts), 3e4);
|
|
4038
|
+
this.reconnectAttempts++;
|
|
4039
|
+
if (!this.opts.silent) {
|
|
4040
|
+
console.log(` \u21BB Reconnecting to registry in ${delay / 1e3}s...`);
|
|
4041
|
+
}
|
|
4042
|
+
this.reconnectTimer = setTimeout(async () => {
|
|
4043
|
+
this.reconnectTimer = null;
|
|
4044
|
+
try {
|
|
4045
|
+
await this.connect();
|
|
4046
|
+
if (!this.opts.silent) {
|
|
4047
|
+
console.log(" \u2713 Reconnected to registry");
|
|
4048
|
+
}
|
|
4049
|
+
} catch {
|
|
4050
|
+
}
|
|
4051
|
+
}, delay);
|
|
4052
|
+
}
|
|
4053
|
+
};
|
|
4054
|
+
|
|
4055
|
+
// src/gateway/relay-dispatch.ts
|
|
4056
|
+
async function requestViaTemporaryRelay(opts) {
|
|
4057
|
+
const {
|
|
4058
|
+
registryUrl,
|
|
4059
|
+
owner,
|
|
4060
|
+
token,
|
|
4061
|
+
targetOwner,
|
|
4062
|
+
targetAgentId,
|
|
4063
|
+
cardId,
|
|
4064
|
+
skillId,
|
|
4065
|
+
params,
|
|
4066
|
+
timeoutMs = 3e5
|
|
4067
|
+
} = opts;
|
|
4068
|
+
const requesterId = `${owner}:req:${randomUUID10()}`;
|
|
4069
|
+
const relay = new RelayClient({
|
|
4070
|
+
registryUrl,
|
|
4071
|
+
owner: requesterId,
|
|
4072
|
+
token,
|
|
4073
|
+
card: {
|
|
4074
|
+
spec_version: "1.0",
|
|
4075
|
+
id: randomUUID10(),
|
|
4076
|
+
owner: requesterId,
|
|
4077
|
+
name: requesterId,
|
|
4078
|
+
description: "Temporary relay requester",
|
|
4079
|
+
level: 1,
|
|
4080
|
+
inputs: [],
|
|
4081
|
+
outputs: [],
|
|
4082
|
+
pricing: { credits_per_call: 0 },
|
|
4083
|
+
availability: { online: false }
|
|
4084
|
+
},
|
|
4085
|
+
onRequest: async () => ({
|
|
4086
|
+
error: { code: -32601, message: "Temporary relay requester does not serve capabilities" }
|
|
4087
|
+
}),
|
|
4088
|
+
silent: true
|
|
4089
|
+
});
|
|
3207
4090
|
try {
|
|
3208
|
-
|
|
3209
|
-
targetOwner: opts.targetOwner,
|
|
3210
|
-
cardId: opts.cardId,
|
|
3211
|
-
skillId: opts.skillId,
|
|
3212
|
-
params: opts.params ?? {},
|
|
3213
|
-
requester: opts.requester,
|
|
3214
|
-
escrowReceipt: opts.escrowReceipt,
|
|
3215
|
-
timeoutMs: opts.timeoutMs
|
|
3216
|
-
});
|
|
4091
|
+
await relay.connect();
|
|
3217
4092
|
} catch (err) {
|
|
4093
|
+
relay.disconnect();
|
|
3218
4094
|
const message = err instanceof Error ? err.message : String(err);
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
4095
|
+
throw new AgentBnBError(`Relay connection failed: ${message}`, "RELAY_UNAVAILABLE");
|
|
4096
|
+
}
|
|
4097
|
+
try {
|
|
4098
|
+
return await requestViaRelay(relay, {
|
|
4099
|
+
targetOwner,
|
|
4100
|
+
targetAgentId,
|
|
4101
|
+
cardId,
|
|
4102
|
+
skillId,
|
|
4103
|
+
params,
|
|
4104
|
+
requester: owner,
|
|
4105
|
+
// actual owner for credit tracking on relay server
|
|
4106
|
+
timeoutMs
|
|
4107
|
+
});
|
|
4108
|
+
} finally {
|
|
4109
|
+
relay.disconnect();
|
|
3226
4110
|
}
|
|
3227
4111
|
}
|
|
3228
4112
|
|
|
3229
4113
|
// src/autonomy/tiers.ts
|
|
3230
|
-
import { randomUUID as
|
|
4114
|
+
import { randomUUID as randomUUID11 } from "crypto";
|
|
3231
4115
|
|
|
3232
4116
|
// src/autonomy/pending-requests.ts
|
|
3233
|
-
import { randomUUID as
|
|
4117
|
+
import { randomUUID as randomUUID12 } from "crypto";
|
|
3234
4118
|
|
|
3235
4119
|
// src/cli/peers.ts
|
|
3236
4120
|
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
|
|
@@ -3247,8 +4131,17 @@ function minMaxNormalize(values) {
|
|
|
3247
4131
|
}
|
|
3248
4132
|
return values.map((v) => (v - min) / (max - min));
|
|
3249
4133
|
}
|
|
3250
|
-
function scorePeers(candidates, selfOwner) {
|
|
3251
|
-
const
|
|
4134
|
+
function scorePeers(candidates, selfOwner, registryDb) {
|
|
4135
|
+
const selfIdentity = registryDb ? resolveCanonicalIdentity(registryDb, selfOwner) : null;
|
|
4136
|
+
const eligible = candidates.filter((c) => {
|
|
4137
|
+
if (c.card.owner === selfOwner) return false;
|
|
4138
|
+
if (!registryDb || !selfIdentity?.resolved) return true;
|
|
4139
|
+
if (typeof c.card.agent_id === "string" && c.card.agent_id.length > 0) {
|
|
4140
|
+
return c.card.agent_id !== selfIdentity.agent_id;
|
|
4141
|
+
}
|
|
4142
|
+
const peerIdentity = resolveCanonicalIdentity(registryDb, c.card.owner);
|
|
4143
|
+
return !peerIdentity.resolved || peerIdentity.agent_id !== selfIdentity.agent_id;
|
|
4144
|
+
});
|
|
3252
4145
|
if (eligible.length === 0) return [];
|
|
3253
4146
|
const successRates = eligible.map((c) => {
|
|
3254
4147
|
if (c.skillMetadata?.success_rate !== void 0) {
|
|
@@ -3473,7 +4366,7 @@ function buildConductorCard(owner) {
|
|
|
3473
4366
|
return CapabilityCardV2Schema.parse(card);
|
|
3474
4367
|
}
|
|
3475
4368
|
function registerConductorCard(db) {
|
|
3476
|
-
const card = buildConductorCard();
|
|
4369
|
+
const card = attachCanonicalAgentId(db, buildConductorCard());
|
|
3477
4370
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3478
4371
|
const existing = db.prepare("SELECT id FROM capability_cards WHERE id = ?").get(card.id);
|
|
3479
4372
|
if (existing) {
|
|
@@ -3574,6 +4467,7 @@ async function executeSingleTask(pt, gatewayToken, timeoutMs, requesterOwner, re
|
|
|
3574
4467
|
async function orchestrate(opts) {
|
|
3575
4468
|
const { subtasks, matches, gatewayToken, resolveAgentUrl, timeoutMs = 3e5, maxBudget, relayClient, requesterOwner } = opts;
|
|
3576
4469
|
const startTime = Date.now();
|
|
4470
|
+
const hardDeadline = startTime + timeoutMs;
|
|
3577
4471
|
const teamMemberMap = /* @__PURE__ */ new Map();
|
|
3578
4472
|
if (opts.team) {
|
|
3579
4473
|
for (const member of opts.team.matched) {
|
|
@@ -3595,6 +4489,12 @@ async function orchestrate(opts) {
|
|
|
3595
4489
|
const waves = computeWaves(subtasks);
|
|
3596
4490
|
const subtaskMap = new Map(subtasks.map((s) => [s.id, s]));
|
|
3597
4491
|
for (const wave of waves) {
|
|
4492
|
+
const remainingWaveMs = hardDeadline - Date.now();
|
|
4493
|
+
if (remainingWaveMs <= 0) {
|
|
4494
|
+
errors.push(`Orchestration timed out after ${timeoutMs}ms`);
|
|
4495
|
+
break;
|
|
4496
|
+
}
|
|
4497
|
+
const waveTimeoutMs = Math.max(1, Math.min(timeoutMs, remainingWaveMs));
|
|
3598
4498
|
if (maxBudget !== void 0 && totalCredits >= maxBudget) {
|
|
3599
4499
|
errors.push(`Budget exceeded: spent ${totalCredits} cr, max ${maxBudget} cr`);
|
|
3600
4500
|
break;
|
|
@@ -3668,7 +4568,7 @@ async function orchestrate(opts) {
|
|
|
3668
4568
|
gatewayUrl,
|
|
3669
4569
|
gatewayToken,
|
|
3670
4570
|
items.map(({ _pt, ...item }) => item),
|
|
3671
|
-
{ timeoutMs }
|
|
4571
|
+
{ timeoutMs: waveTimeoutMs }
|
|
3672
4572
|
);
|
|
3673
4573
|
return items.map((item) => {
|
|
3674
4574
|
const res = batchResults.get(item.id);
|
|
@@ -3692,7 +4592,7 @@ async function orchestrate(opts) {
|
|
|
3692
4592
|
} catch (batchErr) {
|
|
3693
4593
|
return Promise.all(group.map(async (pt) => {
|
|
3694
4594
|
try {
|
|
3695
|
-
const res = await executeSingleTask(pt, gatewayToken,
|
|
4595
|
+
const res = await executeSingleTask(pt, gatewayToken, waveTimeoutMs, requesterOwner, relayClient, resolveAgentUrl);
|
|
3696
4596
|
return { status: "fulfilled", value: res };
|
|
3697
4597
|
} catch (err) {
|
|
3698
4598
|
return { status: "rejected", reason: err };
|
|
@@ -3706,7 +4606,7 @@ async function orchestrate(opts) {
|
|
|
3706
4606
|
batchPromises.push(
|
|
3707
4607
|
(async () => {
|
|
3708
4608
|
try {
|
|
3709
|
-
const res = await executeSingleTask(pt, gatewayToken,
|
|
4609
|
+
const res = await executeSingleTask(pt, gatewayToken, waveTimeoutMs, requesterOwner, relayClient, resolveAgentUrl);
|
|
3710
4610
|
return [{ status: "fulfilled", value: res }];
|
|
3711
4611
|
} catch (err) {
|
|
3712
4612
|
return [{ status: "rejected", reason: err }];
|
|
@@ -3719,7 +4619,7 @@ async function orchestrate(opts) {
|
|
|
3719
4619
|
batchPromises.push(
|
|
3720
4620
|
(async () => {
|
|
3721
4621
|
try {
|
|
3722
|
-
const res = await executeSingleTask(pt, gatewayToken,
|
|
4622
|
+
const res = await executeSingleTask(pt, gatewayToken, waveTimeoutMs, requesterOwner, relayClient, resolveAgentUrl);
|
|
3723
4623
|
return [{ status: "fulfilled", value: res }];
|
|
3724
4624
|
} catch (err) {
|
|
3725
4625
|
return [{ status: "rejected", reason: err }];
|
|
@@ -3796,7 +4696,7 @@ var BudgetManager = class {
|
|
|
3796
4696
|
};
|
|
3797
4697
|
|
|
3798
4698
|
// src/conductor/team-formation.ts
|
|
3799
|
-
import { randomUUID as
|
|
4699
|
+
import { randomUUID as randomUUID13 } from "crypto";
|
|
3800
4700
|
function selectByStrategy(matches, strategy) {
|
|
3801
4701
|
if (matches.length === 0) return void 0;
|
|
3802
4702
|
if (strategy === "balanced") {
|
|
@@ -3813,7 +4713,7 @@ function selectByStrategy(matches, strategy) {
|
|
|
3813
4713
|
}
|
|
3814
4714
|
async function formTeam(opts) {
|
|
3815
4715
|
const { subtasks, strategy, db, conductorOwner, registryUrl } = opts;
|
|
3816
|
-
const team_id =
|
|
4716
|
+
const team_id = randomUUID13();
|
|
3817
4717
|
if (subtasks.length === 0) {
|
|
3818
4718
|
return { team_id, strategy, matched: [], unrouted: [] };
|
|
3819
4719
|
}
|
|
@@ -3909,6 +4809,7 @@ var ConductorMode = class {
|
|
|
3909
4809
|
* @returns Execution result without latency_ms (added by SkillExecutor).
|
|
3910
4810
|
*/
|
|
3911
4811
|
async execute(config, params, onProgress) {
|
|
4812
|
+
const skillTimeoutMs = typeof config.timeout_ms === "number" ? config.timeout_ms : void 0;
|
|
3912
4813
|
const conductorSkill = config.conductor_skill;
|
|
3913
4814
|
if (conductorSkill !== "orchestrate" && conductorSkill !== "plan") {
|
|
3914
4815
|
return {
|
|
@@ -3948,7 +4849,7 @@ var ConductorMode = class {
|
|
|
3948
4849
|
decomposition_depth: decompositionDepth + 1,
|
|
3949
4850
|
orchestration_depth: orchestrationDepth + 1
|
|
3950
4851
|
},
|
|
3951
|
-
timeoutMs: 3e4
|
|
4852
|
+
timeoutMs: skillTimeoutMs ?? 3e4
|
|
3952
4853
|
});
|
|
3953
4854
|
if (Array.isArray(response)) {
|
|
3954
4855
|
const validation = validateAndNormalizeSubtasks(response, {
|
|
@@ -4019,6 +4920,7 @@ var ConductorMode = class {
|
|
|
4019
4920
|
matches: matchMap,
|
|
4020
4921
|
gatewayToken: this.gatewayToken,
|
|
4021
4922
|
resolveAgentUrl: this.resolveAgentUrl,
|
|
4923
|
+
timeoutMs: skillTimeoutMs,
|
|
4022
4924
|
maxBudget: this.maxBudget,
|
|
4023
4925
|
team
|
|
4024
4926
|
});
|
|
@@ -4042,18 +4944,18 @@ var ConductorMode = class {
|
|
|
4042
4944
|
};
|
|
4043
4945
|
|
|
4044
4946
|
// src/credit/escrow-receipt.ts
|
|
4045
|
-
import { z as
|
|
4046
|
-
import { randomUUID as
|
|
4047
|
-
var EscrowReceiptSchema =
|
|
4048
|
-
requester_owner:
|
|
4049
|
-
requester_agent_id:
|
|
4050
|
-
requester_public_key:
|
|
4051
|
-
amount:
|
|
4052
|
-
card_id:
|
|
4053
|
-
skill_id:
|
|
4054
|
-
timestamp:
|
|
4055
|
-
nonce:
|
|
4056
|
-
signature:
|
|
4947
|
+
import { z as z4 } from "zod";
|
|
4948
|
+
import { randomUUID as randomUUID14 } from "crypto";
|
|
4949
|
+
var EscrowReceiptSchema = z4.object({
|
|
4950
|
+
requester_owner: z4.string().min(1),
|
|
4951
|
+
requester_agent_id: z4.string().optional(),
|
|
4952
|
+
requester_public_key: z4.string().min(1),
|
|
4953
|
+
amount: z4.number().positive(),
|
|
4954
|
+
card_id: z4.string().min(1),
|
|
4955
|
+
skill_id: z4.string().optional(),
|
|
4956
|
+
timestamp: z4.string(),
|
|
4957
|
+
nonce: z4.string().uuid(),
|
|
4958
|
+
signature: z4.string().min(1)
|
|
4057
4959
|
});
|
|
4058
4960
|
function createSignedEscrowReceipt(db, privateKey, publicKey, opts) {
|
|
4059
4961
|
const escrowId = holdEscrow(db, opts.owner, opts.amount, opts.cardId);
|
|
@@ -4065,7 +4967,7 @@ function createSignedEscrowReceipt(db, privateKey, publicKey, opts) {
|
|
|
4065
4967
|
card_id: opts.cardId,
|
|
4066
4968
|
...opts.skillId ? { skill_id: opts.skillId } : {},
|
|
4067
4969
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4068
|
-
nonce:
|
|
4970
|
+
nonce: randomUUID14()
|
|
4069
4971
|
};
|
|
4070
4972
|
const signature = signEscrowReceipt(receiptData, privateKey);
|
|
4071
4973
|
const receipt = {
|
|
@@ -4075,36 +4977,56 @@ function createSignedEscrowReceipt(db, privateKey, publicKey, opts) {
|
|
|
4075
4977
|
return { escrowId, receipt };
|
|
4076
4978
|
}
|
|
4077
4979
|
|
|
4980
|
+
// src/credit/settlement.ts
|
|
4981
|
+
function settleProviderEarning(providerDb, providerOwner, receipt) {
|
|
4982
|
+
const feeAmount = Math.floor(receipt.amount * NETWORK_FEE_RATE);
|
|
4983
|
+
const providerAmount = receipt.amount - feeAmount;
|
|
4984
|
+
recordEarning(
|
|
4985
|
+
providerDb,
|
|
4986
|
+
providerOwner,
|
|
4987
|
+
providerAmount,
|
|
4988
|
+
receipt.card_id,
|
|
4989
|
+
receipt.nonce
|
|
4990
|
+
);
|
|
4991
|
+
return { settled: true };
|
|
4992
|
+
}
|
|
4993
|
+
function settleRequesterEscrow(requesterDb, escrowId) {
|
|
4994
|
+
confirmEscrowDebit(requesterDb, escrowId);
|
|
4995
|
+
}
|
|
4996
|
+
function releaseRequesterEscrow(requesterDb, escrowId) {
|
|
4997
|
+
releaseEscrow(requesterDb, escrowId);
|
|
4998
|
+
}
|
|
4999
|
+
|
|
4078
5000
|
// src/identity/identity.ts
|
|
4079
|
-
import { z as
|
|
5001
|
+
import { z as z5 } from "zod";
|
|
4080
5002
|
import { createHash as createHash2, createPrivateKey as createPrivateKey2, createPublicKey as createPublicKey2 } from "crypto";
|
|
4081
5003
|
import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, existsSync as existsSync4, mkdirSync as mkdirSync3 } from "fs";
|
|
4082
5004
|
import { join as join4 } from "path";
|
|
4083
|
-
var AgentIdentitySchema =
|
|
5005
|
+
var AgentIdentitySchema = z5.object({
|
|
4084
5006
|
/** Deterministic ID derived from public key: sha256(hex).slice(0, 16). */
|
|
4085
|
-
agent_id:
|
|
5007
|
+
agent_id: z5.string().min(1),
|
|
4086
5008
|
/** Human-readable owner name (from config or init). */
|
|
4087
|
-
owner:
|
|
5009
|
+
owner: z5.string().min(1),
|
|
4088
5010
|
/** Hex-encoded Ed25519 public key. */
|
|
4089
|
-
public_key:
|
|
5011
|
+
public_key: z5.string().min(1),
|
|
4090
5012
|
/** ISO 8601 timestamp of identity creation. */
|
|
4091
|
-
created_at:
|
|
5013
|
+
created_at: z5.string().datetime(),
|
|
4092
5014
|
/** Optional guarantor info if linked to a human. */
|
|
4093
|
-
guarantor:
|
|
4094
|
-
github_login:
|
|
4095
|
-
verified_at:
|
|
5015
|
+
guarantor: z5.object({
|
|
5016
|
+
github_login: z5.string().min(1),
|
|
5017
|
+
verified_at: z5.string().datetime()
|
|
4096
5018
|
}).optional()
|
|
4097
5019
|
});
|
|
4098
|
-
var AgentCertificateSchema =
|
|
5020
|
+
var AgentCertificateSchema = z5.object({
|
|
4099
5021
|
identity: AgentIdentitySchema,
|
|
4100
5022
|
/** ISO 8601 timestamp of certificate issuance. */
|
|
4101
|
-
issued_at:
|
|
5023
|
+
issued_at: z5.string().datetime(),
|
|
4102
5024
|
/** ISO 8601 timestamp of certificate expiry. */
|
|
4103
|
-
expires_at:
|
|
5025
|
+
expires_at: z5.string().datetime(),
|
|
4104
5026
|
/** Hex-encoded public key of the issuer (same as identity for self-signed). */
|
|
4105
|
-
issuer_public_key:
|
|
5027
|
+
issuer_public_key: z5.string().min(1),
|
|
4106
5028
|
/** Base64url Ed25519 signature over { identity, issued_at, expires_at, issuer_public_key }. */
|
|
4107
|
-
signature:
|
|
5029
|
+
signature: z5.string().min(1)
|
|
4108
5030
|
});
|
|
4109
5031
|
var IDENTITY_FILENAME = "identity.json";
|
|
4110
5032
|
var PRIVATE_KEY_FILENAME = "private.key";
|
|
@@ -4293,6 +5215,34 @@ var AgentBnBConsumer = class {
|
|
|
4293
5215
|
return this.identity;
|
|
4294
5216
|
}
|
|
4295
5217
|
/**
|
|
5218
|
+
* Requests a paid capability via the relay. The relay handles escrow + network fee
|
|
5219
|
+
* server-side, avoiding the fee bypass bug in the signed receipt path.
|
|
5220
|
+
*
|
|
5221
|
+
* @param opts - Relay request options.
|
|
5222
|
+
* @returns The result from the capability execution.
|
|
5223
|
+
* @throws {AgentBnBError} on relay connection failure or execution error.
|
|
5224
|
+
*/
|
|
5225
|
+
async requestViaRelay(opts) {
|
|
5226
|
+
const identity = this.getIdentity();
|
|
5227
|
+
const config = loadConfig();
|
|
5228
|
+
const token = config?.token ?? "";
|
|
5229
|
+
return requestViaTemporaryRelay({
|
|
5230
|
+
registryUrl: opts.registryUrl,
|
|
5231
|
+
owner: identity.owner,
|
|
5232
|
+
token,
|
|
5233
|
+
targetOwner: opts.targetOwner,
|
|
5234
|
+
targetAgentId: opts.targetAgentId,
|
|
5235
|
+
cardId: opts.cardId,
|
|
5236
|
+
skillId: opts.skillId,
|
|
5237
|
+
params: opts.params ?? {},
|
|
5238
|
+
timeoutMs: opts.timeoutMs
|
|
5239
|
+
});
|
|
5240
|
+
}
|
|
5241
|
+
/**
|
|
5242
|
+
* @deprecated Use `requestViaRelay()` for paid remote requests. This method uses
|
|
5243
|
+
* local escrow + signed receipts which bypass the network fee. Still works for
|
|
5244
|
+
* free/local requests.
|
|
5245
|
+
*
|
|
4296
5246
|
* Requests a capability from a remote agent with full escrow lifecycle.
|
|
4297
5247
|
*
|
|
4298
5248
|
* 1. Creates a signed escrow receipt (holds credits locally)
|
|
@@ -4327,6 +5277,10 @@ var AgentBnBConsumer = class {
|
|
|
4327
5277
|
cardId: opts.cardId,
|
|
4328
5278
|
params: opts.params,
|
|
4329
5279
|
timeoutMs: opts.timeoutMs,
|
|
5280
|
+
timeoutHint: {
|
|
5281
|
+
expected_duration_ms: opts.expectedDurationMs,
|
|
5282
|
+
hard_timeout_ms: opts.providerHardTimeoutMs
|
|
5283
|
+
},
|
|
4330
5284
|
escrowReceipt: receipt,
|
|
4331
5285
|
identity: {
|
|
4332
5286
|
agentId: identity.agent_id,
|
|
@@ -4507,16 +5461,16 @@ var AgentBnBProvider = class {
|
|
|
4507
5461
|
};
|
|
4508
5462
|
|
|
4509
5463
|
// src/identity/guarantor.ts
|
|
4510
|
-
import { z as
|
|
4511
|
-
import { randomUUID as
|
|
5464
|
+
import { z as z6 } from "zod";
|
|
5465
|
+
import { randomUUID as randomUUID15 } from "crypto";
|
|
4512
5466
|
var MAX_AGENTS_PER_GUARANTOR = 10;
|
|
4513
5467
|
var GUARANTOR_CREDIT_POOL = 50;
|
|
4514
|
-
var GuarantorRecordSchema =
|
|
4515
|
-
id:
|
|
4516
|
-
github_login:
|
|
4517
|
-
agent_count:
|
|
4518
|
-
credit_pool:
|
|
4519
|
-
created_at:
|
|
5468
|
+
var GuarantorRecordSchema = z6.object({
|
|
5469
|
+
id: z6.string().uuid(),
|
|
5470
|
+
github_login: z6.string().min(1),
|
|
5471
|
+
agent_count: z6.number().int().nonnegative(),
|
|
5472
|
+
credit_pool: z6.number().int().nonnegative(),
|
|
5473
|
+
created_at: z6.string().datetime()
|
|
4520
5474
|
});
|
|
4521
5475
|
var GUARANTOR_SCHEMA = `
|
|
4522
5476
|
CREATE TABLE IF NOT EXISTS guarantors (
|
|
@@ -4547,7 +5501,7 @@ function registerGuarantor(db, githubLogin) {
|
|
|
4547
5501
|
);
|
|
4548
5502
|
}
|
|
4549
5503
|
const record = {
|
|
4550
|
-
id:
|
|
5504
|
+
id: randomUUID15(),
|
|
4551
5505
|
github_login: githubLogin,
|
|
4552
5506
|
agent_count: 0,
|
|
4553
5507
|
credit_pool: GUARANTOR_CREDIT_POOL,
|
|
@@ -4621,12 +5575,12 @@ function getAgentGuarantor(db, agentId) {
|
|
|
4621
5575
|
function initiateGithubAuth() {
|
|
4622
5576
|
return {
|
|
4623
5577
|
auth_url: "https://github.com/login/oauth/authorize?client_id=PLACEHOLDER&scope=read:user",
|
|
4624
|
-
state:
|
|
5578
|
+
state: randomUUID15()
|
|
4625
5579
|
};
|
|
4626
5580
|
}
|
|
4627
5581
|
|
|
4628
5582
|
// src/relay/websocket-relay.ts
|
|
4629
|
-
import { randomUUID as
|
|
5583
|
+
import { randomUUID as randomUUID18 } from "crypto";
|
|
4630
5584
|
|
|
4631
5585
|
// src/relay/relay-credit.ts
|
|
4632
5586
|
function lookupCardPrice(registryDb, cardId, skillId) {
|
|
@@ -4724,7 +5678,9 @@ function processEscrowSettle(creditDb, escrowId, success, providerAgentId, signa
|
|
|
4724
5678
|
throw new Error("Invalid consumer signature on escrow settle");
|
|
4725
5679
|
}
|
|
4726
5680
|
}
|
|
4727
|
-
const escrowRow = creditDb.prepare(
|
|
5681
|
+
const escrowRow = creditDb.prepare(
|
|
5682
|
+
"SELECT amount, owner FROM credit_escrow WHERE id = ? AND status IN ('held', 'started', 'progressing', 'abandoned')"
|
|
5683
|
+
).get(escrowId);
|
|
4728
5684
|
if (!escrowRow) {
|
|
4729
5685
|
throw new Error(`Escrow not found or already settled: ${escrowId}`);
|
|
4730
5686
|
}
|
|
@@ -4756,10 +5712,10 @@ function settleWithNetworkFee(creditDb, escrowId, providerOwner) {
|
|
|
4756
5712
|
}
|
|
4757
5713
|
|
|
4758
5714
|
// src/hub-agent/relay-bridge.ts
|
|
4759
|
-
import { randomUUID as
|
|
5715
|
+
import { randomUUID as randomUUID17 } from "crypto";
|
|
4760
5716
|
|
|
4761
5717
|
// src/hub-agent/job-queue.ts
|
|
4762
|
-
import { randomUUID as
|
|
5718
|
+
import { randomUUID as randomUUID16 } from "crypto";
|
|
4763
5719
|
function updateJobStatus(db, jobId, status, result) {
|
|
4764
5720
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4765
5721
|
if (result !== void 0) {
|
|
@@ -4799,7 +5755,16 @@ function handleJobRelayResponse(opts) {
|
|
|
4799
5755
|
// src/relay/websocket-relay.ts
|
|
4800
5756
|
var RATE_LIMIT_MAX = 60;
|
|
4801
5757
|
var RATE_LIMIT_WINDOW_MS = 6e4;
|
|
4802
|
-
var
|
|
5758
|
+
var RELAY_IDLE_TIMEOUT_MS = 3e4;
|
|
5759
|
+
var RELAY_HARD_TIMEOUT_MS = 3e5;
|
|
5760
|
+
var RELAY_DISCONNECT_GRACE_MS = RELAY_HARD_TIMEOUT_MS + 3e4;
|
|
5761
|
+
function readTimeoutOverride(envKey, fallbackMs) {
|
|
5762
|
+
const raw = process.env[envKey];
|
|
5763
|
+
if (!raw) return fallbackMs;
|
|
5764
|
+
const parsed = Number(raw);
|
|
5765
|
+
if (!Number.isFinite(parsed) || parsed <= 0) return fallbackMs;
|
|
5766
|
+
return Math.floor(parsed);
|
|
5767
|
+
}
|
|
4803
5768
|
function registerWebSocketRelay(server, db, creditDb) {
|
|
4804
5769
|
const connections = /* @__PURE__ */ new Map();
|
|
4805
5770
|
const agentIdToOwner = /* @__PURE__ */ new Map();
|
|
@@ -4813,6 +5778,136 @@ function registerWebSocketRelay(server, db, creditDb) {
|
|
|
4813
5778
|
if (connections.has(target)) return target;
|
|
4814
5779
|
return void 0;
|
|
4815
5780
|
}
|
|
5781
|
+
function clearPendingTimers(pending) {
|
|
5782
|
+
clearTimeout(pending.timeout);
|
|
5783
|
+
if (pending.idleTimeout) clearTimeout(pending.idleTimeout);
|
|
5784
|
+
if (pending.hardTimeout) clearTimeout(pending.hardTimeout);
|
|
5785
|
+
if (pending.graceTimeout) clearTimeout(pending.graceTimeout);
|
|
5786
|
+
}
|
|
5787
|
+
function setPendingTimer(pending, timerType, timer) {
|
|
5788
|
+
const existing = pending[timerType];
|
|
5789
|
+
if (existing) clearTimeout(existing);
|
|
5790
|
+
pending[timerType] = timer;
|
|
5791
|
+
pending.timeout = timer;
|
|
5792
|
+
}
|
|
5793
|
+
function scheduleIdleTimeout(requestId, pending) {
|
|
5794
|
+
const timeoutMs = readTimeoutOverride("AGENTBNB_RELAY_IDLE_TIMEOUT_MS", RELAY_IDLE_TIMEOUT_MS);
|
|
5795
|
+
const timer = setTimeout(() => {
|
|
5796
|
+
const current = pendingRequests.get(requestId);
|
|
5797
|
+
if (!current || (current.lifecycle ?? "held") !== "held") return;
|
|
5798
|
+
pendingRequests.delete(requestId);
|
|
5799
|
+
clearPendingTimers(current);
|
|
5800
|
+
if (current.escrowId && creditDb) {
|
|
5801
|
+
try {
|
|
5802
|
+
releaseForRelay(creditDb, current.escrowId);
|
|
5803
|
+
} catch (e) {
|
|
5804
|
+
console.error("[relay] escrow release on idle timeout failed:", e);
|
|
5805
|
+
}
|
|
5806
|
+
}
|
|
5807
|
+
const originWs = connections.get(current.originOwner);
|
|
5808
|
+
if (originWs && originWs.readyState === 1) {
|
|
5809
|
+
sendMessage(originWs, {
|
|
5810
|
+
type: "response",
|
|
5811
|
+
id: requestId,
|
|
5812
|
+
error: { code: -32603, message: "Relay request timeout" }
|
|
5813
|
+
});
|
|
5814
|
+
}
|
|
5815
|
+
}, timeoutMs);
|
|
5816
|
+
setPendingTimer(pending, "idleTimeout", timer);
|
|
5817
|
+
}
|
|
5818
|
+
function scheduleHardTimeout(requestId, pending) {
|
|
5819
|
+
const timeoutMs = readTimeoutOverride("AGENTBNB_RELAY_HARD_TIMEOUT_MS", RELAY_HARD_TIMEOUT_MS);
|
|
5820
|
+
const timer = setTimeout(() => {
|
|
5821
|
+
const current = pendingRequests.get(requestId);
|
|
5822
|
+
if (!current || current.lifecycle === "abandoned") return;
|
|
5823
|
+
pendingRequests.delete(requestId);
|
|
5824
|
+
clearPendingTimers(current);
|
|
5825
|
+
if (current.escrowId && creditDb) {
|
|
5826
|
+
try {
|
|
5827
|
+
releaseForRelay(creditDb, current.escrowId);
|
|
5828
|
+
} catch (e) {
|
|
5829
|
+
console.error("[relay] escrow release on hard timeout failed:", e);
|
|
5830
|
+
}
|
|
5831
|
+
}
|
|
5832
|
+
const originWs = connections.get(current.originOwner);
|
|
5833
|
+
if (originWs && originWs.readyState === 1) {
|
|
5834
|
+
sendMessage(originWs, {
|
|
5835
|
+
type: "response",
|
|
5836
|
+
id: requestId,
|
|
5837
|
+
error: { code: -32603, message: "Relay request timeout" }
|
|
5838
|
+
});
|
|
5839
|
+
}
|
|
5840
|
+
}, timeoutMs);
|
|
5841
|
+
setPendingTimer(pending, "hardTimeout", timer);
|
|
5842
|
+
}
|
|
5843
|
+
function scheduleGraceTimeout(requestId, pending) {
|
|
5844
|
+
const timeoutMs = readTimeoutOverride(
|
|
5845
|
+
"AGENTBNB_RELAY_DISCONNECT_GRACE_MS",
|
|
5846
|
+
RELAY_DISCONNECT_GRACE_MS
|
|
5847
|
+
);
|
|
5848
|
+
const timer = setTimeout(() => {
|
|
5849
|
+
const current = pendingRequests.get(requestId);
|
|
5850
|
+
if (!current || current.lifecycle !== "abandoned") return;
|
|
5851
|
+
pendingRequests.delete(requestId);
|
|
5852
|
+
clearPendingTimers(current);
|
|
5853
|
+
if (current.escrowId && creditDb) {
|
|
5854
|
+
try {
|
|
5855
|
+
releaseForRelay(creditDb, current.escrowId);
|
|
5856
|
+
} catch (e) {
|
|
5857
|
+
console.error("[relay] escrow release after grace timeout failed:", e);
|
|
5858
|
+
}
|
|
5859
|
+
}
|
|
5860
|
+
}, timeoutMs);
|
|
5861
|
+
setPendingTimer(pending, "graceTimeout", timer);
|
|
5862
|
+
}
|
|
5863
|
+
function transitionPendingToStarted(requestId, pending) {
|
|
5864
|
+
if (pending.lifecycle === "started" || pending.lifecycle === "progressing" || pending.lifecycle === "abandoned") {
|
|
5865
|
+
return;
|
|
5866
|
+
}
|
|
5867
|
+
pending.lifecycle = "started";
|
|
5868
|
+
pending.startedAt = Date.now();
|
|
5869
|
+
if (pending.idleTimeout) clearTimeout(pending.idleTimeout);
|
|
5870
|
+
if (pending.graceTimeout) clearTimeout(pending.graceTimeout);
|
|
5871
|
+
if (pending.escrowId && creditDb) {
|
|
5872
|
+
try {
|
|
5873
|
+
markEscrowStarted(creditDb, pending.escrowId);
|
|
5874
|
+
} catch (e) {
|
|
5875
|
+
console.error("[relay] escrow transition to started failed:", e);
|
|
5876
|
+
}
|
|
5877
|
+
}
|
|
5878
|
+
scheduleHardTimeout(requestId, pending);
|
|
5879
|
+
}
|
|
5880
|
+
function transitionPendingToProgressing(requestId, pending) {
|
|
5881
|
+
if (pending.lifecycle === "abandoned") {
|
|
5882
|
+
return;
|
|
5883
|
+
}
|
|
5884
|
+
if ((pending.lifecycle ?? "held") === "held") {
|
|
5885
|
+
transitionPendingToStarted(requestId, pending);
|
|
5886
|
+
}
|
|
5887
|
+
pending.lifecycle = "progressing";
|
|
5888
|
+
if (pending.escrowId && creditDb) {
|
|
5889
|
+
try {
|
|
5890
|
+
markEscrowProgressing(creditDb, pending.escrowId);
|
|
5891
|
+
} catch (e) {
|
|
5892
|
+
console.error("[relay] escrow transition to progressing failed:", e);
|
|
5893
|
+
}
|
|
5894
|
+
}
|
|
5895
|
+
}
|
|
5896
|
+
function transitionPendingToAbandoned(requestId, pending) {
|
|
5897
|
+
if (pending.lifecycle === "abandoned") return;
|
|
5898
|
+
pending.lifecycle = "abandoned";
|
|
5899
|
+
pending.abandonedAt = Date.now();
|
|
5900
|
+
if (pending.idleTimeout) clearTimeout(pending.idleTimeout);
|
|
5901
|
+
if (pending.hardTimeout) clearTimeout(pending.hardTimeout);
|
|
5902
|
+
if (pending.escrowId && creditDb) {
|
|
5903
|
+
try {
|
|
5904
|
+
markEscrowAbandoned(creditDb, pending.escrowId);
|
|
5905
|
+
} catch (e) {
|
|
5906
|
+
console.error("[relay] escrow transition to abandoned failed:", e);
|
|
5907
|
+
}
|
|
5908
|
+
}
|
|
5909
|
+
scheduleGraceTimeout(requestId, pending);
|
|
5910
|
+
}
|
|
4816
5911
|
function checkRateLimit(owner) {
|
|
4817
5912
|
const now = Date.now();
|
|
4818
5913
|
const entry = rateLimits.get(owner);
|
|
@@ -4862,15 +5957,20 @@ function registerWebSocketRelay(server, db, creditDb) {
|
|
|
4862
5957
|
} catch {
|
|
4863
5958
|
}
|
|
4864
5959
|
}
|
|
4865
|
-
function upsertCard(cardData, owner) {
|
|
4866
|
-
const parsed = AnyCardSchema.safeParse(
|
|
5960
|
+
function upsertCard(cardData, owner, agentId) {
|
|
5961
|
+
const parsed = AnyCardSchema.safeParse(
|
|
5962
|
+
agentId ? { ...cardData, agent_id: agentId } : cardData
|
|
5963
|
+
);
|
|
4867
5964
|
if (!parsed.success) {
|
|
4868
5965
|
throw new AgentBnBError(
|
|
4869
5966
|
`Card validation failed: ${parsed.error.message}`,
|
|
4870
5967
|
"VALIDATION_ERROR"
|
|
4871
5968
|
);
|
|
4872
5969
|
}
|
|
4873
|
-
const card =
|
|
5970
|
+
const card = attachCanonicalAgentId(db, {
|
|
5971
|
+
...parsed.data,
|
|
5972
|
+
availability: { ...parsed.data.availability, online: true }
|
|
5973
|
+
});
|
|
4874
5974
|
const cardId = card.id;
|
|
4875
5975
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4876
5976
|
const existing = db.prepare("SELECT id FROM capability_cards WHERE id = ?").get(cardId);
|
|
@@ -4888,7 +5988,7 @@ function registerWebSocketRelay(server, db, creditDb) {
|
|
|
4888
5988
|
function logAgentJoined(owner, cardName, cardId) {
|
|
4889
5989
|
try {
|
|
4890
5990
|
insertRequestLog(db, {
|
|
4891
|
-
id:
|
|
5991
|
+
id: randomUUID18(),
|
|
4892
5992
|
card_id: cardId,
|
|
4893
5993
|
card_name: cardName,
|
|
4894
5994
|
requester: owner,
|
|
@@ -4924,7 +6024,7 @@ function registerWebSocketRelay(server, db, creditDb) {
|
|
|
4924
6024
|
agentIdToOwner.set(agentEntry.agent_id, owner);
|
|
4925
6025
|
for (const agentCard of agentEntry.cards) {
|
|
4926
6026
|
try {
|
|
4927
|
-
upsertCard(agentCard, owner);
|
|
6027
|
+
upsertCard(agentCard, owner, agentEntry.agent_id);
|
|
4928
6028
|
} catch {
|
|
4929
6029
|
}
|
|
4930
6030
|
}
|
|
@@ -4938,7 +6038,7 @@ function registerWebSocketRelay(server, db, creditDb) {
|
|
|
4938
6038
|
}
|
|
4939
6039
|
let cardId;
|
|
4940
6040
|
try {
|
|
4941
|
-
cardId = upsertCard(card, owner);
|
|
6041
|
+
cardId = upsertCard(card, owner, msg.agent_id);
|
|
4942
6042
|
} catch (err) {
|
|
4943
6043
|
console.error(`[relay] card validation failed for ${owner}:`, err instanceof Error ? err.message : err);
|
|
4944
6044
|
cardId = card.id ?? owner;
|
|
@@ -4948,7 +6048,7 @@ function registerWebSocketRelay(server, db, creditDb) {
|
|
|
4948
6048
|
if (msg.cards && msg.cards.length > 0) {
|
|
4949
6049
|
for (const extraCard of msg.cards) {
|
|
4950
6050
|
try {
|
|
4951
|
-
upsertCard(extraCard, owner);
|
|
6051
|
+
upsertCard(extraCard, owner, msg.agent_id);
|
|
4952
6052
|
} catch {
|
|
4953
6053
|
}
|
|
4954
6054
|
}
|
|
@@ -5003,23 +6103,17 @@ function registerWebSocketRelay(server, db, creditDb) {
|
|
|
5003
6103
|
console.error("[relay] credit hold error (non-fatal):", err);
|
|
5004
6104
|
}
|
|
5005
6105
|
}
|
|
5006
|
-
const
|
|
5007
|
-
|
|
5008
|
-
|
|
5009
|
-
|
|
5010
|
-
|
|
5011
|
-
|
|
5012
|
-
|
|
5013
|
-
|
|
5014
|
-
|
|
5015
|
-
|
|
5016
|
-
|
|
5017
|
-
type: "response",
|
|
5018
|
-
id: msg.id,
|
|
5019
|
-
error: { code: -32603, message: "Relay request timeout" }
|
|
5020
|
-
});
|
|
5021
|
-
}, RELAY_TIMEOUT_MS);
|
|
5022
|
-
pendingRequests.set(msg.id, { originOwner: fromOwner, creditOwner, timeout, escrowId, targetOwner: msg.target_owner });
|
|
6106
|
+
const pending = {
|
|
6107
|
+
originOwner: fromOwner,
|
|
6108
|
+
creditOwner,
|
|
6109
|
+
timeout: setTimeout(() => void 0, 1),
|
|
6110
|
+
escrowId,
|
|
6111
|
+
targetOwner: targetKey ?? msg.target_owner,
|
|
6112
|
+
lifecycle: "held",
|
|
6113
|
+
createdAt: Date.now()
|
|
6114
|
+
};
|
|
6115
|
+
pendingRequests.set(msg.id, pending);
|
|
6116
|
+
scheduleIdleTimeout(msg.id, pending);
|
|
5023
6117
|
sendMessage(targetWs, {
|
|
5024
6118
|
type: "incoming_request",
|
|
5025
6119
|
id: msg.id,
|
|
@@ -5031,30 +6125,23 @@ function registerWebSocketRelay(server, db, creditDb) {
|
|
|
5031
6125
|
escrow_receipt: msg.escrow_receipt
|
|
5032
6126
|
});
|
|
5033
6127
|
}
|
|
6128
|
+
function handleRelayStarted(msg) {
|
|
6129
|
+
const pending = pendingRequests.get(msg.id);
|
|
6130
|
+
if (!pending) return;
|
|
6131
|
+
transitionPendingToStarted(msg.id, pending);
|
|
6132
|
+
const originWs = connections.get(pending.originOwner);
|
|
6133
|
+
if (originWs && originWs.readyState === 1) {
|
|
6134
|
+
sendMessage(originWs, {
|
|
6135
|
+
type: "relay_started",
|
|
6136
|
+
id: msg.id,
|
|
6137
|
+
message: msg.message
|
|
6138
|
+
});
|
|
6139
|
+
}
|
|
6140
|
+
}
|
|
5034
6141
|
function handleRelayProgress(msg) {
|
|
5035
6142
|
const pending = pendingRequests.get(msg.id);
|
|
5036
6143
|
if (!pending) return;
|
|
5037
|
-
|
|
5038
|
-
const newTimeout = setTimeout(() => {
|
|
5039
|
-
const p = pendingRequests.get(msg.id);
|
|
5040
|
-
pendingRequests.delete(msg.id);
|
|
5041
|
-
if (p?.escrowId && creditDb) {
|
|
5042
|
-
try {
|
|
5043
|
-
releaseForRelay(creditDb, p.escrowId);
|
|
5044
|
-
} catch (e) {
|
|
5045
|
-
console.error("[relay] escrow release on progress timeout failed:", e);
|
|
5046
|
-
}
|
|
5047
|
-
}
|
|
5048
|
-
const originWs2 = connections.get(pending.originOwner);
|
|
5049
|
-
if (originWs2 && originWs2.readyState === 1) {
|
|
5050
|
-
sendMessage(originWs2, {
|
|
5051
|
-
type: "response",
|
|
5052
|
-
id: msg.id,
|
|
5053
|
-
error: { code: -32603, message: "Relay request timeout" }
|
|
5054
|
-
});
|
|
5055
|
-
}
|
|
5056
|
-
}, RELAY_TIMEOUT_MS);
|
|
5057
|
-
pending.timeout = newTimeout;
|
|
6144
|
+
transitionPendingToProgressing(msg.id, pending);
|
|
5058
6145
|
const originWs = connections.get(pending.originOwner);
|
|
5059
6146
|
if (originWs && originWs.readyState === 1) {
|
|
5060
6147
|
sendMessage(originWs, {
|
|
@@ -5068,7 +6155,7 @@ function registerWebSocketRelay(server, db, creditDb) {
|
|
|
5068
6155
|
function handleRelayResponse(msg) {
|
|
5069
6156
|
const pending = pendingRequests.get(msg.id);
|
|
5070
6157
|
if (!pending) return;
|
|
5071
|
-
|
|
6158
|
+
clearPendingTimers(pending);
|
|
5072
6159
|
pendingRequests.delete(msg.id);
|
|
5073
6160
|
if (pending.jobId && creditDb) {
|
|
5074
6161
|
try {
|
|
@@ -5131,16 +6218,15 @@ function registerWebSocketRelay(server, db, creditDb) {
|
|
|
5131
6218
|
connections.delete(owner);
|
|
5132
6219
|
rateLimits.delete(owner);
|
|
5133
6220
|
agentCapacities.delete(owner);
|
|
5134
|
-
for (const [agentId,
|
|
5135
|
-
if (
|
|
6221
|
+
for (const [agentId, mappedOwner] of Array.from(agentIdToOwner.entries())) {
|
|
6222
|
+
if (mappedOwner === owner) {
|
|
5136
6223
|
agentIdToOwner.delete(agentId);
|
|
5137
|
-
break;
|
|
5138
6224
|
}
|
|
5139
6225
|
}
|
|
5140
6226
|
markOwnerOffline(owner);
|
|
5141
6227
|
for (const [reqId, pending] of pendingRequests) {
|
|
5142
6228
|
if (pending.targetOwner === owner) {
|
|
5143
|
-
|
|
6229
|
+
clearPendingTimers(pending);
|
|
5144
6230
|
pendingRequests.delete(reqId);
|
|
5145
6231
|
if (pending.escrowId && creditDb) {
|
|
5146
6232
|
try {
|
|
@@ -5158,14 +6244,19 @@ function registerWebSocketRelay(server, db, creditDb) {
|
|
|
5158
6244
|
});
|
|
5159
6245
|
}
|
|
5160
6246
|
} else if (pending.originOwner === owner) {
|
|
5161
|
-
|
|
5162
|
-
|
|
5163
|
-
|
|
5164
|
-
|
|
5165
|
-
|
|
5166
|
-
|
|
5167
|
-
|
|
6247
|
+
const lifecycle = pending.lifecycle ?? "held";
|
|
6248
|
+
if (lifecycle === "held") {
|
|
6249
|
+
clearPendingTimers(pending);
|
|
6250
|
+
pendingRequests.delete(reqId);
|
|
6251
|
+
if (pending.escrowId && creditDb) {
|
|
6252
|
+
try {
|
|
6253
|
+
releaseForRelay(creditDb, pending.escrowId);
|
|
6254
|
+
} catch (e) {
|
|
6255
|
+
console.error("[relay] escrow release on requester disconnect failed:", e);
|
|
6256
|
+
}
|
|
5168
6257
|
}
|
|
6258
|
+
} else {
|
|
6259
|
+
transitionPendingToAbandoned(reqId, pending);
|
|
5169
6260
|
}
|
|
5170
6261
|
}
|
|
5171
6262
|
}
|
|
@@ -5208,7 +6299,9 @@ function registerWebSocketRelay(server, db, creditDb) {
|
|
|
5208
6299
|
return;
|
|
5209
6300
|
}
|
|
5210
6301
|
try {
|
|
5211
|
-
const escrow = creditDb.prepare(
|
|
6302
|
+
const escrow = creditDb.prepare(
|
|
6303
|
+
"SELECT card_id FROM credit_escrow WHERE id = ? AND status IN ('held', 'started', 'progressing', 'abandoned')"
|
|
6304
|
+
).get(msg.escrow_id);
|
|
5212
6305
|
if (!escrow) {
|
|
5213
6306
|
sendMessage(ws, { type: "error", code: "escrow_not_found", message: `Escrow not found: ${msg.escrow_id}`, request_id: msg.request_id });
|
|
5214
6307
|
return;
|
|
@@ -5305,6 +6398,9 @@ function registerWebSocketRelay(server, db, creditDb) {
|
|
|
5305
6398
|
case "relay_response":
|
|
5306
6399
|
handleRelayResponse(msg);
|
|
5307
6400
|
break;
|
|
6401
|
+
case "relay_started":
|
|
6402
|
+
handleRelayStarted(msg);
|
|
6403
|
+
break;
|
|
5308
6404
|
case "relay_progress":
|
|
5309
6405
|
handleRelayProgress(msg);
|
|
5310
6406
|
break;
|
|
@@ -5351,7 +6447,7 @@ function registerWebSocketRelay(server, db, creditDb) {
|
|
|
5351
6447
|
}
|
|
5352
6448
|
connections.clear();
|
|
5353
6449
|
for (const [, pending] of pendingRequests) {
|
|
5354
|
-
|
|
6450
|
+
clearPendingTimers(pending);
|
|
5355
6451
|
}
|
|
5356
6452
|
pendingRequests.clear();
|
|
5357
6453
|
rateLimits.clear();
|
|
@@ -5371,12 +6467,12 @@ function registerWebSocketRelay(server, db, creditDb) {
|
|
|
5371
6467
|
}
|
|
5372
6468
|
|
|
5373
6469
|
// src/onboarding/index.ts
|
|
5374
|
-
import { randomUUID as
|
|
6470
|
+
import { randomUUID as randomUUID20 } from "crypto";
|
|
5375
6471
|
import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
|
|
5376
6472
|
import { join as join5 } from "path";
|
|
5377
6473
|
|
|
5378
6474
|
// src/cli/onboarding.ts
|
|
5379
|
-
import { randomUUID as
|
|
6475
|
+
import { randomUUID as randomUUID19 } from "crypto";
|
|
5380
6476
|
import { createConnection } from "net";
|
|
5381
6477
|
var KNOWN_API_KEYS = [
|
|
5382
6478
|
"OPENAI_API_KEY",
|
|
@@ -5518,7 +6614,7 @@ function capabilitiesToV2Card(capabilities, owner, agentName) {
|
|
|
5518
6614
|
}));
|
|
5519
6615
|
const card = {
|
|
5520
6616
|
spec_version: "2.0",
|
|
5521
|
-
id:
|
|
6617
|
+
id: randomUUID20(),
|
|
5522
6618
|
owner,
|
|
5523
6619
|
agent_name: agentName ?? owner,
|
|
5524
6620
|
skills,
|