@f2a/network 0.1.2

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.
Files changed (234) hide show
  1. package/.github/workflows/ci.yml +113 -0
  2. package/.github/workflows/publish.yml +60 -0
  3. package/LICENSE +21 -0
  4. package/MONOREPO.md +58 -0
  5. package/README.md +280 -0
  6. package/SKILL.md +137 -0
  7. package/dist/adapters/openclaw.d.ts +103 -0
  8. package/dist/adapters/openclaw.d.ts.map +1 -0
  9. package/dist/adapters/openclaw.js +297 -0
  10. package/dist/adapters/openclaw.js.map +1 -0
  11. package/dist/cli/commands.d.ts +17 -0
  12. package/dist/cli/commands.d.ts.map +1 -0
  13. package/dist/cli/commands.js +107 -0
  14. package/dist/cli/commands.js.map +1 -0
  15. package/dist/cli/index.d.ts +6 -0
  16. package/dist/cli/index.d.ts.map +1 -0
  17. package/dist/cli/index.js +203 -0
  18. package/dist/cli/index.js.map +1 -0
  19. package/dist/core/autonomous-economy.d.ts +136 -0
  20. package/dist/core/autonomous-economy.d.ts.map +1 -0
  21. package/dist/core/autonomous-economy.js +255 -0
  22. package/dist/core/autonomous-economy.js.map +1 -0
  23. package/dist/core/connection-manager.d.ts +80 -0
  24. package/dist/core/connection-manager.d.ts.map +1 -0
  25. package/dist/core/connection-manager.js +235 -0
  26. package/dist/core/connection-manager.js.map +1 -0
  27. package/dist/core/connection-manager.test.d.ts +2 -0
  28. package/dist/core/connection-manager.test.d.ts.map +1 -0
  29. package/dist/core/connection-manager.test.js +52 -0
  30. package/dist/core/connection-manager.test.js.map +1 -0
  31. package/dist/core/e2ee-crypto.d.ts +90 -0
  32. package/dist/core/e2ee-crypto.d.ts.map +1 -0
  33. package/dist/core/e2ee-crypto.js +190 -0
  34. package/dist/core/e2ee-crypto.js.map +1 -0
  35. package/dist/core/f2a.d.ts +126 -0
  36. package/dist/core/f2a.d.ts.map +1 -0
  37. package/dist/core/f2a.js +425 -0
  38. package/dist/core/f2a.js.map +1 -0
  39. package/dist/core/identity.d.ts +47 -0
  40. package/dist/core/identity.d.ts.map +1 -0
  41. package/dist/core/identity.js +130 -0
  42. package/dist/core/identity.js.map +1 -0
  43. package/dist/core/identity.test.d.ts +2 -0
  44. package/dist/core/identity.test.d.ts.map +1 -0
  45. package/dist/core/identity.test.js +43 -0
  46. package/dist/core/identity.test.js.map +1 -0
  47. package/dist/core/p2p-network.d.ts +242 -0
  48. package/dist/core/p2p-network.d.ts.map +1 -0
  49. package/dist/core/p2p-network.js +1182 -0
  50. package/dist/core/p2p-network.js.map +1 -0
  51. package/dist/core/reputation-security.d.ts +168 -0
  52. package/dist/core/reputation-security.d.ts.map +1 -0
  53. package/dist/core/reputation-security.js +369 -0
  54. package/dist/core/reputation-security.js.map +1 -0
  55. package/dist/core/reputation.d.ts +179 -0
  56. package/dist/core/reputation.d.ts.map +1 -0
  57. package/dist/core/reputation.js +472 -0
  58. package/dist/core/reputation.js.map +1 -0
  59. package/dist/core/review-committee.d.ts +130 -0
  60. package/dist/core/review-committee.d.ts.map +1 -0
  61. package/dist/core/review-committee.js +251 -0
  62. package/dist/core/review-committee.js.map +1 -0
  63. package/dist/core/serverless.d.ts +155 -0
  64. package/dist/core/serverless.d.ts.map +1 -0
  65. package/dist/core/serverless.js +615 -0
  66. package/dist/core/serverless.js.map +1 -0
  67. package/dist/core/token-manager.d.ts +42 -0
  68. package/dist/core/token-manager.d.ts.map +1 -0
  69. package/dist/core/token-manager.js +122 -0
  70. package/dist/core/token-manager.js.map +1 -0
  71. package/dist/daemon/control-server.d.ts +55 -0
  72. package/dist/daemon/control-server.d.ts.map +1 -0
  73. package/dist/daemon/control-server.js +262 -0
  74. package/dist/daemon/control-server.js.map +1 -0
  75. package/dist/daemon/index.d.ts +35 -0
  76. package/dist/daemon/index.d.ts.map +1 -0
  77. package/dist/daemon/index.js +69 -0
  78. package/dist/daemon/index.js.map +1 -0
  79. package/dist/daemon/main.d.ts +6 -0
  80. package/dist/daemon/main.d.ts.map +1 -0
  81. package/dist/daemon/main.js +38 -0
  82. package/dist/daemon/main.js.map +1 -0
  83. package/dist/daemon/start.d.ts +6 -0
  84. package/dist/daemon/start.d.ts.map +1 -0
  85. package/dist/daemon/start.js +25 -0
  86. package/dist/daemon/start.js.map +1 -0
  87. package/dist/daemon/webhook.d.ts +30 -0
  88. package/dist/daemon/webhook.d.ts.map +1 -0
  89. package/dist/daemon/webhook.js +86 -0
  90. package/dist/daemon/webhook.js.map +1 -0
  91. package/dist/daemon/webhook.test.d.ts +2 -0
  92. package/dist/daemon/webhook.test.d.ts.map +1 -0
  93. package/dist/daemon/webhook.test.js +24 -0
  94. package/dist/daemon/webhook.test.js.map +1 -0
  95. package/dist/index.d.ts +24 -0
  96. package/dist/index.d.ts.map +1 -0
  97. package/dist/index.js +25 -0
  98. package/dist/index.js.map +1 -0
  99. package/dist/protocol/messages.d.ts +739 -0
  100. package/dist/protocol/messages.d.ts.map +1 -0
  101. package/dist/protocol/messages.js +188 -0
  102. package/dist/protocol/messages.js.map +1 -0
  103. package/dist/protocol/messages.test.d.ts +2 -0
  104. package/dist/protocol/messages.test.d.ts.map +1 -0
  105. package/dist/protocol/messages.test.js +55 -0
  106. package/dist/protocol/messages.test.js.map +1 -0
  107. package/dist/types/index.d.ts +247 -0
  108. package/dist/types/index.d.ts.map +1 -0
  109. package/dist/types/index.js +10 -0
  110. package/dist/types/index.js.map +1 -0
  111. package/dist/types/result.d.ts +28 -0
  112. package/dist/types/result.d.ts.map +1 -0
  113. package/dist/types/result.js +16 -0
  114. package/dist/types/result.js.map +1 -0
  115. package/dist/utils/benchmark.d.ts +67 -0
  116. package/dist/utils/benchmark.d.ts.map +1 -0
  117. package/dist/utils/benchmark.js +179 -0
  118. package/dist/utils/benchmark.js.map +1 -0
  119. package/dist/utils/logger.d.ts +105 -0
  120. package/dist/utils/logger.d.ts.map +1 -0
  121. package/dist/utils/logger.js +275 -0
  122. package/dist/utils/logger.js.map +1 -0
  123. package/dist/utils/middleware.d.ts +85 -0
  124. package/dist/utils/middleware.d.ts.map +1 -0
  125. package/dist/utils/middleware.js +173 -0
  126. package/dist/utils/middleware.js.map +1 -0
  127. package/dist/utils/rate-limiter.d.ts +71 -0
  128. package/dist/utils/rate-limiter.d.ts.map +1 -0
  129. package/dist/utils/rate-limiter.js +160 -0
  130. package/dist/utils/rate-limiter.js.map +1 -0
  131. package/dist/utils/signature.d.ts +57 -0
  132. package/dist/utils/signature.d.ts.map +1 -0
  133. package/dist/utils/signature.js +102 -0
  134. package/dist/utils/signature.js.map +1 -0
  135. package/dist/utils/validation.d.ts +504 -0
  136. package/dist/utils/validation.d.ts.map +1 -0
  137. package/dist/utils/validation.js +159 -0
  138. package/dist/utils/validation.js.map +1 -0
  139. package/docs/F2A-PROTOCOL.md +61 -0
  140. package/docs/MOBILE_BOOTSTRAP_DESIGN.md +126 -0
  141. package/docs/a2a-lessons.md +316 -0
  142. package/docs/middleware-guide.md +448 -0
  143. package/docs/readme-update-checklist.md +90 -0
  144. package/docs/reputation-guide.md +396 -0
  145. package/docs/rfcs/001-reputation-system.md +712 -0
  146. package/docs/security-design.md +247 -0
  147. package/install.sh +231 -0
  148. package/package.json +64 -0
  149. package/packages/openclaw-adapter/README.md +510 -0
  150. package/packages/openclaw-adapter/openclaw.plugin.json +106 -0
  151. package/packages/openclaw-adapter/package.json +40 -0
  152. package/packages/openclaw-adapter/src/announcement-queue.test.ts +449 -0
  153. package/packages/openclaw-adapter/src/announcement-queue.ts +403 -0
  154. package/packages/openclaw-adapter/src/capability-detector.test.ts +99 -0
  155. package/packages/openclaw-adapter/src/capability-detector.ts +183 -0
  156. package/packages/openclaw-adapter/src/claim-handlers.test.ts +974 -0
  157. package/packages/openclaw-adapter/src/claim-handlers.ts +482 -0
  158. package/packages/openclaw-adapter/src/connector.business.test.ts +583 -0
  159. package/packages/openclaw-adapter/src/connector.ts +795 -0
  160. package/packages/openclaw-adapter/src/index.test.ts +82 -0
  161. package/packages/openclaw-adapter/src/index.ts +18 -0
  162. package/packages/openclaw-adapter/src/integration.e2e.test.ts +829 -0
  163. package/packages/openclaw-adapter/src/logger.ts +51 -0
  164. package/packages/openclaw-adapter/src/network-client.test.ts +266 -0
  165. package/packages/openclaw-adapter/src/network-client.ts +251 -0
  166. package/packages/openclaw-adapter/src/network-recovery.test.ts +465 -0
  167. package/packages/openclaw-adapter/src/node-manager.test.ts +136 -0
  168. package/packages/openclaw-adapter/src/node-manager.ts +429 -0
  169. package/packages/openclaw-adapter/src/plugin.test.ts +439 -0
  170. package/packages/openclaw-adapter/src/plugin.ts +104 -0
  171. package/packages/openclaw-adapter/src/reputation.test.ts +221 -0
  172. package/packages/openclaw-adapter/src/reputation.ts +368 -0
  173. package/packages/openclaw-adapter/src/task-guard.test.ts +502 -0
  174. package/packages/openclaw-adapter/src/task-guard.ts +860 -0
  175. package/packages/openclaw-adapter/src/task-queue.concurrency.test.ts +462 -0
  176. package/packages/openclaw-adapter/src/task-queue.edge-cases.test.ts +284 -0
  177. package/packages/openclaw-adapter/src/task-queue.persistence.test.ts +408 -0
  178. package/packages/openclaw-adapter/src/task-queue.ts +668 -0
  179. package/packages/openclaw-adapter/src/tool-handlers.test.ts +906 -0
  180. package/packages/openclaw-adapter/src/tool-handlers.ts +574 -0
  181. package/packages/openclaw-adapter/src/types.ts +361 -0
  182. package/packages/openclaw-adapter/src/webhook-pusher.test.ts +188 -0
  183. package/packages/openclaw-adapter/src/webhook-pusher.ts +220 -0
  184. package/packages/openclaw-adapter/src/webhook-server.test.ts +580 -0
  185. package/packages/openclaw-adapter/src/webhook-server.ts +202 -0
  186. package/packages/openclaw-adapter/tsconfig.json +20 -0
  187. package/src/cli/commands.test.ts +157 -0
  188. package/src/cli/commands.ts +129 -0
  189. package/src/cli/index.test.ts +77 -0
  190. package/src/cli/index.ts +234 -0
  191. package/src/core/autonomous-economy.test.ts +291 -0
  192. package/src/core/autonomous-economy.ts +428 -0
  193. package/src/core/e2ee-crypto.test.ts +125 -0
  194. package/src/core/e2ee-crypto.ts +246 -0
  195. package/src/core/f2a.test.ts +269 -0
  196. package/src/core/f2a.ts +618 -0
  197. package/src/core/p2p-network.test.ts +199 -0
  198. package/src/core/p2p-network.ts +1432 -0
  199. package/src/core/reputation-security.test.ts +403 -0
  200. package/src/core/reputation-security.ts +562 -0
  201. package/src/core/reputation.test.ts +260 -0
  202. package/src/core/reputation.ts +576 -0
  203. package/src/core/review-committee.test.ts +380 -0
  204. package/src/core/review-committee.ts +401 -0
  205. package/src/core/token-manager.test.ts +133 -0
  206. package/src/core/token-manager.ts +140 -0
  207. package/src/daemon/control-server.test.ts +216 -0
  208. package/src/daemon/control-server.ts +292 -0
  209. package/src/daemon/index.test.ts +85 -0
  210. package/src/daemon/index.ts +89 -0
  211. package/src/daemon/main.ts +44 -0
  212. package/src/daemon/start.ts +29 -0
  213. package/src/daemon/webhook.test.ts +68 -0
  214. package/src/daemon/webhook.ts +105 -0
  215. package/src/index.test.ts +436 -0
  216. package/src/index.ts +72 -0
  217. package/src/types/index.test.ts +87 -0
  218. package/src/types/index.ts +341 -0
  219. package/src/types/result.ts +68 -0
  220. package/src/utils/benchmark.ts +237 -0
  221. package/src/utils/logger.ts +331 -0
  222. package/src/utils/middleware.ts +229 -0
  223. package/src/utils/rate-limiter.ts +207 -0
  224. package/src/utils/signature.ts +136 -0
  225. package/src/utils/validation.ts +186 -0
  226. package/tests/docker/Dockerfile.node +23 -0
  227. package/tests/docker/Dockerfile.runner +18 -0
  228. package/tests/docker/docker-compose.test.yml +73 -0
  229. package/tests/integration/message-passing.test.ts +109 -0
  230. package/tests/integration/multi-node.test.ts +92 -0
  231. package/tests/integration/p2p-connection.test.ts +83 -0
  232. package/tests/integration/test-config.ts +32 -0
  233. package/tsconfig.json +21 -0
  234. package/vitest.config.ts +26 -0
