@f2a/network 0.1.2 → 0.1.3

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 (136) hide show
  1. package/package.json +8 -1
  2. package/.github/workflows/ci.yml +0 -113
  3. package/.github/workflows/publish.yml +0 -60
  4. package/MONOREPO.md +0 -58
  5. package/SKILL.md +0 -137
  6. package/dist/adapters/openclaw.d.ts +0 -103
  7. package/dist/adapters/openclaw.d.ts.map +0 -1
  8. package/dist/adapters/openclaw.js +0 -297
  9. package/dist/adapters/openclaw.js.map +0 -1
  10. package/dist/core/connection-manager.d.ts +0 -80
  11. package/dist/core/connection-manager.d.ts.map +0 -1
  12. package/dist/core/connection-manager.js +0 -235
  13. package/dist/core/connection-manager.js.map +0 -1
  14. package/dist/core/connection-manager.test.d.ts +0 -2
  15. package/dist/core/connection-manager.test.d.ts.map +0 -1
  16. package/dist/core/connection-manager.test.js +0 -52
  17. package/dist/core/connection-manager.test.js.map +0 -1
  18. package/dist/core/identity.d.ts +0 -47
  19. package/dist/core/identity.d.ts.map +0 -1
  20. package/dist/core/identity.js +0 -130
  21. package/dist/core/identity.js.map +0 -1
  22. package/dist/core/identity.test.d.ts +0 -2
  23. package/dist/core/identity.test.d.ts.map +0 -1
  24. package/dist/core/identity.test.js +0 -43
  25. package/dist/core/identity.test.js.map +0 -1
  26. package/dist/core/serverless.d.ts +0 -155
  27. package/dist/core/serverless.d.ts.map +0 -1
  28. package/dist/core/serverless.js +0 -615
  29. package/dist/core/serverless.js.map +0 -1
  30. package/dist/daemon/webhook.test.d.ts +0 -2
  31. package/dist/daemon/webhook.test.d.ts.map +0 -1
  32. package/dist/daemon/webhook.test.js +0 -24
  33. package/dist/daemon/webhook.test.js.map +0 -1
  34. package/dist/protocol/messages.d.ts +0 -739
  35. package/dist/protocol/messages.d.ts.map +0 -1
  36. package/dist/protocol/messages.js +0 -188
  37. package/dist/protocol/messages.js.map +0 -1
  38. package/dist/protocol/messages.test.d.ts +0 -2
  39. package/dist/protocol/messages.test.d.ts.map +0 -1
  40. package/dist/protocol/messages.test.js +0 -55
  41. package/dist/protocol/messages.test.js.map +0 -1
  42. package/docs/F2A-PROTOCOL.md +0 -61
  43. package/docs/MOBILE_BOOTSTRAP_DESIGN.md +0 -126
  44. package/docs/a2a-lessons.md +0 -316
  45. package/docs/middleware-guide.md +0 -448
  46. package/docs/readme-update-checklist.md +0 -90
  47. package/docs/reputation-guide.md +0 -396
  48. package/docs/rfcs/001-reputation-system.md +0 -712
  49. package/docs/security-design.md +0 -247
  50. package/install.sh +0 -231
  51. package/packages/openclaw-adapter/README.md +0 -510
  52. package/packages/openclaw-adapter/openclaw.plugin.json +0 -106
  53. package/packages/openclaw-adapter/package.json +0 -40
  54. package/packages/openclaw-adapter/src/announcement-queue.test.ts +0 -449
  55. package/packages/openclaw-adapter/src/announcement-queue.ts +0 -403
  56. package/packages/openclaw-adapter/src/capability-detector.test.ts +0 -99
  57. package/packages/openclaw-adapter/src/capability-detector.ts +0 -183
  58. package/packages/openclaw-adapter/src/claim-handlers.test.ts +0 -974
  59. package/packages/openclaw-adapter/src/claim-handlers.ts +0 -482
  60. package/packages/openclaw-adapter/src/connector.business.test.ts +0 -583
  61. package/packages/openclaw-adapter/src/connector.ts +0 -795
  62. package/packages/openclaw-adapter/src/index.test.ts +0 -82
  63. package/packages/openclaw-adapter/src/index.ts +0 -18
  64. package/packages/openclaw-adapter/src/integration.e2e.test.ts +0 -829
  65. package/packages/openclaw-adapter/src/logger.ts +0 -51
  66. package/packages/openclaw-adapter/src/network-client.test.ts +0 -266
  67. package/packages/openclaw-adapter/src/network-client.ts +0 -251
  68. package/packages/openclaw-adapter/src/network-recovery.test.ts +0 -465
  69. package/packages/openclaw-adapter/src/node-manager.test.ts +0 -136
  70. package/packages/openclaw-adapter/src/node-manager.ts +0 -429
  71. package/packages/openclaw-adapter/src/plugin.test.ts +0 -439
  72. package/packages/openclaw-adapter/src/plugin.ts +0 -104
  73. package/packages/openclaw-adapter/src/reputation.test.ts +0 -221
  74. package/packages/openclaw-adapter/src/reputation.ts +0 -368
  75. package/packages/openclaw-adapter/src/task-guard.test.ts +0 -502
  76. package/packages/openclaw-adapter/src/task-guard.ts +0 -860
  77. package/packages/openclaw-adapter/src/task-queue.concurrency.test.ts +0 -462
  78. package/packages/openclaw-adapter/src/task-queue.edge-cases.test.ts +0 -284
  79. package/packages/openclaw-adapter/src/task-queue.persistence.test.ts +0 -408
  80. package/packages/openclaw-adapter/src/task-queue.ts +0 -668
  81. package/packages/openclaw-adapter/src/tool-handlers.test.ts +0 -906
  82. package/packages/openclaw-adapter/src/tool-handlers.ts +0 -574
  83. package/packages/openclaw-adapter/src/types.ts +0 -361
  84. package/packages/openclaw-adapter/src/webhook-pusher.test.ts +0 -188
  85. package/packages/openclaw-adapter/src/webhook-pusher.ts +0 -220
  86. package/packages/openclaw-adapter/src/webhook-server.test.ts +0 -580
  87. package/packages/openclaw-adapter/src/webhook-server.ts +0 -202
  88. package/packages/openclaw-adapter/tsconfig.json +0 -20
  89. package/src/cli/commands.test.ts +0 -157
  90. package/src/cli/commands.ts +0 -129
  91. package/src/cli/index.test.ts +0 -77
  92. package/src/cli/index.ts +0 -234
  93. package/src/core/autonomous-economy.test.ts +0 -291
  94. package/src/core/autonomous-economy.ts +0 -428
  95. package/src/core/e2ee-crypto.test.ts +0 -125
  96. package/src/core/e2ee-crypto.ts +0 -246
  97. package/src/core/f2a.test.ts +0 -269
  98. package/src/core/f2a.ts +0 -618
  99. package/src/core/p2p-network.test.ts +0 -199
  100. package/src/core/p2p-network.ts +0 -1432
  101. package/src/core/reputation-security.test.ts +0 -403
  102. package/src/core/reputation-security.ts +0 -562
  103. package/src/core/reputation.test.ts +0 -260
  104. package/src/core/reputation.ts +0 -576
  105. package/src/core/review-committee.test.ts +0 -380
  106. package/src/core/review-committee.ts +0 -401
  107. package/src/core/token-manager.test.ts +0 -133
  108. package/src/core/token-manager.ts +0 -140
  109. package/src/daemon/control-server.test.ts +0 -216
  110. package/src/daemon/control-server.ts +0 -292
  111. package/src/daemon/index.test.ts +0 -85
  112. package/src/daemon/index.ts +0 -89
  113. package/src/daemon/main.ts +0 -44
  114. package/src/daemon/start.ts +0 -29
  115. package/src/daemon/webhook.test.ts +0 -68
  116. package/src/daemon/webhook.ts +0 -105
  117. package/src/index.test.ts +0 -436
  118. package/src/index.ts +0 -72
  119. package/src/types/index.test.ts +0 -87
  120. package/src/types/index.ts +0 -341
  121. package/src/types/result.ts +0 -68
  122. package/src/utils/benchmark.ts +0 -237
  123. package/src/utils/logger.ts +0 -331
  124. package/src/utils/middleware.ts +0 -229
  125. package/src/utils/rate-limiter.ts +0 -207
  126. package/src/utils/signature.ts +0 -136
  127. package/src/utils/validation.ts +0 -186
  128. package/tests/docker/Dockerfile.node +0 -23
  129. package/tests/docker/Dockerfile.runner +0 -18
  130. package/tests/docker/docker-compose.test.yml +0 -73
  131. package/tests/integration/message-passing.test.ts +0 -109
  132. package/tests/integration/multi-node.test.ts +0 -92
  133. package/tests/integration/p2p-connection.test.ts +0 -83
  134. package/tests/integration/test-config.ts +0 -32
  135. package/tsconfig.json +0 -21
  136. package/vitest.config.ts +0 -26
