@highway1/core 0.1.43 → 0.1.45
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.js +68 -40
- package/dist/index.js.map +1 -1
- package/package.json +18 -4
- package/src/discovery/agent-card-encoder.ts +119 -0
- package/src/discovery/agent-card-schema.ts +87 -0
- package/src/discovery/agent-card-types.ts +99 -0
- package/src/discovery/agent-card.ts +190 -0
- package/src/discovery/bootstrap.ts +63 -0
- package/src/discovery/capability-matcher.ts +167 -0
- package/src/discovery/dht.ts +310 -0
- package/src/discovery/index.ts +3 -0
- package/src/discovery/search-index.ts +247 -0
- package/src/discovery/semantic-search.ts +218 -0
- package/src/identity/did.ts +48 -0
- package/src/identity/document.ts +77 -0
- package/src/identity/index.ts +4 -0
- package/src/identity/keys.ts +79 -0
- package/src/identity/signer.ts +55 -0
- package/src/index.ts +33 -0
- package/src/messaging/codec.ts +47 -0
- package/src/messaging/envelope.ts +107 -0
- package/src/messaging/index.ts +3 -0
- package/src/messaging/router.ts +384 -0
- package/src/transport/connection.ts +77 -0
- package/src/transport/index.ts +2 -0
- package/src/transport/node.ts +152 -0
- package/src/trust/endorsement.ts +167 -0
- package/src/trust/index.ts +194 -0
- package/src/trust/interaction-history.ts +155 -0
- package/src/trust/sybil-defense.ts +232 -0
- package/src/trust/trust-score.ts +136 -0
- package/src/utils/errors.ts +38 -0
- package/src/utils/logger.ts +48 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Capability Matcher
|
|
3
|
+
*
|
|
4
|
+
* Matches semantic queries against agent capabilities
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Capability } from './agent-card-types.js';
|
|
8
|
+
import type { SemanticQuery } from './search-index.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Capability Matcher
|
|
12
|
+
*/
|
|
13
|
+
export class CapabilityMatcher {
|
|
14
|
+
/**
|
|
15
|
+
* Match a query against a capability
|
|
16
|
+
* Returns a score between 0 and 1
|
|
17
|
+
*/
|
|
18
|
+
match(query: SemanticQuery, capability: Capability): number {
|
|
19
|
+
let score = 0;
|
|
20
|
+
let weights = 0;
|
|
21
|
+
|
|
22
|
+
// Exact capability ID match
|
|
23
|
+
if (query.capability && capability.id === query.capability) {
|
|
24
|
+
return 1.0;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Fuzzy capability name match
|
|
28
|
+
if (query.capability) {
|
|
29
|
+
const nameScore = this.fuzzyMatch(query.capability, capability.name);
|
|
30
|
+
score += nameScore * 0.4;
|
|
31
|
+
weights += 0.4;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Text keyword match
|
|
35
|
+
if (query.text) {
|
|
36
|
+
const keywords = this.extractKeywords(query.text);
|
|
37
|
+
const keywordScore = this.matchKeywords(keywords, capability);
|
|
38
|
+
score += keywordScore * 0.4;
|
|
39
|
+
weights += 0.4;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Type hierarchy match
|
|
43
|
+
if (query.capability && capability['@type']) {
|
|
44
|
+
const typeScore = this.matchesType(query.capability, capability['@type']) ? 0.6 : 0;
|
|
45
|
+
score += typeScore * 0.2;
|
|
46
|
+
weights += 0.2;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return weights > 0 ? score / weights : 0;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Extract keywords from natural language text
|
|
54
|
+
*/
|
|
55
|
+
extractKeywords(text: string): string[] {
|
|
56
|
+
// Remove common stop words
|
|
57
|
+
const stopWords = new Set([
|
|
58
|
+
'a', 'an', 'the', 'is', 'are', 'was', 'were', 'be', 'been',
|
|
59
|
+
'to', 'from', 'in', 'on', 'at', 'by', 'for', 'with', 'about',
|
|
60
|
+
'can', 'could', 'should', 'would', 'will', 'do', 'does', 'did',
|
|
61
|
+
'i', 'you', 'he', 'she', 'it', 'we', 'they', 'me', 'him', 'her',
|
|
62
|
+
]);
|
|
63
|
+
|
|
64
|
+
return text
|
|
65
|
+
.toLowerCase()
|
|
66
|
+
.split(/\W+/)
|
|
67
|
+
.filter(word => word.length > 2 && !stopWords.has(word));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Match keywords against capability
|
|
72
|
+
*/
|
|
73
|
+
matchKeywords(keywords: string[], capability: Capability): number {
|
|
74
|
+
if (keywords.length === 0) return 0;
|
|
75
|
+
|
|
76
|
+
const capText = `${capability.name} ${capability.description}`.toLowerCase();
|
|
77
|
+
const matches = keywords.filter(keyword => capText.includes(keyword));
|
|
78
|
+
|
|
79
|
+
return matches.length / keywords.length;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Check if query matches capability type hierarchy
|
|
84
|
+
*/
|
|
85
|
+
matchesType(query: string, type: string): boolean {
|
|
86
|
+
const queryLower = query.toLowerCase();
|
|
87
|
+
const typeLower = type.toLowerCase();
|
|
88
|
+
|
|
89
|
+
// Direct match
|
|
90
|
+
if (typeLower.includes(queryLower)) return true;
|
|
91
|
+
|
|
92
|
+
// Common type mappings
|
|
93
|
+
const typeMap: Record<string, string[]> = {
|
|
94
|
+
translate: ['translation', 'translationservice'],
|
|
95
|
+
review: ['codereview', 'reviewservice'],
|
|
96
|
+
analyze: ['analysis', 'dataanalysis'],
|
|
97
|
+
generate: ['generation', 'textgeneration', 'imagegeneration'],
|
|
98
|
+
search: ['searchservice', 'query'],
|
|
99
|
+
compute: ['computation', 'computationservice'],
|
|
100
|
+
store: ['storage', 'storageservice'],
|
|
101
|
+
message: ['messaging', 'messagingservice'],
|
|
102
|
+
auth: ['authentication', 'authenticationservice'],
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
for (const [key, values] of Object.entries(typeMap)) {
|
|
106
|
+
if (queryLower.includes(key) && values.some(v => typeLower.includes(v))) {
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Fuzzy string matching using Levenshtein distance
|
|
116
|
+
*/
|
|
117
|
+
private fuzzyMatch(query: string, target: string): number {
|
|
118
|
+
const queryLower = query.toLowerCase();
|
|
119
|
+
const targetLower = target.toLowerCase();
|
|
120
|
+
|
|
121
|
+
// Exact match
|
|
122
|
+
if (queryLower === targetLower) return 1.0;
|
|
123
|
+
|
|
124
|
+
// Substring match
|
|
125
|
+
if (targetLower.includes(queryLower)) return 0.8;
|
|
126
|
+
if (queryLower.includes(targetLower)) return 0.7;
|
|
127
|
+
|
|
128
|
+
// Levenshtein distance
|
|
129
|
+
const distance = this.levenshteinDistance(queryLower, targetLower);
|
|
130
|
+
const maxLen = Math.max(queryLower.length, targetLower.length);
|
|
131
|
+
const similarity = 1 - distance / maxLen;
|
|
132
|
+
|
|
133
|
+
// Only return positive scores for reasonable similarity
|
|
134
|
+
return similarity > 0.5 ? similarity * 0.6 : 0;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Calculate Levenshtein distance between two strings
|
|
139
|
+
*/
|
|
140
|
+
private levenshteinDistance(a: string, b: string): number {
|
|
141
|
+
const matrix: number[][] = [];
|
|
142
|
+
|
|
143
|
+
for (let i = 0; i <= b.length; i++) {
|
|
144
|
+
matrix[i] = [i];
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
for (let j = 0; j <= a.length; j++) {
|
|
148
|
+
matrix[0][j] = j;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
for (let i = 1; i <= b.length; i++) {
|
|
152
|
+
for (let j = 1; j <= a.length; j++) {
|
|
153
|
+
if (b.charAt(i - 1) === a.charAt(j - 1)) {
|
|
154
|
+
matrix[i][j] = matrix[i - 1][j - 1];
|
|
155
|
+
} else {
|
|
156
|
+
matrix[i][j] = Math.min(
|
|
157
|
+
matrix[i - 1][j - 1] + 1, // substitution
|
|
158
|
+
matrix[i][j - 1] + 1, // insertion
|
|
159
|
+
matrix[i - 1][j] + 1 // deletion
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return matrix[b.length][a.length];
|
|
166
|
+
}
|
|
167
|
+
}
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
import type { Libp2p } from 'libp2p';
|
|
2
|
+
import type { AgentCard } from './agent-card.js';
|
|
3
|
+
import { createLogger } from '../utils/logger.js';
|
|
4
|
+
import { DiscoveryError } from '../utils/errors.js';
|
|
5
|
+
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string';
|
|
6
|
+
import { toString as uint8ArrayToString } from 'uint8arrays/to-string';
|
|
7
|
+
import { encodeForDHT, decodeFromCBOR } from './agent-card-encoder.js';
|
|
8
|
+
import { createSemanticSearch } from './semantic-search.js';
|
|
9
|
+
import type { SemanticQuery } from './search-index.js';
|
|
10
|
+
|
|
11
|
+
const logger = createLogger('dht');
|
|
12
|
+
|
|
13
|
+
export interface ResolvedDID {
|
|
14
|
+
peerId: string;
|
|
15
|
+
multiaddrs: string[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface CachedPeerInfo {
|
|
19
|
+
peerId: string;
|
|
20
|
+
multiaddrs: string[];
|
|
21
|
+
timestamp: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const peerCache = new Map<string, CachedPeerInfo>();
|
|
25
|
+
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
|
|
26
|
+
|
|
27
|
+
export interface DHTOperations {
|
|
28
|
+
publishAgentCard: (card: AgentCard) => Promise<void>;
|
|
29
|
+
queryAgentCard: (did: string) => Promise<AgentCard | null>;
|
|
30
|
+
queryByCapability: (capability: string) => Promise<AgentCard[]>;
|
|
31
|
+
searchSemantic: (query: SemanticQuery) => Promise<AgentCard[]>;
|
|
32
|
+
resolveDID: (did: string) => Promise<ResolvedDID | null>;
|
|
33
|
+
queryRelayPeers: () => Promise<string[]>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Extract a DHT value from a get() event, handling both VALUE and PEER_RESPONSE */
|
|
37
|
+
function extractValue(event: any): Uint8Array | null {
|
|
38
|
+
if (event.name === 'VALUE' && event.value) return event.value;
|
|
39
|
+
if (event.name === 'PEER_RESPONSE' && event.value) return event.value;
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Normalize capability name for use as a DHT key segment */
|
|
44
|
+
function capKey(cap: string): string {
|
|
45
|
+
return cap.toLowerCase().replace(/[^a-z0-9_-]/g, '_');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Read a DID list stored at a DHT key (newline-separated) */
|
|
49
|
+
async function readDIDList(dht: any, key: Uint8Array): Promise<string[]> {
|
|
50
|
+
try {
|
|
51
|
+
for await (const event of dht.get(key, { signal: AbortSignal.timeout(30000) })) {
|
|
52
|
+
const raw = extractValue(event);
|
|
53
|
+
if (raw) {
|
|
54
|
+
const text = uint8ArrayToString(raw);
|
|
55
|
+
return text.split('\n').filter(Boolean);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
} catch {
|
|
59
|
+
// key not found or timeout
|
|
60
|
+
}
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Write a DID list to a DHT key (newline-separated), deduplicating */
|
|
65
|
+
async function writeDIDList(dht: any, key: Uint8Array, dids: string[]): Promise<void> {
|
|
66
|
+
const value = uint8ArrayFromString([...new Set(dids)].join('\n'));
|
|
67
|
+
// Retry up to 3 times with 30s timeout each
|
|
68
|
+
for (let attempt = 1; attempt <= 3; attempt++) {
|
|
69
|
+
try {
|
|
70
|
+
for await (const _ of dht.put(key, value, { signal: AbortSignal.timeout(30000) })) { /* consume */ }
|
|
71
|
+
return; // Success
|
|
72
|
+
} catch (e: any) {
|
|
73
|
+
if (e?.name === 'AbortError' && attempt < 3) {
|
|
74
|
+
logger.debug(`DHT put timeout, retrying (${attempt}/3)...`);
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (e?.name !== 'AbortError') throw e;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Create DHT operations for a libp2p node
|
|
84
|
+
*/
|
|
85
|
+
export function createDHTOperations(libp2p: Libp2p): DHTOperations {
|
|
86
|
+
const operations: DHTOperations = {} as DHTOperations;
|
|
87
|
+
const searchEngine = createSemanticSearch(operations);
|
|
88
|
+
|
|
89
|
+
return Object.assign(operations, {
|
|
90
|
+
publishAgentCard: async (card: AgentCard) => {
|
|
91
|
+
try {
|
|
92
|
+
const dht = (libp2p as any).services?.dht;
|
|
93
|
+
if (!dht) throw new DiscoveryError('DHT service not available');
|
|
94
|
+
|
|
95
|
+
// 1. Store Agent Card under /clawiverse/agent/<did>
|
|
96
|
+
const agentKey = uint8ArrayFromString(`/clawiverse/agent/${card.did}`);
|
|
97
|
+
// Retry up to 3 times with 30s timeout each
|
|
98
|
+
for (let attempt = 1; attempt <= 3; attempt++) {
|
|
99
|
+
try {
|
|
100
|
+
for await (const _ of dht.put(agentKey, encodeForDHT(card), { signal: AbortSignal.timeout(30000) })) { /* consume */ }
|
|
101
|
+
break; // Success
|
|
102
|
+
} catch (e: any) {
|
|
103
|
+
if (e?.name === 'AbortError' && attempt < 3) {
|
|
104
|
+
logger.debug(`DHT put agent card timeout, retrying (${attempt}/3)...`);
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
logger.warn('DHT put agent card failed (non-fatal)', { error: (e as Error).message });
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// 2. For each capability, append this DID to /clawiverse/cap/<capability>
|
|
113
|
+
// Skip capability indexing in slow networks - rely on local index and semantic search instead
|
|
114
|
+
const caps: string[] = (card.capabilities ?? []).flatMap((c: any) => {
|
|
115
|
+
if (typeof c === 'string') return [c];
|
|
116
|
+
return [c.name, c.id].filter((v): v is string => typeof v === 'string' && v.length > 0);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// Always index under the special "all" key so name/description search works
|
|
120
|
+
caps.push('__all__');
|
|
121
|
+
|
|
122
|
+
// Keep indexing lightweight, but preserve relay discovery path.
|
|
123
|
+
const importantCaps = ['__all__'];
|
|
124
|
+
if (caps.some((cap) => capKey(cap) === 'relay')) {
|
|
125
|
+
importantCaps.push('relay');
|
|
126
|
+
}
|
|
127
|
+
await Promise.all(importantCaps.map(async (cap) => {
|
|
128
|
+
const capKeyStr = `/clawiverse/cap/${capKey(cap)}`;
|
|
129
|
+
const capDHTKey = uint8ArrayFromString(capKeyStr);
|
|
130
|
+
try {
|
|
131
|
+
const existing = await readDIDList(dht, capDHTKey);
|
|
132
|
+
if (!existing.includes(card.did)) {
|
|
133
|
+
await writeDIDList(dht, capDHTKey, [...existing, card.did]);
|
|
134
|
+
logger.debug('Indexed capability in DHT', { cap: capKey(cap), did: card.did });
|
|
135
|
+
}
|
|
136
|
+
} catch (e: any) {
|
|
137
|
+
logger.warn('Failed to index capability (non-fatal)', { cap: capKey(cap), error: (e as Error).message });
|
|
138
|
+
}
|
|
139
|
+
}));
|
|
140
|
+
|
|
141
|
+
// 3. Index locally for fast in-process search
|
|
142
|
+
searchEngine.indexAgentCard(card);
|
|
143
|
+
|
|
144
|
+
logger.info('Published Agent Card to DHT', { did: card.did });
|
|
145
|
+
} catch (error) {
|
|
146
|
+
// DHT publish failure is non-fatal - local index is still available
|
|
147
|
+
logger.warn('Failed to publish Agent Card to DHT (non-fatal)', { error: (error as Error).message });
|
|
148
|
+
// Still index locally even if DHT fails
|
|
149
|
+
searchEngine.indexAgentCard(card);
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
queryAgentCard: async (did: string) => {
|
|
154
|
+
try {
|
|
155
|
+
const dht = (libp2p as any).services?.dht;
|
|
156
|
+
if (!dht) throw new DiscoveryError('DHT service not available');
|
|
157
|
+
|
|
158
|
+
const key = uint8ArrayFromString(`/clawiverse/agent/${did}`);
|
|
159
|
+
for await (const event of dht.get(key)) {
|
|
160
|
+
const raw = extractValue(event);
|
|
161
|
+
if (raw) {
|
|
162
|
+
const card = decodeFromCBOR(raw);
|
|
163
|
+
searchEngine.indexAgentCard(card);
|
|
164
|
+
logger.debug('Found Agent Card in DHT', { did });
|
|
165
|
+
return card;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
logger.debug('Agent Card not found in DHT', { did });
|
|
170
|
+
return null;
|
|
171
|
+
} catch (error) {
|
|
172
|
+
logger.warn('Failed to query Agent Card', { did, error });
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
|
|
177
|
+
queryByCapability: async (capability: string) => {
|
|
178
|
+
try {
|
|
179
|
+
const dht = (libp2p as any).services?.dht;
|
|
180
|
+
|
|
181
|
+
// 1. Check local index first
|
|
182
|
+
const local = searchEngine.getAllIndexedCards().filter(card =>
|
|
183
|
+
card.capabilities.some((cap: any) => {
|
|
184
|
+
const name = typeof cap === 'string' ? cap : cap.name;
|
|
185
|
+
return name?.toLowerCase().includes(capability.toLowerCase());
|
|
186
|
+
})
|
|
187
|
+
);
|
|
188
|
+
if (local.length > 0) return local;
|
|
189
|
+
|
|
190
|
+
// 2. Fall back to DHT capability index
|
|
191
|
+
if (!dht) return [];
|
|
192
|
+
|
|
193
|
+
const capDHTKey = uint8ArrayFromString(`/clawiverse/cap/${capKey(capability)}`);
|
|
194
|
+
const dids = await readDIDList(dht, capDHTKey);
|
|
195
|
+
logger.debug('DHT capability index', { capability, dids });
|
|
196
|
+
|
|
197
|
+
// Fetch each Agent Card in parallel
|
|
198
|
+
const cards = await Promise.all(
|
|
199
|
+
dids.map(async (did) => {
|
|
200
|
+
const key = uint8ArrayFromString(`/clawiverse/agent/${did}`);
|
|
201
|
+
try {
|
|
202
|
+
for await (const event of dht.get(key)) {
|
|
203
|
+
const raw = extractValue(event);
|
|
204
|
+
if (raw) {
|
|
205
|
+
const card = decodeFromCBOR(raw);
|
|
206
|
+
searchEngine.indexAgentCard(card);
|
|
207
|
+
return card;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
} catch { /* skip unreachable */ }
|
|
211
|
+
return null;
|
|
212
|
+
})
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
return cards.filter((c): c is AgentCard => c !== null);
|
|
216
|
+
} catch (error) {
|
|
217
|
+
throw new DiscoveryError('Failed to query by capability', error);
|
|
218
|
+
}
|
|
219
|
+
},
|
|
220
|
+
|
|
221
|
+
searchSemantic: async (query: SemanticQuery) => {
|
|
222
|
+
try {
|
|
223
|
+
// Always pull from DHT __all__ index to discover remote nodes
|
|
224
|
+
const dht = (libp2p as any).services?.dht;
|
|
225
|
+
if (dht) {
|
|
226
|
+
const allKey = uint8ArrayFromString('/clawiverse/cap/__all__');
|
|
227
|
+
const dids = await readDIDList(dht, allKey);
|
|
228
|
+
logger.debug('DHT __all__ index', { count: dids.length });
|
|
229
|
+
|
|
230
|
+
await Promise.all(
|
|
231
|
+
dids.map(async (did) => {
|
|
232
|
+
const key = uint8ArrayFromString(`/clawiverse/agent/${did}`);
|
|
233
|
+
try {
|
|
234
|
+
for await (const event of dht.get(key)) {
|
|
235
|
+
const raw = extractValue(event);
|
|
236
|
+
if (raw) {
|
|
237
|
+
searchEngine.indexAgentCard(decodeFromCBOR(raw));
|
|
238
|
+
break;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
} catch { /* skip */ }
|
|
242
|
+
})
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return searchEngine.search(query);
|
|
247
|
+
} catch (error) {
|
|
248
|
+
throw new DiscoveryError('Failed to perform semantic search', error);
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
|
|
252
|
+
queryRelayPeers: async (): Promise<string[]> => {
|
|
253
|
+
const dht = (libp2p as any).services?.dht;
|
|
254
|
+
if (!dht) return [];
|
|
255
|
+
const capDHTKey = uint8ArrayFromString('/clawiverse/cap/relay');
|
|
256
|
+
const dids = await readDIDList(dht, capDHTKey);
|
|
257
|
+
const addrs: string[] = [];
|
|
258
|
+
await Promise.all(dids.map(async (did) => {
|
|
259
|
+
const card = await operations.queryAgentCard(did);
|
|
260
|
+
if (card?.endpoints) {
|
|
261
|
+
addrs.push(...card.endpoints.filter((e: string) => !e.includes('/p2p-circuit/')));
|
|
262
|
+
}
|
|
263
|
+
}));
|
|
264
|
+
return addrs;
|
|
265
|
+
},
|
|
266
|
+
|
|
267
|
+
resolveDID: async (did: string) => {
|
|
268
|
+
try {
|
|
269
|
+
// Check cache first
|
|
270
|
+
const cached = peerCache.get(did);
|
|
271
|
+
if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
|
|
272
|
+
logger.debug('Using cached peer info', { did });
|
|
273
|
+
return { peerId: cached.peerId, multiaddrs: cached.multiaddrs };
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const dht = (libp2p as any).services?.dht;
|
|
277
|
+
if (!dht) throw new DiscoveryError('DHT service not available');
|
|
278
|
+
|
|
279
|
+
const key = uint8ArrayFromString(`/clawiverse/agent/${did}`);
|
|
280
|
+
for await (const event of dht.get(key)) {
|
|
281
|
+
const raw = extractValue(event);
|
|
282
|
+
if (raw) {
|
|
283
|
+
const card = decodeFromCBOR(raw);
|
|
284
|
+
if (card.peerId) {
|
|
285
|
+
logger.debug('Resolved DID to peer', { did, peerId: card.peerId });
|
|
286
|
+
const result = { peerId: card.peerId, multiaddrs: card.endpoints || [] };
|
|
287
|
+
|
|
288
|
+
// Cache the result
|
|
289
|
+
peerCache.set(did, {
|
|
290
|
+
peerId: result.peerId,
|
|
291
|
+
multiaddrs: result.multiaddrs,
|
|
292
|
+
timestamp: Date.now()
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
return result;
|
|
296
|
+
}
|
|
297
|
+
logger.warn('Agent Card found but has no peerId', { did });
|
|
298
|
+
return null;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
logger.debug('DID not found in DHT', { did });
|
|
303
|
+
return null;
|
|
304
|
+
} catch (error) {
|
|
305
|
+
logger.warn('Failed to resolve DID', { did, error });
|
|
306
|
+
return null;
|
|
307
|
+
}
|
|
308
|
+
},
|
|
309
|
+
});
|
|
310
|
+
}
|