@@ -0,0 +1,1432 @@
1
+ /**
2
+ * P2P 网络管理器
3
+ * 基于 libp2p 实现 Agent 发现与通信
4
+ */
5
+
6
+ import { createLibp2p } from 'libp2p';
7
+ import { tcp } from '@libp2p/tcp';
8
+ import { noise } from '@chainsafe/libp2p-noise';
9
+ import { kadDHT } from '@libp2p/kad-dht';
10
+ import { peerIdFromString } from '@libp2p/peer-id';
11
+ import type { PeerId } from '@libp2p/interface';
12
+ import { multiaddr } from '@multiformats/multiaddr';
13
+ import type { Multiaddr } from '@multiformats/multiaddr';
14
+ import { EventEmitter } from 'eventemitter3';
15
+ import { randomUUID } from 'crypto';
16
+ import type { Libp2p } from '@libp2p/interface';
17
+
18
+ import {
19
+ P2PNetworkConfig,
20
+ AgentInfo,
21
+ AgentCapability,
22
+ F2AMessage,
23
+ PeerInfo,
24
+ PeerDiscoveredEvent,
25
+ PeerConnectedEvent,
26
+ PeerDisconnectedEvent,
27
+ Result,
28
+ TaskRequestPayload,
29
+ TaskResponsePayload,
30
+ DiscoverPayload,
31
+ CapabilityQueryPayload,
32
+ CapabilityResponsePayload,
33
+ success,
34
+ failureFromError,
35
+ createError
36
+ } from '../types/index.js';
37
+ import { E2EECrypto, EncryptedMessage } from './e2ee-crypto.js';
38
+ import { Logger } from '../utils/logger.js';
39
+ import { validateF2AMessage, validateTaskRequestPayload, validateTaskResponsePayload } from '../utils/validation.js';
40
+ import { MiddlewareManager, Middleware } from '../utils/middleware.js';
41
+ import { RequestSigner, loadSignatureConfig, SignedMessage } from '../utils/signature.js';
42
+
43
+ // DHT 服务类型定义
44
+ interface DHTService {
45
+ findPeer(peerId: PeerId): Promise<{ multiaddrs: Multiaddr[] } | null>;
46
+ routingTable?: { size: number };
47
+ }
48
+
49
+ interface Libp2pServices {
50
+ dht?: DHTService;
51
+ }
52
+
53
+ // 加密消息类型定义
54
+ interface EncryptedF2AMessage extends F2AMessage {
55
+ encrypted: true;
56
+ payload: EncryptedMessage;
57
+ }
58
+
59
+ // 类型守卫:检查是否为加密消息
60
+ function isEncryptedMessage(msg: F2AMessage): msg is EncryptedF2AMessage {
61
+ return 'encrypted' in msg && msg.encrypted === true && 'payload' in msg;
62
+ }
63
+
64
+ // 加密消息处理结果
65
+ interface DecryptResult {
66
+ action: 'continue' | 'return';
67
+ message: F2AMessage;
68
+ }
69
+
70
+ // F2A 协议标识
71
+ const F2A_PROTOCOL = '/f2a/1.0.0';
72
+
73
+ // 清理配置
74
+ const PEER_TABLE_CLEANUP_INTERVAL = 5 * 60 * 1000; // 5分钟
75
+ const PEER_STALE_THRESHOLD = 24 * 60 * 60 * 1000; // 24小时
76
+ const PEER_TABLE_MAX_SIZE = 1000; // 最大peer数
77
+ const PEER_TABLE_HIGH_WATERMARK = 0.9; // 高水位线(90%触发主动清理)
78
+ const PEER_TABLE_AGGRESSIVE_CLEANUP_THRESHOLD = 0.8; // 激进清理后保留的目标比例(80%)
79
+
80
+ export interface P2PNetworkEvents {
81
+ 'peer:discovered': (event: PeerDiscoveredEvent) => void;
82
+ 'peer:connected': (event: PeerConnectedEvent) => void;
83
+ 'peer:disconnected': (event: PeerDisconnectedEvent) => void;
84
+ 'message:received': (message: F2AMessage, peerId: string) => void;
85
+ 'error': (error: Error) => void;
86
+ }
87
+
88
+ /** 发现选项 */
89
+ export interface DiscoverOptions {
90
+ /** 发现超时毫秒(默认 2000) */
91
+ timeoutMs?: number;
92
+ /** 是否等待首个响应即返回(默认 false) */
93
+ waitForFirstResponse?: boolean;
94
+ }
95
+
96
+ /**
97
+ * 简单的异步锁实现,用于保护关键资源的并发访问
98
+ */
99
+ class AsyncLock {
100
+ private locked = false;
101
+ private queue: Array<() => void> = [];
102
+
103
+ async acquire(): Promise<void> {
104
+ if (!this.locked) {
105
+ this.locked = true;
106
+ return;
107
+ }
108
+ return new Promise<void>(resolve => {
109
+ this.queue.push(resolve);
110
+ });
111
+ }
112
+
113
+ release(): void {
114
+ const next = this.queue.shift();
115
+ if (next) {
116
+ next();
117
+ } else {
118
+ this.locked = false;
119
+ }
120
+ }
121
+ }
122
+
123
+ export class P2PNetwork extends EventEmitter<P2PNetworkEvents> {
124
+ private node: Libp2p | null = null;
125
+ private config: P2PNetworkConfig;
126
+ private peerTable: Map<string, PeerInfo> = new Map();
127
+ /** 用于保护 peerTable 并发访问的锁 */
128
+ private peerTableLock = new AsyncLock();
129
+ private agentInfo: AgentInfo;
130
+ private pendingTasks: Map<string, {
131
+ resolve: (result: unknown) => void;
132
+ reject: (error: string) => void;
133
+ timeout: NodeJS.Timeout;
134
+ resolved: boolean;
135
+ }> = new Map();
136
+ private cleanupInterval?: NodeJS.Timeout;
137
+ private discoveryInterval?: NodeJS.Timeout;
138
+ private e2eeCrypto: E2EECrypto;
139
+ private enableE2EE: boolean = true;
140
+ private logger: Logger;
141
+ private middlewareManager: MiddlewareManager;
142
+
143
+ constructor(agentInfo: AgentInfo, config: P2PNetworkConfig = {}) {
144
+ super();
145
+ this.agentInfo = agentInfo;
146
+ this.e2eeCrypto = new E2EECrypto();
147
+ this.middlewareManager = new MiddlewareManager();
148
+ this.config = {
149
+ listenPort: 0,
150
+ enableMDNS: true,
151
+ enableDHT: false,
152
+ ...config
153
+ };
154
+ this.logger = new Logger({ component: 'P2P' });
155
+ }
156
+
157
+ /**
158
+ * 启动 P2P 网络
159
+ */
160
+ async start(): Promise<Result<{ peerId: string; addresses: string[] }>> {
161
+ try {
162
+ // 构建监听地址
163
+ const listenAddresses = this.config.listenAddresses || [
164
+ `/ip4/0.0.0.0/tcp/${this.config.listenPort}`
165
+ ];
166
+
167
+ // 创建 libp2p 节点 - 启用 noise 加密
168
+ const services: Record<string, any> = {};
169
+
170
+ // 只有显式启用 DHT 时才添加
171
+ if (this.config.enableDHT === true) {
172
+ services.dht = kadDHT({
173
+ clientMode: !this.config.dhtServerMode, // 默认客户端模式
174
+ });
175
+ }
176
+
177
+ // 注意:不传 privateKey,让 libp2p 自动生成 PeerId
178
+ this.node = await createLibp2p({
179
+ addresses: {
180
+ listen: listenAddresses
181
+ },
182
+ transports: [tcp()],
183
+ connectionEncryption: [noise()],
184
+ services
185
+ });
186
+
187
+ // 设置事件监听
188
+ this.setupEventHandlers();
189
+
190
+ // 启动节点
191
+ await this.node.start();
192
+
193
+ // 获取实际监听地址
194
+ const addrs = this.node.getMultiaddrs().map(ma => ma.toString());
195
+
196
+ // 从节点获取 peer ID
197
+ const peerId = this.node.peerId;
198
+
199
+ // 更新 agentInfo
200
+ this.agentInfo.peerId = peerId.toString();
201
+ this.agentInfo.multiaddrs = addrs;
202
+
203
+ // 初始化 E2EE 加密
204
+ await this.e2eeCrypto.initialize();
205
+ this.agentInfo.encryptionPublicKey = this.e2eeCrypto.getPublicKey() || undefined;
206
+ this.logger.info('E2EE encryption enabled', {
207
+ publicKey: this.agentInfo.encryptionPublicKey?.slice(0, 16)
208
+ });
209
+
210
+ // 连接引导节点
211
+ if (this.config.bootstrapPeers) {
212
+ await this.connectToBootstrapPeers(this.config.bootstrapPeers);
213
+ }
214
+
215
+ // 启动定期发现广播
216
+ this.startDiscoveryBroadcast();
217
+
218
+ // 启动定期清理任务
219
+ this.startCleanupTask();
220
+
221
+ // 延迟 2 秒后广播发现消息,等待连接稳定
222
+ setTimeout(() => {
223
+ this.broadcastDiscovery();
224
+ }, 2000);
225
+
226
+ // 如果启用 DHT,等待 DHT 就绪
227
+ if (this.config.enableDHT !== false && this.node.services.dht) {
228
+ this.logger.info('DHT enabled, waiting for routing table');
229
+ }
230
+
231
+ this.logger.info('Started', { peerId: peerId.toString().slice(0, 16) });
232
+ this.logger.info('Listening', { addresses: addrs });
233
+ this.logger.info('Connection encryption enabled', { protocol: 'Noise' });
234
+
235
+ return success({ peerId: peerId.toString(), addresses: addrs });
236
+ } catch (error) {
237
+ return failureFromError('NETWORK_NOT_STARTED', 'Failed to start P2P network', error as Error);
238
+ }
239
+ }
240
+
241
+ /**
242
+ * 停止 P2P 网络
243
+ */
244
+ async stop(): Promise<void> {
245
+ // 停止清理任务
246
+ if (this.cleanupInterval) {
247
+ clearInterval(this.cleanupInterval);
248
+ this.cleanupInterval = undefined;
249
+ }
250
+
251
+ // 停止发现广播定时器
252
+ if (this.discoveryInterval) {
253
+ clearInterval(this.discoveryInterval);
254
+ this.discoveryInterval = undefined;
255
+ }
256
+
257
+ if (this.node) {
258
+ // 清理待处理任务
259
+ for (const [taskId, { timeout, resolve }] of this.pendingTasks) {
260
+ clearTimeout(timeout);
261
+ resolve({ success: false, error: 'Network stopped' });
262
+ }
263
+ this.pendingTasks.clear();
264
+
265
+ await this.node.stop();
266
+ this.node = null;
267
+ this.logger.info('Stopped');
268
+ }
269
+ }
270
+
271
+ /**
272
+ * 发现网络中的 Agent(按能力过滤)
273
+ * @param capability 可选的能力过滤
274
+ * @param options 发现选项
275
+ */
276
+ async discoverAgents(capability?: string, options?: DiscoverOptions): Promise<AgentInfo[]> {
277
+ const timeoutMs = options?.timeoutMs ?? 2000;
278
+ const waitForFirst = options?.waitForFirstResponse ?? false;
279
+
280
+ // 使用锁保护创建快照,防止并发修改
281
+ const agents: AgentInfo[] = [];
282
+ const seenPeerIds = new Set<string>();
283
+
284
+ await this.peerTableLock.acquire();
285
+ try {
286
+ for (const peer of this.peerTable.values()) {
287
+ if (peer.agentInfo) {
288
+ if (!capability || this.hasCapability(peer.agentInfo, capability)) {
289
+ agents.push(peer.agentInfo);
290
+ seenPeerIds.add(peer.agentInfo.peerId);
291
+ }
292
+ }
293
+ }
294
+ } finally {
295
+ this.peerTableLock.release();
296
+ }
297
+
298
+ // 如果已经有足够的 agents 且不需要等待响应,直接返回
299
+ if (agents.length > 0 && !waitForFirst) {
300
+ return agents;
301
+ }
302
+
303
+ // 广播能力查询以发现更多节点
304
+ await this.broadcast({
305
+ id: randomUUID(),
306
+ type: 'CAPABILITY_QUERY',
307
+ from: this.agentInfo.peerId,
308
+ timestamp: Date.now(),
309
+ payload: { capabilityName: capability } as CapabilityQueryPayload
310
+ });
311
+
312
+ // 使用 Promise.race 等待首个响应或超时
313
+ if (waitForFirst) {
314
+ await new Promise<void>(resolve => {
315
+ const timeout = setTimeout(() => {
316
+ this.off('peer:discovered', onPeerDiscovered);
317
+ resolve();
318
+ }, timeoutMs);
319
+
320
+ const onPeerDiscovered = (event: PeerDiscoveredEvent) => {
321
+ if (!capability || this.hasCapability(event.agentInfo, capability)) {
322
+ // 使用 Set 原子检查,防止重复添加
323
+ if (!seenPeerIds.has(event.agentInfo.peerId)) {
324
+ agents.push(event.agentInfo);
325
+ seenPeerIds.add(event.agentInfo.peerId);
326
+ }
327
+ }
328
+ clearTimeout(timeout);
329
+ this.off('peer:discovered', onPeerDiscovered);
330
+ resolve();
331
+ };
332
+
333
+ this.on('peer:discovered', onPeerDiscovered);
334
+ });
335
+ } else {
336
+ // 等待响应(可配置超时)
337
+ await new Promise(resolve => setTimeout(resolve, timeoutMs));
338
+ }
339
+
340
+ // 再次收集 - 使用锁保护创建快照
341
+ await this.peerTableLock.acquire();
342
+ try {
343
+ for (const peer of this.peerTable.values()) {
344
+ if (peer.agentInfo && !seenPeerIds.has(peer.agentInfo.peerId)) {
345
+ if (!capability || this.hasCapability(peer.agentInfo, capability)) {
346
+ agents.push(peer.agentInfo);
347
+ seenPeerIds.add(peer.agentInfo.peerId);
348
+ }
349
+ }
350
+ }
351
+ } finally {
352
+ this.peerTableLock.release();
353
+ }
354
+
355
+ return agents;
356
+ }
357
+
358
+ /**
359
+ * 向特定 Peer 发送任务请求
360
+ */
361
+ async sendTaskRequest(
362
+ peerId: string,
363
+ taskType: string,
364
+ description: string,
365
+ parameters?: Record<string, unknown>,
366
+ timeout: number = 30000
367
+ ): Promise<Result<unknown>> {
368
+ const taskId = randomUUID();
369
+
370
+ const message: F2AMessage = {
371
+ id: taskId,
372
+ type: 'TASK_REQUEST',
373
+ from: this.agentInfo.peerId,
374
+ to: peerId,
375
+ timestamp: Date.now(),
376
+ payload: {
377
+ taskId,
378
+ taskType,
379
+ description,
380
+ parameters,
381
+ timeout: Math.floor(timeout / 1000)
382
+ } as TaskRequestPayload
383
+ };
384
+
385
+ // 发送消息(启用 E2EE 加密)
386
+ const sendResult = await this.sendMessage(peerId, message, true);
387
+ if (!sendResult.success) {
388
+ return sendResult;
389
+ }
390
+
391
+ // 等待响应
392
+ return new Promise((resolve) => {
393
+ const taskEntry = {
394
+ resolve: (result: unknown) => {
395
+ if (!taskEntry.resolved) {
396
+ taskEntry.resolved = true;
397
+ // 从 Map 中移除,确保不会重复处理
398
+ this.pendingTasks.delete(taskId);
399
+ resolve(success(result));
400
+ }
401
+ },
402
+ reject: (error: string) => {
403
+ if (!taskEntry.resolved) {
404
+ taskEntry.resolved = true;
405
+ // 从 Map 中移除,确保不会重复处理
406
+ this.pendingTasks.delete(taskId);
407
+ resolve({ success: false, error: createError('TASK_FAILED', error) } as Result<unknown>);
408
+ }
409
+ },
410
+ timeout: setTimeout(() => {
411
+ if (!taskEntry.resolved) {
412
+ taskEntry.resolved = true;
413
+ this.pendingTasks.delete(taskId);
414
+ resolve(failureFromError('TIMEOUT', 'Task timeout'));
415
+ }
416
+ }, timeout),
417
+ resolved: false
418
+ };
419
+
420
+ this.pendingTasks.set(taskId, taskEntry);
421
+ });
422
+ }
423
+
424
+ /**
425
+ * 发送任务响应
426
+ */
427
+ async sendTaskResponse(
428
+ peerId: string,
429
+ taskId: string,
430
+ status: 'success' | 'error' | 'rejected' | 'delegated',
431
+ result?: unknown,
432
+ error?: string
433
+ ): Promise<Result<void>> {
434
+ const message: F2AMessage = {
435
+ id: randomUUID(),
436
+ type: 'TASK_RESPONSE',
437
+ from: this.agentInfo.peerId,
438
+ to: peerId,
439
+ timestamp: Date.now(),
440
+ payload: {
441
+ taskId,
442
+ status,
443
+ result,
444
+ error
445
+ } as TaskResponsePayload
446
+ };
447
+
448
+ // 任务响应也启用 E2EE 加密
449
+ return this.sendMessage(peerId, message, true);
450
+ }
451
+
452
+ /**
453
+ * 广播发现消息
454
+ */
455
+ private async broadcastDiscovery(): Promise<void> {
456
+ const message: F2AMessage = {
457
+ id: randomUUID(),
458
+ type: 'DISCOVER',
459
+ from: this.agentInfo.peerId,
460
+ timestamp: Date.now(),
461
+ payload: { agentInfo: this.agentInfo } as DiscoverPayload
462
+ };
463
+
464
+ await this.broadcast(message);
465
+ }
466
+
467
+ /**
468
+ * 广播消息到全网
469
+ */
470
+ private async broadcast(message: F2AMessage): Promise<void> {
471
+ if (!this.node) return;
472
+
473
+ // 向所有已连接的对等节点发送
474
+ const peers = this.node.getPeers();
475
+ const results = await Promise.allSettled(
476
+ peers.map(peer => this.sendMessage(peer.toString(), message))
477
+ );
478
+
479
+ // 记录发送失败的情况
480
+ const failures = results.filter(r =>
481
+ r.status === 'rejected' ||
482
+ (r.status === 'fulfilled' && !r.value.success)
483
+ );
484
+ if (failures.length > 0) {
485
+ this.logger.warn('Broadcast failed to some peers', {
486
+ failed: failures.length,
487
+ total: peers.length
488
+ });
489
+ }
490
+ }
491
+
492
+ /**
493
+ * 向特定 Peer 发送消息
494
+ * @param peerId 目标 Peer ID
495
+ * @param message 消息内容
496
+ * @param encrypt 是否启用 E2EE 加密(默认 false,发现类消息不需要加密)
497
+ */
498
+ private async sendMessage(peerId: string, message: F2AMessage, encrypt: boolean = false): Promise<Result<void>> {
499
+ if (!this.node) {
500
+ return failureFromError('NETWORK_NOT_STARTED', 'P2P network not started');
501
+ }
502
+
503
+ try {
504
+ // 检查是否已连接
505
+ const connections = this.node.getConnections();
506
+ const existingConn = connections.find(c => c.remotePeer.toString() === peerId);
507
+
508
+ let peer;
509
+ if (existingConn) {
510
+ // 已连接,直接使用现有连接
511
+ peer = existingConn;
512
+ } else {
513
+ // 未连接,需要 dial
514
+ const peerInfo = this.peerTable.get(peerId);
515
+ if (!peerInfo || peerInfo.multiaddrs.length === 0) {
516
+ return failureFromError('PEER_NOT_FOUND', `Peer ${peerId} not found`);
517
+ }
518
+ peer = await this.node.dial(peerInfo.multiaddrs[0]);
519
+ }
520
+
521
+ // 准备消息数据(根据是否启用 E2EE 加密)
522
+ let data: Buffer;
523
+ if (encrypt && this.enableE2EE) {
524
+ // 检查是否有共享密钥
525
+ if (!this.e2eeCrypto.canEncryptTo(peerId)) {
526
+ return failureFromError(
527
+ 'ENCRYPTION_NOT_READY',
528
+ 'No shared secret with peer. Wait for key exchange to complete.'
529
+ );
530
+ }
531
+
532
+ // 加密消息内容
533
+ const encrypted = this.e2eeCrypto.encrypt(peerId, JSON.stringify(message));
534
+ if (!encrypted) {
535
+ return failureFromError(
536
+ 'ENCRYPTION_FAILED',
537
+ 'Failed to encrypt message. Cannot proceed in secure mode.'
538
+ );
539
+ }
540
+
541
+ data = Buffer.from(JSON.stringify({
542
+ ...message,
543
+ encrypted: true,
544
+ payload: encrypted
545
+ }));
546
+ } else {
547
+ data = Buffer.from(JSON.stringify(message));
548
+ }
549
+
550
+ // 使用协议流发送消息
551
+ const stream = await peer.newStream(F2A_PROTOCOL);
552
+ await stream.sink([data]);
553
+ await stream.close();
554
+
555
+ return success(undefined);
556
+ } catch (error) {
557
+ const err = error instanceof Error ? error : new Error(String(error));
558
+ return failureFromError('CONNECTION_FAILED', err.message, err);
559
+ }
560
+ }
561
+
562
+ /**
563
+ * 设置 libp2p 事件处理
564
+ */
565
+ private setupEventHandlers(): void {
566
+ if (!this.node) return;
567
+
568
+ // 新连接
569
+ this.node.addEventListener('peer:connect', async (evt) => {
570
+ const peerId = evt.detail.toString();
571
+ this.logger.info('Peer connected', { peerId: peerId.slice(0, 16) });
572
+
573
+ this.emit('peer:connected', {
574
+ peerId,
575
+ direction: 'inbound'
576
+ });
577
+
578
+ // 从连接获取远程 multiaddr
579
+ let multiaddrs: Multiaddr[] = [];
580
+ try {
581
+ if (this.node) {
582
+ const connections = this.node.getConnections();
583
+ const conn = connections.find(c => c.remotePeer.toString() === peerId);
584
+ if (conn && conn.remoteAddr) {
585
+ multiaddrs = [conn.remoteAddr];
586
+ }
587
+ }
588
+ } catch {
589
+ // 无法获取 multiaddrs,使用空数组
590
+ }
591
+
592
+ // 使用原子操作更新路由表
593
+ const now = Date.now();
594
+ await this.upsertPeer(
595
+ peerId,
596
+ () => ({
597
+ peerId,
598
+ multiaddrs,
599
+ connected: true,
600
+ reputation: 50,
601
+ connectedAt: now,
602
+ lastSeen: now
603
+ }),
604
+ (peer) => ({
605
+ ...peer,
606
+ connected: true,
607
+ connectedAt: now,
608
+ lastSeen: now,
609
+ ...(multiaddrs.length > 0 ? { multiaddrs } : {})
610
+ })
611
+ );
612
+ });
613
+
614
+ // 断开连接
615
+ this.node.addEventListener('peer:disconnect', async (evt) => {
616
+ const peerId = evt.detail.toString();
617
+ this.logger.info('Peer disconnected', { peerId: peerId.slice(0, 16) });
618
+
619
+ this.emit('peer:disconnected', { peerId });
620
+
621
+ // 使用原子操作更新路由表
622
+ const updated = await this.updatePeer(peerId, (peer) => ({
623
+ ...peer,
624
+ connected: false,
625
+ lastSeen: Date.now()
626
+ }));
627
+
628
+ if (!updated) {
629
+ // Peer 不在路由表中,记录警告但不创建条目(已断开)
630
+ this.logger.warn('Peer disconnected but not in routing table', { peerId: peerId.slice(0, 16) });
631
+ }
632
+ });
633
+
634
+ // 处理传入的协议流
635
+ this.node.handle(F2A_PROTOCOL, async ({ stream, connection }) => {
636
+ try {
637
+ // 读取数据
638
+ const chunks: Uint8Array[] = [];
639
+ for await (const chunk of stream.source) {
640
+ // chunk 可能是 Uint8Array 或 Uint8ArrayList(来自旧版本库)
641
+ // 统一转换为 Uint8Array
642
+ const data = chunk instanceof Uint8Array
643
+ ? chunk
644
+ : new Uint8Array((chunk as { subarray(): Uint8Array }).subarray());
645
+ chunks.push(data);
646
+ }
647
+
648
+ const data = Buffer.concat(chunks);
649
+
650
+ // 安全解析 JSON,捕获解析错误
651
+ let message: F2AMessage;
652
+ try {
653
+ message = JSON.parse(data.toString());
654
+ } catch (parseError) {
655
+ this.logger.error('Failed to parse message JSON', {
656
+ error: parseError instanceof Error ? parseError.message : String(parseError),
657
+ dataSize: data.length,
658
+ peerId: connection.remotePeer.toString().slice(0, 16)
659
+ });
660
+ return;
661
+ }
662
+
663
+ const peerId = connection.remotePeer.toString();
664
+
665
+ // 处理消息
666
+ await this.handleMessage(message, peerId);
667
+ } catch (error) {
668
+ this.logger.error('Error handling message', { error });
669
+ }
670
+ });
671
+ }
672
+
673
+ /**
674
+ * 处理收到的消息
675
+ */
676
+ private async handleMessage(message: F2AMessage, peerId: string): Promise<void> {
677
+ // 验证消息格式
678
+ const validation = validateF2AMessage(message);
679
+ if (!validation.success) {
680
+ this.logger.warn('Invalid message format', {
681
+ errors: validation.error.errors,
682
+ peerId: peerId.slice(0, 16)
683
+ });
684
+ return;
685
+ }
686
+
687
+ this.logger.info('Received message', { type: message.type, peerId: peerId.slice(0, 16) });
688
+
689
+ // 更新最后活跃时间
690
+ const peerInfo = this.peerTable.get(peerId);
691
+ if (peerInfo) {
692
+ peerInfo.lastSeen = Date.now();
693
+ }
694
+
695
+ // 处理加密消息
696
+ const decryptResult = await this.handleEncryptedMessage(message, peerId);
697
+ if (decryptResult.action === 'return') {
698
+ return;
699
+ }
700
+ message = decryptResult.message;
701
+
702
+ // 执行中间件链
703
+ const middlewareResult = await this.middlewareManager.execute({
704
+ message,
705
+ peerId,
706
+ agentInfo: peerInfo?.agentInfo,
707
+ metadata: new Map()
708
+ });
709
+
710
+ if (middlewareResult.action === 'drop') {
711
+ this.logger.info('Message dropped by middleware', {
712
+ reason: middlewareResult.reason,
713
+ peerId: peerId.slice(0, 16)
714
+ });
715
+ return;
716
+ }
717
+
718
+ // 使用可能被中间件修改后的消息
719
+ message = middlewareResult.context.message;
720
+
721
+ // 根据消息类型分发处理
722
+ await this.dispatchMessage(message, peerId);
723
+
724
+ // 转发给上层处理
725
+ this.emit('message:received', message, peerId);
726
+ }
727
+
728
+ /**
729
+ * 处理加密消息
730
+ * @returns 处理结果,包含是否继续处理和解密后的消息
731
+ */
732
+ private async handleEncryptedMessage(message: F2AMessage, peerId: string): Promise<DecryptResult> {
733
+ if (!isEncryptedMessage(message)) {
734
+ return { action: 'continue', message };
735
+ }
736
+
737
+ const encryptedPayload = message.payload;
738
+ const decrypted = this.e2eeCrypto.decrypt(encryptedPayload);
739
+
740
+ if (decrypted) {
741
+ try {
742
+ const decryptedMessage = JSON.parse(decrypted);
743
+
744
+ // 安全验证:验证解密后的消息发送方身份
745
+ if (encryptedPayload.senderPublicKey) {
746
+ const verificationResult = this.verifySenderIdentity(
747
+ decryptedMessage,
748
+ peerId,
749
+ encryptedPayload.senderPublicKey
750
+ );
751
+ if (!verificationResult.valid) {
752
+ return { action: 'return', message };
753
+ }
754
+ }
755
+
756
+ return { action: 'continue', message: decryptedMessage };
757
+ } catch (error) {
758
+ this.logger.error('Failed to parse decrypted message', { error });
759
+ return { action: 'return', message };
760
+ }
761
+ }
762
+
763
+ // 解密失败,通知发送方
764
+ await this.sendDecryptFailureResponse(message.id, peerId);
765
+ return { action: 'return', message };
766
+ }
767
+
768
+ /**
769
+ * 验证发送方身份
770
+ */
771
+ private verifySenderIdentity(
772
+ message: F2AMessage,
773
+ peerId: string,
774
+ senderPublicKey: string
775
+ ): { valid: boolean } {
776
+ // 验证发送方公钥是否已注册且属于该 peerId
777
+ const registeredKey = this.e2eeCrypto.getPeerPublicKey(peerId);
778
+ if (registeredKey && registeredKey !== senderPublicKey) {
779
+ this.logger.error('Sender identity verification failed: public key mismatch', {
780
+ peerId: peerId.slice(0, 16),
781
+ claimedKey: senderPublicKey.slice(0, 16),
782
+ registeredKey: registeredKey.slice(0, 16)
783
+ });
784
+ return { valid: false };
785
+ }
786
+
787
+ // 如果发送方声称的身份与消息来源不匹配,拒绝处理
788
+ if (message.from && message.from !== peerId) {
789
+ this.logger.error('Sender identity verification failed: from field mismatch', {
790
+ claimedFrom: message.from?.slice(0, 16),
791
+ actualPeerId: peerId.slice(0, 16)
792
+ });
793
+ return { valid: false };
794
+ }
795
+
796
+ return { valid: true };
797
+ }
798
+
799
+ /**
800
+ * 发送解密失败响应
801
+ */
802
+ private async sendDecryptFailureResponse(originalMessageId: string, peerId: string): Promise<void> {
803
+ this.logger.error('Failed to decrypt message', { peerId: peerId.slice(0, 16) });
804
+
805
+ const decryptFailResponse: F2AMessage = {
806
+ id: randomUUID(),
807
+ type: 'DECRYPT_FAILED',
808
+ from: this.agentInfo.peerId,
809
+ to: peerId,
810
+ timestamp: Date.now(),
811
+ payload: {
812
+ originalMessageId,
813
+ error: 'DECRYPTION_FAILED',
814
+ message: 'Unable to decrypt message. Key exchange may be incomplete or keys mismatched.'
815
+ }
816
+ };
817
+
818
+ try {
819
+ await this.sendMessage(peerId, decryptFailResponse, false);
820
+ } catch (sendError) {
821
+ this.logger.error('Failed to send decrypt failure response', {
822
+ peerId: peerId.slice(0, 16),
823
+ error: sendError
824
+ });
825
+ }
826
+ }
827
+
828
+ /**
829
+ * 根据消息类型分发处理
830
+ */
831
+ private async dispatchMessage(message: F2AMessage, peerId: string): Promise<void> {
832
+ switch (message.type) {
833
+ case 'DISCOVER':
834
+ await this.handleDiscoverMessage(message, peerId, true);
835
+ break;
836
+
837
+ case 'DISCOVER_RESP':
838
+ await this.handleDiscoverMessage(message, peerId, false);
839
+ break;
840
+
841
+ case 'CAPABILITY_QUERY':
842
+ await this.handleCapabilityQueryMessage(message, peerId);
843
+ break;
844
+
845
+ case 'CAPABILITY_RESPONSE':
846
+ await this.handleCapabilityResponseMessage(message, peerId);
847
+ break;
848
+
849
+ case 'TASK_RESPONSE':
850
+ await this.handleTaskResponseMessage(message);
851
+ break;
852
+
853
+ case 'DECRYPT_FAILED':
854
+ await this.handleDecryptFailedMessage(message, peerId);
855
+ break;
856
+ }
857
+ }
858
+
859
+ /**
860
+ * 处理发现消息
861
+ */
862
+ private async handleDiscoverMessage(message: F2AMessage, peerId: string, shouldRespond: boolean): Promise<void> {
863
+ const payload = message.payload as DiscoverPayload;
864
+ await this.handleDiscover(payload.agentInfo, peerId, shouldRespond);
865
+ }
866
+
867
+ /**
868
+ * 处理能力响应消息
869
+ */
870
+ private async handleCapabilityResponseMessage(message: F2AMessage, peerId: string): Promise<void> {
871
+ const payload = message.payload as CapabilityResponsePayload;
872
+ this.upsertPeerFromAgentInfo(payload.agentInfo, peerId);
873
+ }
874
+
875
+ /**
876
+ * 处理能力查询消息
877
+ */
878
+ private async handleCapabilityQueryMessage(message: F2AMessage, peerId: string): Promise<void> {
879
+ const payload = message.payload as CapabilityQueryPayload;
880
+ await this.handleCapabilityQuery(payload, peerId);
881
+ }
882
+
883
+ /**
884
+ * 处理任务响应消息
885
+ */
886
+ private async handleTaskResponseMessage(message: F2AMessage): Promise<void> {
887
+ const payloadValidation = validateTaskResponsePayload(message.payload);
888
+ if (!payloadValidation.success) {
889
+ this.logger.warn('Invalid task response payload', {
890
+ errors: payloadValidation.error.errors
891
+ });
892
+ return;
893
+ }
894
+ const payload = message.payload as TaskResponsePayload;
895
+ this.handleTaskResponse(payload);
896
+ }
897
+
898
+ /**
899
+ * 处理解密失败通知消息
900
+ */
901
+ private async handleDecryptFailedMessage(message: F2AMessage, peerId: string): Promise<void> {
902
+ const { originalMessageId, error, message: errorMsg } = message.payload as {
903
+ originalMessageId: string;
904
+ error: string;
905
+ message: string;
906
+ };
907
+
908
+ this.logger.error('Received decrypt failure notification', {
909
+ peerId: peerId.slice(0, 16),
910
+ originalMessageId,
911
+ error,
912
+ message: errorMsg
913
+ });
914
+
915
+ // 尝试重新注册公钥以重新建立加密通道
916
+ const peerInfo = this.peerTable.get(peerId);
917
+ if (peerInfo?.agentInfo?.encryptionPublicKey) {
918
+ this.e2eeCrypto.registerPeerPublicKey(peerId, peerInfo.agentInfo.encryptionPublicKey);
919
+ this.logger.info('Re-registered encryption key after decrypt failure', {
920
+ peerId: peerId.slice(0, 16)
921
+ });
922
+ }
923
+
924
+ // 发出事件通知上层应用
925
+ this.emit('error', new Error(`Decrypt failed for message ${originalMessageId}: ${errorMsg}`));
926
+ }
927
+
928
+ /**
929
+ * 处理发现消息
930
+ */
931
+ /**
932
+ * 处理发现消息
933
+ */
934
+ private async handleDiscover(agentInfo: AgentInfo, peerId: string, shouldRespond: boolean): Promise<void> {
935
+ // 安全验证:确保 agentInfo.peerId 与发送方一致,防止伪造
936
+ if (agentInfo.peerId !== peerId) {
937
+ this.logger.warn('Discovery message rejected: peerId mismatch', {
938
+ claimedPeerId: agentInfo.peerId?.slice(0, 16),
939
+ actualPeerId: peerId.slice(0, 16)
940
+ });
941
+ return;
942
+ }
943
+
944
+ // 使用锁保护容量检查和创建操作的原子性
945
+ await this.peerTableLock.acquire();
946
+ try {
947
+ // 检查是否需要清理以腾出空间
948
+ if (!this.peerTable.has(peerId)) {
949
+ // 新 peer,需要检查容量
950
+ const highWatermark = Math.floor(PEER_TABLE_MAX_SIZE * PEER_TABLE_HIGH_WATERMARK);
951
+ if (this.peerTable.size >= highWatermark) {
952
+ // 接近容量上限,触发激进清理
953
+ this.cleanupStalePeersLocked(true);
954
+ }
955
+
956
+ if (this.peerTable.size >= PEER_TABLE_MAX_SIZE) {
957
+ // 清理后仍无空间,拒绝新 peer
958
+ this.logger.warn('Peer table full, rejecting new peer', {
959
+ peerId: peerId.slice(0, 16),
960
+ currentSize: this.peerTable.size,
961
+ maxSize: PEER_TABLE_MAX_SIZE
962
+ });
963
+ return;
964
+ }
965
+ }
966
+
967
+ // 更新路由表
968
+ const now = Date.now();
969
+ const existing = this.peerTable.get(peerId);
970
+ if (existing) {
971
+ this.peerTable.set(peerId, {
972
+ ...existing,
973
+ agentInfo,
974
+ lastSeen: now,
975
+ multiaddrs: agentInfo.multiaddrs.map(ma => multiaddr(ma))
976
+ });
977
+ } else {
978
+ this.peerTable.set(peerId, {
979
+ peerId,
980
+ agentInfo,
981
+ multiaddrs: agentInfo.multiaddrs.map(ma => multiaddr(ma)),
982
+ connected: false,
983
+ reputation: 50,
984
+ lastSeen: now
985
+ });
986
+ }
987
+ } finally {
988
+ this.peerTableLock.release();
989
+ }
990
+
991
+ // 注册对等方的加密公钥
992
+ if (agentInfo.encryptionPublicKey) {
993
+ this.e2eeCrypto.registerPeerPublicKey(peerId, agentInfo.encryptionPublicKey);
994
+ this.logger.info('Registered encryption key', { peerId: peerId.slice(0, 16) });
995
+ }
996
+
997
+ // 仅对 DISCOVER 请求响应,避免发现响应循环
998
+ if (shouldRespond) {
999
+ const responseResult = await this.sendMessage(peerId, {
1000
+ id: randomUUID(),
1001
+ type: 'DISCOVER_RESP',
1002
+ from: this.agentInfo.peerId,
1003
+ to: peerId,
1004
+ timestamp: Date.now(),
1005
+ payload: { agentInfo: this.agentInfo } as DiscoverPayload
1006
+ });
1007
+
1008
+ if (!responseResult.success) {
1009
+ this.logger.warn('Failed to send discover response', {
1010
+ peerId: peerId.slice(0, 16),
1011
+ error: responseResult.error
1012
+ });
1013
+ }
1014
+ }
1015
+
1016
+ this.emit('peer:discovered', {
1017
+ peerId,
1018
+ agentInfo,
1019
+ multiaddrs: agentInfo.multiaddrs.map(ma => multiaddr(ma))
1020
+ });
1021
+ }
1022
+
1023
+ /**
1024
+ * 将发现到的 Agent 信息更新到 Peer 表
1025
+ */
1026
+ private upsertPeerFromAgentInfo(agentInfo: AgentInfo, peerId: string): void {
1027
+ // 使用锁保护,确保线程安全
1028
+ this.peerTableLock.acquire().then(() => {
1029
+ try {
1030
+ // 检查是否需要清理以腾出空间
1031
+ if (this.peerTable.size >= PEER_TABLE_MAX_SIZE && !this.peerTable.has(peerId)) {
1032
+ this.cleanupStalePeersLocked(true);
1033
+ }
1034
+
1035
+ const existing = this.peerTable.get(peerId);
1036
+ if (existing) {
1037
+ existing.agentInfo = agentInfo;
1038
+ existing.lastSeen = Date.now();
1039
+ existing.multiaddrs = agentInfo.multiaddrs.map(ma => multiaddr(ma));
1040
+ } else {
1041
+ this.peerTable.set(peerId, {
1042
+ peerId,
1043
+ agentInfo,
1044
+ multiaddrs: agentInfo.multiaddrs.map(ma => multiaddr(ma)),
1045
+ connected: false,
1046
+ reputation: 50,
1047
+ lastSeen: Date.now()
1048
+ });
1049
+ }
1050
+ } finally {
1051
+ this.peerTableLock.release();
1052
+ }
1053
+ });
1054
+
1055
+ // 注册对等方的加密公钥
1056
+ if (agentInfo.encryptionPublicKey) {
1057
+ this.e2eeCrypto.registerPeerPublicKey(peerId, agentInfo.encryptionPublicKey);
1058
+ this.logger.info('Registered encryption key', { peerId: peerId.slice(0, 16) });
1059
+ }
1060
+ }
1061
+
1062
+ /**
1063
+ * 处理能力查询
1064
+ */
1065
+ private async handleCapabilityQuery(
1066
+ query: CapabilityQueryPayload,
1067
+ peerId: string
1068
+ ): Promise<void> {
1069
+ // 检查是否匹配
1070
+ const matches = !query.capabilityName ||
1071
+ this.hasCapability(this.agentInfo, query.capabilityName);
1072
+
1073
+ if (matches) {
1074
+ // 发送能力响应
1075
+ await this.sendMessage(peerId, {
1076
+ id: randomUUID(),
1077
+ type: 'CAPABILITY_RESPONSE',
1078
+ from: this.agentInfo.peerId,
1079
+ to: peerId,
1080
+ timestamp: Date.now(),
1081
+ payload: { agentInfo: this.agentInfo } as CapabilityResponsePayload
1082
+ });
1083
+ }
1084
+ }
1085
+
1086
+ /**
1087
+ * 处理任务响应
1088
+ */
1089
+ private handleTaskResponse(payload: TaskResponsePayload): void {
1090
+ const pending = this.pendingTasks.get(payload.taskId);
1091
+ if (!pending) {
1092
+ this.logger.warn('Received response for unknown task', { taskId: payload.taskId });
1093
+ return;
1094
+ }
1095
+
1096
+ // 使用原子操作避免竞态条件:先删除 Map 条目,再检查 resolved 标志
1097
+ // 这确保即使多个响应并发到达,也只有一个能成功获取到 pending 条目
1098
+ this.pendingTasks.delete(payload.taskId);
1099
+
1100
+ if (pending.resolved) {
1101
+ this.logger.warn('Task already resolved, ignoring duplicate response', { taskId: payload.taskId });
1102
+ return;
1103
+ }
1104
+ pending.resolved = true;
1105
+
1106
+ // 清理资源
1107
+ clearTimeout(pending.timeout);
1108
+
1109
+ // 处理结果
1110
+ if (payload.status === 'success') {
1111
+ pending.resolve(payload.result);
1112
+ } else {
1113
+ pending.reject(payload.error || 'Task failed');
1114
+ }
1115
+ }
1116
+
1117
+ /**
1118
+ * 连接引导节点
1119
+ */
1120
+ private async connectToBootstrapPeers(peers: string[]): Promise<void> {
1121
+ if (!this.node) return;
1122
+
1123
+ for (const addr of peers) {
1124
+ try {
1125
+ const ma = multiaddr(addr);
1126
+ await this.node.dial(ma);
1127
+ this.logger.info('Connected to bootstrap', { addr });
1128
+ } catch (error) {
1129
+ this.logger.warn('Failed to connect to bootstrap', { addr });
1130
+ }
1131
+ }
1132
+ }
1133
+
1134
+ /**
1135
+ * 启动定期发现广播
1136
+ */
1137
+ private startDiscoveryBroadcast(): void {
1138
+ // 立即广播一次
1139
+ this.broadcastDiscovery();
1140
+
1141
+ // 每 30 秒广播一次
1142
+ this.discoveryInterval = setInterval(() => {
1143
+ this.broadcastDiscovery();
1144
+ }, 30000);
1145
+ }
1146
+
1147
+ /**
1148
+ * 启动定期清理任务
1149
+ */
1150
+ private startCleanupTask(): void {
1151
+ // 立即执行一次
1152
+ this.cleanupStalePeers();
1153
+
1154
+ // 每 5 分钟清理一次
1155
+ this.cleanupInterval = setInterval(() => {
1156
+ this.cleanupStalePeers();
1157
+ }, PEER_TABLE_CLEANUP_INTERVAL);
1158
+ }
1159
+
1160
+ /**
1161
+ * 清理过期的 Peer 记录(带锁保护)
1162
+ * @param aggressive 是否使用激进清理策略(清理更多条目)
1163
+ */
1164
+ private async cleanupStalePeers(aggressive = false): Promise<void> {
1165
+ await this.peerTableLock.acquire();
1166
+ try {
1167
+ this.cleanupStalePeersLocked(aggressive);
1168
+ } finally {
1169
+ this.peerTableLock.release();
1170
+ }
1171
+ }
1172
+
1173
+ /**
1174
+ * 清理过期的 Peer 记录(内部方法,调用前必须持有锁)
1175
+ * @param aggressive 是否使用激进清理策略(清理更多条目)
1176
+ */
1177
+ private cleanupStalePeersLocked(aggressive = false): void {
1178
+ const now = Date.now();
1179
+ const threshold = aggressive ? 0 : PEER_STALE_THRESHOLD;
1180
+ let cleaned = 0;
1181
+
1182
+ // 激进清理:清理更多类型的条目
1183
+ if (aggressive) {
1184
+ // 1. 清理所有未连接且超过 1 小时的 peer
1185
+ for (const [peerId, peer] of this.peerTable) {
1186
+ if (!peer.connected && now - peer.lastSeen > 60 * 60 * 1000) {
1187
+ this.peerTable.delete(peerId);
1188
+ cleaned++;
1189
+ }
1190
+ }
1191
+
1192
+ // 2. 如果仍然超过高水位线,按最后活跃时间排序后删除最旧的
1193
+ const highWatermark = Math.floor(PEER_TABLE_MAX_SIZE * PEER_TABLE_HIGH_WATERMARK);
1194
+ if (this.peerTable.size > highWatermark) {
1195
+ const targetSize = Math.floor(PEER_TABLE_MAX_SIZE * PEER_TABLE_AGGRESSIVE_CLEANUP_THRESHOLD);
1196
+ const sorted = Array.from(this.peerTable.entries())
1197
+ .sort((a, b) => a[1].lastSeen - b[1].lastSeen);
1198
+
1199
+ // 优先删除未连接的 peer
1200
+ const toRemove = sorted
1201
+ .filter(([_, peer]) => !peer.connected)
1202
+ .slice(0, this.peerTable.size - targetSize);
1203
+
1204
+ for (const [peerId] of toRemove) {
1205
+ this.peerTable.delete(peerId);
1206
+ cleaned++;
1207
+ }
1208
+ }
1209
+ } else {
1210
+ // 常规清理:清理过期条目
1211
+ for (const [peerId, peer] of this.peerTable) {
1212
+ // 清理条件:长时间未活跃,或者未连接且超过一定时间
1213
+ const shouldClean =
1214
+ now - peer.lastSeen > threshold ||
1215
+ (!peer.connected && now - peer.lastSeen > 60 * 60 * 1000); // 未连接超过1小时
1216
+
1217
+ if (shouldClean) {
1218
+ this.peerTable.delete(peerId);
1219
+ cleaned++;
1220
+ }
1221
+ }
1222
+ }
1223
+
1224
+ if (cleaned > 0) {
1225
+ this.logger.info('Cleaned up stale peers', { cleaned, remaining: this.peerTable.size, aggressive });
1226
+ }
1227
+
1228
+ // 如果仍然超过最大容量,按最后活跃时间排序后删除最旧的
1229
+ if (this.peerTable.size > PEER_TABLE_MAX_SIZE) {
1230
+ const sorted = Array.from(this.peerTable.entries())
1231
+ .sort((a, b) => a[1].lastSeen - b[1].lastSeen);
1232
+
1233
+ // 优先删除未连接的 peer
1234
+ const disconnected = sorted.filter(([_, peer]) => !peer.connected);
1235
+ const toRemove = disconnected.length > 0
1236
+ ? disconnected.slice(0, this.peerTable.size - PEER_TABLE_MAX_SIZE)
1237
+ : sorted.slice(0, this.peerTable.size - PEER_TABLE_MAX_SIZE);
1238
+
1239
+ for (const [peerId] of toRemove) {
1240
+ this.peerTable.delete(peerId);
1241
+ cleaned++;
1242
+ }
1243
+
1244
+ this.logger.info('Removed oldest peers to maintain limit', { removed: toRemove.length });
1245
+ }
1246
+ }
1247
+
1248
+ /**
1249
+ * 原子操作:获取 peer 信息
1250
+ */
1251
+ private getPeer(peerId: string): PeerInfo | undefined {
1252
+ return this.peerTable.get(peerId);
1253
+ }
1254
+
1255
+ /**
1256
+ * 原子操作:设置 peer 信息
1257
+ */
1258
+ private setPeer(peerId: string, info: PeerInfo): void {
1259
+ this.peerTable.set(peerId, info);
1260
+ }
1261
+
1262
+ /**
1263
+ * 原子操作:更新 peer 信息(线程安全)
1264
+ * @param peerId Peer ID
1265
+ * @param updater 更新函数,接收当前值,返回新值
1266
+ * @returns 更新后的 peer 信息,如果 peer 不存在则返回 undefined
1267
+ */
1268
+ private async updatePeer(
1269
+ peerId: string,
1270
+ updater: (peer: PeerInfo) => PeerInfo
1271
+ ): Promise<PeerInfo | undefined> {
1272
+ await this.peerTableLock.acquire();
1273
+ try {
1274
+ const peer = this.peerTable.get(peerId);
1275
+ if (!peer) return undefined;
1276
+ const updated = updater(peer);
1277
+ this.peerTable.set(peerId, updated);
1278
+ return updated;
1279
+ } finally {
1280
+ this.peerTableLock.release();
1281
+ }
1282
+ }
1283
+
1284
+ /**
1285
+ * 原子操作:安全地更新或创建 peer
1286
+ * @param peerId Peer ID
1287
+ * @param creator 创建新 peer 的函数(如果不存在)
1288
+ * @param updater 更新函数(如果存在)
1289
+ */
1290
+ private async upsertPeer(
1291
+ peerId: string,
1292
+ creator: () => PeerInfo,
1293
+ updater: (peer: PeerInfo) => PeerInfo
1294
+ ): Promise<PeerInfo> {
1295
+ await this.peerTableLock.acquire();
1296
+ try {
1297
+ const existing = this.peerTable.get(peerId);
1298
+ if (existing) {
1299
+ const updated = updater(existing);
1300
+ this.peerTable.set(peerId, updated);
1301
+ return updated;
1302
+ } else {
1303
+ const created = creator();
1304
+ this.peerTable.set(peerId, created);
1305
+ return created;
1306
+ }
1307
+ } finally {
1308
+ this.peerTableLock.release();
1309
+ }
1310
+ }
1311
+
1312
+ /**
1313
+ * 原子操作:删除 peer
1314
+ */
1315
+ private async deletePeer(peerId: string): Promise<boolean> {
1316
+ await this.peerTableLock.acquire();
1317
+ try {
1318
+ return this.peerTable.delete(peerId);
1319
+ } finally {
1320
+ this.peerTableLock.release();
1321
+ }
1322
+ }
1323
+
1324
+ /**
1325
+ * 检查 Agent 是否有特定能力
1326
+ */
1327
+ private hasCapability(agentInfo: AgentInfo, capabilityName: string): boolean {
1328
+ return agentInfo.capabilities.some(c => c.name === capabilityName);
1329
+ }
1330
+
1331
+ /**
1332
+ * 获取已连接的 Peers
1333
+ */
1334
+ getConnectedPeers(): PeerInfo[] {
1335
+ return Array.from(this.peerTable.values()).filter(p => p.connected);
1336
+ }
1337
+
1338
+ /**
1339
+ * 获取所有已知的 Peers
1340
+ */
1341
+ getAllPeers(): PeerInfo[] {
1342
+ return Array.from(this.peerTable.values());
1343
+ }
1344
+
1345
+ /**
1346
+ * 获取节点 ID
1347
+ */
1348
+ getPeerId(): string | null {
1349
+ return this.agentInfo.peerId;
1350
+ }
1351
+
1352
+ /**
1353
+ * 获取 E2EE 加密公钥
1354
+ */
1355
+ getEncryptionPublicKey(): string | null {
1356
+ return this.e2eeCrypto.getPublicKey();
1357
+ }
1358
+
1359
+ /**
1360
+ * 获取已注册的加密对等方数量
1361
+ */
1362
+ getEncryptedPeerCount(): number {
1363
+ return this.e2eeCrypto.getRegisteredPeerCount();
1364
+ }
1365
+
1366
+ /**
1367
+ * 通过 DHT 查找节点 (全局发现)
1368
+ */
1369
+ async findPeerViaDHT(peerId: string): Promise<Result<string[]>> {
1370
+ if (!this.node) {
1371
+ return failureFromError('NETWORK_NOT_STARTED', 'P2P network not started');
1372
+ }
1373
+
1374
+ const dht = (this.node.services as Libp2pServices).dht;
1375
+ if (!dht) {
1376
+ return failureFromError('DHT_NOT_AVAILABLE', 'DHT service not enabled');
1377
+ }
1378
+
1379
+ try {
1380
+ const peerIdObj = peerIdFromString(peerId);
1381
+ const peerInfo = await dht.findPeer(peerIdObj);
1382
+
1383
+ if (peerInfo && peerInfo.multiaddrs.length > 0) {
1384
+ return success(peerInfo.multiaddrs.map(ma => ma.toString()));
1385
+ }
1386
+
1387
+ return failureFromError('PEER_NOT_FOUND', `Peer ${peerId} not found in DHT`);
1388
+ } catch (error) {
1389
+ return failureFromError('DHT_LOOKUP_FAILED', 'DHT lookup failed', error as Error);
1390
+ }
1391
+ }
1392
+
1393
+ /**
1394
+ * 获取 DHT 路由表大小
1395
+ */
1396
+ getDHTPeerCount(): number {
1397
+ const dht = (this.node?.services as Libp2pServices)?.dht;
1398
+ return dht?.routingTable?.size || 0;
1399
+ }
1400
+
1401
+ /**
1402
+ * 检查 DHT 是否启用
1403
+ */
1404
+ isDHTEnabled(): boolean {
1405
+ return !!(this.node?.services as Libp2pServices)?.dht;
1406
+ }
1407
+
1408
+ /**
1409
+ * 注册中间件
1410
+ * @param middleware 中间件实例
1411
+ */
1412
+ useMiddleware(middleware: Middleware): void {
1413
+ this.middlewareManager.use(middleware);
1414
+ }
1415
+
1416
+ /**
1417
+ * 移除中间件
1418
+ * @param name 中间件名称
1419
+ * @returns 是否成功移除
1420
+ */
1421
+ removeMiddleware(name: string): boolean {
1422
+ return this.middlewareManager.remove(name);
1423
+ }
1424
+
1425
+ /**
1426
+ * 获取已注册的中间件列表
1427
+ * @returns 中间件名称列表
1428
+ */
1429
+ listMiddlewares(): string[] {
1430
+ return this.middlewareManager.list();
1431
+ }
1432
+ }