@highway1/core 0.1.47 → 0.1.51
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/index.d.ts +496 -46
- package/dist/index.js +1034 -626
- package/dist/index.js.map +1 -1
- package/package.json +11 -23
- package/src/discovery/relay-index.ts +98 -0
- package/src/index.ts +9 -3
- package/src/messaging/defense.ts +236 -0
- package/src/messaging/index.ts +5 -0
- package/src/messaging/queue.ts +181 -0
- package/src/messaging/rate-limiter.ts +85 -0
- package/src/messaging/router.ts +121 -327
- package/src/messaging/storage.ts +281 -0
- package/src/messaging/types.ts +149 -0
- package/src/transport/node.ts +3 -1
- package/src/transport/relay-client.ts +390 -0
- package/src/transport/relay-types.ts +195 -0
- package/LICENSE +0 -21
package/dist/index.js
CHANGED
|
@@ -1,22 +1,10 @@
|
|
|
1
1
|
import * as ed25519 from '@noble/ed25519';
|
|
2
2
|
import { base58btc } from 'multiformats/bases/base58';
|
|
3
|
-
import {
|
|
4
|
-
import { tcp } from '@libp2p/tcp';
|
|
5
|
-
import { noise } from '@chainsafe/libp2p-noise';
|
|
6
|
-
import { mplex } from '@libp2p/mplex';
|
|
7
|
-
import { kadDHT, passthroughMapper } from '@libp2p/kad-dht';
|
|
8
|
-
import { bootstrap } from '@libp2p/bootstrap';
|
|
9
|
-
import { identify } from '@libp2p/identify';
|
|
10
|
-
import { ping } from '@libp2p/ping';
|
|
11
|
-
import { circuitRelayTransport, circuitRelayServer } from '@libp2p/circuit-relay-v2';
|
|
3
|
+
import { WebSocket } from 'ws';
|
|
12
4
|
import Ajv from 'ajv';
|
|
13
5
|
import { encode, decode } from 'cbor-x';
|
|
14
|
-
import { fromString } from 'uint8arrays/from-string';
|
|
15
|
-
import { toString } from 'uint8arrays/to-string';
|
|
16
6
|
import lunr from 'lunr';
|
|
17
7
|
import Fuse from 'fuse.js';
|
|
18
|
-
import { peerIdFromString } from '@libp2p/peer-id';
|
|
19
|
-
import { multiaddr } from '@multiformats/multiaddr';
|
|
20
8
|
import { Level } from 'level';
|
|
21
9
|
import { sha256 } from '@noble/hashes/sha256';
|
|
22
10
|
import { bytesToHex } from '@noble/hashes/utils';
|
|
@@ -201,107 +189,282 @@ var createLogger = (prefix, level) => {
|
|
|
201
189
|
return new Logger(prefix, level);
|
|
202
190
|
};
|
|
203
191
|
|
|
204
|
-
// src/transport/
|
|
205
|
-
var logger = createLogger("
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
192
|
+
// src/transport/relay-client.ts
|
|
193
|
+
var logger = createLogger("relay-client");
|
|
194
|
+
var DEFAULT_RECONNECT = {
|
|
195
|
+
baseMs: 1e3,
|
|
196
|
+
jitterMs: 2e3,
|
|
197
|
+
maxDelayMs: 3e4,
|
|
198
|
+
stableAfterMs: 6e4
|
|
199
|
+
};
|
|
200
|
+
function createRelayClient(config) {
|
|
201
|
+
const { relayUrls, did, keyPair, card } = config;
|
|
202
|
+
const reconnectConfig = { ...DEFAULT_RECONNECT, ...config.reconnect };
|
|
203
|
+
const connections = relayUrls.map((url) => ({
|
|
204
|
+
url,
|
|
205
|
+
ws: null,
|
|
206
|
+
connected: false,
|
|
207
|
+
reconnectAttempt: 0,
|
|
208
|
+
reconnectTimer: null,
|
|
209
|
+
stableTimer: null,
|
|
210
|
+
peerCount: 0
|
|
211
|
+
}));
|
|
212
|
+
let deliveryHandler = null;
|
|
213
|
+
let deliveryReportHandler = null;
|
|
214
|
+
let stopped = false;
|
|
215
|
+
async function connectToRelay(conn) {
|
|
216
|
+
if (stopped) return;
|
|
217
|
+
try {
|
|
218
|
+
logger.info("Connecting to relay", { url: conn.url });
|
|
219
|
+
const ws = new WebSocket(conn.url);
|
|
220
|
+
conn.ws = ws;
|
|
221
|
+
await new Promise((resolve, reject) => {
|
|
222
|
+
const timeout = setTimeout(() => {
|
|
223
|
+
reject(new Error("Connection timeout"));
|
|
224
|
+
}, 1e4);
|
|
225
|
+
ws.on("open", async () => {
|
|
226
|
+
clearTimeout(timeout);
|
|
227
|
+
logger.info("WebSocket connected", { url: conn.url });
|
|
228
|
+
try {
|
|
229
|
+
const timestamp = Date.now();
|
|
230
|
+
const helloData = JSON.stringify({ did, card, timestamp });
|
|
231
|
+
const signature = await sign(new TextEncoder().encode(helloData), keyPair.privateKey);
|
|
232
|
+
const hello = {
|
|
233
|
+
type: "HELLO",
|
|
234
|
+
protocolVersion: 1,
|
|
235
|
+
did,
|
|
236
|
+
card,
|
|
237
|
+
timestamp,
|
|
238
|
+
signature
|
|
239
|
+
};
|
|
240
|
+
ws.send(JSON.stringify(hello));
|
|
241
|
+
resolve();
|
|
242
|
+
} catch (err) {
|
|
243
|
+
reject(err);
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
ws.on("error", (err) => {
|
|
247
|
+
clearTimeout(timeout);
|
|
248
|
+
reject(err);
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
ws.on("message", async (data) => {
|
|
252
|
+
try {
|
|
253
|
+
const msg = JSON.parse(data.toString());
|
|
254
|
+
await handleRelayMessage(conn, msg);
|
|
255
|
+
} catch (err) {
|
|
256
|
+
logger.warn("Failed to parse relay message", { error: err.message });
|
|
243
257
|
}
|
|
244
258
|
});
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
// Default K=20, keep for compatibility
|
|
260
|
-
querySelfInterval: 3e4,
|
|
261
|
-
// Self-query every 30 seconds (libp2p default)
|
|
262
|
-
// Allow queries to complete faster in small networks
|
|
263
|
-
allowQueryWithZeroPeers: true
|
|
259
|
+
ws.on("close", () => {
|
|
260
|
+
logger.info("WebSocket closed", { url: conn.url });
|
|
261
|
+
conn.connected = false;
|
|
262
|
+
conn.ws = null;
|
|
263
|
+
if (conn.stableTimer) {
|
|
264
|
+
clearTimeout(conn.stableTimer);
|
|
265
|
+
conn.stableTimer = null;
|
|
266
|
+
}
|
|
267
|
+
if (!stopped) {
|
|
268
|
+
scheduleReconnect(conn);
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
ws.on("error", (err) => {
|
|
272
|
+
logger.warn("WebSocket error", { url: conn.url, error: err.message });
|
|
264
273
|
});
|
|
274
|
+
} catch (err) {
|
|
275
|
+
logger.warn("Failed to connect to relay", { url: conn.url, error: err.message });
|
|
276
|
+
conn.ws = null;
|
|
277
|
+
conn.connected = false;
|
|
278
|
+
if (!stopped) {
|
|
279
|
+
scheduleReconnect(conn);
|
|
280
|
+
}
|
|
265
281
|
}
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
if (
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
logger.
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
return libp2p.getMultiaddrs().map((ma) => ma.toString());
|
|
295
|
-
},
|
|
296
|
-
getPeerId: () => {
|
|
297
|
-
return libp2p.peerId.toString();
|
|
282
|
+
}
|
|
283
|
+
function scheduleReconnect(conn) {
|
|
284
|
+
if (conn.reconnectTimer) return;
|
|
285
|
+
const delay = Math.min(
|
|
286
|
+
reconnectConfig.baseMs * Math.pow(2, conn.reconnectAttempt) + Math.random() * reconnectConfig.jitterMs,
|
|
287
|
+
reconnectConfig.maxDelayMs
|
|
288
|
+
);
|
|
289
|
+
conn.reconnectAttempt++;
|
|
290
|
+
logger.debug("Scheduling reconnect", { url: conn.url, attempt: conn.reconnectAttempt, delayMs: delay });
|
|
291
|
+
conn.reconnectTimer = setTimeout(() => {
|
|
292
|
+
conn.reconnectTimer = null;
|
|
293
|
+
connectToRelay(conn);
|
|
294
|
+
}, delay);
|
|
295
|
+
}
|
|
296
|
+
async function handleRelayMessage(conn, msg) {
|
|
297
|
+
switch (msg.type) {
|
|
298
|
+
case "WELCOME": {
|
|
299
|
+
const welcome = msg;
|
|
300
|
+
logger.info("Received WELCOME", { relayId: welcome.relayId, peers: welcome.peers });
|
|
301
|
+
conn.connected = true;
|
|
302
|
+
conn.peerCount = welcome.peers;
|
|
303
|
+
conn.reconnectAttempt = 0;
|
|
304
|
+
if (conn.stableTimer) clearTimeout(conn.stableTimer);
|
|
305
|
+
conn.stableTimer = setTimeout(() => {
|
|
306
|
+
conn.reconnectAttempt = 0;
|
|
307
|
+
logger.debug("Connection stable", { url: conn.url });
|
|
308
|
+
}, reconnectConfig.stableAfterMs);
|
|
309
|
+
break;
|
|
298
310
|
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
311
|
+
case "DELIVER": {
|
|
312
|
+
const deliver = msg;
|
|
313
|
+
logger.info("Received DELIVER", { messageId: deliver.messageId, from: deliver.from });
|
|
314
|
+
if (deliveryHandler) {
|
|
315
|
+
await deliveryHandler(deliver);
|
|
316
|
+
}
|
|
317
|
+
break;
|
|
318
|
+
}
|
|
319
|
+
case "DELIVERY_REPORT": {
|
|
320
|
+
const report = msg;
|
|
321
|
+
logger.info("Received DELIVERY_REPORT", { messageId: report.messageId, status: report.status });
|
|
322
|
+
if (deliveryReportHandler) {
|
|
323
|
+
deliveryReportHandler(report);
|
|
324
|
+
}
|
|
325
|
+
break;
|
|
326
|
+
}
|
|
327
|
+
case "PONG": {
|
|
328
|
+
conn.peerCount = msg.peers;
|
|
329
|
+
logger.debug("Received PONG", { peers: msg.peers });
|
|
330
|
+
break;
|
|
331
|
+
}
|
|
332
|
+
default:
|
|
333
|
+
logger.debug("Received relay message", { type: msg.type });
|
|
334
|
+
}
|
|
302
335
|
}
|
|
336
|
+
function getConnectedConnection() {
|
|
337
|
+
return connections.find((c) => c.connected && c.ws) || null;
|
|
338
|
+
}
|
|
339
|
+
async function sendToRelay(msg) {
|
|
340
|
+
const conn = getConnectedConnection();
|
|
341
|
+
if (!conn || !conn.ws) {
|
|
342
|
+
throw new TransportError("No connected relay");
|
|
343
|
+
}
|
|
344
|
+
conn.ws.send(JSON.stringify(msg));
|
|
345
|
+
}
|
|
346
|
+
return {
|
|
347
|
+
async start() {
|
|
348
|
+
stopped = false;
|
|
349
|
+
logger.info("Starting relay client", { relays: relayUrls.length });
|
|
350
|
+
for (let i = 0; i < connections.length; i++) {
|
|
351
|
+
const conn = connections[i];
|
|
352
|
+
setTimeout(() => {
|
|
353
|
+
connectToRelay(conn);
|
|
354
|
+
}, Math.random() * 2e3);
|
|
355
|
+
}
|
|
356
|
+
const maxWait = 15e3;
|
|
357
|
+
const start = Date.now();
|
|
358
|
+
while (Date.now() - start < maxWait) {
|
|
359
|
+
if (getConnectedConnection()) {
|
|
360
|
+
logger.info("Relay client started");
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
364
|
+
}
|
|
365
|
+
throw new TransportError("Failed to connect to any relay");
|
|
366
|
+
},
|
|
367
|
+
async stop() {
|
|
368
|
+
stopped = true;
|
|
369
|
+
logger.info("Stopping relay client");
|
|
370
|
+
for (const conn of connections) {
|
|
371
|
+
if (conn.reconnectTimer) {
|
|
372
|
+
clearTimeout(conn.reconnectTimer);
|
|
373
|
+
conn.reconnectTimer = null;
|
|
374
|
+
}
|
|
375
|
+
if (conn.stableTimer) {
|
|
376
|
+
clearTimeout(conn.stableTimer);
|
|
377
|
+
conn.stableTimer = null;
|
|
378
|
+
}
|
|
379
|
+
if (conn.ws) {
|
|
380
|
+
conn.ws.close();
|
|
381
|
+
conn.ws = null;
|
|
382
|
+
}
|
|
383
|
+
conn.connected = false;
|
|
384
|
+
}
|
|
385
|
+
logger.info("Relay client stopped");
|
|
386
|
+
},
|
|
387
|
+
async sendEnvelope(toDid, envelopeBytes) {
|
|
388
|
+
const msg = {
|
|
389
|
+
type: "SEND",
|
|
390
|
+
to: toDid,
|
|
391
|
+
envelope: envelopeBytes
|
|
392
|
+
};
|
|
393
|
+
await sendToRelay(msg);
|
|
394
|
+
logger.debug("Sent envelope", { to: toDid, size: envelopeBytes.length });
|
|
395
|
+
},
|
|
396
|
+
async discover(query, minTrust, limit) {
|
|
397
|
+
const conn = getConnectedConnection();
|
|
398
|
+
const ws = conn?.ws;
|
|
399
|
+
if (!conn || !ws) {
|
|
400
|
+
throw new TransportError("No connected relay");
|
|
401
|
+
}
|
|
402
|
+
return new Promise((resolve, reject) => {
|
|
403
|
+
const timeout = setTimeout(() => {
|
|
404
|
+
reject(new TransportError("Discover timeout"));
|
|
405
|
+
}, 1e4);
|
|
406
|
+
const handler = (data) => {
|
|
407
|
+
try {
|
|
408
|
+
const msg = JSON.parse(data.toString());
|
|
409
|
+
if (msg.type === "DISCOVERED") {
|
|
410
|
+
clearTimeout(timeout);
|
|
411
|
+
ws.off("message", handler);
|
|
412
|
+
resolve(msg.agents);
|
|
413
|
+
}
|
|
414
|
+
} catch (err) {
|
|
415
|
+
}
|
|
416
|
+
};
|
|
417
|
+
ws.on("message", handler);
|
|
418
|
+
ws.send(JSON.stringify({ type: "DISCOVER", query, minTrust, limit }));
|
|
419
|
+
});
|
|
420
|
+
},
|
|
421
|
+
async fetchCard(did2) {
|
|
422
|
+
const conn = getConnectedConnection();
|
|
423
|
+
const ws = conn?.ws;
|
|
424
|
+
if (!conn || !ws) {
|
|
425
|
+
throw new TransportError("No connected relay");
|
|
426
|
+
}
|
|
427
|
+
return new Promise((resolve, reject) => {
|
|
428
|
+
const timeout = setTimeout(() => {
|
|
429
|
+
reject(new TransportError("Fetch card timeout"));
|
|
430
|
+
}, 5e3);
|
|
431
|
+
const handler = (data) => {
|
|
432
|
+
try {
|
|
433
|
+
const msg = JSON.parse(data.toString());
|
|
434
|
+
if (msg.type === "CARD" && msg.did === did2) {
|
|
435
|
+
clearTimeout(timeout);
|
|
436
|
+
ws.off("message", handler);
|
|
437
|
+
resolve(msg.card);
|
|
438
|
+
}
|
|
439
|
+
} catch (err) {
|
|
440
|
+
}
|
|
441
|
+
};
|
|
442
|
+
ws.on("message", handler);
|
|
443
|
+
ws.send(JSON.stringify({ type: "FETCH_CARD", did: did2 }));
|
|
444
|
+
});
|
|
445
|
+
},
|
|
446
|
+
onDeliver(handler) {
|
|
447
|
+
deliveryHandler = handler;
|
|
448
|
+
},
|
|
449
|
+
onDeliveryReport(handler) {
|
|
450
|
+
deliveryReportHandler = handler;
|
|
451
|
+
},
|
|
452
|
+
isConnected() {
|
|
453
|
+
return getConnectedConnection() !== null;
|
|
454
|
+
},
|
|
455
|
+
getConnectedRelays() {
|
|
456
|
+
return connections.filter((c) => c.connected).map((c) => c.url);
|
|
457
|
+
},
|
|
458
|
+
getPeerCount() {
|
|
459
|
+
const conn = getConnectedConnection();
|
|
460
|
+
return conn ? conn.peerCount : 0;
|
|
461
|
+
}
|
|
462
|
+
};
|
|
303
463
|
}
|
|
304
464
|
|
|
465
|
+
// src/transport/relay-types.ts
|
|
466
|
+
var RELAY_PROTOCOL_VERSION = 1;
|
|
467
|
+
|
|
305
468
|
// src/discovery/agent-card-types.ts
|
|
306
469
|
function isLegacyCard(card) {
|
|
307
470
|
return Array.isArray(card.capabilities) && card.capabilities.length > 0 && typeof card.capabilities[0] === "string";
|
|
@@ -557,7 +720,71 @@ function getEncodedSize(card) {
|
|
|
557
720
|
json: encodeForWeb(card).length
|
|
558
721
|
};
|
|
559
722
|
}
|
|
560
|
-
|
|
723
|
+
|
|
724
|
+
// src/discovery/relay-index.ts
|
|
725
|
+
var logger2 = createLogger("relay-index");
|
|
726
|
+
function createRelayIndexOperations(client) {
|
|
727
|
+
return {
|
|
728
|
+
publishAgentCard: async (card) => {
|
|
729
|
+
logger2.debug("Agent Card published via HELLO", { did: card.did });
|
|
730
|
+
},
|
|
731
|
+
queryAgentCard: async (did) => {
|
|
732
|
+
try {
|
|
733
|
+
const card = await client.fetchCard(did);
|
|
734
|
+
if (card) {
|
|
735
|
+
logger2.debug("Found Agent Card via relay", { did });
|
|
736
|
+
} else {
|
|
737
|
+
logger2.debug("Agent Card not found via relay", { did });
|
|
738
|
+
}
|
|
739
|
+
return card;
|
|
740
|
+
} catch (error) {
|
|
741
|
+
logger2.warn("Failed to query Agent Card", { did, error });
|
|
742
|
+
return null;
|
|
743
|
+
}
|
|
744
|
+
},
|
|
745
|
+
queryByCapability: async (capability) => {
|
|
746
|
+
try {
|
|
747
|
+
const results = await client.discover(capability);
|
|
748
|
+
const cards = results.map((r) => r.card);
|
|
749
|
+
logger2.debug("Query by capability", { capability, count: cards.length });
|
|
750
|
+
return cards;
|
|
751
|
+
} catch (error) {
|
|
752
|
+
throw new DiscoveryError("Failed to query by capability", error);
|
|
753
|
+
}
|
|
754
|
+
},
|
|
755
|
+
searchSemantic: async (query) => {
|
|
756
|
+
try {
|
|
757
|
+
const queryText = query.text || query.capability || "";
|
|
758
|
+
const minTrust = query.filters?.minTrustScore;
|
|
759
|
+
const limit = query.limit || 10;
|
|
760
|
+
const results = await client.discover(queryText, minTrust, limit);
|
|
761
|
+
const cards = results.map((r) => r.card);
|
|
762
|
+
logger2.debug("Semantic search", { query: queryText, count: cards.length });
|
|
763
|
+
return cards;
|
|
764
|
+
} catch (error) {
|
|
765
|
+
throw new DiscoveryError("Failed to perform semantic search", error);
|
|
766
|
+
}
|
|
767
|
+
},
|
|
768
|
+
resolveDID: async (did) => {
|
|
769
|
+
try {
|
|
770
|
+
const relays = client.getConnectedRelays();
|
|
771
|
+
if (relays.length === 0) {
|
|
772
|
+
logger2.debug("DID resolution failed: no connected relays", { did });
|
|
773
|
+
return null;
|
|
774
|
+
}
|
|
775
|
+
logger2.debug("Resolved DID to relay", { did, relay: relays[0] });
|
|
776
|
+
return { relayUrl: relays[0] };
|
|
777
|
+
} catch (error) {
|
|
778
|
+
logger2.warn("Failed to resolve DID", { did, error });
|
|
779
|
+
return null;
|
|
780
|
+
}
|
|
781
|
+
},
|
|
782
|
+
queryRelayPeers: async () => {
|
|
783
|
+
return client.getConnectedRelays();
|
|
784
|
+
}
|
|
785
|
+
};
|
|
786
|
+
}
|
|
787
|
+
var logger3 = createLogger("search-index");
|
|
561
788
|
var SearchIndex = class {
|
|
562
789
|
cards = /* @__PURE__ */ new Map();
|
|
563
790
|
lunrIndex;
|
|
@@ -569,7 +796,7 @@ var SearchIndex = class {
|
|
|
569
796
|
indexAgentCard(card) {
|
|
570
797
|
this.cards.set(card.did, card);
|
|
571
798
|
this.needsRebuild = true;
|
|
572
|
-
|
|
799
|
+
logger3.debug("Indexed Agent Card", { did: card.did, capabilities: card.capabilities.length });
|
|
573
800
|
}
|
|
574
801
|
/**
|
|
575
802
|
* Remove an Agent Card from the index
|
|
@@ -577,7 +804,7 @@ var SearchIndex = class {
|
|
|
577
804
|
removeAgentCard(did) {
|
|
578
805
|
this.cards.delete(did);
|
|
579
806
|
this.needsRebuild = true;
|
|
580
|
-
|
|
807
|
+
logger3.debug("Removed Agent Card from index", { did });
|
|
581
808
|
}
|
|
582
809
|
/**
|
|
583
810
|
* Search for agents matching a query
|
|
@@ -604,7 +831,7 @@ var SearchIndex = class {
|
|
|
604
831
|
if (query.limit) {
|
|
605
832
|
results = results.slice(0, query.limit);
|
|
606
833
|
}
|
|
607
|
-
|
|
834
|
+
logger3.debug("Search completed", { query, results: results.length });
|
|
608
835
|
return results;
|
|
609
836
|
}
|
|
610
837
|
/**
|
|
@@ -621,7 +848,7 @@ var SearchIndex = class {
|
|
|
621
848
|
this.lunrIndex = void 0;
|
|
622
849
|
this.fuse = void 0;
|
|
623
850
|
this.needsRebuild = false;
|
|
624
|
-
|
|
851
|
+
logger3.info("Search index cleared");
|
|
625
852
|
}
|
|
626
853
|
/**
|
|
627
854
|
* Get index size
|
|
@@ -633,7 +860,7 @@ var SearchIndex = class {
|
|
|
633
860
|
* Rebuild search indexes
|
|
634
861
|
*/
|
|
635
862
|
rebuild() {
|
|
636
|
-
|
|
863
|
+
logger3.info("Rebuilding search indexes", { cards: this.cards.size });
|
|
637
864
|
const cards = Array.from(this.cards.values());
|
|
638
865
|
this.lunrIndex = lunr(function() {
|
|
639
866
|
this.ref("did");
|
|
@@ -660,7 +887,7 @@ var SearchIndex = class {
|
|
|
660
887
|
includeScore: true
|
|
661
888
|
});
|
|
662
889
|
this.needsRebuild = false;
|
|
663
|
-
|
|
890
|
+
logger3.info("Search indexes rebuilt");
|
|
664
891
|
}
|
|
665
892
|
/**
|
|
666
893
|
* Search by text using Lunr
|
|
@@ -871,7 +1098,7 @@ var CapabilityMatcher = class {
|
|
|
871
1098
|
};
|
|
872
1099
|
|
|
873
1100
|
// src/discovery/semantic-search.ts
|
|
874
|
-
var
|
|
1101
|
+
var logger4 = createLogger("semantic-search");
|
|
875
1102
|
var SemanticSearchEngine = class {
|
|
876
1103
|
constructor(dht) {
|
|
877
1104
|
this.dht = dht;
|
|
@@ -885,12 +1112,12 @@ var SemanticSearchEngine = class {
|
|
|
885
1112
|
* Local-first with network fallback
|
|
886
1113
|
*/
|
|
887
1114
|
async search(query) {
|
|
888
|
-
|
|
1115
|
+
logger4.info("Searching for agents", { query });
|
|
889
1116
|
const localResults = this.index.search(query);
|
|
890
|
-
|
|
1117
|
+
logger4.debug("Local search results", { count: localResults.length });
|
|
891
1118
|
const limit = query.limit || 10;
|
|
892
1119
|
if (localResults.length < limit && this.dht) {
|
|
893
|
-
|
|
1120
|
+
logger4.debug("Insufficient local results, querying network");
|
|
894
1121
|
const networkResults = await this.searchNetwork(query);
|
|
895
1122
|
return this.mergeResults(localResults, networkResults, limit);
|
|
896
1123
|
}
|
|
@@ -936,17 +1163,17 @@ var SemanticSearchEngine = class {
|
|
|
936
1163
|
try {
|
|
937
1164
|
const capability = query.capability || this.extractPrimaryCapability(query.text);
|
|
938
1165
|
if (!capability) {
|
|
939
|
-
|
|
1166
|
+
logger4.debug("No capability extracted from query, skipping network search");
|
|
940
1167
|
return [];
|
|
941
1168
|
}
|
|
942
1169
|
const cards = await this.dht.queryByCapability(capability);
|
|
943
|
-
|
|
1170
|
+
logger4.debug("Network search results", { count: cards.length });
|
|
944
1171
|
return cards.map((card) => {
|
|
945
1172
|
const score = this.scoreCard(card, query);
|
|
946
1173
|
return { card, score };
|
|
947
1174
|
});
|
|
948
1175
|
} catch (error) {
|
|
949
|
-
|
|
1176
|
+
logger4.error("Network search failed", { error });
|
|
950
1177
|
return [];
|
|
951
1178
|
}
|
|
952
1179
|
}
|
|
@@ -1030,236 +1257,6 @@ function createSemanticSearch(dht) {
|
|
|
1030
1257
|
return new SemanticSearchEngine(dht);
|
|
1031
1258
|
}
|
|
1032
1259
|
|
|
1033
|
-
// src/discovery/dht.ts
|
|
1034
|
-
var logger4 = createLogger("dht");
|
|
1035
|
-
var peerCache = /* @__PURE__ */ new Map();
|
|
1036
|
-
var CACHE_TTL = 5 * 60 * 1e3;
|
|
1037
|
-
function extractValue(event) {
|
|
1038
|
-
if (event.name === "VALUE" && event.value) return event.value;
|
|
1039
|
-
if (event.name === "PEER_RESPONSE" && event.value) return event.value;
|
|
1040
|
-
return null;
|
|
1041
|
-
}
|
|
1042
|
-
function capKey(cap) {
|
|
1043
|
-
return cap.toLowerCase().replace(/[^a-z0-9_-]/g, "_");
|
|
1044
|
-
}
|
|
1045
|
-
async function readDIDList(dht, key) {
|
|
1046
|
-
try {
|
|
1047
|
-
for await (const event of dht.get(key, { signal: AbortSignal.timeout(3e4) })) {
|
|
1048
|
-
const raw = extractValue(event);
|
|
1049
|
-
if (raw) {
|
|
1050
|
-
const text = toString(raw);
|
|
1051
|
-
return text.split("\n").filter(Boolean);
|
|
1052
|
-
}
|
|
1053
|
-
}
|
|
1054
|
-
} catch {
|
|
1055
|
-
}
|
|
1056
|
-
return [];
|
|
1057
|
-
}
|
|
1058
|
-
async function writeDIDList(dht, key, dids) {
|
|
1059
|
-
const value = fromString([...new Set(dids)].join("\n"));
|
|
1060
|
-
for (let attempt = 1; attempt <= 3; attempt++) {
|
|
1061
|
-
try {
|
|
1062
|
-
for await (const _ of dht.put(key, value, { signal: AbortSignal.timeout(3e4) })) {
|
|
1063
|
-
}
|
|
1064
|
-
return;
|
|
1065
|
-
} catch (e) {
|
|
1066
|
-
if (e?.name === "AbortError" && attempt < 3) {
|
|
1067
|
-
logger4.debug(`DHT put timeout, retrying (${attempt}/3)...`);
|
|
1068
|
-
continue;
|
|
1069
|
-
}
|
|
1070
|
-
if (e?.name !== "AbortError") throw e;
|
|
1071
|
-
}
|
|
1072
|
-
}
|
|
1073
|
-
}
|
|
1074
|
-
function createDHTOperations(libp2p) {
|
|
1075
|
-
const operations = {};
|
|
1076
|
-
const searchEngine = createSemanticSearch(operations);
|
|
1077
|
-
return Object.assign(operations, {
|
|
1078
|
-
publishAgentCard: async (card) => {
|
|
1079
|
-
try {
|
|
1080
|
-
const dht = libp2p.services?.dht;
|
|
1081
|
-
if (!dht) throw new DiscoveryError("DHT service not available");
|
|
1082
|
-
const agentKey = fromString(`/clawiverse/agent/${card.did}`);
|
|
1083
|
-
for (let attempt = 1; attempt <= 3; attempt++) {
|
|
1084
|
-
try {
|
|
1085
|
-
for await (const _ of dht.put(agentKey, encodeForDHT(card), { signal: AbortSignal.timeout(3e4) })) {
|
|
1086
|
-
}
|
|
1087
|
-
break;
|
|
1088
|
-
} catch (e) {
|
|
1089
|
-
if (e?.name === "AbortError" && attempt < 3) {
|
|
1090
|
-
logger4.debug(`DHT put agent card timeout, retrying (${attempt}/3)...`);
|
|
1091
|
-
continue;
|
|
1092
|
-
}
|
|
1093
|
-
logger4.warn("DHT put agent card failed (non-fatal)", { error: e.message });
|
|
1094
|
-
break;
|
|
1095
|
-
}
|
|
1096
|
-
}
|
|
1097
|
-
const caps = (card.capabilities ?? []).flatMap((c) => {
|
|
1098
|
-
if (typeof c === "string") return [c];
|
|
1099
|
-
return [c.name, c.id].filter((v) => typeof v === "string" && v.length > 0);
|
|
1100
|
-
});
|
|
1101
|
-
caps.push("__all__");
|
|
1102
|
-
const importantCaps = ["__all__"];
|
|
1103
|
-
if (caps.some((cap) => capKey(cap) === "relay")) {
|
|
1104
|
-
importantCaps.push("relay");
|
|
1105
|
-
}
|
|
1106
|
-
await Promise.all(importantCaps.map(async (cap) => {
|
|
1107
|
-
const capKeyStr = `/clawiverse/cap/${capKey(cap)}`;
|
|
1108
|
-
const capDHTKey = fromString(capKeyStr);
|
|
1109
|
-
try {
|
|
1110
|
-
const existing = await readDIDList(dht, capDHTKey);
|
|
1111
|
-
if (!existing.includes(card.did)) {
|
|
1112
|
-
await writeDIDList(dht, capDHTKey, [...existing, card.did]);
|
|
1113
|
-
logger4.debug("Indexed capability in DHT", { cap: capKey(cap), did: card.did });
|
|
1114
|
-
}
|
|
1115
|
-
} catch (e) {
|
|
1116
|
-
logger4.warn("Failed to index capability (non-fatal)", { cap: capKey(cap), error: e.message });
|
|
1117
|
-
}
|
|
1118
|
-
}));
|
|
1119
|
-
searchEngine.indexAgentCard(card);
|
|
1120
|
-
logger4.info("Published Agent Card to DHT", { did: card.did });
|
|
1121
|
-
} catch (error) {
|
|
1122
|
-
logger4.warn("Failed to publish Agent Card to DHT (non-fatal)", { error: error.message });
|
|
1123
|
-
searchEngine.indexAgentCard(card);
|
|
1124
|
-
}
|
|
1125
|
-
},
|
|
1126
|
-
queryAgentCard: async (did) => {
|
|
1127
|
-
try {
|
|
1128
|
-
const dht = libp2p.services?.dht;
|
|
1129
|
-
if (!dht) throw new DiscoveryError("DHT service not available");
|
|
1130
|
-
const key = fromString(`/clawiverse/agent/${did}`);
|
|
1131
|
-
for await (const event of dht.get(key)) {
|
|
1132
|
-
const raw = extractValue(event);
|
|
1133
|
-
if (raw) {
|
|
1134
|
-
const card = decodeFromCBOR(raw);
|
|
1135
|
-
searchEngine.indexAgentCard(card);
|
|
1136
|
-
logger4.debug("Found Agent Card in DHT", { did });
|
|
1137
|
-
return card;
|
|
1138
|
-
}
|
|
1139
|
-
}
|
|
1140
|
-
logger4.debug("Agent Card not found in DHT", { did });
|
|
1141
|
-
return null;
|
|
1142
|
-
} catch (error) {
|
|
1143
|
-
logger4.warn("Failed to query Agent Card", { did, error });
|
|
1144
|
-
return null;
|
|
1145
|
-
}
|
|
1146
|
-
},
|
|
1147
|
-
queryByCapability: async (capability) => {
|
|
1148
|
-
try {
|
|
1149
|
-
const dht = libp2p.services?.dht;
|
|
1150
|
-
const local = searchEngine.getAllIndexedCards().filter(
|
|
1151
|
-
(card) => card.capabilities.some((cap) => {
|
|
1152
|
-
const name = typeof cap === "string" ? cap : cap.name;
|
|
1153
|
-
return name?.toLowerCase().includes(capability.toLowerCase());
|
|
1154
|
-
})
|
|
1155
|
-
);
|
|
1156
|
-
if (local.length > 0) return local;
|
|
1157
|
-
if (!dht) return [];
|
|
1158
|
-
const capDHTKey = fromString(`/clawiverse/cap/${capKey(capability)}`);
|
|
1159
|
-
const dids = await readDIDList(dht, capDHTKey);
|
|
1160
|
-
logger4.debug("DHT capability index", { capability, dids });
|
|
1161
|
-
const cards = await Promise.all(
|
|
1162
|
-
dids.map(async (did) => {
|
|
1163
|
-
const key = fromString(`/clawiverse/agent/${did}`);
|
|
1164
|
-
try {
|
|
1165
|
-
for await (const event of dht.get(key)) {
|
|
1166
|
-
const raw = extractValue(event);
|
|
1167
|
-
if (raw) {
|
|
1168
|
-
const card = decodeFromCBOR(raw);
|
|
1169
|
-
searchEngine.indexAgentCard(card);
|
|
1170
|
-
return card;
|
|
1171
|
-
}
|
|
1172
|
-
}
|
|
1173
|
-
} catch {
|
|
1174
|
-
}
|
|
1175
|
-
return null;
|
|
1176
|
-
})
|
|
1177
|
-
);
|
|
1178
|
-
return cards.filter((c) => c !== null);
|
|
1179
|
-
} catch (error) {
|
|
1180
|
-
throw new DiscoveryError("Failed to query by capability", error);
|
|
1181
|
-
}
|
|
1182
|
-
},
|
|
1183
|
-
searchSemantic: async (query) => {
|
|
1184
|
-
try {
|
|
1185
|
-
const dht = libp2p.services?.dht;
|
|
1186
|
-
if (dht) {
|
|
1187
|
-
const allKey = fromString("/clawiverse/cap/__all__");
|
|
1188
|
-
const dids = await readDIDList(dht, allKey);
|
|
1189
|
-
logger4.debug("DHT __all__ index", { count: dids.length });
|
|
1190
|
-
await Promise.all(
|
|
1191
|
-
dids.map(async (did) => {
|
|
1192
|
-
const key = fromString(`/clawiverse/agent/${did}`);
|
|
1193
|
-
try {
|
|
1194
|
-
for await (const event of dht.get(key)) {
|
|
1195
|
-
const raw = extractValue(event);
|
|
1196
|
-
if (raw) {
|
|
1197
|
-
searchEngine.indexAgentCard(decodeFromCBOR(raw));
|
|
1198
|
-
break;
|
|
1199
|
-
}
|
|
1200
|
-
}
|
|
1201
|
-
} catch {
|
|
1202
|
-
}
|
|
1203
|
-
})
|
|
1204
|
-
);
|
|
1205
|
-
}
|
|
1206
|
-
return searchEngine.search(query);
|
|
1207
|
-
} catch (error) {
|
|
1208
|
-
throw new DiscoveryError("Failed to perform semantic search", error);
|
|
1209
|
-
}
|
|
1210
|
-
},
|
|
1211
|
-
queryRelayPeers: async () => {
|
|
1212
|
-
const dht = libp2p.services?.dht;
|
|
1213
|
-
if (!dht) return [];
|
|
1214
|
-
const capDHTKey = fromString("/clawiverse/cap/relay");
|
|
1215
|
-
const dids = await readDIDList(dht, capDHTKey);
|
|
1216
|
-
const addrs = [];
|
|
1217
|
-
await Promise.all(dids.map(async (did) => {
|
|
1218
|
-
const card = await operations.queryAgentCard(did);
|
|
1219
|
-
if (card?.endpoints) {
|
|
1220
|
-
addrs.push(...card.endpoints.filter((e) => !e.includes("/p2p-circuit/")));
|
|
1221
|
-
}
|
|
1222
|
-
}));
|
|
1223
|
-
return addrs;
|
|
1224
|
-
},
|
|
1225
|
-
resolveDID: async (did) => {
|
|
1226
|
-
try {
|
|
1227
|
-
const cached = peerCache.get(did);
|
|
1228
|
-
if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
|
|
1229
|
-
logger4.debug("Using cached peer info", { did });
|
|
1230
|
-
return { peerId: cached.peerId, multiaddrs: cached.multiaddrs };
|
|
1231
|
-
}
|
|
1232
|
-
const dht = libp2p.services?.dht;
|
|
1233
|
-
if (!dht) throw new DiscoveryError("DHT service not available");
|
|
1234
|
-
const key = fromString(`/clawiverse/agent/${did}`);
|
|
1235
|
-
for await (const event of dht.get(key)) {
|
|
1236
|
-
const raw = extractValue(event);
|
|
1237
|
-
if (raw) {
|
|
1238
|
-
const card = decodeFromCBOR(raw);
|
|
1239
|
-
if (card.peerId) {
|
|
1240
|
-
logger4.debug("Resolved DID to peer", { did, peerId: card.peerId });
|
|
1241
|
-
const result = { peerId: card.peerId, multiaddrs: card.endpoints || [] };
|
|
1242
|
-
peerCache.set(did, {
|
|
1243
|
-
peerId: result.peerId,
|
|
1244
|
-
multiaddrs: result.multiaddrs,
|
|
1245
|
-
timestamp: Date.now()
|
|
1246
|
-
});
|
|
1247
|
-
return result;
|
|
1248
|
-
}
|
|
1249
|
-
logger4.warn("Agent Card found but has no peerId", { did });
|
|
1250
|
-
return null;
|
|
1251
|
-
}
|
|
1252
|
-
}
|
|
1253
|
-
logger4.debug("DID not found in DHT", { did });
|
|
1254
|
-
return null;
|
|
1255
|
-
} catch (error) {
|
|
1256
|
-
logger4.warn("Failed to resolve DID", { did, error });
|
|
1257
|
-
return null;
|
|
1258
|
-
}
|
|
1259
|
-
}
|
|
1260
|
-
});
|
|
1261
|
-
}
|
|
1262
|
-
|
|
1263
1260
|
// src/messaging/envelope.ts
|
|
1264
1261
|
function createEnvelope(from, to, type, protocol, payload, replyTo) {
|
|
1265
1262
|
return {
|
|
@@ -1335,23 +1332,13 @@ function decodeMessageJSON(json) {
|
|
|
1335
1332
|
throw new MessagingError("Failed to decode message from JSON", error);
|
|
1336
1333
|
}
|
|
1337
1334
|
}
|
|
1335
|
+
|
|
1336
|
+
// src/messaging/router.ts
|
|
1338
1337
|
var logger5 = createLogger("router");
|
|
1339
|
-
function
|
|
1340
|
-
if (arrays.length === 0) return new Uint8Array(0);
|
|
1341
|
-
if (arrays.length === 1) return arrays[0];
|
|
1342
|
-
const totalLength = arrays.reduce((acc, arr) => acc + arr.length, 0);
|
|
1343
|
-
const result = new Uint8Array(totalLength);
|
|
1344
|
-
let offset = 0;
|
|
1345
|
-
for (const arr of arrays) {
|
|
1346
|
-
result.set(arr, offset);
|
|
1347
|
-
offset += arr.length;
|
|
1348
|
-
}
|
|
1349
|
-
return result;
|
|
1350
|
-
}
|
|
1351
|
-
function createMessageRouter(libp2p, verifyFn, dht, relayPeers) {
|
|
1338
|
+
function createMessageRouter(relayClient, verifyFn) {
|
|
1352
1339
|
const handlers = /* @__PURE__ */ new Map();
|
|
1353
1340
|
let catchAllHandler;
|
|
1354
|
-
const
|
|
1341
|
+
const pendingRequests = /* @__PURE__ */ new Map();
|
|
1355
1342
|
return {
|
|
1356
1343
|
registerHandler: (protocol, handler) => {
|
|
1357
1344
|
handlers.set(protocol, handler);
|
|
@@ -1365,134 +1352,14 @@ function createMessageRouter(libp2p, verifyFn, dht, relayPeers) {
|
|
|
1365
1352
|
catchAllHandler = handler;
|
|
1366
1353
|
logger5.info("Registered catch-all message handler");
|
|
1367
1354
|
},
|
|
1368
|
-
sendMessage: async (envelope
|
|
1355
|
+
sendMessage: async (envelope) => {
|
|
1369
1356
|
try {
|
|
1370
1357
|
if (!validateEnvelope(envelope)) {
|
|
1371
1358
|
throw new MessagingError("Invalid message envelope");
|
|
1372
1359
|
}
|
|
1373
|
-
let targetPeerIdStr;
|
|
1374
|
-
let targetMultiaddrs = [];
|
|
1375
|
-
if (peerHint) {
|
|
1376
|
-
targetPeerIdStr = peerHint.peerId;
|
|
1377
|
-
targetMultiaddrs = peerHint.multiaddrs;
|
|
1378
|
-
logger5.info("Using peer hint for direct addressing", { peerId: targetPeerIdStr });
|
|
1379
|
-
} else if (dht) {
|
|
1380
|
-
const resolved = await dht.resolveDID(envelope.to);
|
|
1381
|
-
if (resolved) {
|
|
1382
|
-
targetPeerIdStr = resolved.peerId;
|
|
1383
|
-
targetMultiaddrs = resolved.multiaddrs;
|
|
1384
|
-
}
|
|
1385
|
-
}
|
|
1386
|
-
if (!targetPeerIdStr) {
|
|
1387
|
-
throw new MessagingError(
|
|
1388
|
-
`Cannot resolve recipient: ${envelope.to} \u2014 provide peerHint or ensure agent is in DHT`
|
|
1389
|
-
);
|
|
1390
|
-
}
|
|
1391
|
-
const targetPeerId = peerIdFromString(targetPeerIdStr);
|
|
1392
|
-
if (targetMultiaddrs.length > 0) {
|
|
1393
|
-
const mas = targetMultiaddrs.map((a) => multiaddr(a));
|
|
1394
|
-
await libp2p.peerStore.merge(targetPeerId, { multiaddrs: mas });
|
|
1395
|
-
}
|
|
1396
|
-
logger5.info("Dialing peer for message delivery", {
|
|
1397
|
-
peerId: targetPeerIdStr,
|
|
1398
|
-
multiaddrs: targetMultiaddrs
|
|
1399
|
-
});
|
|
1400
|
-
let stream;
|
|
1401
|
-
const DIAL_TIMEOUT = 3e3;
|
|
1402
|
-
const relayMultiaddrs = targetMultiaddrs.filter((a) => a.includes("/p2p-circuit/"));
|
|
1403
|
-
const directMultiaddrs = targetMultiaddrs.filter((a) => !a.includes("/p2p-circuit/"));
|
|
1404
|
-
if (directMultiaddrs.length > 0) {
|
|
1405
|
-
const directDialPromises = directMultiaddrs.map(async (addr) => {
|
|
1406
|
-
try {
|
|
1407
|
-
const conn = await libp2p.dial(multiaddr(addr), {
|
|
1408
|
-
signal: AbortSignal.timeout(DIAL_TIMEOUT)
|
|
1409
|
-
});
|
|
1410
|
-
const s = await conn.newStream(PROTOCOL_PREFIX, { runOnLimitedConnection: true });
|
|
1411
|
-
logger5.info("Direct dial succeeded", { addr });
|
|
1412
|
-
return { conn, stream: s };
|
|
1413
|
-
} catch {
|
|
1414
|
-
return null;
|
|
1415
|
-
}
|
|
1416
|
-
});
|
|
1417
|
-
const winner = await Promise.race(
|
|
1418
|
-
directDialPromises.map((p) => p.then((r) => r || Promise.reject()))
|
|
1419
|
-
).catch(() => void 0);
|
|
1420
|
-
if (winner) {
|
|
1421
|
-
stream = winner.stream;
|
|
1422
|
-
Promise.allSettled(directDialPromises).then((results) => {
|
|
1423
|
-
for (const result of results) {
|
|
1424
|
-
if (result.status === "fulfilled" && result.value && result.value.stream !== stream) {
|
|
1425
|
-
result.value.conn.close().catch(() => {
|
|
1426
|
-
});
|
|
1427
|
-
}
|
|
1428
|
-
}
|
|
1429
|
-
});
|
|
1430
|
-
}
|
|
1431
|
-
}
|
|
1432
|
-
let lastError;
|
|
1433
|
-
if (!stream) {
|
|
1434
|
-
const allRelayAddrs = [
|
|
1435
|
-
...relayMultiaddrs,
|
|
1436
|
-
...(relayPeers ?? []).map((r) => buildCircuitRelayAddr(r, targetPeerIdStr))
|
|
1437
|
-
];
|
|
1438
|
-
const uniqueRelayAddrs = [...new Set(allRelayAddrs)];
|
|
1439
|
-
if (uniqueRelayAddrs.length > 0) {
|
|
1440
|
-
const relayDialPromises = uniqueRelayAddrs.map(async (addr) => {
|
|
1441
|
-
try {
|
|
1442
|
-
const conn = await libp2p.dial(multiaddr(addr), {
|
|
1443
|
-
signal: AbortSignal.timeout(DIAL_TIMEOUT)
|
|
1444
|
-
});
|
|
1445
|
-
logger5.info("Relay connection established", { addr });
|
|
1446
|
-
const s = await conn.newStream(PROTOCOL_PREFIX, { runOnLimitedConnection: true });
|
|
1447
|
-
logger5.info("Relay stream opened", { addr });
|
|
1448
|
-
return { conn, stream: s };
|
|
1449
|
-
} catch (relayErr) {
|
|
1450
|
-
logger5.warn("Relay dial failed", { addr, error: relayErr.message });
|
|
1451
|
-
lastError = relayErr;
|
|
1452
|
-
return null;
|
|
1453
|
-
}
|
|
1454
|
-
});
|
|
1455
|
-
const winner = await Promise.race(
|
|
1456
|
-
relayDialPromises.map((p) => p.then((r) => r || Promise.reject()))
|
|
1457
|
-
).catch(() => void 0);
|
|
1458
|
-
if (winner) {
|
|
1459
|
-
stream = winner.stream;
|
|
1460
|
-
Promise.allSettled(relayDialPromises).then((results) => {
|
|
1461
|
-
for (const result of results) {
|
|
1462
|
-
if (result.status === "fulfilled" && result.value && result.value.stream !== stream) {
|
|
1463
|
-
result.value.conn.close().catch(() => {
|
|
1464
|
-
});
|
|
1465
|
-
}
|
|
1466
|
-
}
|
|
1467
|
-
});
|
|
1468
|
-
}
|
|
1469
|
-
}
|
|
1470
|
-
}
|
|
1471
|
-
if (!stream && dht && "queryRelayPeers" in dht) {
|
|
1472
|
-
const discoveredRelays = await dht.queryRelayPeers();
|
|
1473
|
-
for (const relayAddr of discoveredRelays) {
|
|
1474
|
-
const circuitAddr = buildCircuitRelayAddr(relayAddr, targetPeerIdStr);
|
|
1475
|
-
try {
|
|
1476
|
-
const conn = await libp2p.dial(multiaddr(circuitAddr));
|
|
1477
|
-
stream = await conn.newStream(PROTOCOL_PREFIX, { runOnLimitedConnection: true });
|
|
1478
|
-
logger5.info("DHT-discovered relay succeeded", { relayAddr });
|
|
1479
|
-
break;
|
|
1480
|
-
} catch (e) {
|
|
1481
|
-
logger5.warn("DHT relay failed", { relayAddr, error: e.message });
|
|
1482
|
-
lastError = e;
|
|
1483
|
-
}
|
|
1484
|
-
}
|
|
1485
|
-
}
|
|
1486
|
-
if (!stream) {
|
|
1487
|
-
throw lastError ?? new MessagingError("All dial attempts failed (including DHT-discovered relays)");
|
|
1488
|
-
}
|
|
1489
1360
|
const encoded = encodeMessage(envelope);
|
|
1490
|
-
await
|
|
1491
|
-
|
|
1492
|
-
yield encoded;
|
|
1493
|
-
})()
|
|
1494
|
-
);
|
|
1495
|
-
logger5.info("Message sent over libp2p stream", {
|
|
1361
|
+
await relayClient.sendEnvelope(envelope.to, encoded);
|
|
1362
|
+
logger5.info("Message sent via relay", {
|
|
1496
1363
|
id: envelope.id,
|
|
1497
1364
|
from: envelope.from,
|
|
1498
1365
|
to: envelope.to,
|
|
@@ -1500,38 +1367,15 @@ function createMessageRouter(libp2p, verifyFn, dht, relayPeers) {
|
|
|
1500
1367
|
});
|
|
1501
1368
|
if (envelope.type === "request") {
|
|
1502
1369
|
logger5.debug("Waiting for response to request", { id: envelope.id });
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
const
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
const responseEnvelope = decodeMessage(responseData);
|
|
1513
|
-
logger5.info("Received response", {
|
|
1514
|
-
id: responseEnvelope.id,
|
|
1515
|
-
replyTo: responseEnvelope.replyTo
|
|
1516
|
-
});
|
|
1517
|
-
return responseEnvelope;
|
|
1518
|
-
} else {
|
|
1519
|
-
logger5.warn("No response received for request", { id: envelope.id });
|
|
1520
|
-
return void 0;
|
|
1521
|
-
}
|
|
1522
|
-
})();
|
|
1523
|
-
const timeoutPromise = new Promise((resolve) => {
|
|
1524
|
-
setTimeout(() => {
|
|
1525
|
-
logger5.warn("Response timeout", { id: envelope.id, timeout: RESPONSE_TIMEOUT });
|
|
1526
|
-
resolve(void 0);
|
|
1527
|
-
}, RESPONSE_TIMEOUT);
|
|
1528
|
-
});
|
|
1529
|
-
const response = await Promise.race([responsePromise, timeoutPromise]);
|
|
1530
|
-
return response;
|
|
1531
|
-
} catch (error) {
|
|
1532
|
-
logger5.warn("Error reading response", { error });
|
|
1533
|
-
return void 0;
|
|
1534
|
-
}
|
|
1370
|
+
const RESPONSE_TIMEOUT = 3e4;
|
|
1371
|
+
return new Promise((resolve) => {
|
|
1372
|
+
const timeout = setTimeout(() => {
|
|
1373
|
+
pendingRequests.delete(envelope.id);
|
|
1374
|
+
logger5.warn("Response timeout", { id: envelope.id, timeout: RESPONSE_TIMEOUT });
|
|
1375
|
+
resolve(void 0);
|
|
1376
|
+
}, RESPONSE_TIMEOUT);
|
|
1377
|
+
pendingRequests.set(envelope.id, { resolve, timeout });
|
|
1378
|
+
});
|
|
1535
1379
|
}
|
|
1536
1380
|
return void 0;
|
|
1537
1381
|
} catch (error) {
|
|
@@ -1540,107 +1384,671 @@ function createMessageRouter(libp2p, verifyFn, dht, relayPeers) {
|
|
|
1540
1384
|
}
|
|
1541
1385
|
},
|
|
1542
1386
|
start: async () => {
|
|
1543
|
-
|
|
1387
|
+
relayClient.onDeliver(async (deliverMsg) => {
|
|
1544
1388
|
try {
|
|
1545
|
-
|
|
1389
|
+
const envelope = decodeMessage(deliverMsg.envelope);
|
|
1390
|
+
if (!validateEnvelope(envelope)) {
|
|
1391
|
+
logger5.warn("Received invalid message envelope");
|
|
1392
|
+
return;
|
|
1393
|
+
}
|
|
1394
|
+
const isValidSignature = await verifyEnvelope(envelope, async (signature, data) => {
|
|
1395
|
+
const senderPublicKey = extractPublicKey(envelope.from);
|
|
1396
|
+
return verify(signature, data, senderPublicKey);
|
|
1397
|
+
});
|
|
1398
|
+
if (!isValidSignature) {
|
|
1399
|
+
logger5.warn("Received message with invalid signature", {
|
|
1400
|
+
id: envelope.id,
|
|
1401
|
+
from: envelope.from
|
|
1402
|
+
});
|
|
1403
|
+
return;
|
|
1404
|
+
}
|
|
1405
|
+
try {
|
|
1406
|
+
const { signature, ...envelopeWithoutSig } = envelope;
|
|
1407
|
+
const dataBytes = new TextEncoder().encode(JSON.stringify(envelopeWithoutSig));
|
|
1408
|
+
const signatureBytes = Buffer.from(signature, "hex");
|
|
1409
|
+
const hookValid = await verifyFn(signatureBytes, dataBytes);
|
|
1410
|
+
if (!hookValid) {
|
|
1411
|
+
logger5.warn("Message rejected by custom verifier", {
|
|
1412
|
+
id: envelope.id,
|
|
1413
|
+
from: envelope.from
|
|
1414
|
+
});
|
|
1415
|
+
return;
|
|
1416
|
+
}
|
|
1417
|
+
} catch (error) {
|
|
1418
|
+
logger5.warn("Custom verification hook failed", {
|
|
1419
|
+
id: envelope.id,
|
|
1420
|
+
from: envelope.from,
|
|
1421
|
+
error: error.message
|
|
1422
|
+
});
|
|
1423
|
+
return;
|
|
1424
|
+
}
|
|
1425
|
+
logger5.info("Received message", {
|
|
1426
|
+
id: envelope.id,
|
|
1427
|
+
from: envelope.from,
|
|
1428
|
+
to: envelope.to,
|
|
1429
|
+
protocol: envelope.protocol,
|
|
1430
|
+
type: envelope.type
|
|
1431
|
+
});
|
|
1432
|
+
if (envelope.type === "response" && envelope.replyTo) {
|
|
1433
|
+
const pending = pendingRequests.get(envelope.replyTo);
|
|
1434
|
+
if (pending) {
|
|
1435
|
+
clearTimeout(pending.timeout);
|
|
1436
|
+
pendingRequests.delete(envelope.replyTo);
|
|
1437
|
+
pending.resolve(envelope);
|
|
1438
|
+
logger5.info("Matched response to pending request", {
|
|
1439
|
+
requestId: envelope.replyTo,
|
|
1440
|
+
responseId: envelope.id
|
|
1441
|
+
});
|
|
1442
|
+
return;
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
const handler = handlers.get(envelope.protocol);
|
|
1446
|
+
let response = void 0;
|
|
1447
|
+
if (handler) {
|
|
1448
|
+
response = await handler(envelope);
|
|
1449
|
+
} else if (catchAllHandler) {
|
|
1450
|
+
logger5.debug("Using catch-all handler for protocol", { protocol: envelope.protocol });
|
|
1451
|
+
response = await catchAllHandler(envelope);
|
|
1452
|
+
} else {
|
|
1453
|
+
logger5.warn("No handler for protocol", { protocol: envelope.protocol });
|
|
1454
|
+
}
|
|
1455
|
+
if (response) {
|
|
1456
|
+
const encoded = encodeMessage(response);
|
|
1457
|
+
await relayClient.sendEnvelope(response.to, encoded);
|
|
1458
|
+
logger5.info("Sent response back to sender", {
|
|
1459
|
+
responseId: response.id,
|
|
1460
|
+
replyTo: response.replyTo
|
|
1461
|
+
});
|
|
1462
|
+
}
|
|
1546
1463
|
} catch (error) {
|
|
1547
|
-
logger5.error("Error handling incoming
|
|
1464
|
+
logger5.error("Error handling incoming message", error);
|
|
1548
1465
|
}
|
|
1549
|
-
}
|
|
1466
|
+
});
|
|
1550
1467
|
logger5.info("Message router started");
|
|
1551
1468
|
},
|
|
1552
1469
|
stop: async () => {
|
|
1553
|
-
|
|
1470
|
+
for (const [, pending] of pendingRequests.entries()) {
|
|
1471
|
+
clearTimeout(pending.timeout);
|
|
1472
|
+
pending.resolve(void 0);
|
|
1473
|
+
}
|
|
1474
|
+
pendingRequests.clear();
|
|
1554
1475
|
handlers.clear();
|
|
1555
1476
|
catchAllHandler = void 0;
|
|
1556
1477
|
logger5.info("Message router stopped");
|
|
1557
1478
|
}
|
|
1558
1479
|
};
|
|
1559
1480
|
}
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
}
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1481
|
+
var logger6 = createLogger("message-storage");
|
|
1482
|
+
var MessageStorage = class {
|
|
1483
|
+
db;
|
|
1484
|
+
ready = false;
|
|
1485
|
+
constructor(dbPath) {
|
|
1486
|
+
this.db = new Level(dbPath, { valueEncoding: "json" });
|
|
1487
|
+
}
|
|
1488
|
+
async open() {
|
|
1489
|
+
await this.db.open();
|
|
1490
|
+
this.ready = true;
|
|
1491
|
+
logger6.info("Message storage opened");
|
|
1492
|
+
}
|
|
1493
|
+
async close() {
|
|
1494
|
+
if (this.ready) {
|
|
1495
|
+
await this.db.close();
|
|
1496
|
+
this.ready = false;
|
|
1497
|
+
logger6.info("Message storage closed");
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
// ─── Message Operations ───────────────────────────────────────────────────
|
|
1501
|
+
async putMessage(msg) {
|
|
1502
|
+
const ts = String(msg.receivedAt ?? msg.sentAt ?? Date.now()).padStart(16, "0");
|
|
1503
|
+
const key = `msg:${msg.direction}:${ts}:${msg.envelope.id}`;
|
|
1504
|
+
await this.db.put(key, msg);
|
|
1505
|
+
const idxKey = `idx:from:${msg.envelope.from}:${ts}:${msg.envelope.id}`;
|
|
1506
|
+
await this.db.put(idxKey, "1");
|
|
1507
|
+
}
|
|
1508
|
+
async getMessage(id) {
|
|
1509
|
+
for (const direction of ["inbound", "outbound"]) {
|
|
1510
|
+
const prefix = `msg:${direction}:`;
|
|
1511
|
+
for await (const [, value] of this.db.iterator({
|
|
1512
|
+
gte: prefix,
|
|
1513
|
+
lte: prefix + "\xFF",
|
|
1514
|
+
valueEncoding: "json"
|
|
1515
|
+
})) {
|
|
1516
|
+
if (value.envelope.id === id) return value;
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
return null;
|
|
1520
|
+
}
|
|
1521
|
+
async updateMessage(id, updates) {
|
|
1522
|
+
const msg = await this.getMessage(id);
|
|
1523
|
+
if (!msg) return;
|
|
1524
|
+
const ts = String(msg.receivedAt ?? msg.sentAt ?? Date.now()).padStart(16, "0");
|
|
1525
|
+
const key = `msg:${msg.direction}:${ts}:${id}`;
|
|
1526
|
+
await this.db.put(key, { ...msg, ...updates });
|
|
1527
|
+
}
|
|
1528
|
+
async deleteMessage(id) {
|
|
1529
|
+
const msg = await this.getMessage(id);
|
|
1530
|
+
if (!msg) return;
|
|
1531
|
+
const ts = String(msg.receivedAt ?? msg.sentAt ?? Date.now()).padStart(16, "0");
|
|
1532
|
+
const key = `msg:${msg.direction}:${ts}:${id}`;
|
|
1533
|
+
const idxKey = `idx:from:${msg.envelope.from}:${ts}:${id}`;
|
|
1534
|
+
await this.db.batch([
|
|
1535
|
+
{ type: "del", key },
|
|
1536
|
+
{ type: "del", key: idxKey }
|
|
1537
|
+
]);
|
|
1538
|
+
}
|
|
1539
|
+
async queryMessages(direction, filter = {}, pagination = {}) {
|
|
1540
|
+
const { limit = 50, offset = 0 } = pagination;
|
|
1541
|
+
const prefix = `msg:${direction}:`;
|
|
1542
|
+
const results = [];
|
|
1543
|
+
let total = 0;
|
|
1544
|
+
let skipped = 0;
|
|
1545
|
+
for await (const [, value] of this.db.iterator({
|
|
1546
|
+
gte: prefix,
|
|
1547
|
+
lte: prefix + "\xFF",
|
|
1548
|
+
reverse: true,
|
|
1549
|
+
// newest first
|
|
1550
|
+
valueEncoding: "json"
|
|
1551
|
+
})) {
|
|
1552
|
+
if (!this.matchesFilter(value, filter)) continue;
|
|
1553
|
+
total++;
|
|
1554
|
+
if (skipped < offset) {
|
|
1555
|
+
skipped++;
|
|
1556
|
+
continue;
|
|
1557
|
+
}
|
|
1558
|
+
if (results.length < limit) results.push(value);
|
|
1559
|
+
}
|
|
1560
|
+
return {
|
|
1561
|
+
messages: results,
|
|
1562
|
+
total,
|
|
1563
|
+
hasMore: total > offset + results.length
|
|
1564
|
+
};
|
|
1565
|
+
}
|
|
1566
|
+
matchesFilter(msg, filter) {
|
|
1567
|
+
if (filter.fromDid) {
|
|
1568
|
+
const froms = Array.isArray(filter.fromDid) ? filter.fromDid : [filter.fromDid];
|
|
1569
|
+
if (!froms.includes(msg.envelope.from)) return false;
|
|
1570
|
+
}
|
|
1571
|
+
if (filter.toDid) {
|
|
1572
|
+
const tos = Array.isArray(filter.toDid) ? filter.toDid : [filter.toDid];
|
|
1573
|
+
if (!tos.includes(msg.envelope.to)) return false;
|
|
1574
|
+
}
|
|
1575
|
+
if (filter.protocol) {
|
|
1576
|
+
const protos = Array.isArray(filter.protocol) ? filter.protocol : [filter.protocol];
|
|
1577
|
+
if (!protos.includes(msg.envelope.protocol)) return false;
|
|
1589
1578
|
}
|
|
1579
|
+
if (filter.type && msg.envelope.type !== filter.type) return false;
|
|
1580
|
+
if (filter.unreadOnly && msg.readAt != null) return false;
|
|
1581
|
+
if (filter.status) {
|
|
1582
|
+
const statuses = Array.isArray(filter.status) ? filter.status : [filter.status];
|
|
1583
|
+
if (!statuses.includes(msg.status)) return false;
|
|
1584
|
+
}
|
|
1585
|
+
if (filter.maxAge) {
|
|
1586
|
+
const age = Date.now() - (msg.receivedAt ?? msg.sentAt ?? 0);
|
|
1587
|
+
if (age > filter.maxAge) return false;
|
|
1588
|
+
}
|
|
1589
|
+
if (filter.minTrustScore != null && (msg.trustScore ?? 0) < filter.minTrustScore) return false;
|
|
1590
|
+
return true;
|
|
1591
|
+
}
|
|
1592
|
+
async countMessages(direction, filter = {}) {
|
|
1593
|
+
const prefix = `msg:${direction}:`;
|
|
1594
|
+
let count = 0;
|
|
1595
|
+
for await (const [, value] of this.db.iterator({
|
|
1596
|
+
gte: prefix,
|
|
1597
|
+
lte: prefix + "\xFF",
|
|
1598
|
+
valueEncoding: "json"
|
|
1599
|
+
})) {
|
|
1600
|
+
if (this.matchesFilter(value, filter)) count++;
|
|
1601
|
+
}
|
|
1602
|
+
return count;
|
|
1603
|
+
}
|
|
1604
|
+
// ─── Blocklist ────────────────────────────────────────────────────────────
|
|
1605
|
+
async putBlock(entry) {
|
|
1606
|
+
await this.db.put(`block:${entry.did}`, entry);
|
|
1607
|
+
}
|
|
1608
|
+
async getBlock(did) {
|
|
1590
1609
|
try {
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1610
|
+
return await this.db.get(`block:${did}`);
|
|
1611
|
+
} catch {
|
|
1612
|
+
return null;
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
async deleteBlock(did) {
|
|
1616
|
+
try {
|
|
1617
|
+
await this.db.del(`block:${did}`);
|
|
1618
|
+
} catch {
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
|
+
async listBlocked() {
|
|
1622
|
+
const results = [];
|
|
1623
|
+
for await (const [, value] of this.db.iterator({
|
|
1624
|
+
gte: "block:",
|
|
1625
|
+
lte: "block:\xFF",
|
|
1626
|
+
valueEncoding: "json"
|
|
1627
|
+
})) {
|
|
1628
|
+
results.push(value);
|
|
1629
|
+
}
|
|
1630
|
+
return results;
|
|
1631
|
+
}
|
|
1632
|
+
// ─── Allowlist ────────────────────────────────────────────────────────────
|
|
1633
|
+
async putAllow(entry) {
|
|
1634
|
+
await this.db.put(`allow:${entry.did}`, entry);
|
|
1635
|
+
}
|
|
1636
|
+
async getAllow(did) {
|
|
1637
|
+
try {
|
|
1638
|
+
return await this.db.get(`allow:${did}`);
|
|
1639
|
+
} catch {
|
|
1640
|
+
return null;
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
async deleteAllow(did) {
|
|
1644
|
+
try {
|
|
1645
|
+
await this.db.del(`allow:${did}`);
|
|
1646
|
+
} catch {
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
async listAllowed() {
|
|
1650
|
+
const results = [];
|
|
1651
|
+
for await (const [, value] of this.db.iterator({
|
|
1652
|
+
gte: "allow:",
|
|
1653
|
+
lte: "allow:\xFF",
|
|
1654
|
+
valueEncoding: "json"
|
|
1655
|
+
})) {
|
|
1656
|
+
results.push(value);
|
|
1657
|
+
}
|
|
1658
|
+
return results;
|
|
1659
|
+
}
|
|
1660
|
+
// ─── Seen Cache ───────────────────────────────────────────────────────────
|
|
1661
|
+
async putSeen(entry) {
|
|
1662
|
+
await this.db.put(`seen:${entry.messageId}`, entry);
|
|
1663
|
+
}
|
|
1664
|
+
async getSeen(messageId) {
|
|
1665
|
+
try {
|
|
1666
|
+
return await this.db.get(`seen:${messageId}`);
|
|
1667
|
+
} catch {
|
|
1668
|
+
return null;
|
|
1669
|
+
}
|
|
1670
|
+
}
|
|
1671
|
+
async cleanupSeen(maxAgeMs) {
|
|
1672
|
+
const cutoff = Date.now() - maxAgeMs;
|
|
1673
|
+
const toDelete = [];
|
|
1674
|
+
for await (const [key, value] of this.db.iterator({
|
|
1675
|
+
gte: "seen:",
|
|
1676
|
+
lte: "seen:\xFF",
|
|
1677
|
+
valueEncoding: "json"
|
|
1678
|
+
})) {
|
|
1679
|
+
if (value.seenAt < cutoff) toDelete.push(key);
|
|
1680
|
+
}
|
|
1681
|
+
await this.db.batch(toDelete.map((key) => ({ type: "del", key })));
|
|
1682
|
+
}
|
|
1683
|
+
// ─── Rate Limit State ─────────────────────────────────────────────────────
|
|
1684
|
+
async putRateLimit(state) {
|
|
1685
|
+
await this.db.put(`rate:${state.did}`, state);
|
|
1686
|
+
}
|
|
1687
|
+
async getRateLimit(did) {
|
|
1688
|
+
try {
|
|
1689
|
+
return await this.db.get(`rate:${did}`);
|
|
1690
|
+
} catch {
|
|
1691
|
+
return null;
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
async cleanupRateLimits(maxAgeMs) {
|
|
1695
|
+
const cutoff = Date.now() - maxAgeMs;
|
|
1696
|
+
const toDelete = [];
|
|
1697
|
+
for await (const [key, value] of this.db.iterator({
|
|
1698
|
+
gte: "rate:",
|
|
1699
|
+
lte: "rate:\xFF",
|
|
1700
|
+
valueEncoding: "json"
|
|
1701
|
+
})) {
|
|
1702
|
+
if (value.lastRefill < cutoff) toDelete.push(key);
|
|
1703
|
+
}
|
|
1704
|
+
await this.db.batch(toDelete.map((key) => ({ type: "del", key })));
|
|
1705
|
+
}
|
|
1706
|
+
};
|
|
1707
|
+
|
|
1708
|
+
// src/messaging/queue.ts
|
|
1709
|
+
var logger7 = createLogger("message-queue");
|
|
1710
|
+
var MessageQueue = class {
|
|
1711
|
+
storage;
|
|
1712
|
+
subscriptions = /* @__PURE__ */ new Map();
|
|
1713
|
+
subCounter = 0;
|
|
1714
|
+
constructor(config) {
|
|
1715
|
+
this.storage = new MessageStorage(config.dbPath);
|
|
1716
|
+
}
|
|
1717
|
+
get store() {
|
|
1718
|
+
return this.storage;
|
|
1719
|
+
}
|
|
1720
|
+
async start() {
|
|
1721
|
+
await this.storage.open();
|
|
1722
|
+
logger7.info("Message queue started");
|
|
1723
|
+
}
|
|
1724
|
+
async stop() {
|
|
1725
|
+
await this.storage.close();
|
|
1726
|
+
this.subscriptions.clear();
|
|
1727
|
+
logger7.info("Message queue stopped");
|
|
1728
|
+
}
|
|
1729
|
+
// ─── Inbox ────────────────────────────────────────────────────────────────
|
|
1730
|
+
async getInbox(filter = {}, pagination = {}) {
|
|
1731
|
+
return this.storage.queryMessages("inbound", filter, pagination);
|
|
1732
|
+
}
|
|
1733
|
+
async getMessage(id) {
|
|
1734
|
+
return this.storage.getMessage(id);
|
|
1735
|
+
}
|
|
1736
|
+
async markAsRead(id) {
|
|
1737
|
+
await this.storage.updateMessage(id, { readAt: Date.now() });
|
|
1738
|
+
}
|
|
1739
|
+
async deleteMessage(id) {
|
|
1740
|
+
await this.storage.deleteMessage(id);
|
|
1741
|
+
}
|
|
1742
|
+
// ─── Outbox ───────────────────────────────────────────────────────────────
|
|
1743
|
+
async getOutbox(pagination = {}) {
|
|
1744
|
+
return this.storage.queryMessages("outbound", {}, pagination);
|
|
1745
|
+
}
|
|
1746
|
+
async retryMessage(id) {
|
|
1747
|
+
await this.storage.updateMessage(id, { status: "pending", error: void 0 });
|
|
1748
|
+
}
|
|
1749
|
+
// ─── Enqueue ──────────────────────────────────────────────────────────────
|
|
1750
|
+
async enqueueInbound(envelope, trustScore) {
|
|
1751
|
+
const msg = {
|
|
1752
|
+
envelope,
|
|
1753
|
+
direction: "inbound",
|
|
1754
|
+
status: "pending",
|
|
1755
|
+
receivedAt: Date.now(),
|
|
1756
|
+
trustScore
|
|
1757
|
+
};
|
|
1758
|
+
await this.storage.putMessage(msg);
|
|
1759
|
+
logger7.debug("Enqueued inbound message", { id: envelope.id, from: envelope.from });
|
|
1760
|
+
this.notifySubscribers(msg);
|
|
1761
|
+
return msg;
|
|
1762
|
+
}
|
|
1763
|
+
async enqueueOutbound(envelope) {
|
|
1764
|
+
const msg = {
|
|
1765
|
+
envelope,
|
|
1766
|
+
direction: "outbound",
|
|
1767
|
+
status: "pending",
|
|
1768
|
+
sentAt: Date.now()
|
|
1769
|
+
};
|
|
1770
|
+
await this.storage.putMessage(msg);
|
|
1771
|
+
logger7.debug("Enqueued outbound message", { id: envelope.id, to: envelope.to });
|
|
1772
|
+
return msg;
|
|
1773
|
+
}
|
|
1774
|
+
async markOutboundDelivered(id) {
|
|
1775
|
+
await this.storage.updateMessage(id, { status: "delivered" });
|
|
1776
|
+
}
|
|
1777
|
+
async markOutboundFailed(id, error) {
|
|
1778
|
+
await this.storage.updateMessage(id, { status: "failed", error });
|
|
1779
|
+
}
|
|
1780
|
+
// ─── Subscriptions ────────────────────────────────────────────────────────
|
|
1781
|
+
subscribe(filter, callback) {
|
|
1782
|
+
const id = `sub_${++this.subCounter}`;
|
|
1783
|
+
this.subscriptions.set(id, { id, filter, callback });
|
|
1784
|
+
logger7.debug("Subscription added", { id });
|
|
1785
|
+
return id;
|
|
1786
|
+
}
|
|
1787
|
+
unsubscribe(subscriptionId) {
|
|
1788
|
+
this.subscriptions.delete(subscriptionId);
|
|
1789
|
+
logger7.debug("Subscription removed", { id: subscriptionId });
|
|
1790
|
+
}
|
|
1791
|
+
notifySubscribers(msg) {
|
|
1792
|
+
for (const sub of this.subscriptions.values()) {
|
|
1793
|
+
if (this.matchesSubscriptionFilter(msg, sub.filter)) {
|
|
1794
|
+
Promise.resolve(sub.callback(msg)).catch((err) => {
|
|
1795
|
+
logger7.warn("Subscription callback error", { id: sub.id, error: err.message });
|
|
1599
1796
|
});
|
|
1600
|
-
return;
|
|
1601
1797
|
}
|
|
1602
|
-
}
|
|
1603
|
-
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
matchesSubscriptionFilter(msg, filter) {
|
|
1801
|
+
if (filter.fromDid) {
|
|
1802
|
+
const froms = Array.isArray(filter.fromDid) ? filter.fromDid : [filter.fromDid];
|
|
1803
|
+
if (!froms.includes(msg.envelope.from)) return false;
|
|
1804
|
+
}
|
|
1805
|
+
if (filter.protocol) {
|
|
1806
|
+
const protos = Array.isArray(filter.protocol) ? filter.protocol : [filter.protocol];
|
|
1807
|
+
if (!protos.includes(msg.envelope.protocol)) return false;
|
|
1808
|
+
}
|
|
1809
|
+
if (filter.type && msg.envelope.type !== filter.type) return false;
|
|
1810
|
+
return true;
|
|
1811
|
+
}
|
|
1812
|
+
// ─── Stats ────────────────────────────────────────────────────────────────
|
|
1813
|
+
async getStats() {
|
|
1814
|
+
const [inboxTotal, inboxUnread, outboxPending, outboxFailed, blocked, allowed] = await Promise.all([
|
|
1815
|
+
this.storage.countMessages("inbound"),
|
|
1816
|
+
this.storage.countMessages("inbound", { unreadOnly: true }),
|
|
1817
|
+
this.storage.countMessages("outbound", { status: "pending" }),
|
|
1818
|
+
this.storage.countMessages("outbound", { status: "failed" }),
|
|
1819
|
+
this.storage.listBlocked().then((l) => l.length),
|
|
1820
|
+
this.storage.listAllowed().then((l) => l.length)
|
|
1821
|
+
]);
|
|
1822
|
+
return {
|
|
1823
|
+
inboxTotal,
|
|
1824
|
+
inboxUnread,
|
|
1825
|
+
outboxPending,
|
|
1826
|
+
outboxFailed,
|
|
1827
|
+
blockedAgents: blocked,
|
|
1828
|
+
allowedAgents: allowed,
|
|
1829
|
+
rateLimitedAgents: 0
|
|
1830
|
+
};
|
|
1831
|
+
}
|
|
1832
|
+
};
|
|
1833
|
+
|
|
1834
|
+
// src/messaging/rate-limiter.ts
|
|
1835
|
+
var TokenBucket = class {
|
|
1836
|
+
tokens;
|
|
1837
|
+
lastRefill;
|
|
1838
|
+
capacity;
|
|
1839
|
+
refillRate;
|
|
1840
|
+
// tokens per ms
|
|
1841
|
+
constructor(config, initialTokens, lastRefill) {
|
|
1842
|
+
this.capacity = config.capacity;
|
|
1843
|
+
this.refillRate = config.refillRate;
|
|
1844
|
+
this.tokens = initialTokens ?? config.capacity;
|
|
1845
|
+
this.lastRefill = lastRefill ?? Date.now();
|
|
1846
|
+
}
|
|
1847
|
+
/** Attempt to consume one token. Returns true if allowed. */
|
|
1848
|
+
consume() {
|
|
1849
|
+
this.refill();
|
|
1850
|
+
if (this.tokens >= 1) {
|
|
1851
|
+
this.tokens -= 1;
|
|
1852
|
+
return true;
|
|
1853
|
+
}
|
|
1854
|
+
return false;
|
|
1855
|
+
}
|
|
1856
|
+
getRemaining() {
|
|
1857
|
+
this.refill();
|
|
1858
|
+
return Math.floor(this.tokens);
|
|
1859
|
+
}
|
|
1860
|
+
/** Milliseconds until at least one token is available */
|
|
1861
|
+
getResetTime() {
|
|
1862
|
+
this.refill();
|
|
1863
|
+
if (this.tokens >= 1) return 0;
|
|
1864
|
+
const needed = 1 - this.tokens;
|
|
1865
|
+
return Math.ceil(needed / this.refillRate);
|
|
1866
|
+
}
|
|
1867
|
+
/** Serialize state for persistence */
|
|
1868
|
+
toState() {
|
|
1869
|
+
return { tokens: this.tokens, lastRefill: this.lastRefill };
|
|
1870
|
+
}
|
|
1871
|
+
refill() {
|
|
1872
|
+
const now = Date.now();
|
|
1873
|
+
const elapsed = now - this.lastRefill;
|
|
1874
|
+
const newTokens = elapsed * this.refillRate;
|
|
1875
|
+
this.tokens = Math.min(this.capacity, this.tokens + newTokens);
|
|
1876
|
+
this.lastRefill = now;
|
|
1877
|
+
}
|
|
1878
|
+
};
|
|
1879
|
+
var DEFAULT_RATE_LIMIT_TIERS = {
|
|
1880
|
+
newAgent: { capacity: 10, refillRate: 10 / (60 * 1e3) },
|
|
1881
|
+
// 10/min, burst 10
|
|
1882
|
+
established: { capacity: 100, refillRate: 60 / (60 * 1e3) },
|
|
1883
|
+
// 60/min, burst 100
|
|
1884
|
+
trusted: { capacity: 1e3, refillRate: 600 / (60 * 1e3) }
|
|
1885
|
+
// 600/min, burst 1000
|
|
1886
|
+
};
|
|
1887
|
+
function getTierConfig(trustScore, tiers) {
|
|
1888
|
+
if (trustScore >= 0.6) return tiers.trusted;
|
|
1889
|
+
if (trustScore >= 0.3) return tiers.established;
|
|
1890
|
+
return tiers.newAgent;
|
|
1891
|
+
}
|
|
1892
|
+
|
|
1893
|
+
// src/messaging/defense.ts
|
|
1894
|
+
var logger8 = createLogger("defense");
|
|
1895
|
+
var DefenseMiddleware = class {
|
|
1896
|
+
trust;
|
|
1897
|
+
storage;
|
|
1898
|
+
minTrustScore;
|
|
1899
|
+
autoBlockThreshold;
|
|
1900
|
+
tiers;
|
|
1901
|
+
seenTtlMs;
|
|
1902
|
+
// In-memory LRU-style seen cache (backed by LevelDB for persistence)
|
|
1903
|
+
seenCache = /* @__PURE__ */ new Map();
|
|
1904
|
+
// id → seenAt
|
|
1905
|
+
MAX_SEEN_CACHE = 1e4;
|
|
1906
|
+
// In-memory token buckets (backed by LevelDB for persistence)
|
|
1907
|
+
buckets = /* @__PURE__ */ new Map();
|
|
1908
|
+
constructor(config) {
|
|
1909
|
+
this.trust = config.trustSystem;
|
|
1910
|
+
this.storage = config.storage;
|
|
1911
|
+
this.minTrustScore = config.minTrustScore ?? 0;
|
|
1912
|
+
this.autoBlockThreshold = config.autoBlockThreshold ?? 0.1;
|
|
1913
|
+
this.tiers = config.rateLimitTiers ?? DEFAULT_RATE_LIMIT_TIERS;
|
|
1914
|
+
this.seenTtlMs = config.seenTtlMs ?? 60 * 60 * 1e3;
|
|
1915
|
+
}
|
|
1916
|
+
/**
|
|
1917
|
+
* Run all defense checks on an incoming message.
|
|
1918
|
+
* Returns { allowed: true } if the message should be processed,
|
|
1919
|
+
* or { allowed: false, reason } if it should be dropped.
|
|
1920
|
+
*/
|
|
1921
|
+
async checkMessage(envelope) {
|
|
1922
|
+
const did = envelope.from;
|
|
1923
|
+
if (await this.isAllowed(did)) {
|
|
1924
|
+
this.markAsSeen(envelope.id);
|
|
1925
|
+
return { allowed: true };
|
|
1926
|
+
}
|
|
1927
|
+
if (await this.isBlocked(did)) {
|
|
1928
|
+
logger8.debug("Message rejected: blocked", { id: envelope.id, from: did });
|
|
1929
|
+
return { allowed: false, reason: "blocked" };
|
|
1930
|
+
}
|
|
1931
|
+
if (this.hasSeen(envelope.id)) {
|
|
1932
|
+
logger8.debug("Message rejected: duplicate", { id: envelope.id });
|
|
1933
|
+
return { allowed: false, reason: "duplicate" };
|
|
1934
|
+
}
|
|
1935
|
+
let trustScore = 0;
|
|
1936
|
+
try {
|
|
1937
|
+
const score = await this.trust.getTrustScore(did);
|
|
1938
|
+
trustScore = score.interactionScore;
|
|
1939
|
+
if (trustScore < this.autoBlockThreshold && trustScore > 0) {
|
|
1940
|
+
logger8.warn("Auto-blocking low-trust agent", { did, trustScore });
|
|
1941
|
+
await this.blockAgent(did, `Auto-blocked: trust score ${trustScore.toFixed(2)} below threshold`);
|
|
1942
|
+
return { allowed: false, reason: "blocked" };
|
|
1943
|
+
}
|
|
1944
|
+
if (trustScore < this.minTrustScore) {
|
|
1945
|
+
logger8.debug("Message rejected: trust too low", { id: envelope.id, trustScore });
|
|
1946
|
+
return { allowed: false, reason: "trust_too_low", trustScore };
|
|
1947
|
+
}
|
|
1948
|
+
} catch (err) {
|
|
1949
|
+
logger8.warn("Trust score lookup failed, using 0", { did, error: err.message });
|
|
1950
|
+
}
|
|
1951
|
+
const rateLimitResult = await this.checkRateLimit(did, trustScore);
|
|
1952
|
+
if (!rateLimitResult.allowed) {
|
|
1953
|
+
logger8.debug("Message rejected: rate limited", {
|
|
1604
1954
|
id: envelope.id,
|
|
1605
|
-
from:
|
|
1606
|
-
|
|
1955
|
+
from: did,
|
|
1956
|
+
resetTime: rateLimitResult.resetTime
|
|
1607
1957
|
});
|
|
1608
|
-
return
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1958
|
+
return {
|
|
1959
|
+
allowed: false,
|
|
1960
|
+
reason: "rate_limited",
|
|
1961
|
+
remainingTokens: rateLimitResult.remaining,
|
|
1962
|
+
resetTime: rateLimitResult.resetTime
|
|
1963
|
+
};
|
|
1964
|
+
}
|
|
1965
|
+
this.markAsSeen(envelope.id);
|
|
1966
|
+
return { allowed: true, trustScore, remainingTokens: rateLimitResult.remaining };
|
|
1967
|
+
}
|
|
1968
|
+
// ─── Blocklist ────────────────────────────────────────────────────────────
|
|
1969
|
+
async blockAgent(did, reason, blockedBy = "local") {
|
|
1970
|
+
await this.storage.putBlock({ did, reason, blockedAt: Date.now(), blockedBy });
|
|
1971
|
+
logger8.info("Agent blocked", { did, reason });
|
|
1972
|
+
}
|
|
1973
|
+
async unblockAgent(did) {
|
|
1974
|
+
await this.storage.deleteBlock(did);
|
|
1975
|
+
logger8.info("Agent unblocked", { did });
|
|
1976
|
+
}
|
|
1977
|
+
async isBlocked(did) {
|
|
1978
|
+
return await this.storage.getBlock(did) !== null;
|
|
1979
|
+
}
|
|
1980
|
+
// ─── Allowlist ────────────────────────────────────────────────────────────
|
|
1981
|
+
async allowAgent(did, note) {
|
|
1982
|
+
await this.storage.putAllow({ did, addedAt: Date.now(), note });
|
|
1983
|
+
logger8.info("Agent allowlisted", { did });
|
|
1984
|
+
}
|
|
1985
|
+
async removeFromAllowlist(did) {
|
|
1986
|
+
await this.storage.deleteAllow(did);
|
|
1987
|
+
logger8.info("Agent removed from allowlist", { did });
|
|
1988
|
+
}
|
|
1989
|
+
async isAllowed(did) {
|
|
1990
|
+
return await this.storage.getAllow(did) !== null;
|
|
1991
|
+
}
|
|
1992
|
+
// ─── Rate Limiting ────────────────────────────────────────────────────────
|
|
1993
|
+
async checkRateLimit(did, trustScore) {
|
|
1994
|
+
const tierConfig = getTierConfig(trustScore, this.tiers);
|
|
1995
|
+
let bucket = this.buckets.get(did);
|
|
1996
|
+
if (!bucket) {
|
|
1997
|
+
const persisted = await this.storage.getRateLimit(did);
|
|
1998
|
+
if (persisted) {
|
|
1999
|
+
bucket = new TokenBucket(tierConfig, persisted.tokens, persisted.lastRefill);
|
|
2000
|
+
} else {
|
|
2001
|
+
bucket = new TokenBucket(tierConfig);
|
|
2002
|
+
}
|
|
2003
|
+
this.buckets.set(did, bucket);
|
|
2004
|
+
}
|
|
2005
|
+
const allowed = bucket.consume();
|
|
2006
|
+
const state = bucket.toState();
|
|
2007
|
+
await this.storage.putRateLimit({
|
|
2008
|
+
did,
|
|
2009
|
+
tokens: state.tokens,
|
|
2010
|
+
lastRefill: state.lastRefill,
|
|
2011
|
+
totalRequests: 0,
|
|
2012
|
+
firstSeen: Date.now()
|
|
1616
2013
|
});
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
size: encoded.length
|
|
1633
|
-
});
|
|
1634
|
-
await stream.sink(
|
|
1635
|
-
(async function* () {
|
|
1636
|
-
yield encoded;
|
|
1637
|
-
})()
|
|
1638
|
-
);
|
|
2014
|
+
return {
|
|
2015
|
+
allowed,
|
|
2016
|
+
remaining: bucket.getRemaining(),
|
|
2017
|
+
resetTime: bucket.getResetTime(),
|
|
2018
|
+
limit: tierConfig.capacity
|
|
2019
|
+
};
|
|
2020
|
+
}
|
|
2021
|
+
// ─── Seen Cache (deduplication) ───────────────────────────────────────────
|
|
2022
|
+
hasSeen(messageId) {
|
|
2023
|
+
return this.seenCache.has(messageId);
|
|
2024
|
+
}
|
|
2025
|
+
markAsSeen(messageId) {
|
|
2026
|
+
if (this.seenCache.size >= this.MAX_SEEN_CACHE) {
|
|
2027
|
+
const firstKey = this.seenCache.keys().next().value;
|
|
2028
|
+
if (firstKey) this.seenCache.delete(firstKey);
|
|
1639
2029
|
}
|
|
1640
|
-
|
|
1641
|
-
|
|
2030
|
+
this.seenCache.set(messageId, Date.now());
|
|
2031
|
+
this.storage.putSeen({ messageId, seenAt: Date.now(), fromDid: "" }).catch(() => {
|
|
2032
|
+
});
|
|
1642
2033
|
}
|
|
1643
|
-
|
|
2034
|
+
/** Periodic cleanup of expired seen entries */
|
|
2035
|
+
async cleanupSeen() {
|
|
2036
|
+
const cutoff = Date.now() - this.seenTtlMs;
|
|
2037
|
+
for (const [id, seenAt] of this.seenCache) {
|
|
2038
|
+
if (seenAt < cutoff) this.seenCache.delete(id);
|
|
2039
|
+
}
|
|
2040
|
+
await this.storage.cleanupSeen(this.seenTtlMs);
|
|
2041
|
+
}
|
|
2042
|
+
/** Periodic cleanup of stale rate limit buckets (24h inactive) */
|
|
2043
|
+
async cleanupRateLimits() {
|
|
2044
|
+
const staleMs = 24 * 60 * 60 * 1e3;
|
|
2045
|
+
const cutoff = Date.now() - staleMs;
|
|
2046
|
+
for (const [did, bucket] of this.buckets) {
|
|
2047
|
+
if (bucket.toState().lastRefill < cutoff) this.buckets.delete(did);
|
|
2048
|
+
}
|
|
2049
|
+
await this.storage.cleanupRateLimits(staleMs);
|
|
2050
|
+
}
|
|
2051
|
+
};
|
|
1644
2052
|
|
|
1645
2053
|
// src/trust/trust-score.ts
|
|
1646
2054
|
var TrustMetrics = class {
|
|
@@ -1710,7 +2118,7 @@ function createDefaultTrustScore() {
|
|
|
1710
2118
|
lastUpdated: Date.now()
|
|
1711
2119
|
};
|
|
1712
2120
|
}
|
|
1713
|
-
var
|
|
2121
|
+
var logger9 = createLogger("interaction-history");
|
|
1714
2122
|
var InteractionHistory = class {
|
|
1715
2123
|
db;
|
|
1716
2124
|
constructor(dbPath) {
|
|
@@ -1721,14 +2129,14 @@ var InteractionHistory = class {
|
|
|
1721
2129
|
*/
|
|
1722
2130
|
async open() {
|
|
1723
2131
|
await this.db.open();
|
|
1724
|
-
|
|
2132
|
+
logger9.info("Interaction history database opened", { path: this.db.location });
|
|
1725
2133
|
}
|
|
1726
2134
|
/**
|
|
1727
2135
|
* Close database connection
|
|
1728
2136
|
*/
|
|
1729
2137
|
async close() {
|
|
1730
2138
|
await this.db.close();
|
|
1731
|
-
|
|
2139
|
+
logger9.info("Interaction history database closed");
|
|
1732
2140
|
}
|
|
1733
2141
|
/**
|
|
1734
2142
|
* Record an interaction
|
|
@@ -1736,7 +2144,7 @@ var InteractionHistory = class {
|
|
|
1736
2144
|
async record(interaction) {
|
|
1737
2145
|
const key = `interaction:${interaction.agentDid}:${interaction.timestamp}`;
|
|
1738
2146
|
await this.db.put(key, interaction);
|
|
1739
|
-
|
|
2147
|
+
logger9.debug("Recorded interaction", { agentDid: interaction.agentDid, type: interaction.type });
|
|
1740
2148
|
}
|
|
1741
2149
|
/**
|
|
1742
2150
|
* Get interaction history for an agent
|
|
@@ -1755,7 +2163,7 @@ var InteractionHistory = class {
|
|
|
1755
2163
|
interactions.push(value);
|
|
1756
2164
|
}
|
|
1757
2165
|
} catch (error) {
|
|
1758
|
-
|
|
2166
|
+
logger9.error("Failed to get interaction history", { agentDid, error });
|
|
1759
2167
|
}
|
|
1760
2168
|
return interactions;
|
|
1761
2169
|
}
|
|
@@ -1794,7 +2202,7 @@ var InteractionHistory = class {
|
|
|
1794
2202
|
}
|
|
1795
2203
|
}
|
|
1796
2204
|
} catch (error) {
|
|
1797
|
-
|
|
2205
|
+
logger9.error("Failed to get all agents", { error });
|
|
1798
2206
|
}
|
|
1799
2207
|
return Array.from(agents);
|
|
1800
2208
|
}
|
|
@@ -1811,7 +2219,7 @@ var InteractionHistory = class {
|
|
|
1811
2219
|
keysToDelete.push(key);
|
|
1812
2220
|
}
|
|
1813
2221
|
await this.db.batch(keysToDelete.map((key) => ({ type: "del", key })));
|
|
1814
|
-
|
|
2222
|
+
logger9.info("Deleted interaction history", { agentDid, count: keysToDelete.length });
|
|
1815
2223
|
}
|
|
1816
2224
|
/**
|
|
1817
2225
|
* Clean up old interactions (older than 90 days)
|
|
@@ -1826,14 +2234,14 @@ var InteractionHistory = class {
|
|
|
1826
2234
|
}
|
|
1827
2235
|
if (keysToDelete.length > 0) {
|
|
1828
2236
|
await this.db.batch(keysToDelete.map((key) => ({ type: "del", key })));
|
|
1829
|
-
|
|
2237
|
+
logger9.info("Cleaned up old interactions", { count: keysToDelete.length });
|
|
1830
2238
|
}
|
|
1831
2239
|
return keysToDelete.length;
|
|
1832
2240
|
}
|
|
1833
2241
|
};
|
|
1834
2242
|
|
|
1835
2243
|
// src/trust/endorsement.ts
|
|
1836
|
-
var
|
|
2244
|
+
var logger10 = createLogger("endorsement");
|
|
1837
2245
|
var EndorsementManager = class {
|
|
1838
2246
|
constructor(db, getPublicKey) {
|
|
1839
2247
|
this.db = db;
|
|
@@ -1860,7 +2268,7 @@ var EndorsementManager = class {
|
|
|
1860
2268
|
...endorsement,
|
|
1861
2269
|
signature
|
|
1862
2270
|
};
|
|
1863
|
-
|
|
2271
|
+
logger10.info("Created endorsement", { from: fromDid, to: toDid, score });
|
|
1864
2272
|
return signedEndorsement;
|
|
1865
2273
|
}
|
|
1866
2274
|
/**
|
|
@@ -1874,7 +2282,7 @@ var EndorsementManager = class {
|
|
|
1874
2282
|
const publicKey = await this.getPublicKey(endorsement.from);
|
|
1875
2283
|
return await verifyFn(signatureBytes, data, publicKey);
|
|
1876
2284
|
} catch (error) {
|
|
1877
|
-
|
|
2285
|
+
logger10.error("Failed to verify endorsement", { error });
|
|
1878
2286
|
return false;
|
|
1879
2287
|
}
|
|
1880
2288
|
}
|
|
@@ -1884,7 +2292,7 @@ var EndorsementManager = class {
|
|
|
1884
2292
|
async publish(endorsement) {
|
|
1885
2293
|
const key = `endorsement:${endorsement.to}:${endorsement.from}`;
|
|
1886
2294
|
await this.db.put(key, endorsement);
|
|
1887
|
-
|
|
2295
|
+
logger10.info("Published endorsement", { from: endorsement.from, to: endorsement.to });
|
|
1888
2296
|
}
|
|
1889
2297
|
/**
|
|
1890
2298
|
* Get all endorsements for an agent
|
|
@@ -1900,7 +2308,7 @@ var EndorsementManager = class {
|
|
|
1900
2308
|
endorsements.push(value);
|
|
1901
2309
|
}
|
|
1902
2310
|
} catch (error) {
|
|
1903
|
-
|
|
2311
|
+
logger10.error("Failed to get endorsements", { agentDid, error });
|
|
1904
2312
|
}
|
|
1905
2313
|
return endorsements;
|
|
1906
2314
|
}
|
|
@@ -1916,7 +2324,7 @@ var EndorsementManager = class {
|
|
|
1916
2324
|
}
|
|
1917
2325
|
}
|
|
1918
2326
|
} catch (error) {
|
|
1919
|
-
|
|
2327
|
+
logger10.error("Failed to get endorsements by agent", { fromDid, error });
|
|
1920
2328
|
}
|
|
1921
2329
|
return endorsements;
|
|
1922
2330
|
}
|
|
@@ -1937,10 +2345,10 @@ var EndorsementManager = class {
|
|
|
1937
2345
|
async deleteEndorsement(fromDid, toDid) {
|
|
1938
2346
|
const key = `endorsement:${toDid}:${fromDid}`;
|
|
1939
2347
|
await this.db.del(key);
|
|
1940
|
-
|
|
2348
|
+
logger10.info("Deleted endorsement", { from: fromDid, to: toDid });
|
|
1941
2349
|
}
|
|
1942
2350
|
};
|
|
1943
|
-
var
|
|
2351
|
+
var logger11 = createLogger("sybil-defense");
|
|
1944
2352
|
var SybilDefense = class {
|
|
1945
2353
|
rateLimits = /* @__PURE__ */ new Map();
|
|
1946
2354
|
peerFirstSeen = /* @__PURE__ */ new Map();
|
|
@@ -1970,7 +2378,7 @@ var SybilDefense = class {
|
|
|
1970
2378
|
verifyChallenge(solution) {
|
|
1971
2379
|
const { challenge, solution: solutionNonce } = solution;
|
|
1972
2380
|
if (Date.now() - challenge.timestamp > 60 * 60 * 1e3) {
|
|
1973
|
-
|
|
2381
|
+
logger11.warn("Challenge expired", { did: challenge.did });
|
|
1974
2382
|
return false;
|
|
1975
2383
|
}
|
|
1976
2384
|
const data = `${challenge.did}:${challenge.nonce}:${solutionNonce}`;
|
|
@@ -1978,9 +2386,9 @@ var SybilDefense = class {
|
|
|
1978
2386
|
const leadingZeros = this.countLeadingZeroBits(hash);
|
|
1979
2387
|
const valid = leadingZeros >= challenge.difficulty;
|
|
1980
2388
|
if (valid) {
|
|
1981
|
-
|
|
2389
|
+
logger11.info("Challenge verified", { did: challenge.did, leadingZeros });
|
|
1982
2390
|
} else {
|
|
1983
|
-
|
|
2391
|
+
logger11.warn("Challenge failed", { did: challenge.did, leadingZeros, required: challenge.difficulty });
|
|
1984
2392
|
}
|
|
1985
2393
|
return valid;
|
|
1986
2394
|
}
|
|
@@ -2020,7 +2428,7 @@ var SybilDefense = class {
|
|
|
2020
2428
|
record.requests = record.requests.filter(
|
|
2021
2429
|
(t) => now - t < this.RATE_LIMIT_WINDOW
|
|
2022
2430
|
);
|
|
2023
|
-
|
|
2431
|
+
logger11.debug("Recorded request", { did, count: record.requests.length });
|
|
2024
2432
|
}
|
|
2025
2433
|
/**
|
|
2026
2434
|
* Get peer trust level based on age
|
|
@@ -2045,7 +2453,7 @@ var SybilDefense = class {
|
|
|
2045
2453
|
recordPeerSeen(peerId) {
|
|
2046
2454
|
if (!this.peerFirstSeen.has(peerId)) {
|
|
2047
2455
|
this.peerFirstSeen.set(peerId, Date.now());
|
|
2048
|
-
|
|
2456
|
+
logger11.debug("Recorded new peer", { peerId });
|
|
2049
2457
|
}
|
|
2050
2458
|
}
|
|
2051
2459
|
/**
|
|
@@ -2059,7 +2467,7 @@ var SybilDefense = class {
|
|
|
2059
2467
|
this.rateLimits.delete(did);
|
|
2060
2468
|
}
|
|
2061
2469
|
}
|
|
2062
|
-
|
|
2470
|
+
logger11.info("Cleaned up Sybil defense records", {
|
|
2063
2471
|
rateLimits: this.rateLimits.size,
|
|
2064
2472
|
peers: this.peerFirstSeen.size
|
|
2065
2473
|
});
|
|
@@ -2092,7 +2500,7 @@ var SybilDefense = class {
|
|
|
2092
2500
|
return count;
|
|
2093
2501
|
}
|
|
2094
2502
|
};
|
|
2095
|
-
var
|
|
2503
|
+
var logger12 = createLogger("trust-system");
|
|
2096
2504
|
var TrustSystem = class {
|
|
2097
2505
|
metrics;
|
|
2098
2506
|
history;
|
|
@@ -2115,14 +2523,14 @@ var TrustSystem = class {
|
|
|
2115
2523
|
*/
|
|
2116
2524
|
async start() {
|
|
2117
2525
|
await this.history.open();
|
|
2118
|
-
|
|
2526
|
+
logger12.info("Trust system started");
|
|
2119
2527
|
}
|
|
2120
2528
|
/**
|
|
2121
2529
|
* Shutdown the trust system
|
|
2122
2530
|
*/
|
|
2123
2531
|
async stop() {
|
|
2124
2532
|
await this.history.close();
|
|
2125
|
-
|
|
2533
|
+
logger12.info("Trust system stopped");
|
|
2126
2534
|
}
|
|
2127
2535
|
/**
|
|
2128
2536
|
* Record an interaction
|
|
@@ -2211,13 +2619,13 @@ var TrustSystem = class {
|
|
|
2211
2619
|
await this.history.cleanup();
|
|
2212
2620
|
this.sybilDefense.cleanup();
|
|
2213
2621
|
this.trustCache.clear();
|
|
2214
|
-
|
|
2622
|
+
logger12.info("Trust system cleanup completed");
|
|
2215
2623
|
}
|
|
2216
2624
|
};
|
|
2217
2625
|
function createTrustSystem(config) {
|
|
2218
2626
|
return new TrustSystem(config);
|
|
2219
2627
|
}
|
|
2220
2628
|
|
|
2221
|
-
export { CLAWIVERSE_CONTEXT, CapabilityMatcher, CapabilityTypes, ClawiverseError, DiscoveryError, EndorsementManager, IdentityError, InteractionHistory, LogLevel, Logger, MessagingError, ParameterTypes, SCHEMA_ORG_CONTEXT, SearchIndex, SemanticSearchEngine, SybilDefense, TransportError, TrustMetrics, TrustSystem, clawiverseContext, createAgentCard,
|
|
2629
|
+
export { CLAWIVERSE_CONTEXT, CapabilityMatcher, CapabilityTypes, ClawiverseError, DEFAULT_RATE_LIMIT_TIERS, DefenseMiddleware, DiscoveryError, EndorsementManager, IdentityError, InteractionHistory, LogLevel, Logger, MessageQueue, MessageStorage, MessagingError, ParameterTypes, RELAY_PROTOCOL_VERSION, SCHEMA_ORG_CONTEXT, SearchIndex, SemanticSearchEngine, SybilDefense, TokenBucket, TransportError, TrustMetrics, TrustSystem, clawiverseContext, createAgentCard, createDefaultTrustScore, createEnvelope, createLegacyAgentCard, createLogger, createMessageRouter, createRelayClient, createRelayIndexOperations, createSemanticSearch, createTrustSystem, decodeAgentCard, decodeFromCBOR, decodeFromJSON, decodeMessage, decodeMessageJSON, deriveDID, downgradeToLegacyCard, encodeForDHT, encodeForWeb, encodeMessage, encodeMessageJSON, exportKeyPair, extractPublicKey, generateKeyPair, getAgentCardContext, getEncodedSize, getTierConfig, importKeyPair, isLegacyCard, isValidContext, matchesCapability, sign, signAgentCard, signEnvelope, signMessage, upgradeLegacyCard, validateAgentCard, validateDID, validateEnvelope, verify, verifyAgentCard, verifyEnvelope, verifyMessage };
|
|
2222
2630
|
//# sourceMappingURL=index.js.map
|
|
2223
2631
|
//# sourceMappingURL=index.js.map
|