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
|
@@ -0,0 +1,797 @@
|
|
|
1
|
+
import {
|
|
2
|
+
canonicalizeCreditOwner,
|
|
3
|
+
ensureReliabilityTable,
|
|
4
|
+
migrateCreditOwnerData,
|
|
5
|
+
recordSuccessfulHire
|
|
6
|
+
} from "./chunk-6QMDJVMS.js";
|
|
7
|
+
import {
|
|
8
|
+
getCard,
|
|
9
|
+
getFeedbackForProvider
|
|
10
|
+
} from "./chunk-ZU2TP7CN.js";
|
|
11
|
+
import {
|
|
12
|
+
ensureAgentsTable,
|
|
13
|
+
resolveCanonicalIdentity
|
|
14
|
+
} from "./chunk-EE3V3DXK.js";
|
|
15
|
+
import {
|
|
16
|
+
AgentBnBError
|
|
17
|
+
} from "./chunk-I7KWA7OB.js";
|
|
18
|
+
|
|
19
|
+
// src/credit/ledger.ts
|
|
20
|
+
import Database from "better-sqlite3";
|
|
21
|
+
import { randomUUID } from "crypto";
|
|
22
|
+
var CREDIT_SCHEMA = `
|
|
23
|
+
CREATE TABLE IF NOT EXISTS credit_balances (
|
|
24
|
+
owner TEXT PRIMARY KEY,
|
|
25
|
+
balance INTEGER NOT NULL DEFAULT 0,
|
|
26
|
+
updated_at TEXT NOT NULL
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
CREATE TABLE IF NOT EXISTS credit_transactions (
|
|
30
|
+
id TEXT PRIMARY KEY,
|
|
31
|
+
owner TEXT NOT NULL,
|
|
32
|
+
amount INTEGER NOT NULL,
|
|
33
|
+
reason TEXT NOT NULL,
|
|
34
|
+
reference_id TEXT,
|
|
35
|
+
created_at TEXT NOT NULL
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
CREATE TABLE IF NOT EXISTS credit_escrow (
|
|
39
|
+
id TEXT PRIMARY KEY,
|
|
40
|
+
owner TEXT NOT NULL,
|
|
41
|
+
amount INTEGER NOT NULL,
|
|
42
|
+
card_id TEXT NOT NULL,
|
|
43
|
+
status TEXT NOT NULL DEFAULT 'held',
|
|
44
|
+
created_at TEXT NOT NULL,
|
|
45
|
+
settled_at TEXT
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
CREATE TABLE IF NOT EXISTS provider_registry (
|
|
49
|
+
owner TEXT PRIMARY KEY,
|
|
50
|
+
provider_number INTEGER NOT NULL,
|
|
51
|
+
registered_at TEXT NOT NULL
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
CREATE TABLE IF NOT EXISTS demand_vouchers (
|
|
55
|
+
id TEXT PRIMARY KEY,
|
|
56
|
+
owner TEXT NOT NULL,
|
|
57
|
+
amount INTEGER NOT NULL,
|
|
58
|
+
remaining INTEGER NOT NULL,
|
|
59
|
+
created_at TEXT NOT NULL,
|
|
60
|
+
expires_at TEXT NOT NULL,
|
|
61
|
+
is_active INTEGER NOT NULL DEFAULT 1
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
CREATE INDEX IF NOT EXISTS idx_transactions_owner ON credit_transactions(owner, created_at);
|
|
65
|
+
CREATE INDEX IF NOT EXISTS idx_escrow_owner ON credit_escrow(owner);
|
|
66
|
+
`;
|
|
67
|
+
function openCreditDb(path = ":memory:") {
|
|
68
|
+
const db = new Database(path);
|
|
69
|
+
db.pragma("journal_mode = WAL");
|
|
70
|
+
db.pragma("foreign_keys = ON");
|
|
71
|
+
db.exec(CREDIT_SCHEMA);
|
|
72
|
+
try {
|
|
73
|
+
db.exec("ALTER TABLE credit_escrow ADD COLUMN funding_source TEXT NOT NULL DEFAULT 'balance'");
|
|
74
|
+
} catch {
|
|
75
|
+
}
|
|
76
|
+
ensureReliabilityTable(db);
|
|
77
|
+
ensureAgentsTable(db);
|
|
78
|
+
return db;
|
|
79
|
+
}
|
|
80
|
+
function bootstrapAgent(db, owner, amount = 100) {
|
|
81
|
+
const canonicalOwner = canonicalizeCreditOwner(db, owner);
|
|
82
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
83
|
+
let isNew = false;
|
|
84
|
+
db.transaction(() => {
|
|
85
|
+
const result = db.prepare("INSERT OR IGNORE INTO credit_balances (owner, balance, updated_at) VALUES (?, ?, ?)").run(canonicalOwner, amount, now);
|
|
86
|
+
if (result.changes > 0) {
|
|
87
|
+
isNew = true;
|
|
88
|
+
db.prepare(
|
|
89
|
+
"INSERT INTO credit_transactions (id, owner, amount, reason, reference_id, created_at) VALUES (?, ?, ?, ?, ?, ?)"
|
|
90
|
+
).run(randomUUID(), canonicalOwner, amount, "bootstrap", null, now);
|
|
91
|
+
}
|
|
92
|
+
})();
|
|
93
|
+
if (isNew) {
|
|
94
|
+
issueVoucher(db, canonicalOwner, 50, 30);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function getBalance(db, owner) {
|
|
98
|
+
const canonicalOwner = canonicalizeCreditOwner(db, owner);
|
|
99
|
+
const row = db.prepare("SELECT balance FROM credit_balances WHERE owner = ?").get(canonicalOwner);
|
|
100
|
+
return row?.balance ?? 0;
|
|
101
|
+
}
|
|
102
|
+
function getTransactions(db, owner, opts = 100) {
|
|
103
|
+
const canonicalOwner = canonicalizeCreditOwner(db, owner);
|
|
104
|
+
const page = typeof opts === "number" ? { limit: opts } : opts;
|
|
105
|
+
const limit = page.limit ?? 100;
|
|
106
|
+
const conditions = ["owner = ?"];
|
|
107
|
+
const params = [canonicalOwner];
|
|
108
|
+
if (page.before) {
|
|
109
|
+
conditions.push("created_at < ?");
|
|
110
|
+
params.push(page.before);
|
|
111
|
+
}
|
|
112
|
+
if (page.after) {
|
|
113
|
+
conditions.push("created_at > ?");
|
|
114
|
+
params.push(page.after);
|
|
115
|
+
}
|
|
116
|
+
params.push(limit);
|
|
117
|
+
return db.prepare(
|
|
118
|
+
`SELECT id, owner, amount, reason, reference_id, created_at FROM credit_transactions WHERE ${conditions.join(" AND ")} ORDER BY created_at DESC LIMIT ?`
|
|
119
|
+
).all(...params);
|
|
120
|
+
}
|
|
121
|
+
function registerProvider(db, owner) {
|
|
122
|
+
const canonicalOwner = canonicalizeCreditOwner(db, owner);
|
|
123
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
124
|
+
const maxRow = db.prepare("SELECT MAX(provider_number) as maxNum FROM provider_registry").get();
|
|
125
|
+
const nextNum = (maxRow?.maxNum ?? 0) + 1;
|
|
126
|
+
db.prepare("INSERT OR IGNORE INTO provider_registry (owner, provider_number, registered_at) VALUES (?, ?, ?)").run(canonicalOwner, nextNum, now);
|
|
127
|
+
const row = db.prepare("SELECT provider_number FROM provider_registry WHERE owner = ?").get(canonicalOwner);
|
|
128
|
+
return row.provider_number;
|
|
129
|
+
}
|
|
130
|
+
function getProviderNumber(db, owner) {
|
|
131
|
+
const canonicalOwner = canonicalizeCreditOwner(db, owner);
|
|
132
|
+
const row = db.prepare("SELECT provider_number FROM provider_registry WHERE owner = ?").get(canonicalOwner);
|
|
133
|
+
return row?.provider_number ?? null;
|
|
134
|
+
}
|
|
135
|
+
function getProviderBonus(providerNumber) {
|
|
136
|
+
if (providerNumber <= 50) return 2;
|
|
137
|
+
if (providerNumber <= 200) return 1.5;
|
|
138
|
+
return 1;
|
|
139
|
+
}
|
|
140
|
+
function issueVoucher(db, owner, amount = 50, daysValid = 30) {
|
|
141
|
+
const canonicalOwner = canonicalizeCreditOwner(db, owner);
|
|
142
|
+
const id = randomUUID();
|
|
143
|
+
const now = /* @__PURE__ */ new Date();
|
|
144
|
+
const expiresAt = new Date(now.getTime() + daysValid * 24 * 60 * 60 * 1e3);
|
|
145
|
+
db.prepare(
|
|
146
|
+
"INSERT INTO demand_vouchers (id, owner, amount, remaining, created_at, expires_at, is_active) VALUES (?, ?, ?, ?, ?, ?, 1)"
|
|
147
|
+
).run(id, canonicalOwner, amount, amount, now.toISOString(), expiresAt.toISOString());
|
|
148
|
+
return id;
|
|
149
|
+
}
|
|
150
|
+
function getActiveVoucher(db, owner) {
|
|
151
|
+
const canonicalOwner = canonicalizeCreditOwner(db, owner);
|
|
152
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
153
|
+
const row = db.prepare(
|
|
154
|
+
"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"
|
|
155
|
+
).get(canonicalOwner, now);
|
|
156
|
+
return row ?? null;
|
|
157
|
+
}
|
|
158
|
+
function consumeVoucher(db, voucherId, amount) {
|
|
159
|
+
db.prepare(
|
|
160
|
+
"UPDATE demand_vouchers SET remaining = remaining - ? WHERE id = ? AND remaining >= ?"
|
|
161
|
+
).run(amount, voucherId, amount);
|
|
162
|
+
}
|
|
163
|
+
function migrateOwner(db, oldOwner, newOwner) {
|
|
164
|
+
if (oldOwner === newOwner) return;
|
|
165
|
+
const canonicalNewOwner = canonicalizeCreditOwner(db, newOwner);
|
|
166
|
+
migrateCreditOwnerData(db, oldOwner, canonicalNewOwner);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// src/credit/escrow.ts
|
|
170
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
171
|
+
var NETWORK_FEE_RATE = 0.05;
|
|
172
|
+
var FINALIZABLE_ESCROW_STATUSES = /* @__PURE__ */ new Set([
|
|
173
|
+
"held",
|
|
174
|
+
"started",
|
|
175
|
+
"progressing",
|
|
176
|
+
"abandoned"
|
|
177
|
+
]);
|
|
178
|
+
var TERMINAL_ESCROW_STATUSES = /* @__PURE__ */ new Set(["settled", "released"]);
|
|
179
|
+
function getEscrowForMutation(db, escrowId) {
|
|
180
|
+
const escrow = db.prepare("SELECT id, owner, amount, status, funding_source FROM credit_escrow WHERE id = ?").get(escrowId);
|
|
181
|
+
if (!escrow) {
|
|
182
|
+
throw new AgentBnBError(`Escrow not found: ${escrowId}`, "ESCROW_NOT_FOUND");
|
|
183
|
+
}
|
|
184
|
+
return {
|
|
185
|
+
...escrow,
|
|
186
|
+
owner: canonicalizeCreditOwner(db, escrow.owner)
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
function updateEscrowStatus(db, escrowId, fromStatuses, toStatus) {
|
|
190
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
191
|
+
const transition = db.transaction(() => {
|
|
192
|
+
const escrow = getEscrowForMutation(db, escrowId);
|
|
193
|
+
const current = escrow.status;
|
|
194
|
+
if (!fromStatuses.includes(current)) {
|
|
195
|
+
throw new AgentBnBError(
|
|
196
|
+
`Invalid escrow transition for ${escrowId}: ${current} -> ${toStatus}`,
|
|
197
|
+
"ESCROW_INVALID_TRANSITION"
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
if (current === toStatus) return;
|
|
201
|
+
const settledAt = TERMINAL_ESCROW_STATUSES.has(toStatus) ? now : null;
|
|
202
|
+
db.prepare("UPDATE credit_escrow SET status = ?, settled_at = ? WHERE id = ?").run(
|
|
203
|
+
toStatus,
|
|
204
|
+
settledAt,
|
|
205
|
+
escrowId
|
|
206
|
+
);
|
|
207
|
+
});
|
|
208
|
+
transition();
|
|
209
|
+
}
|
|
210
|
+
function assertEscrowCanFinalize(escrow) {
|
|
211
|
+
const status = escrow.status;
|
|
212
|
+
if (FINALIZABLE_ESCROW_STATUSES.has(status)) {
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
if (TERMINAL_ESCROW_STATUSES.has(status)) {
|
|
216
|
+
throw new AgentBnBError(
|
|
217
|
+
`Escrow ${escrow.id} is already ${status}`,
|
|
218
|
+
"ESCROW_ALREADY_SETTLED"
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
throw new AgentBnBError(
|
|
222
|
+
`Escrow ${escrow.id} has invalid lifecycle status: ${escrow.status}`,
|
|
223
|
+
"ESCROW_INVALID_TRANSITION"
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
function holdEscrow(db, owner, amount, cardId) {
|
|
227
|
+
const canonicalOwner = canonicalizeCreditOwner(db, owner);
|
|
228
|
+
const escrowId = randomUUID2();
|
|
229
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
230
|
+
const hold = db.transaction(() => {
|
|
231
|
+
const voucher = getActiveVoucher(db, canonicalOwner);
|
|
232
|
+
if (voucher && voucher.remaining >= amount) {
|
|
233
|
+
consumeVoucher(db, voucher.id, amount);
|
|
234
|
+
db.prepare(
|
|
235
|
+
"INSERT INTO credit_escrow (id, owner, amount, card_id, status, created_at, funding_source) VALUES (?, ?, ?, ?, ?, ?, ?)"
|
|
236
|
+
).run(escrowId, canonicalOwner, amount, cardId, "held", now, "voucher");
|
|
237
|
+
db.prepare(
|
|
238
|
+
"INSERT INTO credit_transactions (id, owner, amount, reason, reference_id, created_at) VALUES (?, ?, ?, ?, ?, ?)"
|
|
239
|
+
).run(randomUUID2(), canonicalOwner, -amount, "voucher_hold", escrowId, now);
|
|
240
|
+
} else {
|
|
241
|
+
const row = db.prepare("SELECT balance FROM credit_balances WHERE owner = ?").get(canonicalOwner);
|
|
242
|
+
if (!row || row.balance < amount) {
|
|
243
|
+
throw new AgentBnBError("Insufficient credits", "INSUFFICIENT_CREDITS");
|
|
244
|
+
}
|
|
245
|
+
db.prepare(
|
|
246
|
+
"UPDATE credit_balances SET balance = balance - ?, updated_at = ? WHERE owner = ? AND balance >= ?"
|
|
247
|
+
).run(amount, now, canonicalOwner, amount);
|
|
248
|
+
db.prepare(
|
|
249
|
+
"INSERT INTO credit_escrow (id, owner, amount, card_id, status, created_at, funding_source) VALUES (?, ?, ?, ?, ?, ?, ?)"
|
|
250
|
+
).run(escrowId, canonicalOwner, amount, cardId, "held", now, "balance");
|
|
251
|
+
db.prepare(
|
|
252
|
+
"INSERT INTO credit_transactions (id, owner, amount, reason, reference_id, created_at) VALUES (?, ?, ?, ?, ?, ?)"
|
|
253
|
+
).run(randomUUID2(), canonicalOwner, -amount, "escrow_hold", escrowId, now);
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
hold();
|
|
257
|
+
return escrowId;
|
|
258
|
+
}
|
|
259
|
+
function markEscrowStarted(db, escrowId) {
|
|
260
|
+
updateEscrowStatus(db, escrowId, ["held", "started"], "started");
|
|
261
|
+
}
|
|
262
|
+
function markEscrowProgressing(db, escrowId) {
|
|
263
|
+
updateEscrowStatus(db, escrowId, ["held", "started", "progressing"], "progressing");
|
|
264
|
+
}
|
|
265
|
+
function markEscrowAbandoned(db, escrowId) {
|
|
266
|
+
updateEscrowStatus(db, escrowId, ["started", "progressing", "abandoned"], "abandoned");
|
|
267
|
+
}
|
|
268
|
+
function settleEscrow(db, escrowId, recipientOwner) {
|
|
269
|
+
const canonicalRecipientOwner = canonicalizeCreditOwner(db, recipientOwner);
|
|
270
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
271
|
+
const settle = db.transaction(() => {
|
|
272
|
+
const escrow = getEscrowForMutation(db, escrowId);
|
|
273
|
+
assertEscrowCanFinalize(escrow);
|
|
274
|
+
const feeAmount = Math.floor(escrow.amount * NETWORK_FEE_RATE);
|
|
275
|
+
const providerAmount = escrow.amount - feeAmount;
|
|
276
|
+
db.prepare(
|
|
277
|
+
"INSERT OR IGNORE INTO credit_balances (owner, balance, updated_at) VALUES (?, 0, ?)"
|
|
278
|
+
).run(canonicalRecipientOwner, now);
|
|
279
|
+
db.prepare(
|
|
280
|
+
"UPDATE credit_balances SET balance = balance + ?, updated_at = ? WHERE owner = ?"
|
|
281
|
+
).run(providerAmount, now, canonicalRecipientOwner);
|
|
282
|
+
if (feeAmount > 0) {
|
|
283
|
+
db.prepare(
|
|
284
|
+
"INSERT OR IGNORE INTO credit_balances (owner, balance, updated_at) VALUES (?, 0, ?)"
|
|
285
|
+
).run("platform_treasury", now);
|
|
286
|
+
db.prepare(
|
|
287
|
+
"UPDATE credit_balances SET balance = balance + ?, updated_at = ? WHERE owner = ?"
|
|
288
|
+
).run(feeAmount, now, "platform_treasury");
|
|
289
|
+
db.prepare(
|
|
290
|
+
"INSERT INTO credit_transactions (id, owner, amount, reason, reference_id, created_at) VALUES (?, ?, ?, ?, ?, ?)"
|
|
291
|
+
).run(randomUUID2(), "platform_treasury", feeAmount, "network_fee", escrowId, now);
|
|
292
|
+
}
|
|
293
|
+
db.prepare(
|
|
294
|
+
"UPDATE credit_escrow SET status = ?, settled_at = ? WHERE id = ?"
|
|
295
|
+
).run("settled", now, escrowId);
|
|
296
|
+
db.prepare(
|
|
297
|
+
"INSERT INTO credit_transactions (id, owner, amount, reason, reference_id, created_at) VALUES (?, ?, ?, ?, ?, ?)"
|
|
298
|
+
).run(randomUUID2(), canonicalRecipientOwner, providerAmount, "settlement", escrowId, now);
|
|
299
|
+
let providerNum = getProviderNumber(db, canonicalRecipientOwner);
|
|
300
|
+
if (providerNum === null) {
|
|
301
|
+
providerNum = registerProvider(db, canonicalRecipientOwner);
|
|
302
|
+
}
|
|
303
|
+
const bonus = getProviderBonus(providerNum);
|
|
304
|
+
if (bonus > 1) {
|
|
305
|
+
const bonusAmount = Math.floor(providerAmount * (bonus - 1));
|
|
306
|
+
if (bonusAmount > 0) {
|
|
307
|
+
db.prepare(
|
|
308
|
+
"INSERT OR IGNORE INTO credit_balances (owner, balance, updated_at) VALUES (?, 0, ?)"
|
|
309
|
+
).run("platform_treasury", now);
|
|
310
|
+
db.prepare(
|
|
311
|
+
"UPDATE credit_balances SET balance = balance + ?, updated_at = ? WHERE owner = ?"
|
|
312
|
+
).run(bonusAmount, now, canonicalRecipientOwner);
|
|
313
|
+
db.prepare(
|
|
314
|
+
"INSERT INTO credit_transactions (id, owner, amount, reason, reference_id, created_at) VALUES (?, ?, ?, ?, ?, ?)"
|
|
315
|
+
).run(randomUUID2(), canonicalRecipientOwner, bonusAmount, "provider_bonus", escrowId, now);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
try {
|
|
319
|
+
recordSuccessfulHire(db, canonicalRecipientOwner, escrow.owner);
|
|
320
|
+
} catch {
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
settle();
|
|
324
|
+
}
|
|
325
|
+
function releaseEscrow(db, escrowId) {
|
|
326
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
327
|
+
const release = db.transaction(() => {
|
|
328
|
+
const escrow = getEscrowForMutation(db, escrowId);
|
|
329
|
+
assertEscrowCanFinalize(escrow);
|
|
330
|
+
db.prepare(
|
|
331
|
+
"UPDATE credit_balances SET balance = balance + ?, updated_at = ? WHERE owner = ?"
|
|
332
|
+
).run(escrow.amount, now, escrow.owner);
|
|
333
|
+
db.prepare(
|
|
334
|
+
"UPDATE credit_escrow SET status = ?, settled_at = ? WHERE id = ?"
|
|
335
|
+
).run("released", now, escrowId);
|
|
336
|
+
db.prepare(
|
|
337
|
+
"INSERT INTO credit_transactions (id, owner, amount, reason, reference_id, created_at) VALUES (?, ?, ?, ?, ?, ?)"
|
|
338
|
+
).run(randomUUID2(), escrow.owner, escrow.amount, "refund", escrowId, now);
|
|
339
|
+
});
|
|
340
|
+
release();
|
|
341
|
+
}
|
|
342
|
+
function confirmEscrowDebit(db, escrowId) {
|
|
343
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
344
|
+
const confirm = db.transaction(() => {
|
|
345
|
+
const escrow = getEscrowForMutation(db, escrowId);
|
|
346
|
+
assertEscrowCanFinalize(escrow);
|
|
347
|
+
db.prepare(
|
|
348
|
+
"UPDATE credit_escrow SET status = ?, settled_at = ? WHERE id = ?"
|
|
349
|
+
).run("settled", now, escrowId);
|
|
350
|
+
db.prepare(
|
|
351
|
+
"INSERT INTO credit_transactions (id, owner, amount, reason, reference_id, created_at) VALUES (?, ?, ?, ?, ?, ?)"
|
|
352
|
+
).run(randomUUID2(), escrow.owner, 0, "remote_settlement_confirmed", escrowId, now);
|
|
353
|
+
});
|
|
354
|
+
confirm();
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// src/feedback/reputation.ts
|
|
358
|
+
var QUALITY_SCORES = {
|
|
359
|
+
excellent: 1,
|
|
360
|
+
good: 0.8,
|
|
361
|
+
acceptable: 0.6,
|
|
362
|
+
poor: 0.3,
|
|
363
|
+
failed: 0
|
|
364
|
+
};
|
|
365
|
+
var COST_VALUE_SCORES = {
|
|
366
|
+
great: 1,
|
|
367
|
+
fair: 0.6,
|
|
368
|
+
overpriced: 0.2
|
|
369
|
+
};
|
|
370
|
+
var DECAY_DAYS = 30;
|
|
371
|
+
var WEIGHTS = {
|
|
372
|
+
rating: 0.4,
|
|
373
|
+
quality: 0.3,
|
|
374
|
+
would_reuse: 0.2,
|
|
375
|
+
cost_value: 0.1
|
|
376
|
+
};
|
|
377
|
+
function computeReputation(feedbacks) {
|
|
378
|
+
if (feedbacks.length === 0) return 0.5;
|
|
379
|
+
const now = Date.now();
|
|
380
|
+
let weightedSum = 0;
|
|
381
|
+
let totalWeight = 0;
|
|
382
|
+
for (const fb of feedbacks) {
|
|
383
|
+
const feedbackDate = new Date(fb.timestamp).getTime();
|
|
384
|
+
const ageDays = Math.max(0, (now - feedbackDate) / (1e3 * 60 * 60 * 24));
|
|
385
|
+
const recencyWeight = Math.exp(-ageDays / DECAY_DAYS);
|
|
386
|
+
const ratingScore = (fb.rating - 1) / 4;
|
|
387
|
+
const qualityScore = QUALITY_SCORES[fb.result_quality];
|
|
388
|
+
const reuseScore = fb.would_reuse ? 1 : 0;
|
|
389
|
+
const costScore = COST_VALUE_SCORES[fb.cost_value_ratio];
|
|
390
|
+
const componentScore = WEIGHTS.rating * ratingScore + WEIGHTS.quality * qualityScore + WEIGHTS.would_reuse * reuseScore + WEIGHTS.cost_value * costScore;
|
|
391
|
+
weightedSum += recencyWeight * componentScore;
|
|
392
|
+
totalWeight += recencyWeight;
|
|
393
|
+
}
|
|
394
|
+
if (totalWeight === 0) return 0.5;
|
|
395
|
+
const raw = weightedSum / totalWeight;
|
|
396
|
+
return Math.max(0, Math.min(1, raw));
|
|
397
|
+
}
|
|
398
|
+
function getReputationScore(db, agentId) {
|
|
399
|
+
const feedbacks = getFeedbackForProvider(db, agentId);
|
|
400
|
+
return computeReputation(feedbacks);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// src/registry/matcher.ts
|
|
404
|
+
var CACHE_MAX_ENTRIES = 100;
|
|
405
|
+
var CACHE_TTL_MS = 3e4;
|
|
406
|
+
var dbCaches = /* @__PURE__ */ new WeakMap();
|
|
407
|
+
function getDbCache(db) {
|
|
408
|
+
let cache = dbCaches.get(db);
|
|
409
|
+
if (!cache) {
|
|
410
|
+
cache = /* @__PURE__ */ new Map();
|
|
411
|
+
dbCaches.set(db, cache);
|
|
412
|
+
}
|
|
413
|
+
return cache;
|
|
414
|
+
}
|
|
415
|
+
function cacheKey(query, filters) {
|
|
416
|
+
return `${query}|${filters.level ?? ""}|${filters.online ?? ""}|${(filters.apis_used ?? []).join(",")}|${filters.min_reputation ?? ""}`;
|
|
417
|
+
}
|
|
418
|
+
function evictCache(cache) {
|
|
419
|
+
const now = Date.now();
|
|
420
|
+
for (const [key, entry] of cache) {
|
|
421
|
+
if (entry.expiresAt <= now) cache.delete(key);
|
|
422
|
+
}
|
|
423
|
+
while (cache.size > CACHE_MAX_ENTRIES) {
|
|
424
|
+
const firstKey = cache.keys().next().value;
|
|
425
|
+
cache.delete(firstKey);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
function searchCards(db, query, filters = {}) {
|
|
429
|
+
const cache = getDbCache(db);
|
|
430
|
+
const key = cacheKey(query, filters);
|
|
431
|
+
const cached = cache.get(key);
|
|
432
|
+
if (cached && cached.expiresAt > Date.now()) {
|
|
433
|
+
return cached.results;
|
|
434
|
+
}
|
|
435
|
+
const trimmedQuery = query.trim();
|
|
436
|
+
const exactSkillMatches = findCardsByExactSkillId(db, trimmedQuery, filters);
|
|
437
|
+
const words = query.trim().split(/\s+/).map((w) => w.replace(/["*^{}():]/g, "")).filter((w) => w.length > 0);
|
|
438
|
+
if (words.length === 0) {
|
|
439
|
+
return exactSkillMatches;
|
|
440
|
+
}
|
|
441
|
+
const ftsQuery = words.map((w) => `"${w}"`).join(" OR ");
|
|
442
|
+
const conditions = [];
|
|
443
|
+
const params = [ftsQuery];
|
|
444
|
+
if (filters.level !== void 0) {
|
|
445
|
+
conditions.push(`json_extract(cc.data, '$.level') = ?`);
|
|
446
|
+
params.push(filters.level);
|
|
447
|
+
}
|
|
448
|
+
if (filters.online !== void 0) {
|
|
449
|
+
conditions.push(`json_extract(cc.data, '$.availability.online') = ?`);
|
|
450
|
+
params.push(filters.online ? 1 : 0);
|
|
451
|
+
}
|
|
452
|
+
const whereClause = conditions.length > 0 ? `AND ${conditions.join(" AND ")}` : "";
|
|
453
|
+
const sql = `
|
|
454
|
+
SELECT cc.data
|
|
455
|
+
FROM capability_cards cc
|
|
456
|
+
JOIN cards_fts ON cc.rowid = cards_fts.rowid
|
|
457
|
+
WHERE cards_fts MATCH ?
|
|
458
|
+
${whereClause}
|
|
459
|
+
ORDER BY bm25(cards_fts)
|
|
460
|
+
LIMIT 50
|
|
461
|
+
`;
|
|
462
|
+
const stmt = db.prepare(sql);
|
|
463
|
+
const rows = stmt.all(...params);
|
|
464
|
+
const results = rows.map((row) => JSON.parse(row.data));
|
|
465
|
+
const mergedResults = mergeByCardId(exactSkillMatches, results);
|
|
466
|
+
let filtered = mergedResults;
|
|
467
|
+
if (filters.apis_used && filters.apis_used.length > 0) {
|
|
468
|
+
const requiredApis = filters.apis_used;
|
|
469
|
+
filtered = filtered.filter((card) => {
|
|
470
|
+
const cardApis = card.metadata?.apis_used ?? [];
|
|
471
|
+
return requiredApis.every((api) => cardApis.includes(api));
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
if (filters.min_reputation !== void 0 && filters.min_reputation > 0) {
|
|
475
|
+
filtered = applyReputationFilter(db, filtered, filters.min_reputation);
|
|
476
|
+
}
|
|
477
|
+
evictCache(cache);
|
|
478
|
+
cache.set(key, { results: filtered, expiresAt: Date.now() + CACHE_TTL_MS });
|
|
479
|
+
return filtered;
|
|
480
|
+
}
|
|
481
|
+
function mergeByCardId(primary, secondary) {
|
|
482
|
+
const seen = /* @__PURE__ */ new Set();
|
|
483
|
+
const merged = [];
|
|
484
|
+
for (const card of primary) {
|
|
485
|
+
if (seen.has(card.id)) continue;
|
|
486
|
+
seen.add(card.id);
|
|
487
|
+
merged.push(card);
|
|
488
|
+
}
|
|
489
|
+
for (const card of secondary) {
|
|
490
|
+
if (seen.has(card.id)) continue;
|
|
491
|
+
seen.add(card.id);
|
|
492
|
+
merged.push(card);
|
|
493
|
+
}
|
|
494
|
+
return merged;
|
|
495
|
+
}
|
|
496
|
+
function findCardsByExactSkillId(db, query, filters) {
|
|
497
|
+
if (query.length === 0) return [];
|
|
498
|
+
const rows = db.prepare("SELECT data FROM capability_cards").all();
|
|
499
|
+
const cards = rows.map((row) => JSON.parse(row.data));
|
|
500
|
+
return cards.filter((card) => {
|
|
501
|
+
if (filters.level !== void 0 && card.level !== filters.level) return false;
|
|
502
|
+
if (filters.online !== void 0 && card.availability?.online !== filters.online) return false;
|
|
503
|
+
const asRecord = card;
|
|
504
|
+
const skills = asRecord["skills"];
|
|
505
|
+
if (!Array.isArray(skills)) return false;
|
|
506
|
+
return skills.some((skill) => String(skill["id"] ?? "") === query);
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
function filterCards(db, filters) {
|
|
510
|
+
const conditions = [];
|
|
511
|
+
const params = [];
|
|
512
|
+
if (filters.level !== void 0) {
|
|
513
|
+
conditions.push(`json_extract(data, '$.level') = ?`);
|
|
514
|
+
params.push(filters.level);
|
|
515
|
+
}
|
|
516
|
+
if (filters.online !== void 0) {
|
|
517
|
+
conditions.push(`json_extract(data, '$.availability.online') = ?`);
|
|
518
|
+
params.push(filters.online ? 1 : 0);
|
|
519
|
+
}
|
|
520
|
+
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
521
|
+
const sql = `SELECT data FROM capability_cards ${whereClause}`;
|
|
522
|
+
const stmt = db.prepare(sql);
|
|
523
|
+
const rows = stmt.all(...params);
|
|
524
|
+
let cards = rows.map((row) => JSON.parse(row.data));
|
|
525
|
+
if (filters.min_reputation !== void 0 && filters.min_reputation > 0) {
|
|
526
|
+
cards = applyReputationFilter(db, cards, filters.min_reputation);
|
|
527
|
+
}
|
|
528
|
+
return cards;
|
|
529
|
+
}
|
|
530
|
+
function applyReputationFilter(db, cards, minReputation) {
|
|
531
|
+
const owners = [...new Set(cards.map((c) => c.owner))];
|
|
532
|
+
const reputationMap = /* @__PURE__ */ new Map();
|
|
533
|
+
for (const owner of owners) {
|
|
534
|
+
reputationMap.set(owner, getReputationScore(db, owner));
|
|
535
|
+
}
|
|
536
|
+
return cards.filter((card) => {
|
|
537
|
+
const score = reputationMap.get(card.owner) ?? 0.5;
|
|
538
|
+
return score >= minReputation;
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
function buildReputationMap(db, owners) {
|
|
542
|
+
const unique = [...new Set(owners)];
|
|
543
|
+
const map = /* @__PURE__ */ new Map();
|
|
544
|
+
for (const owner of unique) {
|
|
545
|
+
map.set(owner, getReputationScore(db, owner));
|
|
546
|
+
}
|
|
547
|
+
return map;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// src/cli/remote-registry.ts
|
|
551
|
+
var RegistryTimeoutError = class extends AgentBnBError {
|
|
552
|
+
constructor(url) {
|
|
553
|
+
super(
|
|
554
|
+
`Registry at ${url} did not respond within 5s. Showing local results only.`,
|
|
555
|
+
"REGISTRY_TIMEOUT"
|
|
556
|
+
);
|
|
557
|
+
this.name = "RegistryTimeoutError";
|
|
558
|
+
}
|
|
559
|
+
};
|
|
560
|
+
var RegistryConnectionError = class extends AgentBnBError {
|
|
561
|
+
constructor(url) {
|
|
562
|
+
super(
|
|
563
|
+
`Cannot reach ${url}. Is the registry running? Showing local results only.`,
|
|
564
|
+
"REGISTRY_CONNECTION"
|
|
565
|
+
);
|
|
566
|
+
this.name = "RegistryConnectionError";
|
|
567
|
+
}
|
|
568
|
+
};
|
|
569
|
+
var RegistryAuthError = class extends AgentBnBError {
|
|
570
|
+
constructor(url) {
|
|
571
|
+
super(
|
|
572
|
+
`Authentication failed for ${url}. Run \`agentbnb config set token <your-token>\`.`,
|
|
573
|
+
"REGISTRY_AUTH"
|
|
574
|
+
);
|
|
575
|
+
this.name = "RegistryAuthError";
|
|
576
|
+
}
|
|
577
|
+
};
|
|
578
|
+
async function fetchRemoteCards(registryUrl, params, timeoutMs = 5e3) {
|
|
579
|
+
let cardsUrl;
|
|
580
|
+
try {
|
|
581
|
+
cardsUrl = new URL("/cards", registryUrl);
|
|
582
|
+
} catch {
|
|
583
|
+
throw new AgentBnBError(`Invalid registry URL: ${registryUrl}`, "INVALID_REGISTRY_URL");
|
|
584
|
+
}
|
|
585
|
+
const searchParams = new URLSearchParams();
|
|
586
|
+
if (params.q !== void 0) searchParams.set("q", params.q);
|
|
587
|
+
if (params.level !== void 0) searchParams.set("level", String(params.level));
|
|
588
|
+
if (params.online !== void 0) searchParams.set("online", String(params.online));
|
|
589
|
+
if (params.tag !== void 0) searchParams.set("tag", params.tag);
|
|
590
|
+
searchParams.set("limit", "100");
|
|
591
|
+
cardsUrl.search = searchParams.toString();
|
|
592
|
+
const controller = new AbortController();
|
|
593
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
594
|
+
let response;
|
|
595
|
+
try {
|
|
596
|
+
response = await fetch(cardsUrl.toString(), { signal: controller.signal });
|
|
597
|
+
} catch (err) {
|
|
598
|
+
clearTimeout(timer);
|
|
599
|
+
const isTimeout = err instanceof Error && err.name === "AbortError";
|
|
600
|
+
if (isTimeout) {
|
|
601
|
+
throw new RegistryTimeoutError(registryUrl);
|
|
602
|
+
}
|
|
603
|
+
throw new RegistryConnectionError(registryUrl);
|
|
604
|
+
} finally {
|
|
605
|
+
clearTimeout(timer);
|
|
606
|
+
}
|
|
607
|
+
if (response.status === 401 || response.status === 403) {
|
|
608
|
+
throw new RegistryAuthError(registryUrl);
|
|
609
|
+
}
|
|
610
|
+
if (!response.ok) {
|
|
611
|
+
throw new RegistryConnectionError(registryUrl);
|
|
612
|
+
}
|
|
613
|
+
const body = await response.json();
|
|
614
|
+
return body.items;
|
|
615
|
+
}
|
|
616
|
+
function mergeResults(localCards, remoteCards, hasQuery) {
|
|
617
|
+
const taggedLocal = localCards.map((c) => ({ ...c, source: "local" }));
|
|
618
|
+
const taggedRemote = remoteCards.map((c) => ({ ...c, source: "remote" }));
|
|
619
|
+
const localIds = new Set(localCards.map((c) => c.id));
|
|
620
|
+
const dedupedRemote = taggedRemote.filter((c) => !localIds.has(c.id));
|
|
621
|
+
if (!hasQuery) {
|
|
622
|
+
return [...taggedLocal, ...dedupedRemote];
|
|
623
|
+
}
|
|
624
|
+
const result = [];
|
|
625
|
+
const maxLen = Math.max(taggedLocal.length, dedupedRemote.length);
|
|
626
|
+
for (let i = 0; i < maxLen; i++) {
|
|
627
|
+
if (i < taggedLocal.length) result.push(taggedLocal[i]);
|
|
628
|
+
if (i < dedupedRemote.length) result.push(dedupedRemote[i]);
|
|
629
|
+
}
|
|
630
|
+
return result;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
// src/gateway/resolve-target-capability.ts
|
|
634
|
+
function canQueryLocalDb(db) {
|
|
635
|
+
return Boolean(db) && typeof db.prepare === "function";
|
|
636
|
+
}
|
|
637
|
+
function getGatewayUrl(card) {
|
|
638
|
+
if (typeof card.gateway_url === "string" && card.gateway_url.length > 0) {
|
|
639
|
+
return card.gateway_url;
|
|
640
|
+
}
|
|
641
|
+
const internal = card._internal;
|
|
642
|
+
const internalGateway = internal?.["gateway_url"];
|
|
643
|
+
return typeof internalGateway === "string" ? internalGateway : "";
|
|
644
|
+
}
|
|
645
|
+
function isOnline(card) {
|
|
646
|
+
return card.availability?.online !== false;
|
|
647
|
+
}
|
|
648
|
+
function scoreSkill(skill, query) {
|
|
649
|
+
const q = query.toLowerCase();
|
|
650
|
+
if (skill.id.toLowerCase() === q) return 100;
|
|
651
|
+
let score = 0;
|
|
652
|
+
if (skill.id.toLowerCase().includes(q)) score += 40;
|
|
653
|
+
if (skill.name.toLowerCase().includes(q)) score += 20;
|
|
654
|
+
if (skill.description.toLowerCase().includes(q)) score += 10;
|
|
655
|
+
return score;
|
|
656
|
+
}
|
|
657
|
+
function pickSkill(card, queryOrId) {
|
|
658
|
+
const skills = Array.isArray(card.skills) ? card.skills : [];
|
|
659
|
+
if (skills.length === 0) return void 0;
|
|
660
|
+
const exact = skills.find((s) => s.id === queryOrId);
|
|
661
|
+
if (exact) return exact;
|
|
662
|
+
const scored = skills.map((skill) => ({ skill, score: scoreSkill(skill, queryOrId) })).sort((a, b) => b.score - a.score);
|
|
663
|
+
if ((scored[0]?.score ?? 0) > 0) return scored[0]?.skill;
|
|
664
|
+
return skills[0];
|
|
665
|
+
}
|
|
666
|
+
function resolveCardAgentId(card, registryDb) {
|
|
667
|
+
if (typeof card.agent_id === "string" && card.agent_id.length > 0) {
|
|
668
|
+
return card.agent_id;
|
|
669
|
+
}
|
|
670
|
+
if (!canQueryLocalDb(registryDb)) return void 0;
|
|
671
|
+
const resolved = resolveCanonicalIdentity(registryDb, card.owner);
|
|
672
|
+
return resolved.resolved ? resolved.agent_id : void 0;
|
|
673
|
+
}
|
|
674
|
+
function toResolved(card, queryOrId, source, registryDb) {
|
|
675
|
+
const skill = pickSkill(card, queryOrId);
|
|
676
|
+
const gatewayUrl = getGatewayUrl(card);
|
|
677
|
+
const viaRelay = source === "local" ? false : gatewayUrl.length === 0;
|
|
678
|
+
const resolvedSource = viaRelay ? "relay" : source;
|
|
679
|
+
const agentId = resolveCardAgentId(card, registryDb);
|
|
680
|
+
return {
|
|
681
|
+
cardId: card.id,
|
|
682
|
+
skillId: skill?.id,
|
|
683
|
+
owner: card.owner,
|
|
684
|
+
...agentId ? { agent_id: agentId } : {},
|
|
685
|
+
gateway_url: gatewayUrl,
|
|
686
|
+
via_relay: viaRelay,
|
|
687
|
+
credits_per_call: skill?.pricing.credits_per_call ?? card.pricing.credits_per_call,
|
|
688
|
+
source: resolvedSource
|
|
689
|
+
};
|
|
690
|
+
}
|
|
691
|
+
function findLocalBySkillId(db, skillId, onlineOnly) {
|
|
692
|
+
const rows = db.prepare("SELECT data FROM capability_cards").all();
|
|
693
|
+
for (const row of rows) {
|
|
694
|
+
const card = JSON.parse(row.data);
|
|
695
|
+
if (onlineOnly && !isOnline(card)) continue;
|
|
696
|
+
const skills = Array.isArray(card.skills) ? card.skills : [];
|
|
697
|
+
if (skills.some((s) => s.id === skillId)) {
|
|
698
|
+
return card;
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
return null;
|
|
702
|
+
}
|
|
703
|
+
function findRemoteBySkillId(cards, skillId) {
|
|
704
|
+
for (const card of cards) {
|
|
705
|
+
const skills = Array.isArray(card.skills) ? card.skills : [];
|
|
706
|
+
if (skills.some((s) => s.id === skillId)) return card;
|
|
707
|
+
}
|
|
708
|
+
return null;
|
|
709
|
+
}
|
|
710
|
+
function looksLikeCardId(value) {
|
|
711
|
+
return value.startsWith("card-") || /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value);
|
|
712
|
+
}
|
|
713
|
+
async function resolveTargetCapability(queryOrId, options) {
|
|
714
|
+
const { registryDb, registryUrl, onlineOnly = true } = options;
|
|
715
|
+
const needle = queryOrId.trim();
|
|
716
|
+
if (needle.length === 0) return null;
|
|
717
|
+
if (canQueryLocalDb(registryDb)) {
|
|
718
|
+
const byCardId = getCard(registryDb, needle);
|
|
719
|
+
if (byCardId && (!onlineOnly || isOnline(byCardId))) {
|
|
720
|
+
return toResolved(byCardId, needle, "local", registryDb);
|
|
721
|
+
}
|
|
722
|
+
const bySkillId = findLocalBySkillId(registryDb, needle, onlineOnly);
|
|
723
|
+
if (bySkillId) {
|
|
724
|
+
return toResolved(bySkillId, needle, "local", registryDb);
|
|
725
|
+
}
|
|
726
|
+
const localMatches = searchCards(registryDb, needle, { online: onlineOnly ? true : void 0 });
|
|
727
|
+
if (localMatches.length > 0) {
|
|
728
|
+
return toResolved(localMatches[0], needle, "local", registryDb);
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
if (!registryUrl) return null;
|
|
732
|
+
if (looksLikeCardId(needle)) {
|
|
733
|
+
try {
|
|
734
|
+
const cardResp = await fetch(`${registryUrl.replace(/\/$/, "")}/cards/${encodeURIComponent(needle)}`);
|
|
735
|
+
if (cardResp.ok) {
|
|
736
|
+
const remoteCard = await cardResp.json();
|
|
737
|
+
if (!onlineOnly || isOnline(remoteCard)) {
|
|
738
|
+
return toResolved(remoteCard, needle, "remote", registryDb);
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
} catch {
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
try {
|
|
745
|
+
const remoteMatches = await fetchRemoteCards(registryUrl, {
|
|
746
|
+
q: needle,
|
|
747
|
+
...onlineOnly ? { online: true } : {}
|
|
748
|
+
});
|
|
749
|
+
if (remoteMatches.length > 0) {
|
|
750
|
+
const exactSkill = findRemoteBySkillId(remoteMatches, needle);
|
|
751
|
+
if (exactSkill) return toResolved(exactSkill, needle, "remote", registryDb);
|
|
752
|
+
return toResolved(remoteMatches[0], needle, "remote", registryDb);
|
|
753
|
+
}
|
|
754
|
+
} catch {
|
|
755
|
+
}
|
|
756
|
+
try {
|
|
757
|
+
const onlineCards = await fetchRemoteCards(registryUrl, {
|
|
758
|
+
...onlineOnly ? { online: true } : {}
|
|
759
|
+
});
|
|
760
|
+
const exactSkill = findRemoteBySkillId(onlineCards, needle);
|
|
761
|
+
if (exactSkill) return toResolved(exactSkill, needle, "relay", registryDb);
|
|
762
|
+
const tokens = needle.toLowerCase().split(/\s+/).filter((t) => t.length > 0);
|
|
763
|
+
const fuzzy = onlineCards.find((card) => {
|
|
764
|
+
const text = [
|
|
765
|
+
card.name,
|
|
766
|
+
card.description,
|
|
767
|
+
...Array.isArray(card.skills) ? card.skills.map((s) => `${s.id} ${s.name} ${s.description}`) : []
|
|
768
|
+
].join(" ").toLowerCase();
|
|
769
|
+
return tokens.some((token) => text.includes(token));
|
|
770
|
+
});
|
|
771
|
+
if (fuzzy) return toResolved(fuzzy, needle, "relay", registryDb);
|
|
772
|
+
} catch {
|
|
773
|
+
}
|
|
774
|
+
return null;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
export {
|
|
778
|
+
openCreditDb,
|
|
779
|
+
bootstrapAgent,
|
|
780
|
+
getBalance,
|
|
781
|
+
getTransactions,
|
|
782
|
+
migrateOwner,
|
|
783
|
+
holdEscrow,
|
|
784
|
+
markEscrowStarted,
|
|
785
|
+
markEscrowProgressing,
|
|
786
|
+
markEscrowAbandoned,
|
|
787
|
+
settleEscrow,
|
|
788
|
+
releaseEscrow,
|
|
789
|
+
confirmEscrowDebit,
|
|
790
|
+
computeReputation,
|
|
791
|
+
searchCards,
|
|
792
|
+
filterCards,
|
|
793
|
+
buildReputationMap,
|
|
794
|
+
fetchRemoteCards,
|
|
795
|
+
mergeResults,
|
|
796
|
+
resolveTargetCapability
|
|
797
|
+
};
|