@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,974 @@
1
+ /**
2
+ * ClaimHandlers 单元测试
3
+ * 测试认领模式处理器的所有方法
4
+ */
5
+
6
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
7
+ import { ClaimHandlers, type ClaimHandlerParams } from './claim-handlers.js';
8
+ import type { F2AOpenClawAdapter } from './connector.js';
9
+ import type { SessionContext } from './types.js';
10
+
11
+ // 创建 mock 依赖
12
+ const createMockAnnouncementQueue = () => ({
13
+ create: vi.fn(),
14
+ getOpen: vi.fn(),
15
+ get: vi.fn(),
16
+ submitClaim: vi.fn(),
17
+ acceptClaim: vi.fn(),
18
+ rejectClaim: vi.fn(),
19
+ getMyClaims: vi.fn(),
20
+ getStats: vi.fn()
21
+ });
22
+
23
+ const createMockAdapter = () => ({
24
+ announcementQueue: createMockAnnouncementQueue(),
25
+ api: {
26
+ runtime: {
27
+ system: {
28
+ requestHeartbeatNow: vi.fn()
29
+ }
30
+ }
31
+ },
32
+ config: {
33
+ agentName: 'Test Agent'
34
+ }
35
+ });
36
+
37
+ // 创建 mock SessionContext
38
+ const createMockSessionContext = (): SessionContext => ({
39
+ sessionId: 'test-session-123',
40
+ workspace: '/tmp/test-workspace',
41
+ toJSON: vi.fn(() => ({ sessionId: 'test-session-123', workspace: '/tmp/test-workspace' }))
42
+ });
43
+
44
+ // 创建测试用的广播对象
45
+ const createMockAnnouncement = (overrides: any = {}) => ({
46
+ announcementId: 'ann-test-12345678901234567890',
47
+ taskType: 'code-generation',
48
+ description: 'Test task description for announcement',
49
+ from: 'local',
50
+ status: 'open',
51
+ timeout: 300000,
52
+ claims: [],
53
+ ...overrides
54
+ });
55
+
56
+ // 创建测试用的认领对象
57
+ const createMockClaim = (overrides: any = {}) => ({
58
+ claimId: 'claim-test-12345678901234567890',
59
+ announcementId: 'ann-test-12345678901234567890',
60
+ claimant: 'local',
61
+ claimantName: 'Test Agent',
62
+ status: 'pending',
63
+ estimatedTime: 60000,
64
+ confidence: 0.8,
65
+ ...overrides
66
+ });
67
+
68
+ describe('ClaimHandlers', () => {
69
+ let claimHandlers: ClaimHandlers;
70
+ let mockAdapter: any;
71
+ let mockContext: SessionContext;
72
+
73
+ beforeEach(() => {
74
+ vi.clearAllMocks();
75
+ mockAdapter = createMockAdapter();
76
+ mockContext = createMockSessionContext();
77
+ claimHandlers = new ClaimHandlers(mockAdapter as F2AOpenClawAdapter);
78
+ });
79
+
80
+ afterEach(() => {
81
+ vi.restoreAllMocks();
82
+ });
83
+
84
+ describe('handleAnnounce', () => {
85
+ it('应该成功创建任务广播', async () => {
86
+ const announcement = createMockAnnouncement();
87
+
88
+ mockAdapter.announcementQueue.create.mockReturnValue(announcement);
89
+
90
+ const result = await claimHandlers.handleAnnounce({
91
+ task_type: 'code-generation',
92
+ description: 'Write a function to sort an array'
93
+ }, mockContext);
94
+
95
+ expect(result.content).toContain('任务广播已创建');
96
+ expect(result.content).toContain('code-generation');
97
+ expect(result.data?.announcementId).toBe(announcement.announcementId);
98
+ expect(mockAdapter.announcementQueue.create).toHaveBeenCalled();
99
+ });
100
+
101
+ it('应该包含所有可选参数', async () => {
102
+ const announcement = createMockAnnouncement({
103
+ requiredCapabilities: ['typescript', 'algorithms'],
104
+ estimatedComplexity: 7,
105
+ reward: 100
106
+ });
107
+
108
+ mockAdapter.announcementQueue.create.mockReturnValue(announcement);
109
+
110
+ const result = await claimHandlers.handleAnnounce({
111
+ task_type: 'code-generation',
112
+ description: 'Complex task',
113
+ required_capabilities: ['typescript', 'algorithms'],
114
+ estimated_complexity: 7,
115
+ reward: 100,
116
+ timeout: 600000
117
+ }, mockContext);
118
+
119
+ expect(result.content).toContain('所需能力: typescript, algorithms');
120
+ expect(result.content).toContain('复杂度: 7/10');
121
+ expect(result.content).toContain('奖励: 100');
122
+ });
123
+
124
+ it('应该触发心跳通知其他 Agents', async () => {
125
+ const announcement = createMockAnnouncement();
126
+
127
+ mockAdapter.announcementQueue.create.mockReturnValue(announcement);
128
+
129
+ await claimHandlers.handleAnnounce({
130
+ task_type: 'test',
131
+ description: 'Test task'
132
+ }, mockContext);
133
+
134
+ expect(mockAdapter.api.runtime.system.requestHeartbeatNow).toHaveBeenCalled();
135
+ });
136
+
137
+ it('应该处理创建广播失败的情况', async () => {
138
+ mockAdapter.announcementQueue.create.mockImplementation(() => {
139
+ throw new Error('Queue is full');
140
+ });
141
+
142
+ const result = await claimHandlers.handleAnnounce({
143
+ task_type: 'test',
144
+ description: 'Test task'
145
+ }, mockContext);
146
+
147
+ expect(result.content).toContain('创建广播失败');
148
+ expect(result.content).toContain('Queue is full');
149
+ });
150
+
151
+ it('应该使用默认超时时间', async () => {
152
+ let capturedParams: any;
153
+
154
+ mockAdapter.announcementQueue.create.mockImplementation((params) => {
155
+ capturedParams = params;
156
+ return createMockAnnouncement(params);
157
+ });
158
+
159
+ await claimHandlers.handleAnnounce({
160
+ task_type: 'test',
161
+ description: 'Test task'
162
+ }, mockContext);
163
+
164
+ expect(capturedParams.timeout).toBe(300000);
165
+ });
166
+
167
+ it('应该处理缺少 api.runtime 的情况', async () => {
168
+ mockAdapter.api = null;
169
+
170
+ const announcement = createMockAnnouncement();
171
+ mockAdapter.announcementQueue.create.mockReturnValue(announcement);
172
+
173
+ // 不应该抛出错误
174
+ const result = await claimHandlers.handleAnnounce({
175
+ task_type: 'test',
176
+ description: 'Test task'
177
+ }, mockContext);
178
+
179
+ expect(result.content).toContain('任务广播已创建');
180
+ });
181
+
182
+ it('应该正确截断长描述', async () => {
183
+ const longDescription = 'A'.repeat(150);
184
+ const announcement = createMockAnnouncement({ description: longDescription });
185
+
186
+ mockAdapter.announcementQueue.create.mockReturnValue(announcement);
187
+
188
+ const result = await claimHandlers.handleAnnounce({
189
+ task_type: 'test',
190
+ description: longDescription
191
+ }, mockContext);
192
+
193
+ expect(result.content).toContain('...');
194
+ });
195
+ });
196
+
197
+ describe('handleListAnnouncements', () => {
198
+ it('应该返回所有开放的任务广播', async () => {
199
+ const announcements = [
200
+ createMockAnnouncement({ announcementId: 'ann-1' }),
201
+ createMockAnnouncement({ announcementId: 'ann-2' })
202
+ ];
203
+
204
+ mockAdapter.announcementQueue.getOpen.mockReturnValue(announcements);
205
+
206
+ const result = await claimHandlers.handleListAnnouncements({}, mockContext);
207
+
208
+ expect(result.content).toContain('开放的任务广播 (2 个)');
209
+ expect(result.data?.count).toBe(2);
210
+ });
211
+
212
+ it('应该按能力过滤广播', async () => {
213
+ const announcements = [
214
+ createMockAnnouncement({
215
+ announcementId: 'ann-1',
216
+ requiredCapabilities: ['typescript']
217
+ }),
218
+ createMockAnnouncement({
219
+ announcementId: 'ann-2',
220
+ requiredCapabilities: ['python']
221
+ })
222
+ ];
223
+
224
+ mockAdapter.announcementQueue.getOpen.mockReturnValue(announcements);
225
+
226
+ const result = await claimHandlers.handleListAnnouncements({
227
+ capability: 'typescript'
228
+ }, mockContext);
229
+
230
+ expect(result.data?.count).toBe(1);
231
+ expect(result.data?.announcements[0].announcementId).toBe('ann-1');
232
+ });
233
+
234
+ it('应该限制返回数量', async () => {
235
+ const announcements = Array.from({ length: 20 }, (_, i) =>
236
+ createMockAnnouncement({ announcementId: `ann-${i}` })
237
+ );
238
+
239
+ mockAdapter.announcementQueue.getOpen.mockReturnValue(announcements);
240
+
241
+ const result = await claimHandlers.handleListAnnouncements({
242
+ limit: 5
243
+ }, mockContext);
244
+
245
+ expect(result.data?.count).toBe(5);
246
+ });
247
+
248
+ it('应该处理没有开放广播的情况', async () => {
249
+ mockAdapter.announcementQueue.getOpen.mockReturnValue([]);
250
+
251
+ const result = await claimHandlers.handleListAnnouncements({}, mockContext);
252
+
253
+ expect(result.content).toBe('📭 当前没有开放的任务广播');
254
+ });
255
+
256
+ it('应该显示每个广播的认领数量', async () => {
257
+ const announcements = [
258
+ createMockAnnouncement({
259
+ announcementId: 'ann-1',
260
+ claims: [{ claimId: 'claim-1' }, { claimId: 'claim-2' }]
261
+ })
262
+ ];
263
+
264
+ mockAdapter.announcementQueue.getOpen.mockReturnValue(announcements);
265
+
266
+ const result = await claimHandlers.handleListAnnouncements({}, mockContext);
267
+
268
+ expect(result.content).toContain('认领: 2');
269
+ expect(result.data?.announcements[0].claimCount).toBe(2);
270
+ });
271
+
272
+ it('应该显示复杂度和奖励信息', async () => {
273
+ const announcements = [
274
+ createMockAnnouncement({
275
+ estimatedComplexity: 8,
276
+ reward: 500
277
+ })
278
+ ];
279
+
280
+ mockAdapter.announcementQueue.getOpen.mockReturnValue(announcements);
281
+
282
+ const result = await claimHandlers.handleListAnnouncements({}, mockContext);
283
+
284
+ expect(result.content).toContain('复杂度: 8/10');
285
+ expect(result.content).toContain('奖励: 500');
286
+ });
287
+ });
288
+
289
+ describe('handleClaim', () => {
290
+ it('应该成功认领开放的任务广播', async () => {
291
+ const announcement = createMockAnnouncement();
292
+ const claim = createMockClaim();
293
+
294
+ mockAdapter.announcementQueue.get.mockReturnValue(announcement);
295
+ mockAdapter.announcementQueue.submitClaim.mockReturnValue(claim);
296
+
297
+ const result = await claimHandlers.handleClaim({
298
+ announcement_id: 'ann-test-12345678901234567890'
299
+ }, mockContext);
300
+
301
+ expect(result.content).toContain('认领已提交');
302
+ expect(result.data?.claimId).toBe(claim.claimId);
303
+ expect(mockAdapter.announcementQueue.submitClaim).toHaveBeenCalled();
304
+ });
305
+
306
+ it('应该包含预计时间和信心指数', async () => {
307
+ const announcement = createMockAnnouncement();
308
+ const claim = createMockClaim({
309
+ estimatedTime: 120000,
310
+ confidence: 0.95
311
+ });
312
+
313
+ mockAdapter.announcementQueue.get.mockReturnValue(announcement);
314
+ mockAdapter.announcementQueue.submitClaim.mockReturnValue(claim);
315
+
316
+ const result = await claimHandlers.handleClaim({
317
+ announcement_id: 'ann-test-123',
318
+ estimated_time: 120000,
319
+ confidence: 0.95
320
+ }, mockContext);
321
+
322
+ expect(result.content).toContain('预计时间: 120秒');
323
+ expect(result.content).toContain('信心指数: 95%');
324
+ });
325
+
326
+ it('应该处理广播不存在的情况', async () => {
327
+ mockAdapter.announcementQueue.get.mockReturnValue(null);
328
+
329
+ const result = await claimHandlers.handleClaim({
330
+ announcement_id: 'non-existent'
331
+ }, mockContext);
332
+
333
+ expect(result.content).toContain('找不到广播');
334
+ });
335
+
336
+ it('应该处理广播已被认领的情况', async () => {
337
+ const announcement = createMockAnnouncement({ status: 'claimed' });
338
+
339
+ mockAdapter.announcementQueue.get.mockReturnValue(announcement);
340
+
341
+ const result = await claimHandlers.handleClaim({
342
+ announcement_id: 'ann-test'
343
+ }, mockContext);
344
+
345
+ expect(result.content).toContain('该广播已被认领');
346
+ });
347
+
348
+ it('应该处理广播已过期的情况', async () => {
349
+ const announcement = createMockAnnouncement({ status: 'expired' });
350
+
351
+ mockAdapter.announcementQueue.get.mockReturnValue(announcement);
352
+
353
+ const result = await claimHandlers.handleClaim({
354
+ announcement_id: 'ann-test'
355
+ }, mockContext);
356
+
357
+ expect(result.content).toContain('该广播已过期');
358
+ });
359
+
360
+ it('应该检测重复认领', async () => {
361
+ const announcement = createMockAnnouncement({
362
+ claims: [{ claimant: 'local', claimId: 'existing-claim' }]
363
+ });
364
+
365
+ mockAdapter.announcementQueue.get.mockReturnValue(announcement);
366
+
367
+ const result = await claimHandlers.handleClaim({
368
+ announcement_id: 'ann-test'
369
+ }, mockContext);
370
+
371
+ expect(result.content).toContain('你已经认领过这个广播了');
372
+ });
373
+
374
+ it('应该处理认领失败的情况', async () => {
375
+ const announcement = createMockAnnouncement();
376
+
377
+ mockAdapter.announcementQueue.get.mockReturnValue(announcement);
378
+ mockAdapter.announcementQueue.submitClaim.mockReturnValue(null);
379
+
380
+ const result = await claimHandlers.handleClaim({
381
+ announcement_id: 'ann-test'
382
+ }, mockContext);
383
+
384
+ expect(result.content).toBe('❌ 认领失败');
385
+ });
386
+
387
+ it('应该触发心跳通知发布者', async () => {
388
+ const announcement = createMockAnnouncement();
389
+ const claim = createMockClaim();
390
+
391
+ mockAdapter.announcementQueue.get.mockReturnValue(announcement);
392
+ mockAdapter.announcementQueue.submitClaim.mockReturnValue(claim);
393
+
394
+ await claimHandlers.handleClaim({
395
+ announcement_id: 'ann-test'
396
+ }, mockContext);
397
+
398
+ expect(mockAdapter.api.runtime.system.requestHeartbeatNow).toHaveBeenCalled();
399
+ });
400
+ });
401
+
402
+ describe('handleManageClaims', () => {
403
+ describe('action: list', () => {
404
+ it('应该返回广播的所有认领', async () => {
405
+ const announcement = createMockAnnouncement({
406
+ from: 'local',
407
+ claims: [
408
+ createMockClaim({ claimId: 'claim-1', claimantName: 'Agent A' }),
409
+ createMockClaim({ claimId: 'claim-2', claimantName: 'Agent B' })
410
+ ]
411
+ });
412
+
413
+ mockAdapter.announcementQueue.get.mockReturnValue(announcement);
414
+
415
+ const result = await claimHandlers.handleManageClaims({
416
+ announcement_id: 'ann-test',
417
+ action: 'list'
418
+ }, mockContext);
419
+
420
+ expect(result.content).toContain('认领列表 (2 个)');
421
+ expect(result.data?.claims).toHaveLength(2);
422
+ });
423
+
424
+ it('应该处理没有认领的情况', async () => {
425
+ const announcement = createMockAnnouncement({
426
+ from: 'local',
427
+ claims: []
428
+ });
429
+
430
+ mockAdapter.announcementQueue.get.mockReturnValue(announcement);
431
+
432
+ const result = await claimHandlers.handleManageClaims({
433
+ announcement_id: 'ann-test',
434
+ action: 'list'
435
+ }, mockContext);
436
+
437
+ expect(result.content).toBe('📭 暂无认领');
438
+ });
439
+
440
+ it('应该显示认领者的预计时间和信心指数', async () => {
441
+ const announcement = createMockAnnouncement({
442
+ from: 'local',
443
+ claims: [
444
+ createMockClaim({
445
+ estimatedTime: 180000,
446
+ confidence: 0.85
447
+ })
448
+ ]
449
+ });
450
+
451
+ mockAdapter.announcementQueue.get.mockReturnValue(announcement);
452
+
453
+ const result = await claimHandlers.handleManageClaims({
454
+ announcement_id: 'ann-test',
455
+ action: 'list'
456
+ }, mockContext);
457
+
458
+ expect(result.content).toContain('预计: 180s');
459
+ expect(result.content).toContain('信心: 85%');
460
+ });
461
+ });
462
+
463
+ describe('action: accept', () => {
464
+ it('应该成功接受认领', async () => {
465
+ const announcement = createMockAnnouncement({ from: 'local' });
466
+ const claim = createMockClaim({ status: 'accepted' });
467
+
468
+ mockAdapter.announcementQueue.get.mockReturnValue(announcement);
469
+ mockAdapter.announcementQueue.acceptClaim.mockReturnValue(claim);
470
+
471
+ const result = await claimHandlers.handleManageClaims({
472
+ announcement_id: 'ann-test',
473
+ action: 'accept',
474
+ claim_id: 'claim-test'
475
+ }, mockContext);
476
+
477
+ expect(result.content).toContain('已接受认领');
478
+ expect(mockAdapter.announcementQueue.acceptClaim).toHaveBeenCalledWith(
479
+ 'ann-test',
480
+ 'claim-test'
481
+ );
482
+ });
483
+
484
+ it('应该要求提供 claim_id', async () => {
485
+ const announcement = createMockAnnouncement({ from: 'local' });
486
+
487
+ mockAdapter.announcementQueue.get.mockReturnValue(announcement);
488
+
489
+ const result = await claimHandlers.handleManageClaims({
490
+ announcement_id: 'ann-test',
491
+ action: 'accept'
492
+ }, mockContext);
493
+
494
+ expect(result.content).toBe('❌ accept/reject 操作需要提供 claim_id 参数');
495
+ });
496
+
497
+ it('应该处理接受失败的情况', async () => {
498
+ const announcement = createMockAnnouncement({ from: 'local' });
499
+
500
+ mockAdapter.announcementQueue.get.mockReturnValue(announcement);
501
+ mockAdapter.announcementQueue.acceptClaim.mockReturnValue(null);
502
+
503
+ const result = await claimHandlers.handleManageClaims({
504
+ announcement_id: 'ann-test',
505
+ action: 'accept',
506
+ claim_id: 'claim-test'
507
+ }, mockContext);
508
+
509
+ expect(result.content).toBe('❌ 接受认领失败');
510
+ });
511
+ });
512
+
513
+ describe('action: reject', () => {
514
+ it('应该成功拒绝认领', async () => {
515
+ const announcement = createMockAnnouncement({ from: 'local' });
516
+ const claim = createMockClaim({ status: 'rejected' });
517
+
518
+ mockAdapter.announcementQueue.get.mockReturnValue(announcement);
519
+ mockAdapter.announcementQueue.rejectClaim.mockReturnValue(claim);
520
+
521
+ const result = await claimHandlers.handleManageClaims({
522
+ announcement_id: 'ann-test',
523
+ action: 'reject',
524
+ claim_id: 'claim-test'
525
+ }, mockContext);
526
+
527
+ expect(result.content).toContain('已拒绝认领');
528
+ expect(mockAdapter.announcementQueue.rejectClaim).toHaveBeenCalledWith(
529
+ 'ann-test',
530
+ 'claim-test'
531
+ );
532
+ });
533
+
534
+ it('应该要求提供 claim_id', async () => {
535
+ const announcement = createMockAnnouncement({ from: 'local' });
536
+
537
+ mockAdapter.announcementQueue.get.mockReturnValue(announcement);
538
+
539
+ const result = await claimHandlers.handleManageClaims({
540
+ announcement_id: 'ann-test',
541
+ action: 'reject'
542
+ }, mockContext);
543
+
544
+ expect(result.content).toBe('❌ accept/reject 操作需要提供 claim_id 参数');
545
+ });
546
+
547
+ it('应该处理拒绝失败的情况', async () => {
548
+ const announcement = createMockAnnouncement({ from: 'local' });
549
+
550
+ mockAdapter.announcementQueue.get.mockReturnValue(announcement);
551
+ mockAdapter.announcementQueue.rejectClaim.mockReturnValue(null);
552
+
553
+ const result = await claimHandlers.handleManageClaims({
554
+ announcement_id: 'ann-test',
555
+ action: 'reject',
556
+ claim_id: 'claim-test'
557
+ }, mockContext);
558
+
559
+ expect(result.content).toBe('❌ 拒绝认领失败');
560
+ });
561
+ });
562
+
563
+ it('应该处理广播不存在的情况', async () => {
564
+ mockAdapter.announcementQueue.get.mockReturnValue(null);
565
+
566
+ const result = await claimHandlers.handleManageClaims({
567
+ announcement_id: 'non-existent',
568
+ action: 'list'
569
+ }, mockContext);
570
+
571
+ expect(result.content).toContain('找不到广播');
572
+ });
573
+
574
+ it('应该拒绝管理非本机发布的广播', async () => {
575
+ const announcement = createMockAnnouncement({ from: 'remote-peer' });
576
+
577
+ mockAdapter.announcementQueue.get.mockReturnValue(announcement);
578
+
579
+ const result = await claimHandlers.handleManageClaims({
580
+ announcement_id: 'ann-test',
581
+ action: 'list'
582
+ }, mockContext);
583
+
584
+ expect(result.content).toContain('只能管理自己发布的广播');
585
+ });
586
+
587
+ it('应该处理未知操作', async () => {
588
+ const announcement = createMockAnnouncement({ from: 'local' });
589
+
590
+ mockAdapter.announcementQueue.get.mockReturnValue(announcement);
591
+
592
+ const result = await claimHandlers.handleManageClaims({
593
+ announcement_id: 'ann-test',
594
+ action: 'unknown' as any
595
+ }, mockContext);
596
+
597
+ expect(result.content).toBe('❌ action 参数必须是 list, accept 或 reject');
598
+ });
599
+ });
600
+
601
+ describe('handleMyClaims', () => {
602
+ it('应该返回我的所有认领', async () => {
603
+ const claims = [
604
+ createMockClaim({ claimId: 'claim-1', status: 'pending' }),
605
+ createMockClaim({ claimId: 'claim-2', status: 'accepted' })
606
+ ];
607
+
608
+ mockAdapter.announcementQueue.getMyClaims.mockReturnValue(claims);
609
+ mockAdapter.announcementQueue.get.mockReturnValue(createMockAnnouncement());
610
+
611
+ const result = await claimHandlers.handleMyClaims({}, mockContext);
612
+
613
+ expect(result.content).toContain('我的认领 (2 个)');
614
+ expect(result.data?.count).toBe(2);
615
+ });
616
+
617
+ it('应该按状态过滤认领', async () => {
618
+ const claims = [
619
+ createMockClaim({ claimId: 'claim-1', status: 'pending' }),
620
+ createMockClaim({ claimId: 'claim-2', status: 'accepted' })
621
+ ];
622
+
623
+ mockAdapter.announcementQueue.getMyClaims.mockReturnValue(claims);
624
+
625
+ const result = await claimHandlers.handleMyClaims({
626
+ status: 'pending'
627
+ }, mockContext);
628
+
629
+ expect(result.data?.count).toBe(1);
630
+ expect(result.data?.claims[0].status).toBe('pending');
631
+ });
632
+
633
+ it('应该处理没有认领的情况', async () => {
634
+ mockAdapter.announcementQueue.getMyClaims.mockReturnValue([]);
635
+
636
+ const result = await claimHandlers.handleMyClaims({}, mockContext);
637
+
638
+ expect(result.content).toContain('没有的认领');
639
+ });
640
+
641
+ it('应该显示认领状态图标', async () => {
642
+ const claims = [
643
+ createMockClaim({ claimId: 'claim-1', status: 'pending' }),
644
+ createMockClaim({ claimId: 'claim-2', status: 'accepted' }),
645
+ createMockClaim({ claimId: 'claim-3', status: 'rejected' })
646
+ ];
647
+
648
+ mockAdapter.announcementQueue.getMyClaims.mockReturnValue(claims);
649
+ mockAdapter.announcementQueue.get.mockReturnValue(createMockAnnouncement());
650
+
651
+ const result = await claimHandlers.handleMyClaims({ status: 'all' }, mockContext);
652
+
653
+ expect(result.content).toContain('⏳'); // pending
654
+ expect(result.content).toContain('✅'); // accepted
655
+ expect(result.content).toContain('❌'); // rejected
656
+ });
657
+
658
+ it('应该显示已接受认领的提示', async () => {
659
+ const claims = [
660
+ createMockClaim({ claimId: 'claim-1', status: 'accepted' })
661
+ ];
662
+
663
+ mockAdapter.announcementQueue.getMyClaims.mockReturnValue(claims);
664
+ mockAdapter.announcementQueue.get.mockReturnValue(createMockAnnouncement());
665
+
666
+ const result = await claimHandlers.handleMyClaims({}, mockContext);
667
+
668
+ expect(result.content).toContain('可以开始执行');
669
+ });
670
+
671
+ it('应该处理特定状态无认领的情况', async () => {
672
+ mockAdapter.announcementQueue.getMyClaims.mockReturnValue([]);
673
+
674
+ const result = await claimHandlers.handleMyClaims({
675
+ status: 'accepted'
676
+ }, mockContext);
677
+
678
+ expect(result.content).toContain('没有accepted的认领');
679
+ });
680
+ });
681
+
682
+ describe('handleAnnouncementStats', () => {
683
+ it('应该返回任务广播统计信息', async () => {
684
+ mockAdapter.announcementQueue.getStats.mockReturnValue({
685
+ open: 5,
686
+ claimed: 3,
687
+ delegated: 2,
688
+ expired: 1,
689
+ total: 11
690
+ });
691
+
692
+ const result = await claimHandlers.handleAnnouncementStats({}, mockContext);
693
+
694
+ expect(result.content).toContain('开放中: 5');
695
+ expect(result.content).toContain('已认领: 3');
696
+ expect(result.content).toContain('已委托: 2');
697
+ expect(result.content).toContain('已过期: 1');
698
+ expect(result.content).toContain('总计: 11');
699
+ expect(result.data).toEqual({
700
+ open: 5,
701
+ claimed: 3,
702
+ delegated: 2,
703
+ expired: 1,
704
+ total: 11
705
+ });
706
+ });
707
+
708
+ it('应该处理空统计的情况', async () => {
709
+ mockAdapter.announcementQueue.getStats.mockReturnValue({
710
+ open: 0,
711
+ claimed: 0,
712
+ delegated: 0,
713
+ expired: 0,
714
+ total: 0
715
+ });
716
+
717
+ const result = await claimHandlers.handleAnnouncementStats({}, mockContext);
718
+
719
+ expect(result.content).toContain('开放中: 0');
720
+ expect(result.content).toContain('总计: 0');
721
+ });
722
+ });
723
+
724
+ describe('边界条件测试', () => {
725
+ it('handleAnnounce 应该拒绝空描述', async () => {
726
+ const result = await claimHandlers.handleAnnounce({
727
+ task_type: 'test',
728
+ description: ''
729
+ }, mockContext);
730
+
731
+ expect(result.content).toContain('❌ 请提供有效的 description 参数');
732
+ });
733
+
734
+ it('handleAnnounce 应该拒绝空任务类型', async () => {
735
+ const result = await claimHandlers.handleAnnounce({
736
+ task_type: '',
737
+ description: 'test description'
738
+ }, mockContext);
739
+
740
+ expect(result.content).toContain('❌ 请提供有效的 task_type 参数');
741
+ });
742
+
743
+ it('handleAnnounce 应该接受有效的参数', async () => {
744
+ const announcement = createMockAnnouncement({ description: 'valid description' });
745
+ mockAdapter.announcementQueue.create.mockReturnValue(announcement);
746
+
747
+ const result = await claimHandlers.handleAnnounce({
748
+ task_type: 'test',
749
+ description: 'valid description'
750
+ }, mockContext);
751
+
752
+ expect(result.content).toContain('任务广播已创建');
753
+ });
754
+
755
+ it('handleListAnnouncements 应该处理没有 requiredCapabilities 的广播', async () => {
756
+ const announcements = [
757
+ createMockAnnouncement({ requiredCapabilities: undefined })
758
+ ];
759
+
760
+ mockAdapter.announcementQueue.getOpen.mockReturnValue(announcements);
761
+
762
+ const result = await claimHandlers.handleListAnnouncements({}, mockContext);
763
+
764
+ expect(result.content).toContain('开放的任务广播');
765
+ });
766
+
767
+ it('handleClaim 应该处理缺少 api 的情况', async () => {
768
+ mockAdapter.api = undefined;
769
+
770
+ const announcement = createMockAnnouncement();
771
+ const claim = createMockClaim();
772
+
773
+ mockAdapter.announcementQueue.get.mockReturnValue(announcement);
774
+ mockAdapter.announcementQueue.submitClaim.mockReturnValue(claim);
775
+
776
+ const result = await claimHandlers.handleClaim({
777
+ announcement_id: 'ann-test'
778
+ }, mockContext);
779
+
780
+ expect(result.content).toContain('认领已提交');
781
+ });
782
+
783
+ it('handleManageClaims 应该处理空 claims 数组', async () => {
784
+ const announcement = createMockAnnouncement({
785
+ from: 'local',
786
+ claims: undefined
787
+ });
788
+
789
+ mockAdapter.announcementQueue.get.mockReturnValue(announcement);
790
+
791
+ const result = await claimHandlers.handleManageClaims({
792
+ announcement_id: 'ann-test',
793
+ action: 'list'
794
+ }, mockContext);
795
+
796
+ expect(result.content).toBe('📭 暂无认领');
797
+ });
798
+
799
+ it('handleMyClaims 应该处理 announcement 不存在的情况', async () => {
800
+ const claims = [createMockClaim()];
801
+
802
+ mockAdapter.announcementQueue.getMyClaims.mockReturnValue(claims);
803
+ mockAdapter.announcementQueue.get.mockReturnValue(null);
804
+
805
+ const result = await claimHandlers.handleMyClaims({}, mockContext);
806
+
807
+ // 应该处理 announcement 不存在的情况
808
+ expect(result.content).toContain('我的认领');
809
+ });
810
+
811
+ // ========== 新增:输入验证测试 ==========
812
+
813
+ it('handleAnnounce 应该拒绝无效的 estimated_complexity', async () => {
814
+ // 超出范围
815
+ const result1 = await claimHandlers.handleAnnounce({
816
+ task_type: 'test',
817
+ description: 'test',
818
+ estimated_complexity: 15
819
+ }, mockContext);
820
+ expect(result1.content).toContain('estimated_complexity 必须在 1 到 10 之间');
821
+
822
+ // 负数
823
+ const result2 = await claimHandlers.handleAnnounce({
824
+ task_type: 'test',
825
+ description: 'test',
826
+ estimated_complexity: -1
827
+ }, mockContext);
828
+ expect(result2.content).toContain('estimated_complexity 必须在 1 到 10 之间');
829
+
830
+ // 非数字
831
+ const result3 = await claimHandlers.handleAnnounce({
832
+ task_type: 'test',
833
+ description: 'test',
834
+ estimated_complexity: 'high' as any
835
+ }, mockContext);
836
+ expect(result3.content).toContain('estimated_complexity 必须是有效数字');
837
+ });
838
+
839
+ it('handleAnnounce 应该拒绝无效的 reward', async () => {
840
+ // 负数
841
+ const result1 = await claimHandlers.handleAnnounce({
842
+ task_type: 'test',
843
+ description: 'test',
844
+ reward: -100
845
+ }, mockContext);
846
+ expect(result1.content).toContain('reward 不能为负数');
847
+
848
+ // 非数字
849
+ const result2 = await claimHandlers.handleAnnounce({
850
+ task_type: 'test',
851
+ description: 'test',
852
+ reward: 'free' as any
853
+ }, mockContext);
854
+ expect(result2.content).toContain('reward 必须是有效数字');
855
+ });
856
+
857
+ it('handleAnnounce 应该拒绝无效的 timeout', async () => {
858
+ // 负数
859
+ const result1 = await claimHandlers.handleAnnounce({
860
+ task_type: 'test',
861
+ description: 'test',
862
+ timeout: -1000
863
+ }, mockContext);
864
+ expect(result1.content).toContain('timeout 必须大于 0');
865
+
866
+ // 超过 24 小时
867
+ const result2 = await claimHandlers.handleAnnounce({
868
+ task_type: 'test',
869
+ description: 'test',
870
+ timeout: 25 * 60 * 60 * 1000
871
+ }, mockContext);
872
+ expect(result2.content).toContain('timeout 不能超过 24 小时');
873
+
874
+ // 非数字
875
+ const result3 = await claimHandlers.handleAnnounce({
876
+ task_type: 'test',
877
+ description: 'test',
878
+ timeout: 'forever' as any
879
+ }, mockContext);
880
+ expect(result3.content).toContain('timeout 必须是有效数字');
881
+ });
882
+
883
+ it('handleClaim 应该拒绝无效的 estimated_time', async () => {
884
+ const announcement = createMockAnnouncement();
885
+ mockAdapter.announcementQueue.get.mockReturnValue(announcement);
886
+
887
+ // 负数
888
+ const result1 = await claimHandlers.handleClaim({
889
+ announcement_id: 'ann-test',
890
+ estimated_time: -1000
891
+ }, mockContext);
892
+ expect(result1.content).toContain('estimated_time 必须大于 0');
893
+
894
+ // 超过 24 小时
895
+ const result2 = await claimHandlers.handleClaim({
896
+ announcement_id: 'ann-test',
897
+ estimated_time: 25 * 60 * 60 * 1000
898
+ }, mockContext);
899
+ expect(result2.content).toContain('estimated_time 不能超过 24 小时');
900
+
901
+ // 非数字
902
+ const result3 = await claimHandlers.handleClaim({
903
+ announcement_id: 'ann-test',
904
+ estimated_time: 'soon' as any
905
+ }, mockContext);
906
+ expect(result3.content).toContain('estimated_time 必须是有效数字');
907
+ });
908
+
909
+ it('handleClaim 应该拒绝无效的 confidence', async () => {
910
+ const announcement = createMockAnnouncement();
911
+ mockAdapter.announcementQueue.get.mockReturnValue(announcement);
912
+
913
+ // 超过 1
914
+ const result1 = await claimHandlers.handleClaim({
915
+ announcement_id: 'ann-test',
916
+ confidence: 1.5
917
+ }, mockContext);
918
+ expect(result1.content).toContain('confidence 必须在 0 到 1 之间');
919
+
920
+ // 负数
921
+ const result2 = await claimHandlers.handleClaim({
922
+ announcement_id: 'ann-test',
923
+ confidence: -0.5
924
+ }, mockContext);
925
+ expect(result2.content).toContain('confidence 必须在 0 到 1 之间');
926
+
927
+ // 非数字
928
+ const result3 = await claimHandlers.handleClaim({
929
+ announcement_id: 'ann-test',
930
+ confidence: 'high' as any
931
+ }, mockContext);
932
+ expect(result3.content).toContain('confidence 必须是有效数字');
933
+ });
934
+
935
+ it('handleAnnounce 应该接受有效的可选参数', async () => {
936
+ const announcement = createMockAnnouncement({
937
+ description: 'valid description',
938
+ estimatedComplexity: 5,
939
+ reward: 100,
940
+ timeout: 60000
941
+ });
942
+ mockAdapter.announcementQueue.create.mockReturnValue(announcement);
943
+
944
+ const result = await claimHandlers.handleAnnounce({
945
+ task_type: 'test',
946
+ description: 'valid description',
947
+ estimated_complexity: 5,
948
+ reward: 100,
949
+ timeout: 60000
950
+ }, mockContext);
951
+
952
+ expect(result.content).toContain('任务广播已创建');
953
+ });
954
+
955
+ it('handleClaim 应该接受有效的可选参数', async () => {
956
+ const announcement = createMockAnnouncement();
957
+ const claim = createMockClaim({
958
+ estimatedTime: 60000,
959
+ confidence: 0.8
960
+ });
961
+
962
+ mockAdapter.announcementQueue.get.mockReturnValue(announcement);
963
+ mockAdapter.announcementQueue.submitClaim.mockReturnValue(claim);
964
+
965
+ const result = await claimHandlers.handleClaim({
966
+ announcement_id: 'ann-test',
967
+ estimated_time: 60000,
968
+ confidence: 0.8
969
+ }, mockContext);
970
+
971
+ expect(result.content).toContain('认领已提交');
972
+ });
973
+ });
974
+ });