@lawrenceliang-btc/atel-sdk 0.8.7
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/LICENSE +21 -0
- package/README.md +221 -0
- package/bin/atel.mjs +2692 -0
- package/bin/tunnel-manager.mjs +171 -0
- package/dist/anchor/base.d.ts +21 -0
- package/dist/anchor/base.js +26 -0
- package/dist/anchor/bsc.d.ts +20 -0
- package/dist/anchor/bsc.js +25 -0
- package/dist/anchor/evm.d.ts +99 -0
- package/dist/anchor/evm.js +262 -0
- package/dist/anchor/index.d.ts +173 -0
- package/dist/anchor/index.js +165 -0
- package/dist/anchor/mock.d.ts +43 -0
- package/dist/anchor/mock.js +100 -0
- package/dist/anchor/solana.d.ts +95 -0
- package/dist/anchor/solana.js +298 -0
- package/dist/auditor/index.d.ts +54 -0
- package/dist/auditor/index.js +141 -0
- package/dist/collaboration/index.d.ts +146 -0
- package/dist/collaboration/index.js +237 -0
- package/dist/crypto/index.d.ts +162 -0
- package/dist/crypto/index.js +231 -0
- package/dist/endpoint/index.d.ts +147 -0
- package/dist/endpoint/index.js +390 -0
- package/dist/envelope/index.d.ts +104 -0
- package/dist/envelope/index.js +156 -0
- package/dist/executor/index.d.ts +71 -0
- package/dist/executor/index.js +398 -0
- package/dist/gateway/index.d.ts +278 -0
- package/dist/gateway/index.js +520 -0
- package/dist/graph/index.d.ts +215 -0
- package/dist/graph/index.js +524 -0
- package/dist/handshake/index.d.ts +166 -0
- package/dist/handshake/index.js +287 -0
- package/dist/identity/index.d.ts +155 -0
- package/dist/identity/index.js +250 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +28 -0
- package/dist/negotiation/index.d.ts +133 -0
- package/dist/negotiation/index.js +160 -0
- package/dist/network/index.d.ts +78 -0
- package/dist/network/index.js +207 -0
- package/dist/orchestrator/index.d.ts +190 -0
- package/dist/orchestrator/index.js +297 -0
- package/dist/policy/index.d.ts +100 -0
- package/dist/policy/index.js +206 -0
- package/dist/proof/index.d.ts +220 -0
- package/dist/proof/index.js +541 -0
- package/dist/registry/index.d.ts +98 -0
- package/dist/registry/index.js +129 -0
- package/dist/rollback/index.d.ts +76 -0
- package/dist/rollback/index.js +91 -0
- package/dist/schema/capability-schema.json +52 -0
- package/dist/schema/index.d.ts +128 -0
- package/dist/schema/index.js +163 -0
- package/dist/schema/task-schema.json +69 -0
- package/dist/score/index.d.ts +174 -0
- package/dist/score/index.js +275 -0
- package/dist/service/index.d.ts +34 -0
- package/dist/service/index.js +273 -0
- package/dist/service/server.d.ts +7 -0
- package/dist/service/server.js +22 -0
- package/dist/trace/index.d.ts +217 -0
- package/dist/trace/index.js +446 -0
- package/dist/trust/index.d.ts +84 -0
- package/dist/trust/index.js +107 -0
- package/dist/trust-sync/index.d.ts +30 -0
- package/dist/trust-sync/index.js +57 -0
- package/package.json +71 -0
- package/skill/SKILL.md +363 -0
- package/skill/references/commercial.md +184 -0
- package/skill/references/executor.md +356 -0
- package/skill/references/networking.md +64 -0
- package/skill/references/onchain.md +73 -0
- package/skill/references/security.md +96 -0
|
@@ -0,0 +1,524 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Module 8: Trust Graph
|
|
3
|
+
*
|
|
4
|
+
* Multi-dimensional trust graph tracking agent collaboration,
|
|
5
|
+
* scene performance, and trust propagation.
|
|
6
|
+
*
|
|
7
|
+
* Core formulas:
|
|
8
|
+
* DirectTrust(A,B,scene) = success_weighted_rate × recency × consistency × confidence
|
|
9
|
+
* IndirectTrust(A,C) = max path trust × hop_decay^(hops-1)
|
|
10
|
+
* CompositeTrust = α·Direct + β·Indirect + γ·ReputationBonus
|
|
11
|
+
*/
|
|
12
|
+
// ─── Constants ───────────────────────────────────────────────────
|
|
13
|
+
/** Recency decay rate: exp(-λ × days). λ = 0.01 */
|
|
14
|
+
const LAMBDA = 0.01;
|
|
15
|
+
/** Tasks needed for full confidence */
|
|
16
|
+
const FULL_CONFIDENCE_TASKS = 20;
|
|
17
|
+
/** Hop decay for indirect trust: each extra hop × 0.7 */
|
|
18
|
+
const HOP_DECAY = 0.7;
|
|
19
|
+
/** Default max BFS depth for indirect trust */
|
|
20
|
+
const DEFAULT_MAX_DEPTH = 3;
|
|
21
|
+
/** Composite weights */
|
|
22
|
+
const ALPHA = 0.6;
|
|
23
|
+
const BETA = 0.3;
|
|
24
|
+
const GAMMA = 0.1;
|
|
25
|
+
/** Behavior consistency suspicion threshold */
|
|
26
|
+
const BCS_THRESHOLD = 0.7;
|
|
27
|
+
const MAX_CLUSTER_CANDIDATES = 30;
|
|
28
|
+
const MAX_COMBINATION_CHECKS = 5000;
|
|
29
|
+
const RISK_FACTOR_MAP = {
|
|
30
|
+
low: 0.5,
|
|
31
|
+
medium: 1.0,
|
|
32
|
+
high: 2.0,
|
|
33
|
+
critical: 3.0,
|
|
34
|
+
};
|
|
35
|
+
// ─── Helper: Task Weight ─────────────────────────────────────────
|
|
36
|
+
/**
|
|
37
|
+
* Calculate task weight from execution parameters.
|
|
38
|
+
*
|
|
39
|
+
* Formula:
|
|
40
|
+
* task_weight = complexity × value × risk × novelty
|
|
41
|
+
*
|
|
42
|
+
* Where:
|
|
43
|
+
* complexity = min(1, tool_calls × 0.2 + duration_ms / 10000 × 0.3)
|
|
44
|
+
* value = min(1, max_cost / 10)
|
|
45
|
+
* risk = {low:0.5, medium:1.0, high:2.0, critical:3.0}
|
|
46
|
+
* novelty = 1 / (1 + ln(1 + similar_task_count))
|
|
47
|
+
*
|
|
48
|
+
* Example: tool_calls=5, duration_ms=8000, max_cost=5, risk='medium', similar=2
|
|
49
|
+
* complexity = min(1, 5×0.2 + 0.8×0.3) = min(1, 1.24) = 1
|
|
50
|
+
* value = min(1, 0.5) = 0.5
|
|
51
|
+
* risk = 1.0
|
|
52
|
+
* novelty = 1/(1+ln(3)) ≈ 0.476
|
|
53
|
+
* weight = 1 × 0.5 × 1.0 × 0.476 ≈ 0.238
|
|
54
|
+
*/
|
|
55
|
+
export function calculateTaskWeight(params) {
|
|
56
|
+
const { tool_calls, duration_ms, max_cost, risk_level, similar_task_count } = params;
|
|
57
|
+
const complexity = Math.min(1, tool_calls * 0.2 + (duration_ms / 10000) * 0.3);
|
|
58
|
+
const value = Math.min(1, max_cost / 10);
|
|
59
|
+
const risk = RISK_FACTOR_MAP[risk_level] ?? 1.0;
|
|
60
|
+
const novelty = 1.0 / (1 + Math.log(1 + similar_task_count));
|
|
61
|
+
return complexity * value * risk * novelty;
|
|
62
|
+
}
|
|
63
|
+
// ─── Helper: Edge key ────────────────────────────────────────────
|
|
64
|
+
function edgeKey(from, to, scene) {
|
|
65
|
+
return `${from}:${to}:${scene}`;
|
|
66
|
+
}
|
|
67
|
+
// ─── TrustGraph Class ────────────────────────────────────────────
|
|
68
|
+
/**
|
|
69
|
+
* Multi-dimensional trust graph.
|
|
70
|
+
*
|
|
71
|
+
* Nodes = agents, edges = per-scene collaboration records.
|
|
72
|
+
* Supports direct, indirect, and composite trust queries,
|
|
73
|
+
* anomaly detection, and graph export.
|
|
74
|
+
*/
|
|
75
|
+
export class TrustGraph {
|
|
76
|
+
nodes = new Map();
|
|
77
|
+
edges = new Map();
|
|
78
|
+
// ── Node management ──────────────────────────────────────────
|
|
79
|
+
/** Register a new agent node. Idempotent — re-adding updates metadata. */
|
|
80
|
+
addNode(agentId, metadata) {
|
|
81
|
+
const existing = this.nodes.get(agentId);
|
|
82
|
+
if (existing) {
|
|
83
|
+
if (metadata)
|
|
84
|
+
existing.metadata = { ...existing.metadata, ...metadata };
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
this.nodes.set(agentId, {
|
|
88
|
+
agent_id: agentId,
|
|
89
|
+
registered_at: new Date().toISOString(),
|
|
90
|
+
total_interactions: 0,
|
|
91
|
+
scenes: new Set(),
|
|
92
|
+
metadata,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
getNode(agentId) {
|
|
96
|
+
return this.nodes.get(agentId);
|
|
97
|
+
}
|
|
98
|
+
/** Remove a node and all its edges. */
|
|
99
|
+
removeNode(agentId) {
|
|
100
|
+
this.nodes.delete(agentId);
|
|
101
|
+
for (const [key, edge] of this.edges) {
|
|
102
|
+
if (edge.from === agentId || edge.to === agentId) {
|
|
103
|
+
this.edges.delete(key);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
// ── Edge management ──────────────────────────────────────────
|
|
108
|
+
/**
|
|
109
|
+
* Record a single collaboration interaction.
|
|
110
|
+
* Auto-creates nodes if they don't exist.
|
|
111
|
+
* Updates edge statistics and node interaction counts.
|
|
112
|
+
*
|
|
113
|
+
* Consistency score is maintained as a running exponential moving average
|
|
114
|
+
* of success (1) / failure (0) variance across recent interactions.
|
|
115
|
+
*/
|
|
116
|
+
recordInteraction(interaction) {
|
|
117
|
+
const { from, to, scene, success, task_weight, duration_ms } = interaction;
|
|
118
|
+
// Auto-register nodes
|
|
119
|
+
if (!this.nodes.has(from))
|
|
120
|
+
this.addNode(from);
|
|
121
|
+
if (!this.nodes.has(to))
|
|
122
|
+
this.addNode(to);
|
|
123
|
+
const fromNode = this.nodes.get(from);
|
|
124
|
+
const toNode = this.nodes.get(to);
|
|
125
|
+
fromNode.total_interactions++;
|
|
126
|
+
toNode.total_interactions++;
|
|
127
|
+
fromNode.scenes.add(scene);
|
|
128
|
+
toNode.scenes.add(scene);
|
|
129
|
+
const key = edgeKey(from, to, scene);
|
|
130
|
+
let edge = this.edges.get(key);
|
|
131
|
+
if (!edge) {
|
|
132
|
+
edge = {
|
|
133
|
+
from, to, scene,
|
|
134
|
+
total_tasks: 0,
|
|
135
|
+
successful_tasks: 0,
|
|
136
|
+
failed_tasks: 0,
|
|
137
|
+
total_weight: 0,
|
|
138
|
+
successful_weight: 0,
|
|
139
|
+
avg_duration_ms: 0,
|
|
140
|
+
last_interaction: new Date().toISOString(),
|
|
141
|
+
consistency_score: 1.0,
|
|
142
|
+
};
|
|
143
|
+
this.edges.set(key, edge);
|
|
144
|
+
}
|
|
145
|
+
// Update running average duration
|
|
146
|
+
const totalDur = edge.avg_duration_ms * edge.total_tasks + duration_ms;
|
|
147
|
+
edge.total_tasks++;
|
|
148
|
+
edge.avg_duration_ms = totalDur / edge.total_tasks;
|
|
149
|
+
if (success) {
|
|
150
|
+
edge.successful_tasks++;
|
|
151
|
+
edge.successful_weight += task_weight;
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
edge.failed_tasks++;
|
|
155
|
+
}
|
|
156
|
+
edge.total_weight += task_weight;
|
|
157
|
+
edge.last_interaction = new Date().toISOString();
|
|
158
|
+
// Update consistency: EMA of success rate stability
|
|
159
|
+
const currentRate = edge.successful_tasks / edge.total_tasks;
|
|
160
|
+
const deviation = Math.abs((success ? 1 : 0) - currentRate);
|
|
161
|
+
edge.consistency_score = edge.consistency_score * 0.9 + (1 - deviation) * 0.1;
|
|
162
|
+
}
|
|
163
|
+
getEdge(from, to, scene) {
|
|
164
|
+
return this.edges.get(edgeKey(from, to, scene));
|
|
165
|
+
}
|
|
166
|
+
/** Get all edges involving a given agent (as from or to). */
|
|
167
|
+
getEdges(agentId) {
|
|
168
|
+
const result = [];
|
|
169
|
+
for (const edge of this.edges.values()) {
|
|
170
|
+
if (edge.from === agentId || edge.to === agentId) {
|
|
171
|
+
result.push(edge);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return result;
|
|
175
|
+
}
|
|
176
|
+
// ── Trust computation ────────────────────────────────────────
|
|
177
|
+
/**
|
|
178
|
+
* Direct trust between two agents in a scene.
|
|
179
|
+
*
|
|
180
|
+
* Formula:
|
|
181
|
+
* DirectTrust = swr × recency × consistency × confidence
|
|
182
|
+
*
|
|
183
|
+
* swr = successful_weight / total_weight
|
|
184
|
+
* recency = exp(-0.01 × days_since_last)
|
|
185
|
+
* consistency = edge.consistency_score
|
|
186
|
+
* confidence = min(1, total_tasks / 20)
|
|
187
|
+
*
|
|
188
|
+
* Example: swr=0.8, 10 days ago, consistency=0.9, 15 tasks
|
|
189
|
+
* recency = exp(-0.01×10) ≈ 0.905
|
|
190
|
+
* confidence = min(1, 15/20) = 0.75
|
|
191
|
+
* trust = 0.8 × 0.905 × 0.9 × 0.75 ≈ 0.489
|
|
192
|
+
*
|
|
193
|
+
* Returns trust_score=0 when no edge exists.
|
|
194
|
+
*/
|
|
195
|
+
directTrust(from, to, scene) {
|
|
196
|
+
const edge = this.edges.get(edgeKey(from, to, scene));
|
|
197
|
+
if (!edge || edge.total_weight === 0) {
|
|
198
|
+
return { trust_score: 0, confidence: 0, source: 'direct', details: { direct_trust: 0 } };
|
|
199
|
+
}
|
|
200
|
+
const swr = edge.successful_weight / edge.total_weight;
|
|
201
|
+
const daysSince = (Date.now() - new Date(edge.last_interaction).getTime()) / 86_400_000;
|
|
202
|
+
const recency = Math.exp(-LAMBDA * daysSince);
|
|
203
|
+
const consistency = edge.consistency_score;
|
|
204
|
+
const confidence = Math.min(1, edge.total_tasks / FULL_CONFIDENCE_TASKS);
|
|
205
|
+
const score = swr * recency * consistency * confidence;
|
|
206
|
+
return {
|
|
207
|
+
trust_score: score,
|
|
208
|
+
confidence,
|
|
209
|
+
source: 'direct',
|
|
210
|
+
details: { direct_trust: score },
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Indirect trust via intermediate nodes (BFS, max depth 3).
|
|
215
|
+
*
|
|
216
|
+
* For each path P from A to B:
|
|
217
|
+
* path_trust = ∏(DirectTrust(edge_i)) × 0.7^(|P|-1)
|
|
218
|
+
*
|
|
219
|
+
* Returns the strongest path.
|
|
220
|
+
*
|
|
221
|
+
* Example: A→M trust=0.8, M→B trust=0.7, depth=2
|
|
222
|
+
* path_trust = 0.8 × 0.7 × 0.7^(2-1) = 0.8 × 0.7 × 0.7 = 0.392
|
|
223
|
+
*/
|
|
224
|
+
indirectTrust(from, to, scene, maxDepth = DEFAULT_MAX_DEPTH) {
|
|
225
|
+
if (from === to) {
|
|
226
|
+
return { trust_score: 0, confidence: 0, source: 'indirect', details: { indirect_trust: 0 } };
|
|
227
|
+
}
|
|
228
|
+
const queue = [{ node: from, path: [from], trust: 1.0 }];
|
|
229
|
+
let bestTrust = 0;
|
|
230
|
+
let bestPath = [];
|
|
231
|
+
while (queue.length > 0) {
|
|
232
|
+
const current = queue.shift();
|
|
233
|
+
if (current.path.length > maxDepth + 1)
|
|
234
|
+
continue;
|
|
235
|
+
// Find outgoing edges for current node in this scene
|
|
236
|
+
for (const edge of this.edges.values()) {
|
|
237
|
+
if (edge.from !== current.node || edge.scene !== scene)
|
|
238
|
+
continue;
|
|
239
|
+
if (current.path.includes(edge.to))
|
|
240
|
+
continue; // no cycles
|
|
241
|
+
const dt = this.directTrust(edge.from, edge.to, scene);
|
|
242
|
+
const hopCount = current.path.length; // hops so far (edges = nodes - 1)
|
|
243
|
+
const pathTrust = current.trust * dt.trust_score * Math.pow(HOP_DECAY, hopCount - 1);
|
|
244
|
+
if (edge.to === to) {
|
|
245
|
+
// Skip direct (1-hop) paths — indirect means ≥ 2 hops
|
|
246
|
+
if (current.path.length >= 2 && pathTrust > bestTrust) {
|
|
247
|
+
bestTrust = pathTrust;
|
|
248
|
+
bestPath = [...current.path, edge.to];
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
else if (current.path.length < maxDepth + 1) {
|
|
252
|
+
queue.push({ node: edge.to, path: [...current.path, edge.to], trust: current.trust * dt.trust_score });
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return {
|
|
257
|
+
trust_score: bestTrust,
|
|
258
|
+
confidence: bestTrust > 0 ? 0.5 : 0, // indirect trust has lower confidence
|
|
259
|
+
source: 'indirect',
|
|
260
|
+
path: bestPath.length > 0 ? bestPath : undefined,
|
|
261
|
+
details: { indirect_trust: bestTrust },
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Composite trust combining direct, indirect, and reputation.
|
|
266
|
+
*
|
|
267
|
+
* CompositeTrust = α×Direct + β×Indirect + γ×ReputationBonus
|
|
268
|
+
* α=0.6, β=0.3, γ=0.1
|
|
269
|
+
*
|
|
270
|
+
* If no direct trust exists, α's weight transfers to β:
|
|
271
|
+
* β_effective = β + α = 0.9
|
|
272
|
+
*
|
|
273
|
+
* ReputationBonus = global_success_rate × 0.5
|
|
274
|
+
*/
|
|
275
|
+
compositeTrust(from, to, scene) {
|
|
276
|
+
const direct = this.directTrust(from, to, scene);
|
|
277
|
+
const indirect = this.indirectTrust(from, to, scene);
|
|
278
|
+
const repBonus = this._globalSuccessRate() * 0.5;
|
|
279
|
+
let alpha = ALPHA;
|
|
280
|
+
let beta = BETA;
|
|
281
|
+
const gamma = GAMMA;
|
|
282
|
+
// Transfer direct weight to indirect if no direct trust
|
|
283
|
+
if (direct.trust_score === 0) {
|
|
284
|
+
beta = beta + alpha;
|
|
285
|
+
alpha = 0;
|
|
286
|
+
}
|
|
287
|
+
const score = alpha * direct.trust_score + beta * indirect.trust_score + gamma * repBonus;
|
|
288
|
+
const confidence = Math.max(direct.confidence, indirect.confidence);
|
|
289
|
+
return {
|
|
290
|
+
trust_score: score,
|
|
291
|
+
confidence,
|
|
292
|
+
source: 'composite',
|
|
293
|
+
path: indirect.path,
|
|
294
|
+
details: {
|
|
295
|
+
direct_trust: direct.trust_score,
|
|
296
|
+
indirect_trust: indirect.trust_score,
|
|
297
|
+
reputation_bonus: repBonus,
|
|
298
|
+
},
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
/** Global success rate across all edges. */
|
|
302
|
+
_globalSuccessRate() {
|
|
303
|
+
let totalWeight = 0;
|
|
304
|
+
let successWeight = 0;
|
|
305
|
+
for (const edge of this.edges.values()) {
|
|
306
|
+
totalWeight += edge.total_weight;
|
|
307
|
+
successWeight += edge.successful_weight;
|
|
308
|
+
}
|
|
309
|
+
return totalWeight > 0 ? successWeight / totalWeight : 0;
|
|
310
|
+
}
|
|
311
|
+
// ── Queries ──────────────────────────────────────────────────
|
|
312
|
+
/**
|
|
313
|
+
* Scene reputation: average incoming trust for an agent in a scene.
|
|
314
|
+
* Averages directTrust from all agents that have interacted with this agent.
|
|
315
|
+
*/
|
|
316
|
+
sceneReputation(agentId, scene) {
|
|
317
|
+
const incoming = [];
|
|
318
|
+
for (const edge of this.edges.values()) {
|
|
319
|
+
if (edge.to === agentId && edge.scene === scene) {
|
|
320
|
+
const dt = this.directTrust(edge.from, edge.to, scene);
|
|
321
|
+
incoming.push(dt.trust_score);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
if (incoming.length === 0)
|
|
325
|
+
return 0;
|
|
326
|
+
return incoming.reduce((a, b) => a + b, 0) / incoming.length;
|
|
327
|
+
}
|
|
328
|
+
/** Top-K partners by composite trust (across all scenes). */
|
|
329
|
+
topPartners(agentId, k) {
|
|
330
|
+
const partnerMap = new Map();
|
|
331
|
+
for (const edge of this.edges.values()) {
|
|
332
|
+
if (edge.from === agentId) {
|
|
333
|
+
const dt = this.directTrust(edge.from, edge.to, edge.scene);
|
|
334
|
+
const prev = partnerMap.get(edge.to) ?? 0;
|
|
335
|
+
partnerMap.set(edge.to, Math.max(prev, dt.trust_score));
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
return [...partnerMap.entries()]
|
|
339
|
+
.map(([agent_id, trust]) => ({ agent_id, trust }))
|
|
340
|
+
.sort((a, b) => b.trust - a.trust)
|
|
341
|
+
.slice(0, k);
|
|
342
|
+
}
|
|
343
|
+
/** Top-K agents for a scene by reputation. */
|
|
344
|
+
topAgentsForScene(scene, k) {
|
|
345
|
+
const agents = new Set();
|
|
346
|
+
for (const edge of this.edges.values()) {
|
|
347
|
+
if (edge.scene === scene)
|
|
348
|
+
agents.add(edge.to);
|
|
349
|
+
}
|
|
350
|
+
return [...agents]
|
|
351
|
+
.map(agent_id => ({ agent_id, reputation: this.sceneReputation(agent_id, scene) }))
|
|
352
|
+
.sort((a, b) => b.reputation - a.reputation)
|
|
353
|
+
.slice(0, k);
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Strongest trust path from A to B (any depth up to 3).
|
|
357
|
+
* Uses the same BFS as indirectTrust but also considers direct (1-hop).
|
|
358
|
+
*/
|
|
359
|
+
strongestPath(from, to, scene) {
|
|
360
|
+
if (from === to)
|
|
361
|
+
return null;
|
|
362
|
+
const queue = [{ node: from, path: [from], trust: 1.0 }];
|
|
363
|
+
let bestTrust = 0;
|
|
364
|
+
let bestPath = [];
|
|
365
|
+
while (queue.length > 0) {
|
|
366
|
+
const current = queue.shift();
|
|
367
|
+
if (current.path.length > DEFAULT_MAX_DEPTH + 1)
|
|
368
|
+
continue;
|
|
369
|
+
for (const edge of this.edges.values()) {
|
|
370
|
+
if (edge.from !== current.node || edge.scene !== scene)
|
|
371
|
+
continue;
|
|
372
|
+
if (current.path.includes(edge.to))
|
|
373
|
+
continue;
|
|
374
|
+
const dt = this.directTrust(edge.from, edge.to, scene);
|
|
375
|
+
const hopCount = current.path.length;
|
|
376
|
+
const pathTrust = current.trust * dt.trust_score * (hopCount > 1 ? Math.pow(HOP_DECAY, hopCount - 1) : 1);
|
|
377
|
+
if (edge.to === to) {
|
|
378
|
+
if (pathTrust > bestTrust) {
|
|
379
|
+
bestTrust = pathTrust;
|
|
380
|
+
bestPath = [...current.path, edge.to];
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
else if (current.path.length < DEFAULT_MAX_DEPTH + 1) {
|
|
384
|
+
queue.push({ node: edge.to, path: [...current.path, edge.to], trust: current.trust * dt.trust_score });
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
return bestPath.length > 0 ? { path: bestPath, trust: bestTrust } : null;
|
|
389
|
+
}
|
|
390
|
+
// ── Anomaly detection ────────────────────────────────────────
|
|
391
|
+
/**
|
|
392
|
+
* Behavior Consistency Score (BCS).
|
|
393
|
+
*
|
|
394
|
+
* BCS = 1 - (max_success_rate - min_success_rate) across all partners.
|
|
395
|
+
* Suspicious if BCS < 0.7 (agent treats different partners very differently).
|
|
396
|
+
*/
|
|
397
|
+
behaviorConsistencyScore(agentId) {
|
|
398
|
+
const details = {};
|
|
399
|
+
for (const edge of this.edges.values()) {
|
|
400
|
+
if (edge.from === agentId && edge.total_tasks > 0) {
|
|
401
|
+
const rate = edge.successful_tasks / edge.total_tasks;
|
|
402
|
+
details[`${edge.to}:${edge.scene}`] = rate;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
const rates = Object.values(details);
|
|
406
|
+
if (rates.length < 2) {
|
|
407
|
+
return { score: 1.0, suspicious: false, details };
|
|
408
|
+
}
|
|
409
|
+
const maxRate = Math.max(...rates);
|
|
410
|
+
const minRate = Math.min(...rates);
|
|
411
|
+
const score = 1 - (maxRate - minRate);
|
|
412
|
+
return { score, suspicious: score < BCS_THRESHOLD, details };
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Detect suspicious clusters (simplified Sybil detection).
|
|
416
|
+
*
|
|
417
|
+
* For each pair of agents, compute internal vs external interaction ratio.
|
|
418
|
+
* Groups with internal_ratio >> external_ratio are suspicious.
|
|
419
|
+
*/
|
|
420
|
+
detectSuspiciousClusters(minClusterSize = 3) {
|
|
421
|
+
const agentIds = [...this.nodes.keys()];
|
|
422
|
+
if (agentIds.length < minClusterSize)
|
|
423
|
+
return [];
|
|
424
|
+
// Build adjacency with interaction counts
|
|
425
|
+
const interactions = new Map();
|
|
426
|
+
for (const edge of this.edges.values()) {
|
|
427
|
+
if (!interactions.has(edge.from))
|
|
428
|
+
interactions.set(edge.from, new Map());
|
|
429
|
+
const m = interactions.get(edge.from);
|
|
430
|
+
m.set(edge.to, (m.get(edge.to) ?? 0) + edge.total_tasks);
|
|
431
|
+
}
|
|
432
|
+
const clusters = [];
|
|
433
|
+
// Bound the search space to avoid combinatorial explosion.
|
|
434
|
+
const candidates = this._rankAgentsByActivity(agentIds).slice(0, MAX_CLUSTER_CANDIDATES);
|
|
435
|
+
const combos = this._combinations(candidates, minClusterSize, MAX_COMBINATION_CHECKS);
|
|
436
|
+
for (const combo of combos) {
|
|
437
|
+
const comboSet = new Set(combo);
|
|
438
|
+
let internal = 0;
|
|
439
|
+
let external = 0;
|
|
440
|
+
for (const agent of combo) {
|
|
441
|
+
const neighbors = interactions.get(agent);
|
|
442
|
+
if (!neighbors)
|
|
443
|
+
continue;
|
|
444
|
+
for (const [target, count] of neighbors) {
|
|
445
|
+
if (comboSet.has(target)) {
|
|
446
|
+
internal += count;
|
|
447
|
+
}
|
|
448
|
+
else {
|
|
449
|
+
external += count;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
const total = internal + external;
|
|
454
|
+
if (total === 0)
|
|
455
|
+
continue;
|
|
456
|
+
const internalRatio = internal / total;
|
|
457
|
+
// Suspicious if >80% of interactions are internal
|
|
458
|
+
if (internalRatio > 0.8 && internal > minClusterSize) {
|
|
459
|
+
clusters.push({ agents: combo, suspicion_score: internalRatio });
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
return clusters.sort((a, b) => b.suspicion_score - a.suspicion_score);
|
|
463
|
+
}
|
|
464
|
+
/** Generate combinations of size k from array. */
|
|
465
|
+
_combinations(arr, k, maxResults) {
|
|
466
|
+
if (k === 0)
|
|
467
|
+
return [[]];
|
|
468
|
+
if (arr.length < k)
|
|
469
|
+
return [];
|
|
470
|
+
const results = [];
|
|
471
|
+
for (let i = 0; i <= arr.length - k; i++) {
|
|
472
|
+
if (results.length >= maxResults)
|
|
473
|
+
break;
|
|
474
|
+
const rest = this._combinations(arr.slice(i + 1), k - 1, maxResults - results.length);
|
|
475
|
+
for (const combo of rest) {
|
|
476
|
+
if (results.length >= maxResults)
|
|
477
|
+
break;
|
|
478
|
+
results.push([arr[i], ...combo]);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
return results;
|
|
482
|
+
}
|
|
483
|
+
_rankAgentsByActivity(agentIds) {
|
|
484
|
+
const scores = new Map();
|
|
485
|
+
for (const id of agentIds) {
|
|
486
|
+
scores.set(id, 0);
|
|
487
|
+
}
|
|
488
|
+
for (const edge of this.edges.values()) {
|
|
489
|
+
scores.set(edge.from, (scores.get(edge.from) ?? 0) + edge.total_tasks);
|
|
490
|
+
scores.set(edge.to, (scores.get(edge.to) ?? 0) + edge.total_tasks);
|
|
491
|
+
}
|
|
492
|
+
return [...agentIds].sort((a, b) => (scores.get(b) ?? 0) - (scores.get(a) ?? 0));
|
|
493
|
+
}
|
|
494
|
+
// ── Export / Stats ──────────────────────────────────────────
|
|
495
|
+
/** Export the full graph as plain objects (Sets converted to arrays). */
|
|
496
|
+
exportGraph() {
|
|
497
|
+
return {
|
|
498
|
+
nodes: [...this.nodes.values()],
|
|
499
|
+
edges: [...this.edges.values()],
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
/** Aggregate statistics. */
|
|
503
|
+
getStats() {
|
|
504
|
+
const scenes = new Set();
|
|
505
|
+
let totalInteractions = 0;
|
|
506
|
+
let trustSum = 0;
|
|
507
|
+
let trustCount = 0;
|
|
508
|
+
for (const edge of this.edges.values()) {
|
|
509
|
+
scenes.add(edge.scene);
|
|
510
|
+
totalInteractions += edge.total_tasks;
|
|
511
|
+
if (edge.total_weight > 0) {
|
|
512
|
+
trustSum += edge.successful_weight / edge.total_weight;
|
|
513
|
+
trustCount++;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
return {
|
|
517
|
+
total_nodes: this.nodes.size,
|
|
518
|
+
total_edges: this.edges.size,
|
|
519
|
+
total_interactions: totalInteractions,
|
|
520
|
+
scenes: [...scenes],
|
|
521
|
+
avg_trust: trustCount > 0 ? trustSum / trustCount : 0,
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Module: Handshake Protocol
|
|
3
|
+
*
|
|
4
|
+
* Mutual identity verification + encrypted session establishment.
|
|
5
|
+
* Uses challenge-response with Ed25519 signatures and X25519 key exchange.
|
|
6
|
+
*
|
|
7
|
+
* Flow:
|
|
8
|
+
* A → B: handshake_init {did_a, pubkey_a, enc_pubkey_a, challenge_a}
|
|
9
|
+
* B → A: handshake_ack {did_b, pubkey_b, enc_pubkey_b, challenge_b, sign(challenge_a)}
|
|
10
|
+
* A → B: handshake_confirm {sign(challenge_b)}
|
|
11
|
+
* ✅ Encrypted session established (X25519 shared secret derived)
|
|
12
|
+
*/
|
|
13
|
+
import type { AgentIdentity } from '../identity/index.js';
|
|
14
|
+
import { type ATELMessage } from '../envelope/index.js';
|
|
15
|
+
import { EncryptionManager } from '../crypto/index.js';
|
|
16
|
+
/** Wallet addresses with DID-signed proof of ownership */
|
|
17
|
+
export interface WalletBundle {
|
|
18
|
+
addresses: {
|
|
19
|
+
solana?: string;
|
|
20
|
+
base?: string;
|
|
21
|
+
bsc?: string;
|
|
22
|
+
};
|
|
23
|
+
/** DID signature over sorted JSON of addresses — proves ownership */
|
|
24
|
+
proof: string;
|
|
25
|
+
}
|
|
26
|
+
/** Handshake init payload */
|
|
27
|
+
export interface HandshakeInitPayload {
|
|
28
|
+
did: string;
|
|
29
|
+
publicKey: string;
|
|
30
|
+
encPublicKey: string;
|
|
31
|
+
challenge: string;
|
|
32
|
+
capabilities?: string[];
|
|
33
|
+
/** Wallet addresses for on-chain trust verification */
|
|
34
|
+
wallets?: {
|
|
35
|
+
solana?: string;
|
|
36
|
+
base?: string;
|
|
37
|
+
bsc?: string;
|
|
38
|
+
};
|
|
39
|
+
/** Signed wallet bundle (v0.8.3+) */
|
|
40
|
+
walletBundle?: WalletBundle;
|
|
41
|
+
}
|
|
42
|
+
/** Handshake ack payload */
|
|
43
|
+
export interface HandshakeAckPayload {
|
|
44
|
+
did: string;
|
|
45
|
+
publicKey: string;
|
|
46
|
+
encPublicKey: string;
|
|
47
|
+
challenge: string;
|
|
48
|
+
challengeResponse: string;
|
|
49
|
+
capabilities?: string[];
|
|
50
|
+
/** Wallet addresses for on-chain trust verification */
|
|
51
|
+
wallets?: {
|
|
52
|
+
solana?: string;
|
|
53
|
+
base?: string;
|
|
54
|
+
bsc?: string;
|
|
55
|
+
};
|
|
56
|
+
/** Signed wallet bundle (v0.8.3+) */
|
|
57
|
+
walletBundle?: WalletBundle;
|
|
58
|
+
}
|
|
59
|
+
/** Handshake confirm payload */
|
|
60
|
+
export interface HandshakeConfirmPayload {
|
|
61
|
+
challengeResponse: string;
|
|
62
|
+
}
|
|
63
|
+
/** An established session between two agents */
|
|
64
|
+
export interface Session {
|
|
65
|
+
/** Unique session identifier */
|
|
66
|
+
sessionId: string;
|
|
67
|
+
/** Local agent DID */
|
|
68
|
+
localDid: string;
|
|
69
|
+
/** Remote agent DID */
|
|
70
|
+
remoteDid: string;
|
|
71
|
+
/** Remote agent's Ed25519 public key */
|
|
72
|
+
remotePublicKey: Uint8Array;
|
|
73
|
+
/** Whether E2E encryption is active for this session */
|
|
74
|
+
encrypted: boolean;
|
|
75
|
+
/** Remote agent's capabilities (if provided) */
|
|
76
|
+
remoteCapabilities?: string[];
|
|
77
|
+
/** Remote agent's wallet addresses (if provided) */
|
|
78
|
+
remoteWallets?: {
|
|
79
|
+
solana?: string;
|
|
80
|
+
base?: string;
|
|
81
|
+
bsc?: string;
|
|
82
|
+
};
|
|
83
|
+
/** Whether remote wallet ownership is DID-verified */
|
|
84
|
+
remoteWalletsVerified?: boolean;
|
|
85
|
+
/** Session creation timestamp */
|
|
86
|
+
createdAt: string;
|
|
87
|
+
/** Session expiry timestamp */
|
|
88
|
+
expiresAt: string;
|
|
89
|
+
/** Session state */
|
|
90
|
+
state: 'active' | 'expired';
|
|
91
|
+
}
|
|
92
|
+
/** Handshake configuration */
|
|
93
|
+
export interface HandshakeConfig {
|
|
94
|
+
/** Session TTL in seconds (default: 3600 = 1 hour) */
|
|
95
|
+
sessionTtlSec?: number;
|
|
96
|
+
/** Challenge length in bytes (default: 32) */
|
|
97
|
+
challengeBytes?: number;
|
|
98
|
+
/** Enable E2E encryption (default: true) */
|
|
99
|
+
enableEncryption?: boolean;
|
|
100
|
+
}
|
|
101
|
+
export declare class HandshakeError extends Error {
|
|
102
|
+
constructor(message: string);
|
|
103
|
+
}
|
|
104
|
+
/** Create a signed wallet bundle proving DID ownership of wallet addresses */
|
|
105
|
+
export declare function createWalletBundle(addresses: {
|
|
106
|
+
solana?: string;
|
|
107
|
+
base?: string;
|
|
108
|
+
bsc?: string;
|
|
109
|
+
}, secretKey: Uint8Array): WalletBundle;
|
|
110
|
+
/** Verify a wallet bundle's DID signature */
|
|
111
|
+
export declare function verifyWalletBundle(bundle: WalletBundle, publicKey: Uint8Array): boolean;
|
|
112
|
+
/**
|
|
113
|
+
* Manages handshake flows, active sessions, and encryption.
|
|
114
|
+
*
|
|
115
|
+
* Supports both initiator and responder roles.
|
|
116
|
+
* Automatically establishes E2E encryption during handshake.
|
|
117
|
+
*/
|
|
118
|
+
export declare class HandshakeManager {
|
|
119
|
+
private readonly identity;
|
|
120
|
+
private readonly sessionTtlSec;
|
|
121
|
+
private readonly challengeBytes;
|
|
122
|
+
private readonly enableEncryption;
|
|
123
|
+
private readonly sessions;
|
|
124
|
+
private readonly pendingChallenges;
|
|
125
|
+
private readonly pendingEncKeys;
|
|
126
|
+
private readonly pendingInitPayloads;
|
|
127
|
+
/** Encryption manager for E2E encrypted communication */
|
|
128
|
+
readonly encryption: EncryptionManager;
|
|
129
|
+
constructor(identity: AgentIdentity, config?: HandshakeConfig);
|
|
130
|
+
/**
|
|
131
|
+
* Create a handshake_init message (Step 1).
|
|
132
|
+
*/
|
|
133
|
+
createInit(remoteDid: string, wallets?: {
|
|
134
|
+
solana?: string;
|
|
135
|
+
base?: string;
|
|
136
|
+
bsc?: string;
|
|
137
|
+
}): ATELMessage<HandshakeInitPayload>;
|
|
138
|
+
/**
|
|
139
|
+
* Process a handshake_ack message and create handshake_confirm (Step 3).
|
|
140
|
+
*/
|
|
141
|
+
processAck(ackMessage: ATELMessage<HandshakeAckPayload>): {
|
|
142
|
+
confirm: ATELMessage<HandshakeConfirmPayload>;
|
|
143
|
+
session: Session;
|
|
144
|
+
};
|
|
145
|
+
/**
|
|
146
|
+
* Process a handshake_init message and create handshake_ack (Step 2).
|
|
147
|
+
*/
|
|
148
|
+
processInit(initMessage: ATELMessage<HandshakeInitPayload>, wallets?: {
|
|
149
|
+
solana?: string;
|
|
150
|
+
base?: string;
|
|
151
|
+
bsc?: string;
|
|
152
|
+
}): ATELMessage<HandshakeAckPayload>;
|
|
153
|
+
/**
|
|
154
|
+
* Process a handshake_confirm message (Step 3, responder side).
|
|
155
|
+
*/
|
|
156
|
+
processConfirm(confirmMessage: ATELMessage<HandshakeConfirmPayload>, initiatorPublicKey: Uint8Array, initiatorCapabilities?: string[], initiatorWallets?: {
|
|
157
|
+
solana?: string;
|
|
158
|
+
base?: string;
|
|
159
|
+
bsc?: string;
|
|
160
|
+
}, initiatorWalletBundle?: WalletBundle): Session;
|
|
161
|
+
getSession(remoteDid: string): Session | undefined;
|
|
162
|
+
hasActiveSession(remoteDid: string): boolean;
|
|
163
|
+
getActiveSessions(): Session[];
|
|
164
|
+
terminateSession(remoteDid: string): void;
|
|
165
|
+
private createSession;
|
|
166
|
+
}
|