@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,583 @@
1
+ /**
2
+ * F2A OpenClaw Connector - 业务逻辑测试
3
+ * 测试核心功能,使用真实业务逻辑而非无意义的 mock
4
+ */
5
+
6
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
7
+ import { mkdirSync, rmSync, existsSync } from 'fs';
8
+ import { F2AOpenClawAdapter } from './connector.js';
9
+ import { ReputationSystem } from './reputation.js';
10
+ import { CapabilityDetector } from './capability-detector.js';
11
+ import { TaskQueue } from './task-queue.js';
12
+ import type { F2APluginConfig, AgentCapability, AgentInfo, TaskRequest } from './types.js';
13
+
14
+ describe('F2AOpenClawAdapter 业务逻辑', () => {
15
+ describe('配置合并', () => {
16
+ it('应该使用默认配置当未提供可选配置时', () => {
17
+ const connector = new F2AOpenClawAdapter();
18
+ const minimalConfig = {};
19
+
20
+ // 验证默认配置值
21
+ const mergedConfig = (connector as any).mergeConfig(minimalConfig);
22
+
23
+ expect(mergedConfig.autoStart).toBe(true);
24
+ expect(mergedConfig.webhookPort).toBe(9002);
25
+ expect(mergedConfig.agentName).toBe('OpenClaw Agent');
26
+ expect(mergedConfig.dataDir).toBe('./f2a-data');
27
+ expect(mergedConfig.maxQueuedTasks).toBe(100);
28
+ expect(mergedConfig.reputation.enabled).toBe(true);
29
+ expect(mergedConfig.reputation.initialScore).toBe(50);
30
+ expect(mergedConfig.security.requireConfirmation).toBe(false);
31
+ });
32
+
33
+ it('应该覆盖默认配置当提供自定义值时', () => {
34
+ const connector = new F2AOpenClawAdapter();
35
+ const customConfig = {
36
+ autoStart: false,
37
+ webhookPort: 9999,
38
+ agentName: 'Custom Agent',
39
+ maxQueuedTasks: 50,
40
+ reputation: {
41
+ enabled: false,
42
+ initialScore: 100
43
+ }
44
+ };
45
+
46
+ const mergedConfig = (connector as any).mergeConfig(customConfig);
47
+
48
+ expect(mergedConfig.autoStart).toBe(false);
49
+ expect(mergedConfig.webhookPort).toBe(9999);
50
+ expect(mergedConfig.agentName).toBe('Custom Agent');
51
+ expect(mergedConfig.maxQueuedTasks).toBe(50);
52
+ expect(mergedConfig.reputation.enabled).toBe(false);
53
+ expect(mergedConfig.reputation.initialScore).toBe(100);
54
+ });
55
+
56
+ it('应该正确处理 bootstrapPeers 配置', () => {
57
+ const connector = new F2AOpenClawAdapter();
58
+ const configWithPeers = {
59
+ bootstrapPeers: ['/ip4/1.2.3.4/tcp/9000/p2p/peer-1', '/ip4/5.6.7.8/tcp/9000/p2p/peer-2']
60
+ };
61
+
62
+ const mergedConfig = (connector as any).mergeConfig(configWithPeers);
63
+
64
+ expect(mergedConfig.bootstrapPeers).toHaveLength(2);
65
+ expect(mergedConfig.bootstrapPeers[0]).toBe('/ip4/1.2.3.4/tcp/9000/p2p/peer-1');
66
+ });
67
+ });
68
+
69
+ describe('Agent 解析', () => {
70
+ it('应该通过 #索引 格式解析 Agent', async () => {
71
+ const connector = new F2AOpenClawAdapter();
72
+ const mockAgents: AgentInfo[] = [
73
+ { peerId: 'peer-1', displayName: 'Agent A', agentType: 'test', version: '1.0', capabilities: [], multiaddrs: [], lastSeen: Date.now() },
74
+ { peerId: 'peer-2', displayName: 'Agent B', agentType: 'test', version: '1.0', capabilities: [], multiaddrs: [], lastSeen: Date.now() }
75
+ ];
76
+
77
+ // 模拟 discoverAgents 返回数据
78
+ (connector as any).networkClient = {
79
+ discoverAgents: async () => ({ success: true, data: mockAgents })
80
+ };
81
+
82
+ const result = await (connector as any).resolveAgent('#1');
83
+ expect(result?.displayName).toBe('Agent A');
84
+
85
+ const result2 = await (connector as any).resolveAgent('#2');
86
+ expect(result2?.displayName).toBe('Agent B');
87
+ });
88
+
89
+ it('应该通过 displayName 精确匹配解析 Agent', async () => {
90
+ const connector = new F2AOpenClawAdapter();
91
+ const mockAgents: AgentInfo[] = [
92
+ { peerId: 'peer-1', displayName: 'MacBook-Pro', agentType: 'test', version: '1.0', capabilities: [], multiaddrs: [], lastSeen: Date.now() }
93
+ ];
94
+
95
+ (connector as any).networkClient = {
96
+ discoverAgents: async () => ({ success: true, data: mockAgents })
97
+ };
98
+
99
+ const result = await (connector as any).resolveAgent('MacBook-Pro');
100
+ expect(result?.peerId).toBe('peer-1');
101
+ });
102
+
103
+ it('应该通过 peerId 前缀模糊匹配解析 Agent', async () => {
104
+ const connector = new F2AOpenClawAdapter();
105
+ const mockAgents: AgentInfo[] = [
106
+ { peerId: 'f2a-abc123', displayName: 'Test Agent', agentType: 'test', version: '1.0', capabilities: [], multiaddrs: [], lastSeen: Date.now() }
107
+ ];
108
+
109
+ (connector as any).networkClient = {
110
+ discoverAgents: async () => ({ success: true, data: mockAgents })
111
+ };
112
+
113
+ const result = await (connector as any).resolveAgent('f2a-ab');
114
+ expect(result?.peerId).toBe('f2a-abc123');
115
+ });
116
+
117
+ it('应该返回 null 当找不到匹配的 Agent', async () => {
118
+ const connector = new F2AOpenClawAdapter();
119
+
120
+ (connector as any).networkClient = {
121
+ discoverAgents: async () => ({ success: true, data: [] })
122
+ };
123
+
124
+ const result = await (connector as any).resolveAgent('NonExistent');
125
+ expect(result).toBeNull();
126
+ });
127
+
128
+ it('应该通过 displayName 模糊匹配解析 Agent', async () => {
129
+ const connector = new F2AOpenClawAdapter();
130
+ const mockAgents: AgentInfo[] = [
131
+ { peerId: 'peer-1', displayName: 'My Special Agent', agentType: 'test', version: '1.0', capabilities: [], multiaddrs: [], lastSeen: Date.now() }
132
+ ];
133
+
134
+ (connector as any).networkClient = {
135
+ discoverAgents: async () => ({ success: true, data: mockAgents })
136
+ };
137
+
138
+ const result = await (connector as any).resolveAgent('special');
139
+ expect(result?.peerId).toBe('peer-1');
140
+ });
141
+ });
142
+
143
+ describe('广播结果格式化', () => {
144
+ it('应该正确格式化成功的广播结果', () => {
145
+ const connector = new F2AOpenClawAdapter();
146
+ const results = [
147
+ { agent: 'Agent A', success: true, latency: 100 },
148
+ { agent: 'Agent B', success: true, latency: 200 }
149
+ ];
150
+
151
+ const formatted = (connector as any).formatBroadcastResults(results);
152
+
153
+ expect(formatted).toContain('✅ Agent A (100ms)');
154
+ expect(formatted).toContain('✅ Agent B (200ms)');
155
+ expect(formatted).toContain('完成');
156
+ });
157
+
158
+ it('应该正确格式化失败的广播结果', () => {
159
+ const connector = new F2AOpenClawAdapter();
160
+ const results = [
161
+ { agent: 'Agent A', success: false, error: 'Timeout' }
162
+ ];
163
+
164
+ const formatted = (connector as any).formatBroadcastResults(results);
165
+
166
+ expect(formatted).toContain('❌ Agent A');
167
+ expect(formatted).toContain('失败: Timeout');
168
+ });
169
+
170
+ it('应该处理没有延迟信息的结果', () => {
171
+ const connector = new F2AOpenClawAdapter();
172
+ const results = [
173
+ { agent: 'Agent A', success: true }
174
+ ];
175
+
176
+ const formatted = (connector as any).formatBroadcastResults(results);
177
+
178
+ expect(formatted).toContain('✅ Agent A');
179
+ expect(formatted).not.toContain('undefined');
180
+ });
181
+ });
182
+
183
+ describe('Token 生成', () => {
184
+ it('应该生成符合格式的 token', () => {
185
+ const connector = new F2AOpenClawAdapter();
186
+ const token = (connector as any).generateToken();
187
+
188
+ expect(token).toMatch(/^f2a-[A-Za-z0-9]{32}$/);
189
+ });
190
+
191
+ it('应该生成不同的 token 每次调用', () => {
192
+ const connector = new F2AOpenClawAdapter();
193
+ const token1 = (connector as any).generateToken();
194
+ const token2 = (connector as any).generateToken();
195
+
196
+ expect(token1).not.toBe(token2);
197
+ });
198
+ });
199
+ });
200
+
201
+ describe('ReputationSystem 业务逻辑', () => {
202
+ const testDir = '/tmp/f2a-test-reputation-' + Date.now();
203
+
204
+ beforeEach(() => {
205
+ // 创建测试目录
206
+ try {
207
+ mkdirSync(testDir, { recursive: true });
208
+ } catch (e) {
209
+ // 目录可能已存在
210
+ }
211
+ });
212
+
213
+ afterEach(() => {
214
+ // 清理测试目录
215
+ try {
216
+ rmSync(testDir, { recursive: true, force: true });
217
+ } catch (e) {
218
+ // 忽略清理错误
219
+ }
220
+ });
221
+
222
+ describe('信誉计算', () => {
223
+ it('新 peer 应该获得初始信誉分', () => {
224
+ const config = { enabled: true, initialScore: 50, minScoreForService: 20, decayRate: 0.01 };
225
+ const reputation = new ReputationSystem(config, testDir);
226
+
227
+ const rep = reputation.getReputation('new-peer');
228
+ expect(rep.score).toBe(50);
229
+ expect(rep.successfulTasks).toBe(0);
230
+ expect(rep.failedTasks).toBe(0);
231
+ });
232
+
233
+ it('成功任务应该增加信誉分', () => {
234
+ const config = { enabled: true, initialScore: 50, minScoreForService: 20, decayRate: 0.01 };
235
+ const reputation = new ReputationSystem(config, testDir);
236
+
237
+ reputation.recordSuccess('peer-1', 'task-1', 100);
238
+ const rep = reputation.getReputation('peer-1');
239
+
240
+ expect(rep.score).toBeGreaterThan(50);
241
+ expect(rep.successfulTasks).toBe(1);
242
+ expect(rep.avgResponseTime).toBe(100);
243
+ });
244
+
245
+ it('多次成功任务应该累计', () => {
246
+ const config = { enabled: true, initialScore: 50, minScoreForService: 20, decayRate: 0.01 };
247
+ const reputation = new ReputationSystem(config, testDir);
248
+
249
+ reputation.recordSuccess('peer-1', 'task-1', 100);
250
+ reputation.recordSuccess('peer-1', 'task-2', 200);
251
+ const rep = reputation.getReputation('peer-1');
252
+
253
+ expect(rep.successfulTasks).toBe(2);
254
+ // 平均响应时间使用加权移动平均: 100 * 0.7 + 200 * 0.3 = 130
255
+ expect(rep.avgResponseTime).toBe(130);
256
+ });
257
+
258
+ it('失败任务应该降低信誉分', () => {
259
+ const config = { enabled: true, initialScore: 50, minScoreForService: 20, decayRate: 0.01 };
260
+ const reputation = new ReputationSystem(config, testDir);
261
+
262
+ reputation.recordFailure('peer-1', 'task-1', 'Error');
263
+ const rep = reputation.getReputation('peer-1');
264
+
265
+ expect(rep.score).toBeLessThan(50);
266
+ expect(rep.failedTasks).toBe(1);
267
+ });
268
+
269
+ it('信誉低于阈值应该被拒绝服务', () => {
270
+ const config = { enabled: true, initialScore: 50, minScoreForService: 20, decayRate: 0.01 };
271
+ const reputation = new ReputationSystem(config, testDir);
272
+
273
+ // 多次失败降低信誉
274
+ for (let i = 0; i < 10; i++) {
275
+ reputation.recordFailure('bad-peer', `task-${i}`, 'Error');
276
+ }
277
+
278
+ expect(reputation.isAllowed('bad-peer')).toBe(false);
279
+ });
280
+
281
+ it('信誉高于阈值应该被允许服务', () => {
282
+ const config = { enabled: true, initialScore: 50, minScoreForService: 20, decayRate: 0.01 };
283
+ const reputation = new ReputationSystem(config, testDir);
284
+
285
+ reputation.recordSuccess('good-peer', 'task-1', 100);
286
+
287
+ expect(reputation.isAllowed('good-peer')).toBe(true);
288
+ });
289
+
290
+ it('禁用信誉系统时应该允许所有 peer', () => {
291
+ const config = { enabled: false, initialScore: 50, minScoreForService: 20, decayRate: 0.01 };
292
+ const reputation = new ReputationSystem(config, testDir);
293
+
294
+ // 即使多次失败也应该允许
295
+ for (let i = 0; i < 20; i++) {
296
+ reputation.recordFailure('any-peer', `task-${i}`, 'Error');
297
+ }
298
+
299
+ expect(reputation.isAllowed('any-peer')).toBe(true);
300
+ });
301
+ });
302
+
303
+ describe('历史记录', () => {
304
+ it('应该记录成功任务历史', () => {
305
+ const config = { enabled: true, initialScore: 50, minScoreForService: 20, decayRate: 0.01 };
306
+ const reputation = new ReputationSystem(config, testDir);
307
+
308
+ reputation.recordSuccess('peer-1', 'task-1', 100);
309
+ const rep = reputation.getReputation('peer-1');
310
+
311
+ expect(rep.history.length).toBe(1);
312
+ expect(rep.history[0].type).toBe('task_success');
313
+ expect(rep.history[0].taskId).toBe('task-1');
314
+ });
315
+
316
+ it('应该记录失败任务历史', () => {
317
+ const config = { enabled: true, initialScore: 50, minScoreForService: 20, decayRate: 0.01 };
318
+ const reputation = new ReputationSystem(config, testDir);
319
+
320
+ reputation.recordFailure('peer-1', 'task-1', 'Timeout');
321
+ const rep = reputation.getReputation('peer-1');
322
+
323
+ expect(rep.history.length).toBe(1);
324
+ expect(rep.history[0].type).toBe('task_failure');
325
+ expect(rep.history[0].reason).toBe('Timeout');
326
+ });
327
+
328
+ it('应该限制历史记录数量', () => {
329
+ const config = { enabled: true, initialScore: 50, minScoreForService: 20, decayRate: 0.01 };
330
+ const reputation = new ReputationSystem(config, testDir);
331
+
332
+ // 添加超过 100 条记录
333
+ for (let i = 0; i < 110; i++) {
334
+ reputation.recordSuccess('peer-1', `task-${i}`, 100);
335
+ }
336
+
337
+ const rep = reputation.getReputation('peer-1');
338
+ expect(rep.history.length).toBeLessThanOrEqual(100);
339
+ });
340
+ });
341
+
342
+ describe('高信誉节点', () => {
343
+ it('应该返回信誉高于阈值的节点', () => {
344
+ const config = { enabled: true, initialScore: 50, minScoreForService: 20, decayRate: 0.01 };
345
+ const reputation = new ReputationSystem(config, testDir);
346
+
347
+ // peer-1: 高信誉
348
+ reputation.recordSuccess('peer-1', 'task-1', 100);
349
+ reputation.recordSuccess('peer-1', 'task-2', 100);
350
+
351
+ // peer-2: 低信誉
352
+ reputation.recordFailure('peer-2', 'task-1', 'Error');
353
+
354
+ const highRepNodes = reputation.getHighReputationNodes(55);
355
+ const peerIds = highRepNodes.map(n => n.peerId);
356
+
357
+ expect(peerIds).toContain('peer-1');
358
+ expect(peerIds).not.toContain('peer-2');
359
+ });
360
+ });
361
+ });
362
+
363
+ describe('CapabilityDetector 业务逻辑', () => {
364
+ describe('getDefaultCapabilities', () => {
365
+ it('应该返回默认能力列表', () => {
366
+ const detector = new CapabilityDetector();
367
+ const capabilities = detector.getDefaultCapabilities();
368
+
369
+ expect(capabilities.length).toBeGreaterThan(0);
370
+ expect(capabilities.some(c => c.name === 'file-operation')).toBe(true);
371
+ expect(capabilities.some(c => c.name === 'command-execution')).toBe(true);
372
+ expect(capabilities.some(c => c.name === 'web-browsing')).toBe(true);
373
+ expect(capabilities.some(c => c.name === 'code-generation')).toBe(true);
374
+ expect(capabilities.some(c => c.name === 'task-delegation')).toBe(true);
375
+ });
376
+
377
+ it('每个能力应该有完整的定义', () => {
378
+ const detector = new CapabilityDetector();
379
+ const capabilities = detector.getDefaultCapabilities();
380
+
381
+ for (const cap of capabilities) {
382
+ expect(cap.name).toBeDefined();
383
+ expect(cap.description).toBeDefined();
384
+ expect(cap.parameters).toBeDefined();
385
+ expect(Object.keys(cap.parameters).length).toBeGreaterThan(0);
386
+ }
387
+ });
388
+ });
389
+
390
+ describe('mergeCustomCapabilities', () => {
391
+ it('应该添加自定义能力', () => {
392
+ const detector = new CapabilityDetector();
393
+ const defaults = detector.getDefaultCapabilities();
394
+ const custom = ['custom-ml', 'custom-analysis'];
395
+
396
+ const merged = detector.mergeCustomCapabilities(defaults, custom);
397
+
398
+ expect(merged.length).toBe(defaults.length + 2);
399
+ expect(merged.some(c => c.name === 'custom-ml')).toBe(true);
400
+ });
401
+
402
+ it('不应该重复已存在的能力', () => {
403
+ const detector = new CapabilityDetector();
404
+ const defaults = detector.getDefaultCapabilities();
405
+ const custom = ['code-generation']; // 已存在
406
+
407
+ const merged = detector.mergeCustomCapabilities(defaults, custom);
408
+
409
+ const count = merged.filter(c => c.name === 'code-generation').length;
410
+ expect(count).toBe(1);
411
+ });
412
+ });
413
+ });
414
+
415
+ describe('TaskQueue 业务逻辑', () => {
416
+ let taskQueue: TaskQueue;
417
+
418
+ beforeEach(() => {
419
+ taskQueue = new TaskQueue({ maxSize: 10, maxAgeMs: 60000 });
420
+ });
421
+
422
+ describe('任务管理', () => {
423
+ it('应该添加任务到队列', () => {
424
+ const task: TaskRequest = {
425
+ taskId: 'task-1',
426
+ taskType: 'test',
427
+ description: 'Test task',
428
+ from: 'peer-1',
429
+ timestamp: Date.now(),
430
+ timeout: 60000
431
+ };
432
+
433
+ const queued = taskQueue.add(task);
434
+
435
+ expect(queued.taskId).toBe('task-1');
436
+ expect(queued.status).toBe('pending');
437
+ expect(queued.createdAt).toBeDefined();
438
+ });
439
+
440
+ it('应该获取待处理任务', () => {
441
+ const task1 = { taskId: 'task-1', taskType: 'test', description: 'Task 1', from: 'peer-1', timestamp: Date.now(), timeout: 60000 };
442
+ const task2 = { taskId: 'task-2', taskType: 'test', description: 'Task 2', from: 'peer-2', timestamp: Date.now(), timeout: 60000 };
443
+
444
+ taskQueue.add(task1 as TaskRequest);
445
+ taskQueue.add(task2 as TaskRequest);
446
+
447
+ const pending = taskQueue.getPending();
448
+
449
+ expect(pending.length).toBe(2);
450
+ expect(pending[0].taskId).toBe('task-1'); // FIFO 顺序
451
+ });
452
+
453
+ it('应该限制返回的待处理任务数量', () => {
454
+ for (let i = 0; i < 5; i++) {
455
+ taskQueue.add({
456
+ taskId: `task-${i}`,
457
+ taskType: 'test',
458
+ description: `Task ${i}`,
459
+ from: 'peer-1',
460
+ timestamp: Date.now(),
461
+ timeout: 60000
462
+ } as TaskRequest);
463
+ }
464
+
465
+ const pending = taskQueue.getPending(3);
466
+ expect(pending.length).toBe(3);
467
+ });
468
+
469
+ it('应该标记任务为处理中', () => {
470
+ const task = {
471
+ taskId: 'task-1',
472
+ taskType: 'test',
473
+ description: 'Test',
474
+ from: 'peer-1',
475
+ timestamp: Date.now(),
476
+ timeout: 60000
477
+ } as TaskRequest;
478
+
479
+ taskQueue.add(task);
480
+ const processing = taskQueue.markProcessing('task-1');
481
+
482
+ expect(processing?.status).toBe('processing');
483
+ expect(processing?.updatedAt).toBeDefined();
484
+ });
485
+
486
+ it('应该完成任务并记录结果', () => {
487
+ const task = {
488
+ taskId: 'task-1',
489
+ taskType: 'test',
490
+ description: 'Test',
491
+ from: 'peer-1',
492
+ timestamp: Date.now(),
493
+ timeout: 60000
494
+ } as TaskRequest;
495
+
496
+ taskQueue.add(task);
497
+ const completed = taskQueue.complete('task-1', {
498
+ taskId: 'task-1',
499
+ status: 'success',
500
+ result: 'Done',
501
+ latency: 1000
502
+ });
503
+
504
+ expect(completed?.status).toBe('completed');
505
+ expect(completed?.result).toBe('Done');
506
+ expect(completed?.latency).toBe(1000);
507
+ });
508
+
509
+ it('应该标记失败任务', () => {
510
+ const task = {
511
+ taskId: 'task-1',
512
+ taskType: 'test',
513
+ description: 'Test',
514
+ from: 'peer-1',
515
+ timestamp: Date.now(),
516
+ timeout: 60000
517
+ } as TaskRequest;
518
+
519
+ taskQueue.add(task);
520
+ const failed = taskQueue.complete('task-1', {
521
+ taskId: 'task-1',
522
+ status: 'error',
523
+ error: 'Something went wrong',
524
+ latency: 500
525
+ });
526
+
527
+ expect(failed?.status).toBe('failed');
528
+ expect(failed?.error).toBe('Something went wrong');
529
+ });
530
+ });
531
+
532
+ describe('队列统计', () => {
533
+ it('应该返回正确的统计信息', () => {
534
+ // 添加不同状态的任务
535
+ taskQueue.add({ taskId: 'p1', taskType: 'test', description: 'P1', from: 'peer-1', timestamp: Date.now(), timeout: 60000 } as TaskRequest);
536
+ taskQueue.add({ taskId: 'p2', taskType: 'test', description: 'P2', from: 'peer-1', timestamp: Date.now(), timeout: 60000 } as TaskRequest);
537
+
538
+ taskQueue.add({ taskId: 'proc1', taskType: 'test', description: 'Proc1', from: 'peer-1', timestamp: Date.now(), timeout: 60000 } as TaskRequest);
539
+ taskQueue.markProcessing('proc1');
540
+
541
+ taskQueue.add({ taskId: 'comp1', taskType: 'test', description: 'Comp1', from: 'peer-1', timestamp: Date.now(), timeout: 60000 } as TaskRequest);
542
+ taskQueue.complete('comp1', { taskId: 'comp1', status: 'success', latency: 100 });
543
+
544
+ taskQueue.add({ taskId: 'fail1', taskType: 'test', description: 'Fail1', from: 'peer-1', timestamp: Date.now(), timeout: 60000 } as TaskRequest);
545
+ taskQueue.complete('fail1', { taskId: 'fail1', status: 'error', error: 'Failed', latency: 100 });
546
+
547
+ const stats = taskQueue.getStats();
548
+
549
+ expect(stats.pending).toBe(2);
550
+ expect(stats.processing).toBe(1);
551
+ expect(stats.completed).toBe(1);
552
+ expect(stats.failed).toBe(1);
553
+ expect(stats.total).toBe(5); // 所有任务都计入 total
554
+ });
555
+ });
556
+
557
+ describe('队列限制', () => {
558
+ it('应该限制队列大小', () => {
559
+ const smallQueue = new TaskQueue({ maxSize: 3 });
560
+
561
+ smallQueue.add({ taskId: 't1', taskType: 'test', description: 'T1', from: 'peer-1', timestamp: Date.now(), timeout: 60000 } as TaskRequest);
562
+ smallQueue.add({ taskId: 't2', taskType: 'test', description: 'T2', from: 'peer-1', timestamp: Date.now(), timeout: 60000 } as TaskRequest);
563
+ smallQueue.add({ taskId: 't3', taskType: 'test', description: 'T3', from: 'peer-1', timestamp: Date.now(), timeout: 60000 } as TaskRequest);
564
+
565
+ expect(() => {
566
+ smallQueue.add({ taskId: 't4', taskType: 'test', description: 'T4', from: 'peer-1', timestamp: Date.now(), timeout: 60000 } as TaskRequest);
567
+ }).toThrow('Task queue is full');
568
+ });
569
+
570
+ it('应该清理过期任务', () => {
571
+ const queue = new TaskQueue({ maxAgeMs: 100 }); // 100ms 过期
572
+
573
+ queue.add({ taskId: 'old', taskType: 'test', description: 'Old', from: 'peer-1', timestamp: Date.now(), timeout: 60000 } as TaskRequest);
574
+
575
+ // 等待过期
576
+ setTimeout(() => {
577
+ queue.cleanup();
578
+ const stats = queue.getStats();
579
+ expect(stats.total).toBe(0);
580
+ }, 150);
581
+ });
582
+ });
583
+ });