@@ -1,403 +0,0 @@
1
- /**
2
- * F2A Announcement Queue
3
- * 管理任务广播和认领(Claim Pattern)
4
- *
5
- * 设计说明:
6
- * - 默认导出类而非单例,便于测试和依赖注入
7
- * - createAnnouncementQueue() 工厂函数提供默认实例
8
- */
9
-
10
- import type { TaskAnnouncement, TaskClaim } from './types.js';
11
- import { randomUUID } from 'crypto';
12
- import { queueLogger as logger } from './logger.js';
13
- import { EventEmitter } from 'eventemitter3';
14
-
15
- /**
16
- * 任务广播队列统计信息
17
- *
18
- * 提供任务广播各状态的数量统计,用于监控队列健康状态和任务处理进度。
19
- *
20
- * @example
21
- * ```typescript
22
- * const stats: AnnouncementQueueStats = {
23
- * open: 5, // 5 个开放认领的任务
24
- * claimed: 3, // 3 个已被认领
25
- * delegated: 12, // 12 个已委托执行
26
- * expired: 2, // 2 个已过期
27
- * total: 22 // 总计 22 个广播
28
- * };
29
- *
30
- * // 监控队列健康状态
31
- * if (stats.open > 10) {
32
- * console.warn('待认领任务过多,考虑通知更多 Agent');
33
- * }
34
- * ```
35
- */
36
- export interface AnnouncementQueueStats {
37
- /** 开放认领中的任务数量 */
38
- open: number;
39
- /** 已被认领的任务数量 */
40
- claimed: number;
41
- /** 已委托执行的任务数量 */
42
- delegated: number;
43
- /** 已过期的任务数量 */
44
- expired: number;
45
- /** 任务广播总数 */
46
- total: number;
47
- }
48
-
49
- export interface AnnouncementQueueOptions {
50
- maxSize?: number;
51
- maxAgeMs?: number;
52
- }
53
-
54
- /** 过期事件载荷 */
55
- export interface AnnouncementExpiredEvent {
56
- announcementId: string;
57
- taskType: string;
58
- from: string;
59
- timestamp: number;
60
- reason: 'timeout' | 'manual';
61
- }
62
-
63
- /** 事件类型定义 */
64
- export interface AnnouncementQueueEvents {
65
- 'announcement:expired': (event: AnnouncementExpiredEvent) => void;
66
- 'announcement:created': (announcement: TaskAnnouncement) => void;
67
- 'announcement:claimed': (announcement: TaskAnnouncement, claim: TaskClaim) => void;
68
- }
69
-
70
- export class AnnouncementQueue extends EventEmitter<AnnouncementQueueEvents> {
71
- private announcements = new Map<string, TaskAnnouncement>();
72
- private maxSize: number;
73
- private maxAgeMs: number;
74
- /** 正在处理的广播 ID 集合,用于防止并发操作 */
75
- private processingLocks = new Set<string>();
76
-
77
- constructor(options?: { maxSize?: number; maxAgeMs?: number }) {
78
- super();
79
- this.maxSize = options?.maxSize || 100;
80
- this.maxAgeMs = options?.maxAgeMs || 30 * 60 * 1000; // 30分钟
81
- }
82
-
83
- /**
84
- * 创建任务广播
85
- */
86
- create(announcement: Omit<TaskAnnouncement, 'announcementId' | 'timestamp' | 'status' | 'claims'>): TaskAnnouncement {
87
- // 清理过期
88
- this.cleanup();
89
-
90
- // 检查容量
91
- if (this.announcements.size >= this.maxSize) {
92
- logger.error(' create: queue is full, size=%d, maxSize=%d', this.announcements.size, this.maxSize);
93
- throw new Error('Announcement queue is full');
94
- }
95
-
96
- // 使用 crypto.randomUUID() 生成唯一 ID,避免碰撞
97
- const id = `ann-${randomUUID()}`;
98
- const created: TaskAnnouncement = {
99
- ...announcement,
100
- announcementId: id,
101
- timestamp: Date.now(),
102
- status: 'open',
103
- claims: []
104
- };
105
-
106
- this.announcements.set(id, created);
107
- logger.info(' create: announcementId=%s, from=%s, taskType=%s', id, announcement.from, announcement.taskType);
108
-
109
- // 发出创建事件
110
- this.emit('announcement:created', created);
111
-
112
- return created;
113
- }
114
-
115
- /**
116
- * 获取所有开放的广播
117
- */
118
- getOpen(): TaskAnnouncement[] {
119
- // 在获取开放广播前先清理过期数据,避免返回过期数据
120
- this.cleanup();
121
-
122
- return Array.from(this.announcements.values())
123
- .filter(a => a.status === 'open')
124
- .sort((a, b) => a.timestamp - b.timestamp);
125
- }
126
-
127
- /**
128
- * 获取特定广播
129
- */
130
- get(announcementId: string): TaskAnnouncement | undefined {
131
- return this.announcements.get(announcementId);
132
- }
133
-
134
- /**
135
- * 提交认领
136
- */
137
- submitClaim(
138
- announcementId: string,
139
- claim: Omit<TaskClaim, 'claimId' | 'timestamp' | 'status' | 'announcementId'>
140
- ): TaskClaim | null {
141
- const announcement = this.announcements.get(announcementId);
142
- if (!announcement) {
143
- logger.warn(' submitClaim: announcement not found, id=%s, claimant=%s', announcementId, claim.claimant);
144
- return null;
145
- }
146
- if (announcement.status !== 'open') {
147
- logger.warn(' submitClaim: announcement not open, id=%s, status=%s, claimant=%s', announcementId, announcement.status, claim.claimant);
148
- return null;
149
- }
150
-
151
- // 检查该 claimant 是否已经提交过认领(防止重复认领)
152
- const existingClaim = announcement.claims?.find(c => c.claimant === claim.claimant);
153
- if (existingClaim) {
154
- logger.info(' submitClaim: duplicate claim ignored, id=%s, claimant=%s, existingClaimId=%s', announcementId, claim.claimant, existingClaim.claimId);
155
- // 返回已存在的认领,而不是创建新的
156
- return existingClaim;
157
- }
158
-
159
- // 使用 crypto.randomUUID() 生成唯一 ID
160
- const claimId = `claim-${randomUUID()}`;
161
- const created: TaskClaim = {
162
- ...claim,
163
- claimId,
164
- announcementId,
165
- timestamp: Date.now(),
166
- status: 'pending'
167
- };
168
-
169
- if (!announcement.claims) {
170
- announcement.claims = [];
171
- }
172
- announcement.claims.push(created);
173
-
174
- logger.info(' submitClaim: claimId=%s, announcementId=%s, claimant=%s', claimId, announcementId, claim.claimant);
175
- return created;
176
- }
177
-
178
- /**
179
- * 接受认领
180
- * 使用锁机制防止竞态条件
181
- *
182
- * P1 修复:确保锁在任何情况下都能被释放,包括异常情况
183
- */
184
- acceptClaim(announcementId: string, claimId: string): TaskClaim | null {
185
- const announcement = this.announcements.get(announcementId);
186
- if (!announcement) {
187
- logger.warn(' acceptClaim: announcement not found, id=%s, claimId=%s', announcementId, claimId);
188
- return null;
189
- }
190
-
191
- // 检查是否已被锁定(正在被其他操作处理)
192
- if (this.processingLocks.has(announcementId)) {
193
- logger.warn(' acceptClaim: announcement is being processed, id=%s, claimId=%s', announcementId, claimId);
194
- return null;
195
- }
196
-
197
- // 检查广播状态
198
- if (announcement.status !== 'open') {
199
- logger.warn(' acceptClaim: announcement not open, id=%s, status=%s, claimId=%s', announcementId, announcement.status, claimId);
200
- return null;
201
- }
202
-
203
- const claim = announcement.claims?.find(c => c.claimId === claimId);
204
- if (!claim) {
205
- logger.warn(' acceptClaim: claim not found, announcementId=%s, claimId=%s', announcementId, claimId);
206
- return null;
207
- }
208
-
209
- // 获取锁
210
- this.processingLocks.add(announcementId);
211
-
212
- try {
213
- // 再次检查广播状态(双重检查)
214
- if (announcement.status !== 'open') {
215
- logger.warn(' acceptClaim: race condition detected, announcement status changed, id=%s, status=%s, claimId=%s', announcementId, announcement.status, claimId);
216
- return null;
217
- }
218
-
219
- // 标记该认领为接受
220
- claim.status = 'accepted';
221
-
222
- // 拒绝其他认领(幂等操作,已拒绝的保持不变)
223
- const rejectedCount = announcement.claims?.filter(c => {
224
- if (c.claimId !== claimId && c.status !== 'rejected') {
225
- c.status = 'rejected';
226
- return true;
227
- }
228
- return false;
229
- }).length || 0;
230
-
231
- // 标记广播为已认领
232
- announcement.status = 'claimed';
233
-
234
- logger.info(' acceptClaim: claimId=%s, announcementId=%s, claimant=%s, rejectedCount=%d', claimId, announcementId, claim.claimant, rejectedCount);
235
-
236
- // 发出认领事件(在锁内发出,确保状态一致)
237
- this.emit('announcement:claimed', announcement, claim);
238
-
239
- return claim;
240
- } catch (error) {
241
- // P1 修复:记录异常并返回 null,但不改变状态
242
- logger.error(' acceptClaim: unexpected error, id=%s, claimId=%s, error=%s', announcementId, claimId, error);
243
- // 注意:不恢复状态,因为操作可能已部分完成
244
- // 但锁会在 finally 中释放
245
- return null;
246
- } finally {
247
- // 确保锁在任何情况下都能被释放
248
- this.processingLocks.delete(announcementId);
249
- }
250
- }
251
-
252
- /**
253
- * 拒绝认领
254
- */
255
- rejectClaim(announcementId: string, claimId: string): TaskClaim | null {
256
- const announcement = this.announcements.get(announcementId);
257
- if (!announcement) {
258
- logger.warn(' rejectClaim: announcement not found, id=%s, claimId=%s', announcementId, claimId);
259
- return null;
260
- }
261
-
262
- const claim = announcement.claims?.find(c => c.claimId === claimId);
263
- if (!claim) {
264
- logger.warn(' rejectClaim: claim not found, announcementId=%s, claimId=%s', announcementId, claimId);
265
- return null;
266
- }
267
-
268
- claim.status = 'rejected';
269
- logger.info(' rejectClaim: claimId=%s, announcementId=%s, claimant=%s', claimId, announcementId, claim.claimant);
270
- return claim;
271
- }
272
-
273
- /**
274
- * 标记为已委托
275
- */
276
- markDelegated(announcementId: string): boolean {
277
- const announcement = this.announcements.get(announcementId);
278
- if (!announcement) return false;
279
-
280
- announcement.status = 'delegated';
281
- return true;
282
- }
283
-
284
- /**
285
- * 获取我的认领(作为认领方)
286
- */
287
- getMyClaims(claimantId: string): TaskClaim[] {
288
- const claims: TaskClaim[] = [];
289
- for (const announcement of this.announcements.values()) {
290
- const myClaims = announcement.claims?.filter(c => c.claimant === claimantId);
291
- if (myClaims) {
292
- claims.push(...myClaims);
293
- }
294
- }
295
- return claims.sort((a, b) => b.timestamp - a.timestamp);
296
- }
297
-
298
- /**
299
- * 获取我的广播(作为发布方)
300
- */
301
- getMyAnnouncements(fromId: string): TaskAnnouncement[] {
302
- return Array.from(this.announcements.values())
303
- .filter(a => a.from === fromId)
304
- .sort((a, b) => b.timestamp - a.timestamp);
305
- }
306
-
307
- /**
308
- * 获取统计
309
- */
310
- getStats(): AnnouncementQueueStats {
311
- const all = Array.from(this.announcements.values());
312
- return {
313
- open: all.filter(a => a.status === 'open').length,
314
- claimed: all.filter(a => a.status === 'claimed').length,
315
- delegated: all.filter(a => a.status === 'delegated').length,
316
- expired: all.filter(a => a.status === 'expired').length,
317
- total: all.length
318
- };
319
- }
320
-
321
- /**
322
- * 清理过期
323
- */
324
- cleanup(): void {
325
- const now = Date.now();
326
- let expiredCount = 0;
327
- let deletedCount = 0;
328
-
329
- for (const [id, announcement] of this.announcements) {
330
- const age = now - announcement.timestamp;
331
- if (age > this.maxAgeMs) {
332
- if (announcement.status === 'open') {
333
- announcement.status = 'expired';
334
- expiredCount++;
335
-
336
- // 发出过期事件,通知外部系统
337
- const expiredEvent: AnnouncementExpiredEvent = {
338
- announcementId: announcement.announcementId,
339
- taskType: announcement.taskType,
340
- from: announcement.from,
341
- timestamp: announcement.timestamp,
342
- reason: 'timeout'
343
- };
344
- this.emit('announcement:expired', expiredEvent);
345
- logger.info('cleanup: announcement expired, id=%s, taskType=%s, from=%s',
346
- announcement.announcementId, announcement.taskType, announcement.from);
347
- }
348
- // 删除已过期一段时间的
349
- if (age > this.maxAgeMs * 2) {
350
- this.announcements.delete(id);
351
- deletedCount++;
352
- }
353
- }
354
- }
355
-
356
- if (expiredCount > 0 || deletedCount > 0) {
357
- logger.info(' cleanup: expired=%d, deleted=%d, remaining=%d', expiredCount, deletedCount, this.announcements.size);
358
- }
359
- }
360
-
361
- /**
362
- * 清空
363
- */
364
- clear(): void {
365
- this.announcements.clear();
366
- // P1 修复:同时清理孤立锁
367
- this.processingLocks.clear();
368
- }
369
-
370
- /**
371
- * 强制清除孤立锁
372
- * P1 修复:用于处理异常导致的锁未释放情况
373
- *
374
- * @param maxLockAgeMs 锁的最大存活时间(毫秒),默认 30 秒
375
- * @returns 清除的锁数量
376
- */
377
- forceClearOrphanLocks(maxLockAgeMs: number = 30000): number {
378
- // 由于当前实现没有记录锁的获取时间,我们只能清除所有锁
379
- // 这是一个安全操作,因为锁只是为了防止并发,不会丢失数据
380
- const count = this.processingLocks.size;
381
- this.processingLocks.clear();
382
- if (count > 0) {
383
- logger.warn(' forceClearOrphanLocks: cleared %d orphan locks', count);
384
- }
385
- return count;
386
- }
387
-
388
- /**
389
- * 检查是否有孤立锁
390
- * 用于监控和调试
391
- */
392
- hasOrphanLocks(): boolean {
393
- return this.processingLocks.size > 0;
394
- }
395
- }
396
-
397
- // 工厂函数:创建新的 AnnouncementQueue 实例
398
- export function createAnnouncementQueue(options?: { maxSize?: number; maxAgeMs?: number }): AnnouncementQueue {
399
- return new AnnouncementQueue(options);
400
- }
401
-
402
- // 默认实例(向后兼容,但建议使用依赖注入)
403
- export const announcementQueue = new AnnouncementQueue();
@@ -1,99 +0,0 @@
1
- import { describe, it, expect, beforeEach } from 'vitest';
2
- import { CapabilityDetector } from './capability-detector';
3
-
4
- describe('CapabilityDetector', () => {
5
- let detector: CapabilityDetector;
6
-
7
- beforeEach(() => {
8
- detector = new CapabilityDetector();
9
- });
10
-
11
- describe('getDefaultCapabilities', () => {
12
- it('should return default capabilities list', () => {
13
- const capabilities = detector.getDefaultCapabilities();
14
-
15
- expect(capabilities.length).toBeGreaterThan(0);
16
- // 检查是否包含核心能力
17
- expect(capabilities.some(c => c.name === 'file-operation')).toBe(true);
18
- expect(capabilities.some(c => c.name === 'command-execution')).toBe(true);
19
- expect(capabilities.some(c => c.name === 'web-browsing')).toBe(true);
20
- expect(capabilities.some(c => c.name === 'code-generation')).toBe(true);
21
- expect(capabilities.some(c => c.name === 'task-delegation')).toBe(true);
22
- });
23
-
24
- it('should return capabilities with proper structure', () => {
25
- const capabilities = detector.getDefaultCapabilities();
26
-
27
- for (const cap of capabilities) {
28
- expect(cap.name).toBeDefined();
29
- expect(cap.description).toBeDefined();
30
- expect(cap.parameters).toBeDefined();
31
- expect(typeof cap.name).toBe('string');
32
- expect(typeof cap.description).toBe('string');
33
- expect(typeof cap.parameters).toBe('object');
34
- }
35
- });
36
-
37
- it('should include tools array for relevant capabilities', () => {
38
- const capabilities = detector.getDefaultCapabilities();
39
-
40
- const fileOp = capabilities.find(c => c.name === 'file-operation');
41
- expect(fileOp?.tools).toContain('read');
42
- expect(fileOp?.tools).toContain('write');
43
- });
44
- });
45
-
46
- describe('mergeCustomCapabilities', () => {
47
- it('should add custom capabilities to defaults', () => {
48
- const defaults = detector.getDefaultCapabilities();
49
- const custom = ['custom-ml', 'custom-data-analysis'];
50
-
51
- const merged = detector.mergeCustomCapabilities(defaults, custom);
52
-
53
- expect(merged.length).toBe(defaults.length + 2);
54
- expect(merged.some(c => c.name === 'custom-ml')).toBe(true);
55
- expect(merged.some(c => c.name === 'custom-data-analysis')).toBe(true);
56
- });
57
-
58
- it('should not duplicate existing capabilities', () => {
59
- const defaults = detector.getDefaultCapabilities();
60
- const custom = ['code-generation']; // 已存在的能力
61
-
62
- const merged = detector.mergeCustomCapabilities(defaults, custom);
63
-
64
- const count = merged.filter(c => c.name === 'code-generation').length;
65
- expect(count).toBe(1);
66
- });
67
-
68
- it('should create proper structure for custom capabilities', () => {
69
- const defaults = detector.getDefaultCapabilities();
70
- const custom = ['my-custom-capability'];
71
-
72
- const merged = detector.mergeCustomCapabilities(defaults, custom);
73
- const customCap = merged.find(c => c.name === 'my-custom-capability');
74
-
75
- expect(customCap).toBeDefined();
76
- expect(customCap?.description).toContain('my-custom-capability');
77
- expect(customCap?.parameters).toBeDefined();
78
- });
79
-
80
- it('should handle empty custom list', () => {
81
- const defaults = detector.getDefaultCapabilities();
82
- const merged = detector.mergeCustomCapabilities(defaults, []);
83
-
84
- expect(merged.length).toBe(defaults.length);
85
- });
86
-
87
- it('should preserve default capabilities order', () => {
88
- const defaults = detector.getDefaultCapabilities();
89
- const custom = ['zzz-custom'];
90
-
91
- const merged = detector.mergeCustomCapabilities(defaults, custom);
92
-
93
- // 默认能力应该在前,自定义在后
94
- const defaultNames = defaults.map(c => c.name);
95
- const mergedDefaultNames = merged.slice(0, defaults.length).map(c => c.name);
96
- expect(mergedDefaultNames).toEqual(defaultNames);
97
- });
98
- });
99
- });
@@ -1,183 +0,0 @@
1
- /**
2
- * OpenClaw 能力检测器
3
- * 提供默认能力列表(外部插件无法动态检测 OpenClaw 内部能力)
4
- */
5
-
6
- import type { AgentCapability, ParameterSchema } from './types.js';
7
-
8
- export interface OpenClawCapabilities {
9
- tools: string[];
10
- skills: string[];
11
- }
12
-
13
- export class CapabilityDetector {
14
- /**
15
- * 获取默认能力列表
16
- * 外部插件无法直接访问 OpenClaw 内部,使用预定义的能力列表
17
- */
18
- getDefaultCapabilities(): AgentCapability[] {
19
- return [
20
- {
21
- name: 'file-operation',
22
- description: 'Read and write files on the local system',
23
- tools: ['read', 'write', 'edit', 'list'],
24
- parameters: {
25
- action: {
26
- type: 'string',
27
- description: 'Action to perform: read, write, edit, list',
28
- required: true
29
- },
30
- path: {
31
- type: 'string',
32
- description: 'File or directory path',
33
- required: true
34
- }
35
- }
36
- },
37
- {
38
- name: 'command-execution',
39
- description: 'Execute shell commands',
40
- tools: ['exec', 'bash'],
41
- parameters: {
42
- command: {
43
- type: 'string',
44
- description: 'Shell command to execute',
45
- required: true
46
- },
47
- cwd: {
48
- type: 'string',
49
- description: 'Working directory',
50
- required: false
51
- }
52
- }
53
- },
54
- {
55
- name: 'web-browsing',
56
- description: 'Browse web pages and fetch content',
57
- tools: ['browser', 'fetch'],
58
- parameters: {
59
- url: {
60
- type: 'string',
61
- description: 'URL to browse or fetch',
62
- required: true
63
- },
64
- action: {
65
- type: 'string',
66
- description: 'Action: fetch, screenshot, click, type',
67
- required: false
68
- }
69
- }
70
- },
71
- {
72
- name: 'image-analysis',
73
- description: 'Analyze images using vision models',
74
- tools: ['image', 'analyze'],
75
- parameters: {
76
- image: {
77
- type: 'string',
78
- description: 'Image path or URL',
79
- required: true
80
- },
81
- prompt: {
82
- type: 'string',
83
- description: 'Analysis prompt',
84
- required: false
85
- }
86
- }
87
- },
88
- {
89
- name: 'subagent-creation',
90
- description: 'Create sub-agents for parallel task execution',
91
- tools: ['sessions_spawn', 'subagents'],
92
- parameters: {
93
- task: {
94
- type: 'string',
95
- description: 'Task description for sub-agent',
96
- required: true
97
- },
98
- agentId: {
99
- type: 'string',
100
- description: 'Agent type to spawn',
101
- required: false
102
- }
103
- }
104
- },
105
- {
106
- name: 'messaging',
107
- description: 'Send messages to users or channels',
108
- tools: ['message', 'notify'],
109
- parameters: {
110
- target: {
111
- type: 'string',
112
- description: 'Message target',
113
- required: true
114
- },
115
- content: {
116
- type: 'string',
117
- description: 'Message content',
118
- required: true
119
- }
120
- }
121
- },
122
- {
123
- name: 'code-generation',
124
- description: 'Generate code in various programming languages',
125
- tools: ['generate', 'refactor', 'explain'],
126
- parameters: {
127
- language: {
128
- type: 'string',
129
- description: 'Programming language',
130
- required: true
131
- },
132
- description: {
133
- type: 'string',
134
- description: 'What the code should do',
135
- required: true
136
- }
137
- }
138
- },
139
- {
140
- name: 'task-delegation',
141
- description: 'Delegate tasks to other agents in the network',
142
- tools: ['delegate', 'discover'],
143
- parameters: {
144
- capability: {
145
- type: 'string',
146
- description: 'Required capability',
147
- required: true
148
- },
149
- description: {
150
- type: 'string',
151
- description: 'Task description',
152
- required: true
153
- }
154
- }
155
- }
156
- ];
157
- }
158
-
159
- /**
160
- * 合并自定义能力
161
- */
162
- mergeCustomCapabilities(defaults: AgentCapability[], custom: string[]): AgentCapability[] {
163
- const merged = [...defaults];
164
-
165
- for (const capName of custom) {
166
- if (!merged.find(c => c.name === capName)) {
167
- merged.push({
168
- name: capName,
169
- description: `Custom capability: ${capName}`,
170
- parameters: {
171
- query: {
172
- type: 'string',
173
- description: 'Input for this capability',
174
- required: true
175
- }
176
- }
177
- });
178
- }
179
- }
180
-
181
- return merged;
182
- }
183
- }