@highway1/core 0.1.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/index.d.ts +920 -0
- package/dist/index.js +1876 -0
- package/dist/index.js.map +1 -0
- package/package.json +40 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1876 @@
|
|
|
1
|
+
import * as ed25519 from '@noble/ed25519';
|
|
2
|
+
import { base58btc } from 'multiformats/bases/base58';
|
|
3
|
+
import { createLibp2p } from 'libp2p';
|
|
4
|
+
import { tcp } from '@libp2p/tcp';
|
|
5
|
+
import { noise } from '@chainsafe/libp2p-noise';
|
|
6
|
+
import { mplex } from '@libp2p/mplex';
|
|
7
|
+
import { kadDHT, passthroughMapper, removePrivateAddressesMapper } 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';
|
|
12
|
+
import Ajv from 'ajv';
|
|
13
|
+
import { encode, decode } from 'cbor-x';
|
|
14
|
+
import { fromString } from 'uint8arrays/from-string';
|
|
15
|
+
import lunr from 'lunr';
|
|
16
|
+
import Fuse from 'fuse.js';
|
|
17
|
+
import { peerIdFromString } from '@libp2p/peer-id';
|
|
18
|
+
import { multiaddr } from '@multiformats/multiaddr';
|
|
19
|
+
import { Level } from 'level';
|
|
20
|
+
import { sha256 } from '@noble/hashes/sha256';
|
|
21
|
+
import { bytesToHex } from '@noble/hashes/utils';
|
|
22
|
+
|
|
23
|
+
// src/identity/keys.ts
|
|
24
|
+
|
|
25
|
+
// src/utils/errors.ts
|
|
26
|
+
var ClawiverseError = class extends Error {
|
|
27
|
+
constructor(message, code, details) {
|
|
28
|
+
super(message);
|
|
29
|
+
this.code = code;
|
|
30
|
+
this.details = details;
|
|
31
|
+
this.name = "ClawiverseError";
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
var IdentityError = class extends ClawiverseError {
|
|
35
|
+
constructor(message, details) {
|
|
36
|
+
super(message, "IDENTITY_ERROR", details);
|
|
37
|
+
this.name = "IdentityError";
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
var TransportError = class extends ClawiverseError {
|
|
41
|
+
constructor(message, details) {
|
|
42
|
+
super(message, "TRANSPORT_ERROR", details);
|
|
43
|
+
this.name = "TransportError";
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
var DiscoveryError = class extends ClawiverseError {
|
|
47
|
+
constructor(message, details) {
|
|
48
|
+
super(message, "DISCOVERY_ERROR", details);
|
|
49
|
+
this.name = "DiscoveryError";
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
var MessagingError = class extends ClawiverseError {
|
|
53
|
+
constructor(message, details) {
|
|
54
|
+
super(message, "MESSAGING_ERROR", details);
|
|
55
|
+
this.name = "MessagingError";
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// src/identity/keys.ts
|
|
60
|
+
async function generateKeyPair() {
|
|
61
|
+
try {
|
|
62
|
+
const privateKey = ed25519.utils.randomPrivateKey();
|
|
63
|
+
const publicKey = await ed25519.getPublicKeyAsync(privateKey);
|
|
64
|
+
return {
|
|
65
|
+
publicKey,
|
|
66
|
+
privateKey
|
|
67
|
+
};
|
|
68
|
+
} catch (error) {
|
|
69
|
+
throw new IdentityError("Failed to generate key pair", error);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
async function sign(message, privateKey) {
|
|
73
|
+
try {
|
|
74
|
+
return await ed25519.signAsync(message, privateKey);
|
|
75
|
+
} catch (error) {
|
|
76
|
+
throw new IdentityError("Failed to sign message", error);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
async function verify(signature, message, publicKey) {
|
|
80
|
+
try {
|
|
81
|
+
return await ed25519.verifyAsync(signature, message, publicKey);
|
|
82
|
+
} catch (error) {
|
|
83
|
+
throw new IdentityError("Failed to verify signature", error);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function exportKeyPair(keyPair) {
|
|
87
|
+
return {
|
|
88
|
+
publicKey: Buffer.from(keyPair.publicKey).toString("hex"),
|
|
89
|
+
privateKey: Buffer.from(keyPair.privateKey).toString("hex")
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
function importKeyPair(exported) {
|
|
93
|
+
return {
|
|
94
|
+
publicKey: new Uint8Array(Buffer.from(exported.publicKey, "hex")),
|
|
95
|
+
privateKey: new Uint8Array(Buffer.from(exported.privateKey, "hex"))
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function deriveDID(publicKey) {
|
|
99
|
+
try {
|
|
100
|
+
const encoded = base58btc.encode(publicKey);
|
|
101
|
+
return `did:clawiverse:${encoded}`;
|
|
102
|
+
} catch (error) {
|
|
103
|
+
throw new IdentityError("Failed to derive DID", error);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function extractPublicKey(did) {
|
|
107
|
+
if (!did.startsWith("did:clawiverse:")) {
|
|
108
|
+
throw new IdentityError("Invalid DID format: must start with did:clawiverse:");
|
|
109
|
+
}
|
|
110
|
+
try {
|
|
111
|
+
const encoded = did.replace("did:clawiverse:", "");
|
|
112
|
+
return base58btc.decode(encoded);
|
|
113
|
+
} catch (error) {
|
|
114
|
+
throw new IdentityError("Failed to extract public key from DID", error);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
function validateDID(did) {
|
|
118
|
+
if (!did.startsWith("did:clawiverse:")) {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
try {
|
|
122
|
+
const encoded = did.replace("did:clawiverse:", "");
|
|
123
|
+
base58btc.decode(encoded);
|
|
124
|
+
return true;
|
|
125
|
+
} catch {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// src/identity/signer.ts
|
|
131
|
+
async function signMessage(payload, privateKey, publicKey) {
|
|
132
|
+
try {
|
|
133
|
+
const signature = await sign(payload, privateKey);
|
|
134
|
+
const signer = deriveDID(publicKey);
|
|
135
|
+
return {
|
|
136
|
+
payload,
|
|
137
|
+
signature,
|
|
138
|
+
signer
|
|
139
|
+
};
|
|
140
|
+
} catch (error) {
|
|
141
|
+
throw new IdentityError("Failed to sign message", error);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
async function verifyMessage(signedMessage, expectedPublicKey) {
|
|
145
|
+
try {
|
|
146
|
+
const expectedDID = deriveDID(expectedPublicKey);
|
|
147
|
+
if (signedMessage.signer !== expectedDID) {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
return await verify(
|
|
151
|
+
signedMessage.signature,
|
|
152
|
+
signedMessage.payload,
|
|
153
|
+
expectedPublicKey
|
|
154
|
+
);
|
|
155
|
+
} catch (error) {
|
|
156
|
+
throw new IdentityError("Failed to verify message", error);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// src/utils/logger.ts
|
|
161
|
+
var LogLevel = /* @__PURE__ */ ((LogLevel2) => {
|
|
162
|
+
LogLevel2[LogLevel2["DEBUG"] = 0] = "DEBUG";
|
|
163
|
+
LogLevel2[LogLevel2["INFO"] = 1] = "INFO";
|
|
164
|
+
LogLevel2[LogLevel2["WARN"] = 2] = "WARN";
|
|
165
|
+
LogLevel2[LogLevel2["ERROR"] = 3] = "ERROR";
|
|
166
|
+
return LogLevel2;
|
|
167
|
+
})(LogLevel || {});
|
|
168
|
+
var Logger = class {
|
|
169
|
+
level;
|
|
170
|
+
prefix;
|
|
171
|
+
constructor(prefix, level = 1 /* INFO */) {
|
|
172
|
+
this.prefix = prefix;
|
|
173
|
+
this.level = level;
|
|
174
|
+
}
|
|
175
|
+
setLevel(level) {
|
|
176
|
+
this.level = level;
|
|
177
|
+
}
|
|
178
|
+
debug(message, ...args) {
|
|
179
|
+
if (this.level <= 0 /* DEBUG */) {
|
|
180
|
+
console.debug(`[${this.prefix}] DEBUG:`, message, ...args);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
info(message, ...args) {
|
|
184
|
+
if (this.level <= 1 /* INFO */) {
|
|
185
|
+
console.info(`[${this.prefix}] INFO:`, message, ...args);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
warn(message, ...args) {
|
|
189
|
+
if (this.level <= 2 /* WARN */) {
|
|
190
|
+
console.warn(`[${this.prefix}] WARN:`, message, ...args);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
error(message, ...args) {
|
|
194
|
+
if (this.level <= 3 /* ERROR */) {
|
|
195
|
+
console.error(`[${this.prefix}] ERROR:`, message, ...args);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
var createLogger = (prefix, level) => {
|
|
200
|
+
return new Logger(prefix, level);
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
// src/transport/node.ts
|
|
204
|
+
var logger = createLogger("transport");
|
|
205
|
+
async function createNode(config) {
|
|
206
|
+
try {
|
|
207
|
+
const {
|
|
208
|
+
listenAddresses = ["/ip4/0.0.0.0/tcp/0"],
|
|
209
|
+
bootstrapPeers = [],
|
|
210
|
+
enableDHT = true,
|
|
211
|
+
allowPrivateAddresses = false,
|
|
212
|
+
enableRelay = false,
|
|
213
|
+
privateKey
|
|
214
|
+
} = config;
|
|
215
|
+
const libp2pConfig = {
|
|
216
|
+
addresses: {
|
|
217
|
+
listen: listenAddresses
|
|
218
|
+
},
|
|
219
|
+
...privateKey ? { privateKey } : {},
|
|
220
|
+
transports: [
|
|
221
|
+
tcp(),
|
|
222
|
+
circuitRelayTransport()
|
|
223
|
+
],
|
|
224
|
+
connectionEncrypters: [noise()],
|
|
225
|
+
streamMuxers: [mplex()],
|
|
226
|
+
services: {
|
|
227
|
+
identify: identify(),
|
|
228
|
+
ping: ping()
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
if (enableRelay) {
|
|
232
|
+
libp2pConfig.services.relay = circuitRelayServer();
|
|
233
|
+
}
|
|
234
|
+
if (enableDHT) {
|
|
235
|
+
libp2pConfig.services.dht = kadDHT({
|
|
236
|
+
clientMode: false,
|
|
237
|
+
peerInfoMapper: allowPrivateAddresses ? passthroughMapper : removePrivateAddressesMapper,
|
|
238
|
+
validators: {
|
|
239
|
+
clawiverse: async () => {
|
|
240
|
+
}
|
|
241
|
+
},
|
|
242
|
+
selectors: {
|
|
243
|
+
clawiverse: () => 0
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
if (bootstrapPeers.length > 0) {
|
|
248
|
+
libp2pConfig.peerDiscovery = [
|
|
249
|
+
bootstrap({
|
|
250
|
+
list: bootstrapPeers
|
|
251
|
+
})
|
|
252
|
+
];
|
|
253
|
+
}
|
|
254
|
+
const libp2p = await createLibp2p(libp2pConfig);
|
|
255
|
+
logger.info("Libp2p node created");
|
|
256
|
+
return {
|
|
257
|
+
libp2p,
|
|
258
|
+
start: async () => {
|
|
259
|
+
await libp2p.start();
|
|
260
|
+
logger.info("Node started", {
|
|
261
|
+
peerId: libp2p.peerId.toString(),
|
|
262
|
+
addresses: libp2p.getMultiaddrs().map((ma) => ma.toString()),
|
|
263
|
+
relay: enableRelay
|
|
264
|
+
});
|
|
265
|
+
},
|
|
266
|
+
stop: async () => {
|
|
267
|
+
await libp2p.stop();
|
|
268
|
+
logger.info("Node stopped");
|
|
269
|
+
},
|
|
270
|
+
getMultiaddrs: () => {
|
|
271
|
+
return libp2p.getMultiaddrs().map((ma) => ma.toString());
|
|
272
|
+
},
|
|
273
|
+
getPeerId: () => {
|
|
274
|
+
return libp2p.peerId.toString();
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
} catch (error) {
|
|
278
|
+
throw new TransportError("Failed to create transport node", error);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// src/discovery/agent-card-types.ts
|
|
283
|
+
function isLegacyCard(card) {
|
|
284
|
+
return Array.isArray(card.capabilities) && card.capabilities.length > 0 && typeof card.capabilities[0] === "string";
|
|
285
|
+
}
|
|
286
|
+
function upgradeLegacyCard(legacy) {
|
|
287
|
+
return {
|
|
288
|
+
...legacy,
|
|
289
|
+
capabilities: legacy.capabilities.map((cap) => ({
|
|
290
|
+
id: cap,
|
|
291
|
+
name: cap,
|
|
292
|
+
description: `Capability: ${cap}`
|
|
293
|
+
}))
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
function downgradeToLegacyCard(card) {
|
|
297
|
+
const { "@context": _, trust: __, ...rest } = card;
|
|
298
|
+
return {
|
|
299
|
+
...rest,
|
|
300
|
+
capabilities: card.capabilities.map((cap) => cap.id)
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// src/discovery/agent-card.ts
|
|
305
|
+
var legacyAgentCardSchema = {
|
|
306
|
+
type: "object",
|
|
307
|
+
properties: {
|
|
308
|
+
did: { type: "string", pattern: "^did:clawiverse:[1-9A-HJ-NP-Za-km-z]+$" },
|
|
309
|
+
name: { type: "string", minLength: 1, maxLength: 100 },
|
|
310
|
+
description: { type: "string", maxLength: 500 },
|
|
311
|
+
version: { type: "string", pattern: "^\\d+\\.\\d+\\.\\d+$" },
|
|
312
|
+
capabilities: {
|
|
313
|
+
type: "array",
|
|
314
|
+
items: { type: "string" },
|
|
315
|
+
minItems: 0,
|
|
316
|
+
maxItems: 50
|
|
317
|
+
},
|
|
318
|
+
endpoints: {
|
|
319
|
+
type: "array",
|
|
320
|
+
items: { type: "string" },
|
|
321
|
+
minItems: 0,
|
|
322
|
+
maxItems: 10
|
|
323
|
+
},
|
|
324
|
+
peerId: { type: "string", nullable: true },
|
|
325
|
+
metadata: {
|
|
326
|
+
type: "object",
|
|
327
|
+
nullable: true,
|
|
328
|
+
required: []
|
|
329
|
+
},
|
|
330
|
+
timestamp: { type: "number" }
|
|
331
|
+
},
|
|
332
|
+
required: ["did", "name", "description", "version", "capabilities", "endpoints", "timestamp"],
|
|
333
|
+
additionalProperties: false
|
|
334
|
+
};
|
|
335
|
+
var ajv = new Ajv();
|
|
336
|
+
var validateLegacySchema = ajv.compile(legacyAgentCardSchema);
|
|
337
|
+
function createAgentCard(did, name, description, capabilities, endpoints = [], peerId, metadata) {
|
|
338
|
+
return {
|
|
339
|
+
did,
|
|
340
|
+
name,
|
|
341
|
+
description,
|
|
342
|
+
version: "1.0.0",
|
|
343
|
+
capabilities,
|
|
344
|
+
endpoints,
|
|
345
|
+
peerId,
|
|
346
|
+
metadata,
|
|
347
|
+
timestamp: Date.now()
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
function createLegacyAgentCard(did, name, description, capabilities, endpoints = [], peerId, metadata) {
|
|
351
|
+
return {
|
|
352
|
+
did,
|
|
353
|
+
name,
|
|
354
|
+
description,
|
|
355
|
+
version: "1.0.0",
|
|
356
|
+
capabilities,
|
|
357
|
+
endpoints,
|
|
358
|
+
peerId,
|
|
359
|
+
metadata,
|
|
360
|
+
timestamp: Date.now()
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
function validateAgentCard(card) {
|
|
364
|
+
if (typeof card !== "object" || card === null) {
|
|
365
|
+
return false;
|
|
366
|
+
}
|
|
367
|
+
const c = card;
|
|
368
|
+
if (typeof c.signature !== "string") {
|
|
369
|
+
return false;
|
|
370
|
+
}
|
|
371
|
+
if (isLegacyCard(c)) {
|
|
372
|
+
return validateLegacySchema(c);
|
|
373
|
+
}
|
|
374
|
+
return typeof c.did === "string" && typeof c.name === "string" && typeof c.description === "string" && typeof c.version === "string" && Array.isArray(c.capabilities) && Array.isArray(c.endpoints) && typeof c.timestamp === "number";
|
|
375
|
+
}
|
|
376
|
+
async function signAgentCard(card, signFn) {
|
|
377
|
+
try {
|
|
378
|
+
const cardJson = JSON.stringify(card);
|
|
379
|
+
const cardBytes = new TextEncoder().encode(cardJson);
|
|
380
|
+
const signature = await signFn(cardBytes);
|
|
381
|
+
return {
|
|
382
|
+
...card,
|
|
383
|
+
signature: Buffer.from(signature).toString("hex")
|
|
384
|
+
};
|
|
385
|
+
} catch (error) {
|
|
386
|
+
throw new DiscoveryError("Failed to sign Agent Card", error);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
async function verifyAgentCard(card, verifyFn) {
|
|
390
|
+
try {
|
|
391
|
+
const { signature, ...cardWithoutSig } = card;
|
|
392
|
+
const cardJson = JSON.stringify(cardWithoutSig);
|
|
393
|
+
const cardBytes = new TextEncoder().encode(cardJson);
|
|
394
|
+
const signatureBytes = Buffer.from(signature, "hex");
|
|
395
|
+
return await verifyFn(signatureBytes, cardBytes);
|
|
396
|
+
} catch (error) {
|
|
397
|
+
throw new DiscoveryError("Failed to verify Agent Card", error);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
function matchesCapability(card, capability) {
|
|
401
|
+
if (isLegacyCard(card)) {
|
|
402
|
+
return card.capabilities.some(
|
|
403
|
+
(cap) => cap.toLowerCase().includes(capability.toLowerCase())
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
return card.capabilities.some(
|
|
407
|
+
(cap) => cap.id.toLowerCase().includes(capability.toLowerCase()) || cap.name.toLowerCase().includes(capability.toLowerCase()) || cap.description.toLowerCase().includes(capability.toLowerCase())
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// src/discovery/agent-card-schema.ts
|
|
412
|
+
var CLAWIVERSE_CONTEXT = "https://clawiverse.org/context/v1";
|
|
413
|
+
var SCHEMA_ORG_CONTEXT = "https://schema.org";
|
|
414
|
+
var clawiverseContext = {
|
|
415
|
+
"@context": {
|
|
416
|
+
"@vocab": CLAWIVERSE_CONTEXT,
|
|
417
|
+
"schema": SCHEMA_ORG_CONTEXT,
|
|
418
|
+
"AgentCard": "schema:SoftwareApplication",
|
|
419
|
+
"Capability": "schema:Action",
|
|
420
|
+
"did": "@id",
|
|
421
|
+
"name": "schema:name",
|
|
422
|
+
"description": "schema:description",
|
|
423
|
+
"version": "schema:softwareVersion",
|
|
424
|
+
"capabilities": {
|
|
425
|
+
"@id": "schema:potentialAction",
|
|
426
|
+
"@type": "@id",
|
|
427
|
+
"@container": "@list"
|
|
428
|
+
},
|
|
429
|
+
"endpoints": {
|
|
430
|
+
"@id": "schema:url",
|
|
431
|
+
"@container": "@list"
|
|
432
|
+
},
|
|
433
|
+
"peerId": "clawiverse:peerId",
|
|
434
|
+
"trust": "clawiverse:trustScore",
|
|
435
|
+
"metadata": "schema:additionalProperty",
|
|
436
|
+
"timestamp": "schema:dateModified",
|
|
437
|
+
"signature": "clawiverse:signature",
|
|
438
|
+
"parameters": {
|
|
439
|
+
"@id": "schema:object",
|
|
440
|
+
"@container": "@list"
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
};
|
|
444
|
+
var CapabilityTypes = {
|
|
445
|
+
TRANSLATION: "TranslationService",
|
|
446
|
+
CODE_REVIEW: "CodeReviewService",
|
|
447
|
+
DATA_ANALYSIS: "DataAnalysisService",
|
|
448
|
+
TEXT_GENERATION: "TextGenerationService",
|
|
449
|
+
IMAGE_GENERATION: "ImageGenerationService",
|
|
450
|
+
SEARCH: "SearchService",
|
|
451
|
+
COMPUTATION: "ComputationService",
|
|
452
|
+
STORAGE: "StorageService",
|
|
453
|
+
MESSAGING: "MessagingService",
|
|
454
|
+
AUTHENTICATION: "AuthenticationService"
|
|
455
|
+
};
|
|
456
|
+
var ParameterTypes = {
|
|
457
|
+
STRING: "string",
|
|
458
|
+
NUMBER: "number",
|
|
459
|
+
BOOLEAN: "boolean",
|
|
460
|
+
OBJECT: "object",
|
|
461
|
+
ARRAY: "array"
|
|
462
|
+
};
|
|
463
|
+
function getAgentCardContext() {
|
|
464
|
+
return [SCHEMA_ORG_CONTEXT, CLAWIVERSE_CONTEXT];
|
|
465
|
+
}
|
|
466
|
+
function isValidContext(context) {
|
|
467
|
+
if (!Array.isArray(context)) return false;
|
|
468
|
+
return context.every((c) => typeof c === "string");
|
|
469
|
+
}
|
|
470
|
+
function encodeForDHT(card) {
|
|
471
|
+
try {
|
|
472
|
+
const { "@context": _, ...cardWithoutContext } = card;
|
|
473
|
+
return encode(cardWithoutContext);
|
|
474
|
+
} catch (error) {
|
|
475
|
+
throw new DiscoveryError("Failed to encode Agent Card as CBOR", error);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
function encodeForWeb(card) {
|
|
479
|
+
try {
|
|
480
|
+
const cardWithContext = {
|
|
481
|
+
"@context": card["@context"] || getAgentCardContext(),
|
|
482
|
+
...card
|
|
483
|
+
};
|
|
484
|
+
return JSON.stringify(cardWithContext, null, 2);
|
|
485
|
+
} catch (error) {
|
|
486
|
+
throw new DiscoveryError("Failed to encode Agent Card as JSON-LD", error);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
function decodeFromCBOR(data) {
|
|
490
|
+
try {
|
|
491
|
+
const decoded = decode(data);
|
|
492
|
+
if (isLegacyCard(decoded)) {
|
|
493
|
+
return upgradeLegacyCard(decoded);
|
|
494
|
+
}
|
|
495
|
+
if (!decoded["@context"]) {
|
|
496
|
+
decoded["@context"] = getAgentCardContext();
|
|
497
|
+
}
|
|
498
|
+
return decoded;
|
|
499
|
+
} catch (error) {
|
|
500
|
+
throw new DiscoveryError("Failed to decode Agent Card from CBOR", error);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
function decodeFromJSON(json) {
|
|
504
|
+
try {
|
|
505
|
+
const decoded = JSON.parse(json);
|
|
506
|
+
if (isLegacyCard(decoded)) {
|
|
507
|
+
return upgradeLegacyCard(decoded);
|
|
508
|
+
}
|
|
509
|
+
if (!decoded["@context"]) {
|
|
510
|
+
decoded["@context"] = getAgentCardContext();
|
|
511
|
+
}
|
|
512
|
+
return decoded;
|
|
513
|
+
} catch (error) {
|
|
514
|
+
throw new DiscoveryError("Failed to decode Agent Card from JSON", error);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
function decodeAgentCard(data) {
|
|
518
|
+
if (typeof data === "string") {
|
|
519
|
+
return decodeFromJSON(data);
|
|
520
|
+
}
|
|
521
|
+
try {
|
|
522
|
+
return decodeFromCBOR(data);
|
|
523
|
+
} catch {
|
|
524
|
+
const text = new TextDecoder().decode(data);
|
|
525
|
+
if (text.trim().startsWith("{")) {
|
|
526
|
+
return decodeFromJSON(text);
|
|
527
|
+
}
|
|
528
|
+
throw new DiscoveryError("Unable to decode Agent Card: unknown format");
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
function getEncodedSize(card) {
|
|
532
|
+
return {
|
|
533
|
+
cbor: encodeForDHT(card).length,
|
|
534
|
+
json: encodeForWeb(card).length
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
var logger2 = createLogger("search-index");
|
|
538
|
+
var SearchIndex = class {
|
|
539
|
+
cards = /* @__PURE__ */ new Map();
|
|
540
|
+
lunrIndex;
|
|
541
|
+
fuse;
|
|
542
|
+
needsRebuild = false;
|
|
543
|
+
/**
|
|
544
|
+
* Add or update an Agent Card in the index
|
|
545
|
+
*/
|
|
546
|
+
indexAgentCard(card) {
|
|
547
|
+
this.cards.set(card.did, card);
|
|
548
|
+
this.needsRebuild = true;
|
|
549
|
+
logger2.debug("Indexed Agent Card", { did: card.did, capabilities: card.capabilities.length });
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* Remove an Agent Card from the index
|
|
553
|
+
*/
|
|
554
|
+
removeAgentCard(did) {
|
|
555
|
+
this.cards.delete(did);
|
|
556
|
+
this.needsRebuild = true;
|
|
557
|
+
logger2.debug("Removed Agent Card from index", { did });
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* Search for agents matching a query
|
|
561
|
+
*/
|
|
562
|
+
search(query) {
|
|
563
|
+
if (this.needsRebuild) {
|
|
564
|
+
this.rebuild();
|
|
565
|
+
}
|
|
566
|
+
let results = [];
|
|
567
|
+
if (query.text && this.lunrIndex) {
|
|
568
|
+
results = this.searchByText(query.text);
|
|
569
|
+
} else if (query.capability && this.fuse) {
|
|
570
|
+
results = this.searchByCapability(query.capability);
|
|
571
|
+
} else {
|
|
572
|
+
results = Array.from(this.cards.values()).map((card) => ({
|
|
573
|
+
card,
|
|
574
|
+
score: 1
|
|
575
|
+
}));
|
|
576
|
+
}
|
|
577
|
+
if (query.filters) {
|
|
578
|
+
results = this.applyFilters(results, query.filters);
|
|
579
|
+
}
|
|
580
|
+
results.sort((a, b) => b.score - a.score);
|
|
581
|
+
if (query.limit) {
|
|
582
|
+
results = results.slice(0, query.limit);
|
|
583
|
+
}
|
|
584
|
+
logger2.debug("Search completed", { query, results: results.length });
|
|
585
|
+
return results;
|
|
586
|
+
}
|
|
587
|
+
/**
|
|
588
|
+
* Get all indexed cards
|
|
589
|
+
*/
|
|
590
|
+
getAllCards() {
|
|
591
|
+
return Array.from(this.cards.values());
|
|
592
|
+
}
|
|
593
|
+
/**
|
|
594
|
+
* Clear the index
|
|
595
|
+
*/
|
|
596
|
+
clear() {
|
|
597
|
+
this.cards.clear();
|
|
598
|
+
this.lunrIndex = void 0;
|
|
599
|
+
this.fuse = void 0;
|
|
600
|
+
this.needsRebuild = false;
|
|
601
|
+
logger2.info("Search index cleared");
|
|
602
|
+
}
|
|
603
|
+
/**
|
|
604
|
+
* Get index size
|
|
605
|
+
*/
|
|
606
|
+
size() {
|
|
607
|
+
return this.cards.size;
|
|
608
|
+
}
|
|
609
|
+
/**
|
|
610
|
+
* Rebuild search indexes
|
|
611
|
+
*/
|
|
612
|
+
rebuild() {
|
|
613
|
+
logger2.info("Rebuilding search indexes", { cards: this.cards.size });
|
|
614
|
+
const cards = Array.from(this.cards.values());
|
|
615
|
+
this.lunrIndex = lunr(function() {
|
|
616
|
+
this.ref("did");
|
|
617
|
+
this.field("name", { boost: 10 });
|
|
618
|
+
this.field("description", { boost: 5 });
|
|
619
|
+
this.field("capabilities");
|
|
620
|
+
for (const card of cards) {
|
|
621
|
+
this.add({
|
|
622
|
+
did: card.did,
|
|
623
|
+
name: card.name,
|
|
624
|
+
description: card.description,
|
|
625
|
+
capabilities: card.capabilities.map((cap) => `${cap.name} ${cap.description}`).join(" ")
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
});
|
|
629
|
+
this.fuse = new Fuse(Array.from(this.cards.values()), {
|
|
630
|
+
keys: [
|
|
631
|
+
{ name: "name", weight: 0.3 },
|
|
632
|
+
{ name: "description", weight: 0.2 },
|
|
633
|
+
{ name: "capabilities.name", weight: 0.3 },
|
|
634
|
+
{ name: "capabilities.description", weight: 0.2 }
|
|
635
|
+
],
|
|
636
|
+
threshold: 0.4,
|
|
637
|
+
includeScore: true
|
|
638
|
+
});
|
|
639
|
+
this.needsRebuild = false;
|
|
640
|
+
logger2.info("Search indexes rebuilt");
|
|
641
|
+
}
|
|
642
|
+
/**
|
|
643
|
+
* Search by text using Lunr
|
|
644
|
+
*/
|
|
645
|
+
searchByText(text) {
|
|
646
|
+
if (!this.lunrIndex) return [];
|
|
647
|
+
const lunrResults = this.lunrIndex.search(text);
|
|
648
|
+
return lunrResults.map((result) => ({
|
|
649
|
+
card: this.cards.get(result.ref),
|
|
650
|
+
score: result.score
|
|
651
|
+
}));
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* Search by capability using Fuse
|
|
655
|
+
*/
|
|
656
|
+
searchByCapability(capability) {
|
|
657
|
+
if (!this.fuse) return [];
|
|
658
|
+
const fuseResults = this.fuse.search(capability);
|
|
659
|
+
return fuseResults.map((result) => ({
|
|
660
|
+
card: result.item,
|
|
661
|
+
score: 1 - (result.score || 0)
|
|
662
|
+
// Fuse score is distance, convert to similarity
|
|
663
|
+
}));
|
|
664
|
+
}
|
|
665
|
+
/**
|
|
666
|
+
* Apply filters to search results
|
|
667
|
+
*/
|
|
668
|
+
applyFilters(results, filters) {
|
|
669
|
+
return results.filter((result) => {
|
|
670
|
+
const { card } = result;
|
|
671
|
+
if (filters.minTrustScore !== void 0 && card.trust) {
|
|
672
|
+
const overallTrust = card.trust.interactionScore * 0.4 + Math.min(card.trust.endorsements / 10, 1) * 0.2 + card.trust.completionRate * 0.2 + card.trust.uptime * 0.2;
|
|
673
|
+
if (overallTrust < filters.minTrustScore) {
|
|
674
|
+
return false;
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
if (filters.language) {
|
|
678
|
+
const hasLanguage = card.capabilities.some(
|
|
679
|
+
(cap) => {
|
|
680
|
+
const metadata = cap.metadata;
|
|
681
|
+
if (!metadata) return false;
|
|
682
|
+
return metadata.language === filters.language || Array.isArray(metadata.languages) && metadata.languages.includes(filters.language);
|
|
683
|
+
}
|
|
684
|
+
);
|
|
685
|
+
if (!hasLanguage) return false;
|
|
686
|
+
}
|
|
687
|
+
if (filters.tags && filters.tags.length > 0) {
|
|
688
|
+
const cardTags = Array.isArray(card.metadata?.tags) ? card.metadata.tags : [];
|
|
689
|
+
const hasAllTags = filters.tags.every((tag) => cardTags.includes(tag));
|
|
690
|
+
if (!hasAllTags) return false;
|
|
691
|
+
}
|
|
692
|
+
return true;
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
};
|
|
696
|
+
|
|
697
|
+
// src/discovery/capability-matcher.ts
|
|
698
|
+
var CapabilityMatcher = class {
|
|
699
|
+
/**
|
|
700
|
+
* Match a query against a capability
|
|
701
|
+
* Returns a score between 0 and 1
|
|
702
|
+
*/
|
|
703
|
+
match(query, capability) {
|
|
704
|
+
let score = 0;
|
|
705
|
+
let weights = 0;
|
|
706
|
+
if (query.capability && capability.id === query.capability) {
|
|
707
|
+
return 1;
|
|
708
|
+
}
|
|
709
|
+
if (query.capability) {
|
|
710
|
+
const nameScore = this.fuzzyMatch(query.capability, capability.name);
|
|
711
|
+
score += nameScore * 0.4;
|
|
712
|
+
weights += 0.4;
|
|
713
|
+
}
|
|
714
|
+
if (query.text) {
|
|
715
|
+
const keywords = this.extractKeywords(query.text);
|
|
716
|
+
const keywordScore = this.matchKeywords(keywords, capability);
|
|
717
|
+
score += keywordScore * 0.4;
|
|
718
|
+
weights += 0.4;
|
|
719
|
+
}
|
|
720
|
+
if (query.capability && capability["@type"]) {
|
|
721
|
+
const typeScore = this.matchesType(query.capability, capability["@type"]) ? 0.6 : 0;
|
|
722
|
+
score += typeScore * 0.2;
|
|
723
|
+
weights += 0.2;
|
|
724
|
+
}
|
|
725
|
+
return weights > 0 ? score / weights : 0;
|
|
726
|
+
}
|
|
727
|
+
/**
|
|
728
|
+
* Extract keywords from natural language text
|
|
729
|
+
*/
|
|
730
|
+
extractKeywords(text) {
|
|
731
|
+
const stopWords = /* @__PURE__ */ new Set([
|
|
732
|
+
"a",
|
|
733
|
+
"an",
|
|
734
|
+
"the",
|
|
735
|
+
"is",
|
|
736
|
+
"are",
|
|
737
|
+
"was",
|
|
738
|
+
"were",
|
|
739
|
+
"be",
|
|
740
|
+
"been",
|
|
741
|
+
"to",
|
|
742
|
+
"from",
|
|
743
|
+
"in",
|
|
744
|
+
"on",
|
|
745
|
+
"at",
|
|
746
|
+
"by",
|
|
747
|
+
"for",
|
|
748
|
+
"with",
|
|
749
|
+
"about",
|
|
750
|
+
"can",
|
|
751
|
+
"could",
|
|
752
|
+
"should",
|
|
753
|
+
"would",
|
|
754
|
+
"will",
|
|
755
|
+
"do",
|
|
756
|
+
"does",
|
|
757
|
+
"did",
|
|
758
|
+
"i",
|
|
759
|
+
"you",
|
|
760
|
+
"he",
|
|
761
|
+
"she",
|
|
762
|
+
"it",
|
|
763
|
+
"we",
|
|
764
|
+
"they",
|
|
765
|
+
"me",
|
|
766
|
+
"him",
|
|
767
|
+
"her"
|
|
768
|
+
]);
|
|
769
|
+
return text.toLowerCase().split(/\W+/).filter((word) => word.length > 2 && !stopWords.has(word));
|
|
770
|
+
}
|
|
771
|
+
/**
|
|
772
|
+
* Match keywords against capability
|
|
773
|
+
*/
|
|
774
|
+
matchKeywords(keywords, capability) {
|
|
775
|
+
if (keywords.length === 0) return 0;
|
|
776
|
+
const capText = `${capability.name} ${capability.description}`.toLowerCase();
|
|
777
|
+
const matches = keywords.filter((keyword) => capText.includes(keyword));
|
|
778
|
+
return matches.length / keywords.length;
|
|
779
|
+
}
|
|
780
|
+
/**
|
|
781
|
+
* Check if query matches capability type hierarchy
|
|
782
|
+
*/
|
|
783
|
+
matchesType(query, type) {
|
|
784
|
+
const queryLower = query.toLowerCase();
|
|
785
|
+
const typeLower = type.toLowerCase();
|
|
786
|
+
if (typeLower.includes(queryLower)) return true;
|
|
787
|
+
const typeMap = {
|
|
788
|
+
translate: ["translation", "translationservice"],
|
|
789
|
+
review: ["codereview", "reviewservice"],
|
|
790
|
+
analyze: ["analysis", "dataanalysis"],
|
|
791
|
+
generate: ["generation", "textgeneration", "imagegeneration"],
|
|
792
|
+
search: ["searchservice", "query"],
|
|
793
|
+
compute: ["computation", "computationservice"],
|
|
794
|
+
store: ["storage", "storageservice"],
|
|
795
|
+
message: ["messaging", "messagingservice"],
|
|
796
|
+
auth: ["authentication", "authenticationservice"]
|
|
797
|
+
};
|
|
798
|
+
for (const [key, values] of Object.entries(typeMap)) {
|
|
799
|
+
if (queryLower.includes(key) && values.some((v) => typeLower.includes(v))) {
|
|
800
|
+
return true;
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
return false;
|
|
804
|
+
}
|
|
805
|
+
/**
|
|
806
|
+
* Fuzzy string matching using Levenshtein distance
|
|
807
|
+
*/
|
|
808
|
+
fuzzyMatch(query, target) {
|
|
809
|
+
const queryLower = query.toLowerCase();
|
|
810
|
+
const targetLower = target.toLowerCase();
|
|
811
|
+
if (queryLower === targetLower) return 1;
|
|
812
|
+
if (targetLower.includes(queryLower)) return 0.8;
|
|
813
|
+
if (queryLower.includes(targetLower)) return 0.7;
|
|
814
|
+
const distance = this.levenshteinDistance(queryLower, targetLower);
|
|
815
|
+
const maxLen = Math.max(queryLower.length, targetLower.length);
|
|
816
|
+
const similarity = 1 - distance / maxLen;
|
|
817
|
+
return similarity > 0.5 ? similarity * 0.6 : 0;
|
|
818
|
+
}
|
|
819
|
+
/**
|
|
820
|
+
* Calculate Levenshtein distance between two strings
|
|
821
|
+
*/
|
|
822
|
+
levenshteinDistance(a, b) {
|
|
823
|
+
const matrix = [];
|
|
824
|
+
for (let i = 0; i <= b.length; i++) {
|
|
825
|
+
matrix[i] = [i];
|
|
826
|
+
}
|
|
827
|
+
for (let j = 0; j <= a.length; j++) {
|
|
828
|
+
matrix[0][j] = j;
|
|
829
|
+
}
|
|
830
|
+
for (let i = 1; i <= b.length; i++) {
|
|
831
|
+
for (let j = 1; j <= a.length; j++) {
|
|
832
|
+
if (b.charAt(i - 1) === a.charAt(j - 1)) {
|
|
833
|
+
matrix[i][j] = matrix[i - 1][j - 1];
|
|
834
|
+
} else {
|
|
835
|
+
matrix[i][j] = Math.min(
|
|
836
|
+
matrix[i - 1][j - 1] + 1,
|
|
837
|
+
// substitution
|
|
838
|
+
matrix[i][j - 1] + 1,
|
|
839
|
+
// insertion
|
|
840
|
+
matrix[i - 1][j] + 1
|
|
841
|
+
// deletion
|
|
842
|
+
);
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
return matrix[b.length][a.length];
|
|
847
|
+
}
|
|
848
|
+
};
|
|
849
|
+
|
|
850
|
+
// src/discovery/semantic-search.ts
|
|
851
|
+
var logger3 = createLogger("semantic-search");
|
|
852
|
+
var SemanticSearchEngine = class {
|
|
853
|
+
constructor(dht) {
|
|
854
|
+
this.dht = dht;
|
|
855
|
+
this.index = new SearchIndex();
|
|
856
|
+
this.matcher = new CapabilityMatcher();
|
|
857
|
+
}
|
|
858
|
+
index;
|
|
859
|
+
matcher;
|
|
860
|
+
/**
|
|
861
|
+
* Main search interface
|
|
862
|
+
* Local-first with network fallback
|
|
863
|
+
*/
|
|
864
|
+
async search(query) {
|
|
865
|
+
logger3.info("Searching for agents", { query });
|
|
866
|
+
const localResults = this.index.search(query);
|
|
867
|
+
logger3.debug("Local search results", { count: localResults.length });
|
|
868
|
+
const limit = query.limit || 10;
|
|
869
|
+
if (localResults.length < limit && this.dht) {
|
|
870
|
+
logger3.debug("Insufficient local results, querying network");
|
|
871
|
+
const networkResults = await this.searchNetwork(query);
|
|
872
|
+
return this.mergeResults(localResults, networkResults, limit);
|
|
873
|
+
}
|
|
874
|
+
return localResults.map((r) => r.card);
|
|
875
|
+
}
|
|
876
|
+
/**
|
|
877
|
+
* Index an Agent Card for local search
|
|
878
|
+
*/
|
|
879
|
+
indexAgentCard(card) {
|
|
880
|
+
this.index.indexAgentCard(card);
|
|
881
|
+
}
|
|
882
|
+
/**
|
|
883
|
+
* Remove an Agent Card from local index
|
|
884
|
+
*/
|
|
885
|
+
removeAgentCard(did) {
|
|
886
|
+
this.index.removeAgentCard(did);
|
|
887
|
+
}
|
|
888
|
+
/**
|
|
889
|
+
* Get all indexed cards
|
|
890
|
+
*/
|
|
891
|
+
getAllIndexedCards() {
|
|
892
|
+
return this.index.getAllCards();
|
|
893
|
+
}
|
|
894
|
+
/**
|
|
895
|
+
* Clear local index
|
|
896
|
+
*/
|
|
897
|
+
clearIndex() {
|
|
898
|
+
this.index.clear();
|
|
899
|
+
}
|
|
900
|
+
/**
|
|
901
|
+
* Get index size
|
|
902
|
+
*/
|
|
903
|
+
getIndexSize() {
|
|
904
|
+
return this.index.size();
|
|
905
|
+
}
|
|
906
|
+
/**
|
|
907
|
+
* Search network via DHT
|
|
908
|
+
*/
|
|
909
|
+
async searchNetwork(query) {
|
|
910
|
+
if (!this.dht) {
|
|
911
|
+
return [];
|
|
912
|
+
}
|
|
913
|
+
try {
|
|
914
|
+
const capability = query.capability || this.extractPrimaryCapability(query.text);
|
|
915
|
+
if (!capability) {
|
|
916
|
+
logger3.debug("No capability extracted from query, skipping network search");
|
|
917
|
+
return [];
|
|
918
|
+
}
|
|
919
|
+
const cards = await this.dht.queryByCapability(capability);
|
|
920
|
+
logger3.debug("Network search results", { count: cards.length });
|
|
921
|
+
return cards.map((card) => {
|
|
922
|
+
const score = this.scoreCard(card, query);
|
|
923
|
+
return { card, score };
|
|
924
|
+
});
|
|
925
|
+
} catch (error) {
|
|
926
|
+
logger3.error("Network search failed", { error });
|
|
927
|
+
return [];
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
/**
|
|
931
|
+
* Merge local and network results, removing duplicates
|
|
932
|
+
*/
|
|
933
|
+
mergeResults(local, network, limit) {
|
|
934
|
+
const seen = /* @__PURE__ */ new Set();
|
|
935
|
+
const merged = [];
|
|
936
|
+
for (const result of local) {
|
|
937
|
+
if (!seen.has(result.card.did)) {
|
|
938
|
+
seen.add(result.card.did);
|
|
939
|
+
merged.push(result);
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
for (const result of network) {
|
|
943
|
+
if (!seen.has(result.card.did)) {
|
|
944
|
+
seen.add(result.card.did);
|
|
945
|
+
merged.push(result);
|
|
946
|
+
this.index.indexAgentCard(result.card);
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
merged.sort((a, b) => b.score - a.score);
|
|
950
|
+
return merged.slice(0, limit).map((r) => r.card);
|
|
951
|
+
}
|
|
952
|
+
/**
|
|
953
|
+
* Score a card against a query
|
|
954
|
+
*/
|
|
955
|
+
scoreCard(card, query) {
|
|
956
|
+
let totalScore = 0;
|
|
957
|
+
let count = 0;
|
|
958
|
+
for (const capability of card.capabilities) {
|
|
959
|
+
const score = this.matcher.match(query, capability);
|
|
960
|
+
if (score > 0) {
|
|
961
|
+
totalScore += score;
|
|
962
|
+
count++;
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
const avgScore = count > 0 ? totalScore / count : 0;
|
|
966
|
+
if (card.trust) {
|
|
967
|
+
const trustBoost = card.trust.interactionScore * 0.2;
|
|
968
|
+
return Math.min(avgScore + trustBoost, 1);
|
|
969
|
+
}
|
|
970
|
+
return avgScore;
|
|
971
|
+
}
|
|
972
|
+
/**
|
|
973
|
+
* Extract primary capability from natural language text
|
|
974
|
+
*/
|
|
975
|
+
extractPrimaryCapability(text) {
|
|
976
|
+
if (!text) return void 0;
|
|
977
|
+
const keywords = this.matcher.extractKeywords(text);
|
|
978
|
+
const capabilityKeywords = [
|
|
979
|
+
"translate",
|
|
980
|
+
"translation",
|
|
981
|
+
"review",
|
|
982
|
+
"code",
|
|
983
|
+
"analyze",
|
|
984
|
+
"analysis",
|
|
985
|
+
"generate",
|
|
986
|
+
"generation",
|
|
987
|
+
"search",
|
|
988
|
+
"query",
|
|
989
|
+
"compute",
|
|
990
|
+
"calculation",
|
|
991
|
+
"store",
|
|
992
|
+
"storage",
|
|
993
|
+
"message",
|
|
994
|
+
"messaging",
|
|
995
|
+
"auth",
|
|
996
|
+
"authentication"
|
|
997
|
+
];
|
|
998
|
+
for (const keyword of keywords) {
|
|
999
|
+
if (capabilityKeywords.includes(keyword)) {
|
|
1000
|
+
return keyword;
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
return keywords[0];
|
|
1004
|
+
}
|
|
1005
|
+
};
|
|
1006
|
+
function createSemanticSearch(dht) {
|
|
1007
|
+
return new SemanticSearchEngine(dht);
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
// src/discovery/dht.ts
|
|
1011
|
+
var logger4 = createLogger("dht");
|
|
1012
|
+
function createDHTOperations(libp2p) {
|
|
1013
|
+
const operations = {};
|
|
1014
|
+
const searchEngine = createSemanticSearch(operations);
|
|
1015
|
+
return Object.assign(operations, {
|
|
1016
|
+
publishAgentCard: async (card) => {
|
|
1017
|
+
try {
|
|
1018
|
+
const dht = libp2p.services?.dht;
|
|
1019
|
+
if (!dht) {
|
|
1020
|
+
throw new DiscoveryError("DHT service not available");
|
|
1021
|
+
}
|
|
1022
|
+
const key = fromString(`/clawiverse/agent/${card.did}`);
|
|
1023
|
+
const value = encodeForDHT(card);
|
|
1024
|
+
for await (const event of dht.put(key, value)) {
|
|
1025
|
+
logger4.debug("DHT put event", { name: event.name });
|
|
1026
|
+
}
|
|
1027
|
+
searchEngine.indexAgentCard(card);
|
|
1028
|
+
logger4.info("Published Agent Card to DHT", { did: card.did });
|
|
1029
|
+
} catch (error) {
|
|
1030
|
+
throw new DiscoveryError("Failed to publish Agent Card", error);
|
|
1031
|
+
}
|
|
1032
|
+
},
|
|
1033
|
+
queryAgentCard: async (did) => {
|
|
1034
|
+
try {
|
|
1035
|
+
const dht = libp2p.services?.dht;
|
|
1036
|
+
if (!dht) {
|
|
1037
|
+
throw new DiscoveryError("DHT service not available");
|
|
1038
|
+
}
|
|
1039
|
+
const key = fromString(`/clawiverse/agent/${did}`);
|
|
1040
|
+
for await (const event of dht.get(key)) {
|
|
1041
|
+
if (event.name === "VALUE") {
|
|
1042
|
+
const card = decodeFromCBOR(event.value);
|
|
1043
|
+
searchEngine.indexAgentCard(card);
|
|
1044
|
+
logger4.debug("Found Agent Card in DHT", { did });
|
|
1045
|
+
return card;
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
logger4.debug("Agent Card not found in DHT", { did });
|
|
1049
|
+
return null;
|
|
1050
|
+
} catch (error) {
|
|
1051
|
+
logger4.warn("Failed to query Agent Card", { did, error });
|
|
1052
|
+
return null;
|
|
1053
|
+
}
|
|
1054
|
+
},
|
|
1055
|
+
queryByCapability: async (capability) => {
|
|
1056
|
+
try {
|
|
1057
|
+
const query = { capability, limit: 20 };
|
|
1058
|
+
return searchEngine.search(query);
|
|
1059
|
+
} catch (error) {
|
|
1060
|
+
throw new DiscoveryError("Failed to query by capability", error);
|
|
1061
|
+
}
|
|
1062
|
+
},
|
|
1063
|
+
searchSemantic: async (query) => {
|
|
1064
|
+
try {
|
|
1065
|
+
return searchEngine.search(query);
|
|
1066
|
+
} catch (error) {
|
|
1067
|
+
throw new DiscoveryError("Failed to perform semantic search", error);
|
|
1068
|
+
}
|
|
1069
|
+
},
|
|
1070
|
+
resolveDID: async (did) => {
|
|
1071
|
+
try {
|
|
1072
|
+
const dht = libp2p.services?.dht;
|
|
1073
|
+
if (!dht) {
|
|
1074
|
+
throw new DiscoveryError("DHT service not available");
|
|
1075
|
+
}
|
|
1076
|
+
const key = fromString(`/clawiverse/agent/${did}`);
|
|
1077
|
+
for await (const event of dht.get(key)) {
|
|
1078
|
+
if (event.name === "VALUE") {
|
|
1079
|
+
const card = decodeFromCBOR(event.value);
|
|
1080
|
+
if (card.peerId) {
|
|
1081
|
+
logger4.debug("Resolved DID to peer", { did, peerId: card.peerId });
|
|
1082
|
+
return {
|
|
1083
|
+
peerId: card.peerId,
|
|
1084
|
+
multiaddrs: card.endpoints || []
|
|
1085
|
+
};
|
|
1086
|
+
}
|
|
1087
|
+
logger4.warn("Agent Card found but has no peerId", { did });
|
|
1088
|
+
return null;
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
logger4.debug("DID not found in DHT", { did });
|
|
1092
|
+
return null;
|
|
1093
|
+
} catch (error) {
|
|
1094
|
+
logger4.warn("Failed to resolve DID", { did, error });
|
|
1095
|
+
return null;
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
});
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
// src/messaging/envelope.ts
|
|
1102
|
+
function createEnvelope(from, to, type, protocol, payload, replyTo) {
|
|
1103
|
+
return {
|
|
1104
|
+
id: generateMessageId(),
|
|
1105
|
+
from,
|
|
1106
|
+
to,
|
|
1107
|
+
type,
|
|
1108
|
+
protocol,
|
|
1109
|
+
payload,
|
|
1110
|
+
timestamp: Date.now(),
|
|
1111
|
+
replyTo
|
|
1112
|
+
};
|
|
1113
|
+
}
|
|
1114
|
+
async function signEnvelope(envelope, signFn) {
|
|
1115
|
+
try {
|
|
1116
|
+
const envelopeJson = JSON.stringify(envelope);
|
|
1117
|
+
const envelopeBytes = new TextEncoder().encode(envelopeJson);
|
|
1118
|
+
const signature = await signFn(envelopeBytes);
|
|
1119
|
+
return {
|
|
1120
|
+
...envelope,
|
|
1121
|
+
signature: Buffer.from(signature).toString("hex")
|
|
1122
|
+
};
|
|
1123
|
+
} catch (error) {
|
|
1124
|
+
throw new MessagingError("Failed to sign envelope", error);
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
async function verifyEnvelope(envelope, verifyFn) {
|
|
1128
|
+
try {
|
|
1129
|
+
const { signature, ...envelopeWithoutSig } = envelope;
|
|
1130
|
+
const envelopeJson = JSON.stringify(envelopeWithoutSig);
|
|
1131
|
+
const envelopeBytes = new TextEncoder().encode(envelopeJson);
|
|
1132
|
+
const signatureBytes = Buffer.from(signature, "hex");
|
|
1133
|
+
return await verifyFn(signatureBytes, envelopeBytes);
|
|
1134
|
+
} catch (error) {
|
|
1135
|
+
throw new MessagingError("Failed to verify envelope", error);
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
function validateEnvelope(msg) {
|
|
1139
|
+
if (typeof msg !== "object" || msg === null) {
|
|
1140
|
+
return false;
|
|
1141
|
+
}
|
|
1142
|
+
const m = msg;
|
|
1143
|
+
return typeof m.id === "string" && typeof m.from === "string" && m.from.startsWith("did:clawiverse:") && typeof m.to === "string" && m.to.startsWith("did:clawiverse:") && (m.type === "request" || m.type === "response" || m.type === "notification") && typeof m.protocol === "string" && m.payload !== void 0 && typeof m.timestamp === "number" && typeof m.signature === "string";
|
|
1144
|
+
}
|
|
1145
|
+
function generateMessageId() {
|
|
1146
|
+
return `msg_${Date.now()}_${Math.random().toString(36).substring(2, 15)}`;
|
|
1147
|
+
}
|
|
1148
|
+
function encodeMessage(envelope) {
|
|
1149
|
+
try {
|
|
1150
|
+
return encode(envelope);
|
|
1151
|
+
} catch (error) {
|
|
1152
|
+
throw new MessagingError("Failed to encode message", error);
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
function decodeMessage(data) {
|
|
1156
|
+
try {
|
|
1157
|
+
return decode(data);
|
|
1158
|
+
} catch (error) {
|
|
1159
|
+
throw new MessagingError("Failed to decode message", error);
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
function encodeMessageJSON(envelope) {
|
|
1163
|
+
try {
|
|
1164
|
+
return JSON.stringify(envelope, null, 2);
|
|
1165
|
+
} catch (error) {
|
|
1166
|
+
throw new MessagingError("Failed to encode message to JSON", error);
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
function decodeMessageJSON(json) {
|
|
1170
|
+
try {
|
|
1171
|
+
return JSON.parse(json);
|
|
1172
|
+
} catch (error) {
|
|
1173
|
+
throw new MessagingError("Failed to decode message from JSON", error);
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
var logger5 = createLogger("router");
|
|
1177
|
+
function createMessageRouter(libp2p, verifyFn, dht) {
|
|
1178
|
+
const handlers = /* @__PURE__ */ new Map();
|
|
1179
|
+
const PROTOCOL_PREFIX = "/clawiverse/msg/1.0.0";
|
|
1180
|
+
return {
|
|
1181
|
+
registerHandler: (protocol, handler) => {
|
|
1182
|
+
handlers.set(protocol, handler);
|
|
1183
|
+
logger5.info("Registered message handler", { protocol });
|
|
1184
|
+
},
|
|
1185
|
+
unregisterHandler: (protocol) => {
|
|
1186
|
+
handlers.delete(protocol);
|
|
1187
|
+
logger5.info("Unregistered message handler", { protocol });
|
|
1188
|
+
},
|
|
1189
|
+
sendMessage: async (envelope, peerHint) => {
|
|
1190
|
+
try {
|
|
1191
|
+
if (!validateEnvelope(envelope)) {
|
|
1192
|
+
throw new MessagingError("Invalid message envelope");
|
|
1193
|
+
}
|
|
1194
|
+
let targetPeerIdStr;
|
|
1195
|
+
let targetMultiaddrs = [];
|
|
1196
|
+
if (peerHint) {
|
|
1197
|
+
targetPeerIdStr = peerHint.peerId;
|
|
1198
|
+
targetMultiaddrs = peerHint.multiaddrs;
|
|
1199
|
+
logger5.info("Using peer hint for direct addressing", { peerId: targetPeerIdStr });
|
|
1200
|
+
} else if (dht) {
|
|
1201
|
+
const resolved = await dht.resolveDID(envelope.to);
|
|
1202
|
+
if (resolved) {
|
|
1203
|
+
targetPeerIdStr = resolved.peerId;
|
|
1204
|
+
targetMultiaddrs = resolved.multiaddrs;
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
if (!targetPeerIdStr) {
|
|
1208
|
+
throw new MessagingError(
|
|
1209
|
+
`Cannot resolve recipient: ${envelope.to} \u2014 provide peerHint or ensure agent is in DHT`
|
|
1210
|
+
);
|
|
1211
|
+
}
|
|
1212
|
+
const targetPeerId = peerIdFromString(targetPeerIdStr);
|
|
1213
|
+
if (targetMultiaddrs.length > 0) {
|
|
1214
|
+
const mas = targetMultiaddrs.map((a) => multiaddr(a));
|
|
1215
|
+
await libp2p.peerStore.merge(targetPeerId, { multiaddrs: mas });
|
|
1216
|
+
}
|
|
1217
|
+
logger5.info("Dialing peer for message delivery", {
|
|
1218
|
+
peerId: targetPeerIdStr,
|
|
1219
|
+
multiaddrs: targetMultiaddrs
|
|
1220
|
+
});
|
|
1221
|
+
const stream = await libp2p.dialProtocol(targetPeerId, PROTOCOL_PREFIX);
|
|
1222
|
+
const encoded = encodeMessage(envelope);
|
|
1223
|
+
await stream.sink(
|
|
1224
|
+
(async function* () {
|
|
1225
|
+
yield encoded;
|
|
1226
|
+
})()
|
|
1227
|
+
);
|
|
1228
|
+
logger5.info("Message sent over libp2p stream", {
|
|
1229
|
+
id: envelope.id,
|
|
1230
|
+
from: envelope.from,
|
|
1231
|
+
to: envelope.to,
|
|
1232
|
+
protocol: envelope.protocol
|
|
1233
|
+
});
|
|
1234
|
+
return void 0;
|
|
1235
|
+
} catch (error) {
|
|
1236
|
+
if (error instanceof MessagingError) throw error;
|
|
1237
|
+
throw new MessagingError("Failed to send message", error);
|
|
1238
|
+
}
|
|
1239
|
+
},
|
|
1240
|
+
start: async () => {
|
|
1241
|
+
await libp2p.handle(PROTOCOL_PREFIX, async ({ stream }) => {
|
|
1242
|
+
try {
|
|
1243
|
+
await handleIncomingStream(stream, handlers, verifyFn);
|
|
1244
|
+
} catch (error) {
|
|
1245
|
+
logger5.error("Error handling incoming stream", error);
|
|
1246
|
+
}
|
|
1247
|
+
});
|
|
1248
|
+
logger5.info("Message router started");
|
|
1249
|
+
},
|
|
1250
|
+
stop: async () => {
|
|
1251
|
+
await libp2p.unhandle(PROTOCOL_PREFIX);
|
|
1252
|
+
handlers.clear();
|
|
1253
|
+
logger5.info("Message router stopped");
|
|
1254
|
+
}
|
|
1255
|
+
};
|
|
1256
|
+
}
|
|
1257
|
+
async function handleIncomingStream(stream, handlers, _verifyFn) {
|
|
1258
|
+
try {
|
|
1259
|
+
const chunks = [];
|
|
1260
|
+
for await (const chunk of stream.source) {
|
|
1261
|
+
chunks.push(chunk.subarray());
|
|
1262
|
+
}
|
|
1263
|
+
const data = new Uint8Array(
|
|
1264
|
+
chunks.reduce((acc, chunk) => acc + chunk.length, 0)
|
|
1265
|
+
);
|
|
1266
|
+
let offset = 0;
|
|
1267
|
+
for (const chunk of chunks) {
|
|
1268
|
+
data.set(chunk, offset);
|
|
1269
|
+
offset += chunk.length;
|
|
1270
|
+
}
|
|
1271
|
+
const envelope = decodeMessage(data);
|
|
1272
|
+
if (!validateEnvelope(envelope)) {
|
|
1273
|
+
logger5.warn("Received invalid message envelope");
|
|
1274
|
+
return;
|
|
1275
|
+
}
|
|
1276
|
+
logger5.info("Received message", {
|
|
1277
|
+
id: envelope.id,
|
|
1278
|
+
from: envelope.from,
|
|
1279
|
+
to: envelope.to,
|
|
1280
|
+
protocol: envelope.protocol,
|
|
1281
|
+
payload: envelope.payload
|
|
1282
|
+
});
|
|
1283
|
+
const handler = handlers.get(envelope.protocol);
|
|
1284
|
+
if (!handler) {
|
|
1285
|
+
logger5.warn("No handler for protocol", { protocol: envelope.protocol });
|
|
1286
|
+
return;
|
|
1287
|
+
}
|
|
1288
|
+
const response = await handler(envelope);
|
|
1289
|
+
if (response) {
|
|
1290
|
+
const encoded = encodeMessage(response);
|
|
1291
|
+
logger5.debug("Response prepared", { size: encoded.length });
|
|
1292
|
+
}
|
|
1293
|
+
} catch (error) {
|
|
1294
|
+
logger5.error("Error handling incoming message", error);
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
// src/trust/trust-score.ts
|
|
1299
|
+
var TrustMetrics = class {
|
|
1300
|
+
/**
|
|
1301
|
+
* Calculate trust score from interaction history
|
|
1302
|
+
*/
|
|
1303
|
+
calculateScore(stats, endorsements, uptime) {
|
|
1304
|
+
const volumeWeight = Math.min(stats.totalInteractions / 100, 1);
|
|
1305
|
+
const interactionScore = stats.successRate * volumeWeight;
|
|
1306
|
+
const completionRate = stats.successRate;
|
|
1307
|
+
return {
|
|
1308
|
+
interactionScore,
|
|
1309
|
+
endorsements,
|
|
1310
|
+
completionRate,
|
|
1311
|
+
responseTime: stats.avgResponseTime,
|
|
1312
|
+
uptime,
|
|
1313
|
+
lastUpdated: Date.now()
|
|
1314
|
+
};
|
|
1315
|
+
}
|
|
1316
|
+
/**
|
|
1317
|
+
* Calculate overall trust level (0-1)
|
|
1318
|
+
*/
|
|
1319
|
+
calculateOverallTrust(score) {
|
|
1320
|
+
const weights = {
|
|
1321
|
+
interaction: 0.4,
|
|
1322
|
+
endorsement: 0.2,
|
|
1323
|
+
completion: 0.2,
|
|
1324
|
+
uptime: 0.2
|
|
1325
|
+
};
|
|
1326
|
+
const endorsementScore = Math.min(score.endorsements / 10, 1);
|
|
1327
|
+
return score.interactionScore * weights.interaction + endorsementScore * weights.endorsement + score.completionRate * weights.completion + score.uptime * weights.uptime;
|
|
1328
|
+
}
|
|
1329
|
+
/**
|
|
1330
|
+
* Get trust level category
|
|
1331
|
+
*/
|
|
1332
|
+
getTrustLevel(score) {
|
|
1333
|
+
const overall = this.calculateOverallTrust(score);
|
|
1334
|
+
if (score.interactionScore === 0) return "new";
|
|
1335
|
+
if (overall < 0.3) return "low";
|
|
1336
|
+
if (overall < 0.6) return "medium";
|
|
1337
|
+
if (overall < 0.8) return "high";
|
|
1338
|
+
return "trusted";
|
|
1339
|
+
}
|
|
1340
|
+
/**
|
|
1341
|
+
* Check if agent should be rate limited
|
|
1342
|
+
*/
|
|
1343
|
+
shouldRateLimit(score, agentAge) {
|
|
1344
|
+
const overall = this.calculateOverallTrust(score);
|
|
1345
|
+
const ONE_DAY = 24 * 60 * 60 * 1e3;
|
|
1346
|
+
if (agentAge < ONE_DAY && overall < 0.3) {
|
|
1347
|
+
return true;
|
|
1348
|
+
}
|
|
1349
|
+
if (overall < 0.1) {
|
|
1350
|
+
return true;
|
|
1351
|
+
}
|
|
1352
|
+
return false;
|
|
1353
|
+
}
|
|
1354
|
+
};
|
|
1355
|
+
function createDefaultTrustScore() {
|
|
1356
|
+
return {
|
|
1357
|
+
interactionScore: 0,
|
|
1358
|
+
endorsements: 0,
|
|
1359
|
+
completionRate: 0,
|
|
1360
|
+
responseTime: 0,
|
|
1361
|
+
uptime: 1,
|
|
1362
|
+
// Assume online initially
|
|
1363
|
+
lastUpdated: Date.now()
|
|
1364
|
+
};
|
|
1365
|
+
}
|
|
1366
|
+
var logger6 = createLogger("interaction-history");
|
|
1367
|
+
var InteractionHistory = class {
|
|
1368
|
+
db;
|
|
1369
|
+
constructor(dbPath) {
|
|
1370
|
+
this.db = new Level(dbPath, { valueEncoding: "json" });
|
|
1371
|
+
}
|
|
1372
|
+
/**
|
|
1373
|
+
* Open database connection
|
|
1374
|
+
*/
|
|
1375
|
+
async open() {
|
|
1376
|
+
await this.db.open();
|
|
1377
|
+
logger6.info("Interaction history database opened", { path: this.db.location });
|
|
1378
|
+
}
|
|
1379
|
+
/**
|
|
1380
|
+
* Close database connection
|
|
1381
|
+
*/
|
|
1382
|
+
async close() {
|
|
1383
|
+
await this.db.close();
|
|
1384
|
+
logger6.info("Interaction history database closed");
|
|
1385
|
+
}
|
|
1386
|
+
/**
|
|
1387
|
+
* Record an interaction
|
|
1388
|
+
*/
|
|
1389
|
+
async record(interaction) {
|
|
1390
|
+
const key = `interaction:${interaction.agentDid}:${interaction.timestamp}`;
|
|
1391
|
+
await this.db.put(key, interaction);
|
|
1392
|
+
logger6.debug("Recorded interaction", { agentDid: interaction.agentDid, type: interaction.type });
|
|
1393
|
+
}
|
|
1394
|
+
/**
|
|
1395
|
+
* Get interaction history for an agent
|
|
1396
|
+
*/
|
|
1397
|
+
async getHistory(agentDid, limit = 100) {
|
|
1398
|
+
const interactions = [];
|
|
1399
|
+
const prefix = `interaction:${agentDid}:`;
|
|
1400
|
+
try {
|
|
1401
|
+
for await (const [_, value] of this.db.iterator({
|
|
1402
|
+
gte: prefix,
|
|
1403
|
+
lte: prefix + "\xFF",
|
|
1404
|
+
limit,
|
|
1405
|
+
reverse: true
|
|
1406
|
+
// Most recent first
|
|
1407
|
+
})) {
|
|
1408
|
+
interactions.push(value);
|
|
1409
|
+
}
|
|
1410
|
+
} catch (error) {
|
|
1411
|
+
logger6.error("Failed to get interaction history", { agentDid, error });
|
|
1412
|
+
}
|
|
1413
|
+
return interactions;
|
|
1414
|
+
}
|
|
1415
|
+
/**
|
|
1416
|
+
* Get interaction statistics for an agent
|
|
1417
|
+
*/
|
|
1418
|
+
async getStats(agentDid) {
|
|
1419
|
+
const history = await this.getHistory(agentDid, 1e3);
|
|
1420
|
+
if (history.length === 0) {
|
|
1421
|
+
return {
|
|
1422
|
+
totalInteractions: 0,
|
|
1423
|
+
successRate: 0,
|
|
1424
|
+
avgResponseTime: 0,
|
|
1425
|
+
lastInteraction: 0
|
|
1426
|
+
};
|
|
1427
|
+
}
|
|
1428
|
+
const successCount = history.filter((i) => i.success).length;
|
|
1429
|
+
const totalResponseTime = history.reduce((sum, i) => sum + i.responseTime, 0);
|
|
1430
|
+
return {
|
|
1431
|
+
totalInteractions: history.length,
|
|
1432
|
+
successRate: successCount / history.length,
|
|
1433
|
+
avgResponseTime: totalResponseTime / history.length,
|
|
1434
|
+
lastInteraction: history[0].timestamp
|
|
1435
|
+
};
|
|
1436
|
+
}
|
|
1437
|
+
/**
|
|
1438
|
+
* Get all agents with interaction history
|
|
1439
|
+
*/
|
|
1440
|
+
async getAllAgents() {
|
|
1441
|
+
const agents = /* @__PURE__ */ new Set();
|
|
1442
|
+
try {
|
|
1443
|
+
for await (const [key] of this.db.iterator()) {
|
|
1444
|
+
const match = key.match(/^interaction:([^:]+):/);
|
|
1445
|
+
if (match) {
|
|
1446
|
+
agents.add(match[1]);
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
} catch (error) {
|
|
1450
|
+
logger6.error("Failed to get all agents", { error });
|
|
1451
|
+
}
|
|
1452
|
+
return Array.from(agents);
|
|
1453
|
+
}
|
|
1454
|
+
/**
|
|
1455
|
+
* Delete all interactions for an agent
|
|
1456
|
+
*/
|
|
1457
|
+
async deleteAgent(agentDid) {
|
|
1458
|
+
const prefix = `interaction:${agentDid}:`;
|
|
1459
|
+
const keysToDelete = [];
|
|
1460
|
+
for await (const [key] of this.db.iterator({
|
|
1461
|
+
gte: prefix,
|
|
1462
|
+
lte: prefix + "\xFF"
|
|
1463
|
+
})) {
|
|
1464
|
+
keysToDelete.push(key);
|
|
1465
|
+
}
|
|
1466
|
+
await this.db.batch(keysToDelete.map((key) => ({ type: "del", key })));
|
|
1467
|
+
logger6.info("Deleted interaction history", { agentDid, count: keysToDelete.length });
|
|
1468
|
+
}
|
|
1469
|
+
/**
|
|
1470
|
+
* Clean up old interactions (older than 90 days)
|
|
1471
|
+
*/
|
|
1472
|
+
async cleanup(maxAge = 90 * 24 * 60 * 60 * 1e3) {
|
|
1473
|
+
const cutoff = Date.now() - maxAge;
|
|
1474
|
+
const keysToDelete = [];
|
|
1475
|
+
for await (const [key, value] of this.db.iterator()) {
|
|
1476
|
+
if (value.timestamp < cutoff) {
|
|
1477
|
+
keysToDelete.push(key);
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
if (keysToDelete.length > 0) {
|
|
1481
|
+
await this.db.batch(keysToDelete.map((key) => ({ type: "del", key })));
|
|
1482
|
+
logger6.info("Cleaned up old interactions", { count: keysToDelete.length });
|
|
1483
|
+
}
|
|
1484
|
+
return keysToDelete.length;
|
|
1485
|
+
}
|
|
1486
|
+
};
|
|
1487
|
+
|
|
1488
|
+
// src/trust/endorsement.ts
|
|
1489
|
+
var logger7 = createLogger("endorsement");
|
|
1490
|
+
var EndorsementManager = class {
|
|
1491
|
+
constructor(db, getPublicKey) {
|
|
1492
|
+
this.db = db;
|
|
1493
|
+
this.getPublicKey = getPublicKey;
|
|
1494
|
+
}
|
|
1495
|
+
/**
|
|
1496
|
+
* Create an endorsement
|
|
1497
|
+
*/
|
|
1498
|
+
async endorse(fromDid, toDid, score, reason, signFn) {
|
|
1499
|
+
if (score < 0 || score > 1) {
|
|
1500
|
+
throw new Error("Score must be between 0 and 1");
|
|
1501
|
+
}
|
|
1502
|
+
const endorsement = {
|
|
1503
|
+
from: fromDid,
|
|
1504
|
+
to: toDid,
|
|
1505
|
+
score,
|
|
1506
|
+
reason,
|
|
1507
|
+
timestamp: Date.now()
|
|
1508
|
+
};
|
|
1509
|
+
const data = new TextEncoder().encode(JSON.stringify(endorsement));
|
|
1510
|
+
const signatureBytes = await signFn(data);
|
|
1511
|
+
const signature = Buffer.from(signatureBytes).toString("hex");
|
|
1512
|
+
const signedEndorsement = {
|
|
1513
|
+
...endorsement,
|
|
1514
|
+
signature
|
|
1515
|
+
};
|
|
1516
|
+
logger7.info("Created endorsement", { from: fromDid, to: toDid, score });
|
|
1517
|
+
return signedEndorsement;
|
|
1518
|
+
}
|
|
1519
|
+
/**
|
|
1520
|
+
* Verify endorsement signature
|
|
1521
|
+
*/
|
|
1522
|
+
async verify(endorsement, verifyFn) {
|
|
1523
|
+
try {
|
|
1524
|
+
const { signature, ...endorsementWithoutSig } = endorsement;
|
|
1525
|
+
const data = new TextEncoder().encode(JSON.stringify(endorsementWithoutSig));
|
|
1526
|
+
const signatureBytes = Buffer.from(signature, "hex");
|
|
1527
|
+
const publicKey = await this.getPublicKey(endorsement.from);
|
|
1528
|
+
return await verifyFn(signatureBytes, data, publicKey);
|
|
1529
|
+
} catch (error) {
|
|
1530
|
+
logger7.error("Failed to verify endorsement", { error });
|
|
1531
|
+
return false;
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
/**
|
|
1535
|
+
* Publish endorsement to local database
|
|
1536
|
+
*/
|
|
1537
|
+
async publish(endorsement) {
|
|
1538
|
+
const key = `endorsement:${endorsement.to}:${endorsement.from}`;
|
|
1539
|
+
await this.db.put(key, endorsement);
|
|
1540
|
+
logger7.info("Published endorsement", { from: endorsement.from, to: endorsement.to });
|
|
1541
|
+
}
|
|
1542
|
+
/**
|
|
1543
|
+
* Get all endorsements for an agent
|
|
1544
|
+
*/
|
|
1545
|
+
async getEndorsements(agentDid) {
|
|
1546
|
+
const endorsements = [];
|
|
1547
|
+
const prefix = `endorsement:${agentDid}:`;
|
|
1548
|
+
try {
|
|
1549
|
+
for await (const [_, value] of this.db.iterator({
|
|
1550
|
+
gte: prefix,
|
|
1551
|
+
lte: prefix + "\xFF"
|
|
1552
|
+
})) {
|
|
1553
|
+
endorsements.push(value);
|
|
1554
|
+
}
|
|
1555
|
+
} catch (error) {
|
|
1556
|
+
logger7.error("Failed to get endorsements", { agentDid, error });
|
|
1557
|
+
}
|
|
1558
|
+
return endorsements;
|
|
1559
|
+
}
|
|
1560
|
+
/**
|
|
1561
|
+
* Get endorsements given by an agent
|
|
1562
|
+
*/
|
|
1563
|
+
async getEndorsementsBy(fromDid) {
|
|
1564
|
+
const endorsements = [];
|
|
1565
|
+
try {
|
|
1566
|
+
for await (const [_, value] of this.db.iterator()) {
|
|
1567
|
+
if (value.from === fromDid) {
|
|
1568
|
+
endorsements.push(value);
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
} catch (error) {
|
|
1572
|
+
logger7.error("Failed to get endorsements by agent", { fromDid, error });
|
|
1573
|
+
}
|
|
1574
|
+
return endorsements;
|
|
1575
|
+
}
|
|
1576
|
+
/**
|
|
1577
|
+
* Calculate average endorsement score
|
|
1578
|
+
*/
|
|
1579
|
+
async getAverageScore(agentDid) {
|
|
1580
|
+
const endorsements = await this.getEndorsements(agentDid);
|
|
1581
|
+
if (endorsements.length === 0) {
|
|
1582
|
+
return 0;
|
|
1583
|
+
}
|
|
1584
|
+
const totalScore = endorsements.reduce((sum, e) => sum + e.score, 0);
|
|
1585
|
+
return totalScore / endorsements.length;
|
|
1586
|
+
}
|
|
1587
|
+
/**
|
|
1588
|
+
* Delete an endorsement
|
|
1589
|
+
*/
|
|
1590
|
+
async deleteEndorsement(fromDid, toDid) {
|
|
1591
|
+
const key = `endorsement:${toDid}:${fromDid}`;
|
|
1592
|
+
await this.db.del(key);
|
|
1593
|
+
logger7.info("Deleted endorsement", { from: fromDid, to: toDid });
|
|
1594
|
+
}
|
|
1595
|
+
};
|
|
1596
|
+
var logger8 = createLogger("sybil-defense");
|
|
1597
|
+
var SybilDefense = class {
|
|
1598
|
+
rateLimits = /* @__PURE__ */ new Map();
|
|
1599
|
+
peerFirstSeen = /* @__PURE__ */ new Map();
|
|
1600
|
+
NEW_AGENT_WINDOW = 24 * 60 * 60 * 1e3;
|
|
1601
|
+
// 24 hours
|
|
1602
|
+
RATE_LIMIT_WINDOW = 60 * 60 * 1e3;
|
|
1603
|
+
// 1 hour
|
|
1604
|
+
MAX_REQUESTS_NEW = 10;
|
|
1605
|
+
// Max requests per hour for new agents
|
|
1606
|
+
ESTABLISHED_THRESHOLD = 7 * 24 * 60 * 60 * 1e3;
|
|
1607
|
+
// 7 days
|
|
1608
|
+
/**
|
|
1609
|
+
* Generate a Hashcash challenge for a new DID
|
|
1610
|
+
*/
|
|
1611
|
+
generateChallenge(did, difficulty = 20) {
|
|
1612
|
+
const nonce = this.generateNonce();
|
|
1613
|
+
return {
|
|
1614
|
+
did,
|
|
1615
|
+
difficulty,
|
|
1616
|
+
nonce,
|
|
1617
|
+
timestamp: Date.now()
|
|
1618
|
+
};
|
|
1619
|
+
}
|
|
1620
|
+
/**
|
|
1621
|
+
* Verify a Hashcash challenge solution
|
|
1622
|
+
*/
|
|
1623
|
+
verifyChallenge(solution) {
|
|
1624
|
+
const { challenge, solution: solutionNonce } = solution;
|
|
1625
|
+
if (Date.now() - challenge.timestamp > 60 * 60 * 1e3) {
|
|
1626
|
+
logger8.warn("Challenge expired", { did: challenge.did });
|
|
1627
|
+
return false;
|
|
1628
|
+
}
|
|
1629
|
+
const data = `${challenge.did}:${challenge.nonce}:${solutionNonce}`;
|
|
1630
|
+
const hash = sha256(new TextEncoder().encode(data));
|
|
1631
|
+
const leadingZeros = this.countLeadingZeroBits(hash);
|
|
1632
|
+
const valid = leadingZeros >= challenge.difficulty;
|
|
1633
|
+
if (valid) {
|
|
1634
|
+
logger8.info("Challenge verified", { did: challenge.did, leadingZeros });
|
|
1635
|
+
} else {
|
|
1636
|
+
logger8.warn("Challenge failed", { did: challenge.did, leadingZeros, required: challenge.difficulty });
|
|
1637
|
+
}
|
|
1638
|
+
return valid;
|
|
1639
|
+
}
|
|
1640
|
+
/**
|
|
1641
|
+
* Check if an agent should be rate limited
|
|
1642
|
+
*/
|
|
1643
|
+
isRateLimited(did) {
|
|
1644
|
+
const record = this.rateLimits.get(did);
|
|
1645
|
+
if (!record) {
|
|
1646
|
+
return false;
|
|
1647
|
+
}
|
|
1648
|
+
const now = Date.now();
|
|
1649
|
+
const agentAge = now - record.firstSeen;
|
|
1650
|
+
if (agentAge > this.NEW_AGENT_WINDOW) {
|
|
1651
|
+
return false;
|
|
1652
|
+
}
|
|
1653
|
+
const recentRequests = record.requests.filter(
|
|
1654
|
+
(t) => now - t < this.RATE_LIMIT_WINDOW
|
|
1655
|
+
);
|
|
1656
|
+
return recentRequests.length >= this.MAX_REQUESTS_NEW;
|
|
1657
|
+
}
|
|
1658
|
+
/**
|
|
1659
|
+
* Record a request from an agent
|
|
1660
|
+
*/
|
|
1661
|
+
recordRequest(did) {
|
|
1662
|
+
const now = Date.now();
|
|
1663
|
+
let record = this.rateLimits.get(did);
|
|
1664
|
+
if (!record) {
|
|
1665
|
+
record = {
|
|
1666
|
+
did,
|
|
1667
|
+
requests: [],
|
|
1668
|
+
firstSeen: now
|
|
1669
|
+
};
|
|
1670
|
+
this.rateLimits.set(did, record);
|
|
1671
|
+
}
|
|
1672
|
+
record.requests.push(now);
|
|
1673
|
+
record.requests = record.requests.filter(
|
|
1674
|
+
(t) => now - t < this.RATE_LIMIT_WINDOW
|
|
1675
|
+
);
|
|
1676
|
+
logger8.debug("Recorded request", { did, count: record.requests.length });
|
|
1677
|
+
}
|
|
1678
|
+
/**
|
|
1679
|
+
* Get peer trust level based on age
|
|
1680
|
+
*/
|
|
1681
|
+
getPeerTrustLevel(peerId) {
|
|
1682
|
+
const firstSeen = this.peerFirstSeen.get(peerId);
|
|
1683
|
+
if (!firstSeen) {
|
|
1684
|
+
return "new";
|
|
1685
|
+
}
|
|
1686
|
+
const age = Date.now() - firstSeen;
|
|
1687
|
+
if (age < this.NEW_AGENT_WINDOW) {
|
|
1688
|
+
return "new";
|
|
1689
|
+
} else if (age < this.ESTABLISHED_THRESHOLD) {
|
|
1690
|
+
return "established";
|
|
1691
|
+
} else {
|
|
1692
|
+
return "trusted";
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1695
|
+
/**
|
|
1696
|
+
* Record first seen time for a peer
|
|
1697
|
+
*/
|
|
1698
|
+
recordPeerSeen(peerId) {
|
|
1699
|
+
if (!this.peerFirstSeen.has(peerId)) {
|
|
1700
|
+
this.peerFirstSeen.set(peerId, Date.now());
|
|
1701
|
+
logger8.debug("Recorded new peer", { peerId });
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1704
|
+
/**
|
|
1705
|
+
* Clean up old records
|
|
1706
|
+
*/
|
|
1707
|
+
cleanup() {
|
|
1708
|
+
const now = Date.now();
|
|
1709
|
+
const cutoff = now - this.NEW_AGENT_WINDOW;
|
|
1710
|
+
for (const [did, record] of this.rateLimits.entries()) {
|
|
1711
|
+
if (record.firstSeen < cutoff) {
|
|
1712
|
+
this.rateLimits.delete(did);
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
logger8.info("Cleaned up Sybil defense records", {
|
|
1716
|
+
rateLimits: this.rateLimits.size,
|
|
1717
|
+
peers: this.peerFirstSeen.size
|
|
1718
|
+
});
|
|
1719
|
+
}
|
|
1720
|
+
/**
|
|
1721
|
+
* Generate a random nonce
|
|
1722
|
+
*/
|
|
1723
|
+
generateNonce() {
|
|
1724
|
+
const bytes = new Uint8Array(16);
|
|
1725
|
+
crypto.getRandomValues(bytes);
|
|
1726
|
+
return bytesToHex(bytes);
|
|
1727
|
+
}
|
|
1728
|
+
/**
|
|
1729
|
+
* Count leading zero bits in a hash
|
|
1730
|
+
*/
|
|
1731
|
+
countLeadingZeroBits(hash) {
|
|
1732
|
+
let count = 0;
|
|
1733
|
+
for (const byte of hash) {
|
|
1734
|
+
if (byte === 0) {
|
|
1735
|
+
count += 8;
|
|
1736
|
+
} else {
|
|
1737
|
+
let b = byte;
|
|
1738
|
+
while ((b & 128) === 0) {
|
|
1739
|
+
count++;
|
|
1740
|
+
b <<= 1;
|
|
1741
|
+
}
|
|
1742
|
+
break;
|
|
1743
|
+
}
|
|
1744
|
+
}
|
|
1745
|
+
return count;
|
|
1746
|
+
}
|
|
1747
|
+
};
|
|
1748
|
+
var logger9 = createLogger("trust-system");
|
|
1749
|
+
var TrustSystem = class {
|
|
1750
|
+
metrics;
|
|
1751
|
+
history;
|
|
1752
|
+
endorsements;
|
|
1753
|
+
sybilDefense;
|
|
1754
|
+
trustCache = /* @__PURE__ */ new Map();
|
|
1755
|
+
CACHE_TTL = 5 * 60 * 1e3;
|
|
1756
|
+
// 5 minutes
|
|
1757
|
+
constructor(config) {
|
|
1758
|
+
this.metrics = new TrustMetrics();
|
|
1759
|
+
this.history = new InteractionHistory(`${config.dbPath}/interactions`);
|
|
1760
|
+
const endorsementDb = new Level(`${config.dbPath}/endorsements`, {
|
|
1761
|
+
valueEncoding: "json"
|
|
1762
|
+
});
|
|
1763
|
+
this.endorsements = new EndorsementManager(endorsementDb, config.getPublicKey);
|
|
1764
|
+
this.sybilDefense = new SybilDefense();
|
|
1765
|
+
}
|
|
1766
|
+
/**
|
|
1767
|
+
* Initialize the trust system
|
|
1768
|
+
*/
|
|
1769
|
+
async start() {
|
|
1770
|
+
await this.history.open();
|
|
1771
|
+
logger9.info("Trust system started");
|
|
1772
|
+
}
|
|
1773
|
+
/**
|
|
1774
|
+
* Shutdown the trust system
|
|
1775
|
+
*/
|
|
1776
|
+
async stop() {
|
|
1777
|
+
await this.history.close();
|
|
1778
|
+
logger9.info("Trust system stopped");
|
|
1779
|
+
}
|
|
1780
|
+
/**
|
|
1781
|
+
* Record an interaction
|
|
1782
|
+
*/
|
|
1783
|
+
async recordInteraction(interaction) {
|
|
1784
|
+
await this.history.record(interaction);
|
|
1785
|
+
this.sybilDefense.recordRequest(interaction.agentDid);
|
|
1786
|
+
this.trustCache.delete(interaction.agentDid);
|
|
1787
|
+
}
|
|
1788
|
+
/**
|
|
1789
|
+
* Get trust score for an agent
|
|
1790
|
+
*/
|
|
1791
|
+
async getTrustScore(agentDid) {
|
|
1792
|
+
const cached = this.trustCache.get(agentDid);
|
|
1793
|
+
if (cached && Date.now() - cached.timestamp < this.CACHE_TTL) {
|
|
1794
|
+
return cached.score;
|
|
1795
|
+
}
|
|
1796
|
+
const stats = await this.history.getStats(agentDid);
|
|
1797
|
+
const endorsementList = await this.endorsements.getEndorsements(agentDid);
|
|
1798
|
+
const uptime = 1;
|
|
1799
|
+
const score = this.metrics.calculateScore(stats, endorsementList.length, uptime);
|
|
1800
|
+
this.trustCache.set(agentDid, { score, timestamp: Date.now() });
|
|
1801
|
+
return score;
|
|
1802
|
+
}
|
|
1803
|
+
/**
|
|
1804
|
+
* Create an endorsement
|
|
1805
|
+
*/
|
|
1806
|
+
async endorse(fromDid, toDid, score, reason, signFn) {
|
|
1807
|
+
const endorsement = await this.endorsements.endorse(fromDid, toDid, score, reason, signFn);
|
|
1808
|
+
await this.endorsements.publish(endorsement);
|
|
1809
|
+
this.trustCache.delete(toDid);
|
|
1810
|
+
return endorsement;
|
|
1811
|
+
}
|
|
1812
|
+
/**
|
|
1813
|
+
* Get endorsements for an agent
|
|
1814
|
+
*/
|
|
1815
|
+
async getEndorsements(agentDid) {
|
|
1816
|
+
return this.endorsements.getEndorsements(agentDid);
|
|
1817
|
+
}
|
|
1818
|
+
/**
|
|
1819
|
+
* Verify an endorsement
|
|
1820
|
+
*/
|
|
1821
|
+
async verifyEndorsement(endorsement, verifyFn) {
|
|
1822
|
+
return this.endorsements.verify(endorsement, verifyFn);
|
|
1823
|
+
}
|
|
1824
|
+
/**
|
|
1825
|
+
* Check if agent should be rate limited
|
|
1826
|
+
*/
|
|
1827
|
+
isRateLimited(agentDid) {
|
|
1828
|
+
return this.sybilDefense.isRateLimited(agentDid);
|
|
1829
|
+
}
|
|
1830
|
+
/**
|
|
1831
|
+
* Generate Sybil defense challenge
|
|
1832
|
+
*/
|
|
1833
|
+
generateChallenge(did, difficulty) {
|
|
1834
|
+
return this.sybilDefense.generateChallenge(did, difficulty);
|
|
1835
|
+
}
|
|
1836
|
+
/**
|
|
1837
|
+
* Verify Sybil defense challenge
|
|
1838
|
+
*/
|
|
1839
|
+
verifyChallenge(solution) {
|
|
1840
|
+
return this.sybilDefense.verifyChallenge(solution);
|
|
1841
|
+
}
|
|
1842
|
+
/**
|
|
1843
|
+
* Get peer trust level
|
|
1844
|
+
*/
|
|
1845
|
+
getPeerTrustLevel(peerId) {
|
|
1846
|
+
return this.sybilDefense.getPeerTrustLevel(peerId);
|
|
1847
|
+
}
|
|
1848
|
+
/**
|
|
1849
|
+
* Record peer seen
|
|
1850
|
+
*/
|
|
1851
|
+
recordPeerSeen(peerId) {
|
|
1852
|
+
this.sybilDefense.recordPeerSeen(peerId);
|
|
1853
|
+
}
|
|
1854
|
+
/**
|
|
1855
|
+
* Get interaction history
|
|
1856
|
+
*/
|
|
1857
|
+
async getHistory(agentDid, limit) {
|
|
1858
|
+
return this.history.getHistory(agentDid, limit);
|
|
1859
|
+
}
|
|
1860
|
+
/**
|
|
1861
|
+
* Clean up old data
|
|
1862
|
+
*/
|
|
1863
|
+
async cleanup() {
|
|
1864
|
+
await this.history.cleanup();
|
|
1865
|
+
this.sybilDefense.cleanup();
|
|
1866
|
+
this.trustCache.clear();
|
|
1867
|
+
logger9.info("Trust system cleanup completed");
|
|
1868
|
+
}
|
|
1869
|
+
};
|
|
1870
|
+
function createTrustSystem(config) {
|
|
1871
|
+
return new TrustSystem(config);
|
|
1872
|
+
}
|
|
1873
|
+
|
|
1874
|
+
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, createDHTOperations, createDefaultTrustScore, createEnvelope, createLegacyAgentCard, createLogger, createMessageRouter, createNode, createSemanticSearch, createTrustSystem, decodeAgentCard, decodeFromCBOR, decodeFromJSON, decodeMessage, decodeMessageJSON, deriveDID, downgradeToLegacyCard, encodeForDHT, encodeForWeb, encodeMessage, encodeMessageJSON, exportKeyPair, extractPublicKey, generateKeyPair, getAgentCardContext, getEncodedSize, importKeyPair, isLegacyCard, isValidContext, matchesCapability, sign, signAgentCard, signEnvelope, signMessage, upgradeLegacyCard, validateAgentCard, validateDID, validateEnvelope, verify, verifyAgentCard, verifyEnvelope, verifyMessage };
|
|
1875
|
+
//# sourceMappingURL=index.js.map
|
|
1876
|
+
//# sourceMappingURL=index.js.map
|