@f2a/openclaw-adapter 0.1.0 → 0.1.4

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 (61) hide show
  1. package/dist/announcement-queue.d.ts +72 -1
  2. package/dist/announcement-queue.d.ts.map +1 -1
  3. package/dist/announcement-queue.js +145 -20
  4. package/dist/announcement-queue.js.map +1 -1
  5. package/dist/claim-handlers.d.ts +75 -0
  6. package/dist/claim-handlers.d.ts.map +1 -0
  7. package/dist/claim-handlers.js +368 -0
  8. package/dist/claim-handlers.js.map +1 -0
  9. package/dist/connector.d.ts +45 -18
  10. package/dist/connector.d.ts.map +1 -1
  11. package/dist/connector.js +219 -583
  12. package/dist/connector.js.map +1 -1
  13. package/dist/index.d.ts +1 -1
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +2 -1
  16. package/dist/index.js.map +1 -1
  17. package/dist/logger.d.ts +28 -0
  18. package/dist/logger.d.ts.map +1 -0
  19. package/dist/logger.js +44 -0
  20. package/dist/logger.js.map +1 -0
  21. package/dist/network-client.d.ts +17 -1
  22. package/dist/network-client.d.ts.map +1 -1
  23. package/dist/network-client.js +119 -23
  24. package/dist/network-client.js.map +1 -1
  25. package/dist/node-manager.d.ts +20 -0
  26. package/dist/node-manager.d.ts.map +1 -1
  27. package/dist/node-manager.js +194 -18
  28. package/dist/node-manager.js.map +1 -1
  29. package/dist/plugin.d.ts +1 -1
  30. package/dist/plugin.d.ts.map +1 -1
  31. package/dist/plugin.js +4 -5
  32. package/dist/plugin.js.map +1 -1
  33. package/dist/reputation.d.ts +22 -1
  34. package/dist/reputation.d.ts.map +1 -1
  35. package/dist/reputation.js +102 -5
  36. package/dist/reputation.js.map +1 -1
  37. package/dist/task-guard.d.ts +82 -0
  38. package/dist/task-guard.d.ts.map +1 -1
  39. package/dist/task-guard.js +392 -15
  40. package/dist/task-guard.js.map +1 -1
  41. package/dist/task-queue.d.ts +50 -7
  42. package/dist/task-queue.d.ts.map +1 -1
  43. package/dist/task-queue.js +445 -12
  44. package/dist/task-queue.js.map +1 -1
  45. package/dist/tool-handlers.d.ts +96 -0
  46. package/dist/tool-handlers.d.ts.map +1 -0
  47. package/dist/tool-handlers.js +431 -0
  48. package/dist/tool-handlers.js.map +1 -0
  49. package/dist/types.d.ts +98 -10
  50. package/dist/types.d.ts.map +1 -1
  51. package/dist/types.js +10 -0
  52. package/dist/types.js.map +1 -1
  53. package/dist/webhook-pusher.d.ts +71 -0
  54. package/dist/webhook-pusher.d.ts.map +1 -0
  55. package/dist/webhook-pusher.js +174 -0
  56. package/dist/webhook-pusher.js.map +1 -0
  57. package/dist/webhook-server.d.ts +8 -1
  58. package/dist/webhook-server.d.ts.map +1 -1
  59. package/dist/webhook-server.js +42 -7
  60. package/dist/webhook-server.js.map +1 -1
  61. package/package.json +21 -8
package/dist/connector.js CHANGED
@@ -12,9 +12,14 @@ const reputation_js_1 = require("./reputation.js");
12
12
  const capability_detector_js_1 = require("./capability-detector.js");
13
13
  const task_queue_js_1 = require("./task-queue.js");
14
14
  const announcement_queue_js_1 = require("./announcement-queue.js");
15
+ const webhook_pusher_js_1 = require("./webhook-pusher.js");
16
+ const task_guard_js_1 = require("./task-guard.js");
17
+ const tool_handlers_js_1 = require("./tool-handlers.js");
18
+ const claim_handlers_js_1 = require("./claim-handlers.js");
19
+ const logger_js_1 = require("./logger.js");
15
20
  class F2AOpenClawAdapter {
16
21
  name = 'f2a-openclaw-adapter';
17
- version = '0.2.0';
22
+ version = '0.3.0';
18
23
  nodeManager;
19
24
  networkClient;
20
25
  webhookServer;
@@ -22,15 +27,38 @@ class F2AOpenClawAdapter {
22
27
  capabilityDetector;
23
28
  taskQueue;
24
29
  announcementQueue;
30
+ webhookPusher;
31
+ // 处理器实例(延迟初始化)
32
+ _toolHandlers;
33
+ _claimHandlers;
25
34
  config;
26
35
  nodeConfig;
27
36
  capabilities = [];
28
37
  api;
38
+ pollTimer;
39
+ /**
40
+ * 获取工具处理器(延迟初始化,支持未初始化时调用getTools)
41
+ */
42
+ get toolHandlers() {
43
+ if (!this._toolHandlers) {
44
+ this._toolHandlers = new tool_handlers_js_1.ToolHandlers(this);
45
+ }
46
+ return this._toolHandlers;
47
+ }
48
+ /**
49
+ * 获取认领处理器(延迟初始化)
50
+ */
51
+ get claimHandlers() {
52
+ if (!this._claimHandlers) {
53
+ this._claimHandlers = new claim_handlers_js_1.ClaimHandlers(this);
54
+ }
55
+ return this._claimHandlers;
56
+ }
29
57
  /**
30
58
  * 初始化插件
31
59
  */
32
60
  async initialize(config) {
33
- console.log('[F2A Plugin] 初始化...');
61
+ logger_js_1.pluginLogger.info('初始化...');
34
62
  // 保存 API 引用(用于触发心跳等)
35
63
  this.api = config._api;
36
64
  // 合并配置
@@ -43,11 +71,19 @@ class F2AOpenClawAdapter {
43
71
  enableMDNS: this.config.enableMDNS ?? true,
44
72
  bootstrapPeers: this.config.bootstrapPeers || []
45
73
  };
46
- // 初始化任务队列
74
+ // 初始化任务队列(带持久化)
75
+ const dataDir = this.config.dataDir || './f2a-data';
47
76
  this.taskQueue = new task_queue_js_1.TaskQueue({
48
77
  maxSize: this.config.maxQueuedTasks || 100,
49
- maxAgeMs: 24 * 60 * 60 * 1000 // 24小时
78
+ maxAgeMs: 24 * 60 * 60 * 1000, // 24小时
79
+ persistDir: dataDir,
80
+ persistEnabled: true
50
81
  });
82
+ // 初始化 Webhook 推送器
83
+ if (this.config.webhookPush?.enabled !== false && this.config.webhookPush?.url) {
84
+ this.webhookPusher = new webhook_pusher_js_1.WebhookPusher(this.config.webhookPush);
85
+ logger_js_1.pluginLogger.info('Webhook 推送已启用');
86
+ }
51
87
  // 初始化广播队列
52
88
  this.announcementQueue = new announcement_queue_js_1.AnnouncementQueue({
53
89
  maxSize: 50,
@@ -63,6 +99,7 @@ class F2AOpenClawAdapter {
63
99
  decayRate: 0.01
64
100
  }, this.config.dataDir || './f2a-data');
65
101
  this.capabilityDetector = new capability_detector_js_1.CapabilityDetector();
102
+ // 处理器使用 getter 延迟初始化,无需在此显式创建
66
103
  // 启动 F2A Node
67
104
  if (this.config.autoStart) {
68
105
  const result = await this.nodeManager.ensureRunning();
@@ -76,14 +113,73 @@ class F2AOpenClawAdapter {
76
113
  this.capabilities = this.capabilityDetector.mergeCustomCapabilities(this.capabilities, this.config.capabilities);
77
114
  }
78
115
  // 启动 Webhook 服务器
79
- this.webhookServer = new webhook_server_js_1.WebhookServer(this.config.webhookPort, this.createWebhookHandler());
116
+ this.webhookServer = new webhook_server_js_1.WebhookServer(this.config.webhookPort || 0, this.createWebhookHandler());
80
117
  await this.webhookServer.start();
81
118
  // 注册到 F2A Node
82
119
  await this.registerToNode();
83
- console.log('[F2A Plugin] 初始化完成');
84
- console.log(`[F2A Plugin] Agent 名称: ${this.config.agentName}`);
85
- console.log(`[F2A Plugin] 能力数: ${this.capabilities.length}`);
86
- console.log(`[F2A Plugin] Webhook: ${this.webhookServer.getUrl()}`);
120
+ logger_js_1.pluginLogger.info('初始化完成');
121
+ logger_js_1.pluginLogger.info(`Agent 名称: ${this.config.agentName}`);
122
+ logger_js_1.pluginLogger.info(`能力数: ${this.capabilities.length}`);
123
+ logger_js_1.pluginLogger.info(`Webhook: ${this.webhookServer.getUrl()}`);
124
+ // 启动兜底轮询(降低到 60 秒)
125
+ this.startFallbackPolling();
126
+ }
127
+ /**
128
+ * 兜底轮询
129
+ * 当 webhook 推送失败时,轮询确保任务不会丢失
130
+ */
131
+ startFallbackPolling() {
132
+ const interval = this.config.pollInterval || 60000; // 默认 60 秒
133
+ this.pollTimer = setInterval(async () => {
134
+ // P1 修复:定期检查并重置超时的 processing 任务,防止僵尸任务
135
+ this.resetTimedOutProcessingTasks();
136
+ if (!this.webhookPusher) {
137
+ // 没有配置 webhook,不轮询(保持原有轮询模式)
138
+ return;
139
+ }
140
+ try {
141
+ // 获取未推送的任务
142
+ const pending = this.taskQueue.getWebhookPending();
143
+ if (pending.length > 0) {
144
+ logger_js_1.pluginLogger.info(`兜底轮询: ${pending.length} 个待推送任务`);
145
+ for (const task of pending) {
146
+ const result = await this.webhookPusher.pushTask(task);
147
+ if (result.success) {
148
+ this.taskQueue.markWebhookPushed(task.taskId);
149
+ }
150
+ }
151
+ }
152
+ }
153
+ catch (error) {
154
+ logger_js_1.pluginLogger.error('兜底轮询失败:', error);
155
+ }
156
+ }, interval);
157
+ }
158
+ /**
159
+ * P1 修复:重置超时的 processing 任务
160
+ * 如果任务在 processing 状态停留超过超时时间,将其重置为 pending
161
+ * 防止因处理失败导致的僵尸任务
162
+ */
163
+ resetTimedOutProcessingTasks() {
164
+ const stats = this.taskQueue.getStats();
165
+ if (stats.processing === 0) {
166
+ return; // 没有处理中的任务,无需检查
167
+ }
168
+ const allTasks = this.taskQueue.getAll();
169
+ const now = Date.now();
170
+ const processingTimeout = this.config.processingTimeoutMs || 5 * 60 * 1000; // 默认 5 分钟
171
+ for (const task of allTasks) {
172
+ if (task.status === 'processing') {
173
+ const taskTimeout = task.timeout || 30000; // 使用任务自身的超时或默认 30 秒
174
+ const maxAllowedTime = Math.max(taskTimeout * 2, processingTimeout); // 至少 2 倍任务超时或 processingTimeout
175
+ const processingTime = now - (task.updatedAt || task.createdAt);
176
+ if (processingTime > maxAllowedTime) {
177
+ logger_js_1.pluginLogger.warn(`检测到僵尸任务 ${task.taskId.slice(0, 8)}... (processing ${Math.round(processingTime / 1000)}s),重置为 pending`);
178
+ // 将任务重置为 pending 状态
179
+ this.taskQueue.resetProcessingTask(task.taskId);
180
+ }
181
+ }
182
+ }
87
183
  }
88
184
  /**
89
185
  * 获取插件提供的 Tools
@@ -105,7 +201,7 @@ class F2AOpenClawAdapter {
105
201
  required: false
106
202
  }
107
203
  },
108
- handler: this.handleDiscover.bind(this)
204
+ handler: this.toolHandlers.handleDiscover.bind(this.toolHandlers)
109
205
  },
110
206
  {
111
207
  name: 'f2a_delegate',
@@ -132,7 +228,7 @@ class F2AOpenClawAdapter {
132
228
  required: false
133
229
  }
134
230
  },
135
- handler: this.handleDelegate.bind(this)
231
+ handler: this.toolHandlers.handleDelegate.bind(this.toolHandlers)
136
232
  },
137
233
  {
138
234
  name: 'f2a_broadcast',
@@ -154,13 +250,13 @@ class F2AOpenClawAdapter {
154
250
  required: false
155
251
  }
156
252
  },
157
- handler: this.handleBroadcast.bind(this)
253
+ handler: this.toolHandlers.handleBroadcast.bind(this.toolHandlers)
158
254
  },
159
255
  {
160
256
  name: 'f2a_status',
161
257
  description: '查看 F2A 网络状态和已连接 Peers',
162
258
  parameters: {},
163
- handler: this.handleStatus.bind(this)
259
+ handler: this.toolHandlers.handleStatus.bind(this.toolHandlers)
164
260
  },
165
261
  {
166
262
  name: 'f2a_reputation',
@@ -178,7 +274,7 @@ class F2AOpenClawAdapter {
178
274
  required: false
179
275
  }
180
276
  },
181
- handler: this.handleReputation.bind(this)
277
+ handler: this.toolHandlers.handleReputation.bind(this.toolHandlers)
182
278
  },
183
279
  // 新增:任务队列相关工具
184
280
  {
@@ -197,7 +293,7 @@ class F2AOpenClawAdapter {
197
293
  enum: ['pending', 'processing', 'completed', 'failed']
198
294
  }
199
295
  },
200
- handler: this.handlePollTasks.bind(this)
296
+ handler: this.toolHandlers.handlePollTasks.bind(this.toolHandlers)
201
297
  },
202
298
  {
203
299
  name: 'f2a_submit_result',
@@ -220,13 +316,13 @@ class F2AOpenClawAdapter {
220
316
  enum: ['success', 'error']
221
317
  }
222
318
  },
223
- handler: this.handleSubmitResult.bind(this)
319
+ handler: this.toolHandlers.handleSubmitResult.bind(this.toolHandlers)
224
320
  },
225
321
  {
226
322
  name: 'f2a_task_stats',
227
323
  description: '查看任务队列统计信息',
228
324
  parameters: {},
229
- handler: this.handleTaskStats.bind(this)
325
+ handler: this.toolHandlers.handleTaskStats.bind(this.toolHandlers)
230
326
  },
231
327
  // 认领模式工具
232
328
  {
@@ -264,7 +360,7 @@ class F2AOpenClawAdapter {
264
360
  required: false
265
361
  }
266
362
  },
267
- handler: this.handleAnnounce.bind(this)
363
+ handler: this.claimHandlers.handleAnnounce.bind(this.claimHandlers)
268
364
  },
269
365
  {
270
366
  name: 'f2a_list_announcements',
@@ -281,7 +377,7 @@ class F2AOpenClawAdapter {
281
377
  required: false
282
378
  }
283
379
  },
284
- handler: this.handleListAnnouncements.bind(this)
380
+ handler: this.claimHandlers.handleListAnnouncements.bind(this.claimHandlers)
285
381
  },
286
382
  {
287
383
  name: 'f2a_claim',
@@ -303,7 +399,7 @@ class F2AOpenClawAdapter {
303
399
  required: false
304
400
  }
305
401
  },
306
- handler: this.handleClaim.bind(this)
402
+ handler: this.claimHandlers.handleClaim.bind(this.claimHandlers)
307
403
  },
308
404
  {
309
405
  name: 'f2a_manage_claims',
@@ -326,7 +422,7 @@ class F2AOpenClawAdapter {
326
422
  required: false
327
423
  }
328
424
  },
329
- handler: this.handleManageClaims.bind(this)
425
+ handler: this.claimHandlers.handleManageClaims.bind(this.claimHandlers)
330
426
  },
331
427
  {
332
428
  name: 'f2a_my_claims',
@@ -339,13 +435,13 @@ class F2AOpenClawAdapter {
339
435
  enum: ['pending', 'accepted', 'rejected', 'all']
340
436
  }
341
437
  },
342
- handler: this.handleMyClaims.bind(this)
438
+ handler: this.claimHandlers.handleMyClaims.bind(this.claimHandlers)
343
439
  },
344
440
  {
345
441
  name: 'f2a_announcement_stats',
346
442
  description: '查看任务广播统计',
347
443
  parameters: {},
348
- handler: this.handleAnnouncementStats.bind(this)
444
+ handler: this.claimHandlers.handleAnnouncementStats.bind(this.claimHandlers)
349
445
  }
350
446
  ];
351
447
  }
@@ -385,20 +481,48 @@ class F2AOpenClawAdapter {
385
481
  // 检查白名单/黑名单
386
482
  const whitelist = this.config.security?.whitelist || [];
387
483
  const blacklist = this.config.security?.blacklist || [];
388
- if (whitelist.length > 0 && !whitelist.includes(payload.from)) {
484
+ const isWhitelisted = whitelist.length > 0 && whitelist.includes(payload.from);
485
+ const isBlacklisted = blacklist.includes(payload.from);
486
+ if (whitelist.length > 0 && !isWhitelisted) {
389
487
  return {
390
488
  accepted: false,
391
489
  taskId: payload.taskId,
392
490
  reason: 'Not in whitelist'
393
491
  };
394
492
  }
395
- if (blacklist.includes(payload.from)) {
493
+ if (isBlacklisted) {
396
494
  return {
397
495
  accepted: false,
398
496
  taskId: payload.taskId,
399
497
  reason: 'In blacklist'
400
498
  };
401
499
  }
500
+ // TaskGuard 安全检查
501
+ const requesterReputation = this.reputationSystem.getReputation(payload.from);
502
+ const taskGuardContext = {
503
+ requesterReputation,
504
+ isWhitelisted,
505
+ isBlacklisted,
506
+ recentTaskCount: 0 // Will be tracked internally by TaskGuard
507
+ };
508
+ const taskGuardReport = task_guard_js_1.taskGuard.check(payload, taskGuardContext);
509
+ if (!taskGuardReport.passed) {
510
+ // 任务被阻止
511
+ const blockReasons = taskGuardReport.blocks.map(b => b.message).join('; ');
512
+ logger_js_1.pluginLogger.warn(`TaskGuard 阻止任务 ${payload.taskId}: ${blockReasons}`);
513
+ return {
514
+ accepted: false,
515
+ taskId: payload.taskId,
516
+ reason: `TaskGuard blocked: ${blockReasons}`
517
+ };
518
+ }
519
+ if (taskGuardReport.requiresConfirmation) {
520
+ // 任务需要确认(警告但不阻止)
521
+ const warnReasons = taskGuardReport.warnings.map(w => w.message).join('; ');
522
+ logger_js_1.pluginLogger.warn(`TaskGuard 警告 ${payload.taskId}: ${warnReasons}`);
523
+ // 未来可以扩展为请求用户确认
524
+ // 目前记录警告但继续处理任务
525
+ }
402
526
  // 检查队列是否已满
403
527
  const stats = this.taskQueue.getStats();
404
528
  if (stats.pending >= (this.config.maxQueuedTasks || 100)) {
@@ -410,7 +534,18 @@ class F2AOpenClawAdapter {
410
534
  }
411
535
  // 添加任务到队列
412
536
  try {
413
- this.taskQueue.add(payload);
537
+ const task = this.taskQueue.add(payload);
538
+ // 优先使用 webhook 推送
539
+ if (this.webhookPusher) {
540
+ const result = await this.webhookPusher.pushTask(task);
541
+ if (result.success) {
542
+ this.taskQueue.markWebhookPushed(task.taskId);
543
+ logger_js_1.pluginLogger.info(`任务 ${task.taskId} 已通过 webhook 推送 (${result.latency}ms)`);
544
+ }
545
+ else {
546
+ logger_js_1.pluginLogger.info(`Webhook 推送失败: ${result.error},任务将在轮询时处理`);
547
+ }
548
+ }
414
549
  // 触发 OpenClaw 心跳,让它知道有新任务
415
550
  this.api?.runtime?.system?.requestHeartbeatNow?.();
416
551
  return {
@@ -447,560 +582,10 @@ class F2AOpenClawAdapter {
447
582
  capabilities: this.capabilities
448
583
  });
449
584
  }
450
- // ========== Tool Handlers ==========
451
- async handleDiscover(params, context) {
452
- const result = await this.networkClient.discoverAgents(params.capability);
453
- if (!result.success) {
454
- return { content: `发现失败: ${result.error}` };
455
- }
456
- let agents = result.data || [];
457
- // 过滤信誉
458
- if (params.min_reputation !== undefined) {
459
- agents = agents.filter(a => {
460
- const rep = this.reputationSystem.getReputation(a.peerId);
461
- return rep.score >= params.min_reputation;
462
- });
463
- }
464
- if (agents.length === 0) {
465
- return { content: '🔍 未发现符合条件的 Agents' };
466
- }
467
- const content = `
468
- 🔍 发现 ${agents.length} 个 Agents:
469
-
470
- ${agents.map((a, i) => {
471
- const rep = this.reputationSystem.getReputation(a.peerId);
472
- return `${i + 1}. ${a.displayName} (信誉: ${rep.score})
473
- ID: ${a.peerId.slice(0, 20)}...
474
- 能力: ${a.capabilities?.map(c => c.name).join(', ') || '无'}`;
475
- }).join('\n\n')}
476
-
477
- 💡 使用方式:
478
- - 委托任务: 让 ${agents[0]?.displayName} 帮我写代码
479
- - 指定ID: 委托给 #1 分析数据
480
- `.trim();
481
- return {
482
- content,
483
- data: { agents, count: agents.length }
484
- };
485
- }
486
- async handleDelegate(params, context) {
487
- // 解析 Agent 引用
488
- const targetAgent = await this.resolveAgent(params.agent);
489
- if (!targetAgent) {
490
- return { content: `❌ 找不到 Agent: ${params.agent}` };
491
- }
492
- // 检查信誉
493
- if (!this.reputationSystem.isAllowed(targetAgent.peerId)) {
494
- return {
495
- content: `⚠️ ${targetAgent.displayName} 信誉过低 (${this.reputationSystem.getReputation(targetAgent.peerId).score}),建议谨慎委托`
496
- };
497
- }
498
- console.log(`[F2A Plugin] 委托任务给 ${targetAgent.displayName}...`);
499
- const result = await this.networkClient.delegateTask({
500
- peerId: targetAgent.peerId,
501
- taskType: 'openclaw-task',
502
- description: params.task,
503
- parameters: {
504
- context: params.context,
505
- sessionContext: context.toJSON()
506
- },
507
- timeout: params.timeout || 60000
508
- });
509
- if (!result.success) {
510
- // 记录失败
511
- this.reputationSystem.recordFailure(targetAgent.peerId, 'unknown', result.error);
512
- return { content: `❌ 委托失败: ${result.error}` };
513
- }
514
- return {
515
- content: `✅ ${targetAgent.displayName} 已完成任务:\n\n${JSON.stringify(result.data, null, 2)}`,
516
- data: result.data
517
- };
518
- }
519
- async handleBroadcast(params, context) {
520
- const discoverResult = await this.networkClient.discoverAgents(params.capability);
521
- if (!discoverResult.success || !discoverResult.data?.length) {
522
- return { content: `❌ 未发现具备 "${params.capability}" 能力的 Agents` };
523
- }
524
- const agents = discoverResult.data;
525
- console.log(`[F2A Plugin] 广播任务给 ${agents.length} 个 Agents...`);
526
- // 并行委托
527
- const promises = agents.map(async (agent) => {
528
- const start = Date.now();
529
- const result = await this.networkClient.delegateTask({
530
- peerId: agent.peerId,
531
- taskType: 'openclaw-task',
532
- description: params.task,
533
- parameters: { sessionContext: context.toJSON() },
534
- timeout: 60000
535
- });
536
- const latency = Date.now() - start;
537
- return {
538
- agent: agent.displayName,
539
- peerId: agent.peerId,
540
- success: result.success,
541
- result: result.data,
542
- error: result.error,
543
- latency
544
- };
545
- });
546
- const results = await Promise.allSettled(promises);
547
- const settled = results.map((r, i) => r.status === 'fulfilled' ? r.value : {
548
- agent: agents[i].displayName,
549
- success: false,
550
- error: String(r.reason)
551
- });
552
- const successful = settled.filter(r => r.success);
553
- const minResponses = params.min_responses || 1;
554
- if (successful.length < minResponses) {
555
- return {
556
- content: `⚠️ 仅 ${successful.length} 个成功响应(需要 ${minResponses})\n\n${this.formatBroadcastResults(settled)}`
557
- };
558
- }
559
- return {
560
- content: `✅ 收到 ${successful.length}/${settled.length} 个成功响应:\n\n${this.formatBroadcastResults(settled)}`,
561
- data: { results: settled }
562
- };
563
- }
564
- async handleStatus(params, context) {
565
- const [nodeStatus, peersResult] = await Promise.all([
566
- this.nodeManager.getStatus(),
567
- this.networkClient.getConnectedPeers()
568
- ]);
569
- if (!nodeStatus.success) {
570
- return { content: `❌ 获取状态失败: ${nodeStatus.error}` };
571
- }
572
- const peers = peersResult.success ? (peersResult.data || []) : [];
573
- const taskStats = this.taskQueue.getStats();
574
- const content = `
575
- 🟢 F2A 状态: ${nodeStatus.data?.running ? '运行中' : '已停止'}
576
- 📡 本机 PeerID: ${nodeStatus.data?.peerId || 'N/A'}
577
- ⏱️ 运行时间: ${nodeStatus.data?.uptime ? Math.floor(nodeStatus.data.uptime / 60) + ' 分钟' : 'N/A'}
578
- 🔗 已连接 Peers: ${peers.length}
579
- 📋 任务队列: ${taskStats.pending} 待处理, ${taskStats.processing} 处理中, ${taskStats.completed} 已完成
580
-
581
- ${peers.map(p => {
582
- const rep = this.reputationSystem.getReputation(p.peerId);
583
- return ` • ${p.agentInfo?.displayName || 'Unknown'} (信誉: ${rep.score})\n ID: ${p.peerId.slice(0, 20)}...`;
584
- }).join('\n')}
585
- `.trim();
586
- return { content, data: { status: nodeStatus.data, peers, taskStats } };
587
- }
588
- async handleReputation(params, context) {
589
- switch (params.action) {
590
- case 'list': {
591
- const reps = this.reputationSystem.getAllReputations();
592
- return {
593
- content: `📊 信誉记录 (${reps.length} 条):\n\n${reps.map(r => ` ${r.peerId.slice(0, 20)}...: ${r.score} (成功: ${r.successfulTasks}, 失败: ${r.failedTasks})`).join('\n')}`
594
- };
595
- }
596
- case 'view': {
597
- if (!params.peer_id) {
598
- return { content: '❌ 请提供 peer_id' };
599
- }
600
- const rep = this.reputationSystem.getReputation(params.peer_id);
601
- return {
602
- content: `📊 Peer ${params.peer_id.slice(0, 20)}...:\n` +
603
- ` 信誉分: ${rep.score}\n` +
604
- ` 成功任务: ${rep.successfulTasks}\n` +
605
- ` 失败任务: ${rep.failedTasks}\n` +
606
- ` 平均响应: ${rep.avgResponseTime.toFixed(0)}ms\n` +
607
- ` 最后交互: ${new Date(rep.lastInteraction).toLocaleString()}`
608
- };
609
- }
610
- case 'block': {
611
- if (!params.peer_id) {
612
- return { content: '❌ 请提供 peer_id' };
613
- }
614
- if (!this.config.security) {
615
- this.config.security = { requireConfirmation: false, whitelist: [], blacklist: [], maxTasksPerMinute: 10 };
616
- }
617
- this.config.security.blacklist.push(params.peer_id);
618
- return { content: `🚫 已屏蔽 ${params.peer_id.slice(0, 20)}...` };
619
- }
620
- case 'unblock': {
621
- if (!params.peer_id) {
622
- return { content: '❌ 请提供 peer_id' };
623
- }
624
- if (!this.config.security) {
625
- this.config.security = { requireConfirmation: false, whitelist: [], blacklist: [], maxTasksPerMinute: 10 };
626
- }
627
- this.config.security.blacklist = this.config.security.blacklist.filter(id => id !== params.peer_id);
628
- return { content: `✅ 已解除屏蔽 ${params.peer_id.slice(0, 20)}...` };
629
- }
630
- default:
631
- return { content: `❌ 未知操作: ${params.action}` };
632
- }
633
- }
634
- // ========== 新增:任务队列相关 Handlers ==========
635
- async handlePollTasks(params, context) {
636
- let tasks;
637
- if (params.status) {
638
- tasks = this.taskQueue.getAll().filter(t => t.status === params.status);
639
- }
640
- else {
641
- // 默认返回待处理任务
642
- tasks = this.taskQueue.getPending(params.limit || 10);
643
- }
644
- if (tasks.length === 0) {
645
- return { content: '📭 没有符合条件的任务' };
646
- }
647
- const content = `
648
- 📋 任务列表 (${tasks.length} 个):
649
-
650
- ${tasks.map(t => {
651
- const statusIcon = {
652
- pending: '⏳',
653
- processing: '🔄',
654
- completed: '✅',
655
- failed: '❌'
656
- }[t.status];
657
- return `${statusIcon} [${t.taskId.slice(0, 8)}...] ${t.description.slice(0, 50)}${t.description.length > 50 ? '...' : ''}
658
- 来自: ${t.from.slice(0, 16)}...
659
- 类型: ${t.taskType} | 状态: ${t.status} | 创建: ${new Date(t.createdAt).toLocaleTimeString()}`;
660
- }).join('\n\n')}
661
-
662
- 💡 使用方式:
663
- - 查看详情: 使用 task_id 查询
664
- - 提交结果: f2a_submit_result 工具
665
- `.trim();
666
- return {
667
- content,
668
- data: {
669
- count: tasks.length,
670
- tasks: tasks.map(t => ({
671
- taskId: t.taskId,
672
- from: t.from,
673
- description: t.description,
674
- taskType: t.taskType,
675
- parameters: t.parameters,
676
- status: t.status,
677
- createdAt: t.createdAt,
678
- timeout: t.timeout
679
- }))
680
- }
681
- };
682
- }
683
- async handleSubmitResult(params, context) {
684
- // 查找任务
685
- const task = this.taskQueue.get(params.task_id);
686
- if (!task) {
687
- return { content: `❌ 找不到任务: ${params.task_id}` };
688
- }
689
- // 更新任务状态
690
- const response = {
691
- taskId: params.task_id,
692
- status: params.status,
693
- result: params.status === 'success' ? params.result : undefined,
694
- error: params.status === 'error' ? params.result : undefined,
695
- latency: Date.now() - task.createdAt
696
- };
697
- this.taskQueue.complete(params.task_id, response);
698
- // 发送响应给原节点
699
- const sendResult = await this.networkClient.sendTaskResponse(task.from, response);
700
- if (!sendResult.success) {
701
- return {
702
- content: `⚠️ 结果已记录,但发送给原节点失败: ${sendResult.error}`,
703
- data: { taskId: params.task_id, sent: false }
704
- };
705
- }
706
- // 更新信誉
707
- if (params.status === 'success') {
708
- this.reputationSystem.recordSuccess(task.from, params.task_id, response.latency);
709
- }
710
- else {
711
- this.reputationSystem.recordFailure(task.from, params.task_id, params.result);
712
- }
713
- return {
714
- content: `✅ 任务结果已提交并发送给原节点\n 任务ID: ${params.task_id.slice(0, 16)}...\n 状态: ${params.status}\n 响应时间: ${response.latency}ms`,
715
- data: { taskId: params.task_id, sent: true, latency: response.latency }
716
- };
717
- }
718
- async handleTaskStats(params, context) {
719
- const stats = this.taskQueue.getStats();
720
- const content = `
721
- 📊 任务队列统计:
722
-
723
- ⏳ 待处理: ${stats.pending}
724
- 🔄 处理中: ${stats.processing}
725
- ✅ 已完成: ${stats.completed}
726
- ❌ 失败: ${stats.failed}
727
- 📦 总计: ${stats.total}
728
-
729
- 💡 使用 f2a_poll_tasks 查看详细任务列表
730
- `.trim();
731
- return { content, data: stats };
732
- }
733
- // ========== 认领模式 Handlers ==========
734
- async handleAnnounce(params, context) {
735
- try {
736
- const announcement = this.announcementQueue.create({
737
- taskType: params.task_type,
738
- description: params.description,
739
- requiredCapabilities: params.required_capabilities,
740
- estimatedComplexity: params.estimated_complexity,
741
- reward: params.reward,
742
- timeout: params.timeout || 300000,
743
- from: 'local', // 实际应该从网络获取本机ID
744
- });
745
- // 触发心跳让其他Agent知道有新广播
746
- this.api?.runtime?.system?.requestHeartbeatNow?.();
747
- const content = `
748
- 📢 任务广播已创建
749
-
750
- ID: ${announcement.announcementId}
751
- 类型: ${announcement.taskType}
752
- 描述: ${announcement.description.slice(0, 100)}${announcement.description.length > 100 ? '...' : ''}
753
- ${announcement.requiredCapabilities ? `所需能力: ${announcement.requiredCapabilities.join(', ')}` : ''}
754
- ${announcement.estimatedComplexity ? `复杂度: ${announcement.estimatedComplexity}/10` : ''}
755
- ${announcement.reward ? `奖励: ${announcement.reward}` : ''}
756
- 超时: ${Math.round(announcement.timeout / 1000)}秒
757
-
758
- 💡 使用 f2a_manage_claims 查看认领情况
759
- `.trim();
760
- return {
761
- content,
762
- data: {
763
- announcementId: announcement.announcementId,
764
- status: announcement.status
765
- }
766
- };
767
- }
768
- catch (error) {
769
- return {
770
- content: `❌ 创建广播失败: ${error.message}`,
771
- data: { error: error.message }
772
- };
773
- }
774
- }
775
- async handleListAnnouncements(params, context) {
776
- let announcements = this.announcementQueue.getOpen();
777
- // 按能力过滤
778
- if (params.capability) {
779
- announcements = announcements.filter(a => a.requiredCapabilities?.includes(params.capability));
780
- }
781
- // 限制数量
782
- const limit = params.limit || 10;
783
- announcements = announcements.slice(0, limit);
784
- if (announcements.length === 0) {
785
- return { content: '📭 当前没有开放的任务广播' };
786
- }
787
- const content = `
788
- 📢 开放的任务广播 (${announcements.length} 个):
789
-
790
- ${announcements.map((a, i) => {
791
- const claimCount = a.claims?.length || 0;
792
- return `${i + 1}. [${a.announcementId.slice(0, 8)}...] ${a.description.slice(0, 50)}${a.description.length > 50 ? '...' : ''}
793
- 类型: ${a.taskType} | 认领: ${claimCount} | 复杂度: ${a.estimatedComplexity || '?'}/10
794
- ${a.reward ? `奖励: ${a.reward} | ` : ''}超时: ${Math.round(a.timeout / 1000)}s`;
795
- }).join('\n\n')}
796
-
797
- 💡 使用 f2a_claim 认领任务
798
- `.trim();
799
- return {
800
- content,
801
- data: {
802
- count: announcements.length,
803
- announcements: announcements.map(a => ({
804
- announcementId: a.announcementId,
805
- taskType: a.taskType,
806
- description: a.description.slice(0, 100),
807
- requiredCapabilities: a.requiredCapabilities,
808
- estimatedComplexity: a.estimatedComplexity,
809
- reward: a.reward,
810
- claimCount: a.claims?.length || 0
811
- }))
812
- }
813
- };
814
- }
815
- async handleClaim(params, context) {
816
- const announcement = this.announcementQueue.get(params.announcement_id);
817
- if (!announcement) {
818
- return { content: `❌ 找不到广播: ${params.announcement_id}` };
819
- }
820
- if (announcement.status !== 'open') {
821
- return { content: `❌ 该广播已${announcement.status === 'claimed' ? '被认领' : '过期'}` };
822
- }
823
- // 检查是否已有认领
824
- const existingClaim = announcement.claims?.find(c => c.claimant === 'local');
825
- if (existingClaim) {
826
- return { content: `⚠️ 你已经认领过这个广播了 (认领ID: ${existingClaim.claimId.slice(0, 8)}...)` };
827
- }
828
- const claim = this.announcementQueue.submitClaim(params.announcement_id, {
829
- claimant: 'local', // 实际应该从网络获取本机ID
830
- claimantName: this.config.agentName,
831
- estimatedTime: params.estimated_time,
832
- confidence: params.confidence
833
- });
834
- if (!claim) {
835
- return { content: '❌ 认领失败' };
836
- }
837
- // 触发心跳
838
- this.api?.runtime?.system?.requestHeartbeatNow?.();
839
- return {
840
- content: `
841
- ✅ 认领已提交
842
-
843
- 广播ID: ${params.announcement_id.slice(0, 16)}...
844
- 认领ID: ${claim.claimId.slice(0, 16)}...
845
- ${params.estimated_time ? `预计时间: ${Math.round(params.estimated_time / 1000)}秒` : ''}
846
- ${params.confidence ? `信心指数: ${Math.round(params.confidence * 100)}%` : ''}
847
-
848
- ⏳ 等待广播发布者接受...
849
- 💡 使用 f2a_my_claims 查看认领状态
850
- `.trim(),
851
- data: {
852
- claimId: claim.claimId,
853
- status: claim.status
854
- }
855
- };
856
- }
857
- async handleManageClaims(params, context) {
858
- const announcement = this.announcementQueue.get(params.announcement_id);
859
- if (!announcement) {
860
- return { content: `❌ 找不到广播: ${params.announcement_id}` };
861
- }
862
- // 检查是否是本机的广播
863
- if (announcement.from !== 'local') {
864
- return { content: '❌ 只能管理自己发布的广播' };
865
- }
866
- switch (params.action) {
867
- case 'list': {
868
- const claims = announcement.claims || [];
869
- if (claims.length === 0) {
870
- return { content: '📭 暂无认领' };
871
- }
872
- const content = `
873
- 📋 认领列表 (${claims.length} 个):
874
-
875
- ${claims.map((c, i) => {
876
- const statusIcon = { pending: '⏳', accepted: '✅', rejected: '❌' }[c.status];
877
- return `${i + 1}. ${statusIcon} [${c.claimId.slice(0, 8)}...] ${c.claimantName || c.claimant.slice(0, 16)}...
878
- ${c.estimatedTime ? `预计: ${Math.round(c.estimatedTime / 1000)}s | ` : ''}${c.confidence ? `信心: ${Math.round(c.confidence * 100)}%` : ''}`;
879
- }).join('\n\n')}
880
-
881
- 💡 使用 accept/reject 操作认领
882
- `.trim();
883
- return { content, data: { claims } };
884
- }
885
- case 'accept': {
886
- if (!params.claim_id) {
887
- return { content: '❌ 请提供 claim_id' };
888
- }
889
- const claim = this.announcementQueue.acceptClaim(params.announcement_id, params.claim_id);
890
- if (!claim) {
891
- return { content: '❌ 接受认领失败' };
892
- }
893
- return {
894
- content: `
895
- ✅ 已接受认领
896
-
897
- 认领ID: ${params.claim_id.slice(0, 16)}...
898
- 认领者: ${claim.claimantName || claim.claimant.slice(0, 16)}...
899
-
900
- 现在可以正式委托任务给对方了。
901
- `.trim(),
902
- data: { claim }
903
- };
904
- }
905
- case 'reject': {
906
- if (!params.claim_id) {
907
- return { content: '❌ 请提供 claim_id' };
908
- }
909
- const claim = this.announcementQueue.rejectClaim(params.announcement_id, params.claim_id);
910
- if (!claim) {
911
- return { content: '❌ 拒绝认领失败' };
912
- }
913
- return {
914
- content: `
915
- 🚫 已拒绝认领
916
-
917
- 认领ID: ${params.claim_id.slice(0, 16)}...
918
- 认领者: ${claim.claimantName || claim.claimant.slice(0, 16)}...
919
- `.trim()
920
- };
921
- }
922
- default:
923
- return { content: `❌ 未知操作: ${params.action}` };
924
- }
925
- }
926
- async handleMyClaims(params, context) {
927
- const status = params.status || 'all';
928
- let claims = this.announcementQueue.getMyClaims('local');
929
- // 状态过滤
930
- if (status !== 'all') {
931
- claims = claims.filter(c => c.status === status);
932
- }
933
- if (claims.length === 0) {
934
- return { content: `📭 没有${status === 'all' ? '' : status}的认领` };
935
- }
936
- const content = `
937
- 📋 我的认领 (${claims.length} 个):
938
-
939
- ${claims.map((c, i) => {
940
- const announcement = this.announcementQueue.get(c.announcementId);
941
- const statusIcon = { pending: '⏳', accepted: '✅', rejected: '❌' }[c.status];
942
- return `${i + 1}. ${statusIcon} [${c.claimId.slice(0, 8)}...]
943
- 广播: ${announcement?.description.slice(0, 40)}...
944
- 状态: ${c.status}${c.status === 'accepted' ? ' (可以开始执行)' : ''}`;
945
- }).join('\n\n')}
946
- `.trim();
947
- return {
948
- content,
949
- data: {
950
- count: claims.length,
951
- claims: claims.map(c => ({
952
- claimId: c.claimId,
953
- announcementId: c.announcementId,
954
- status: c.status,
955
- estimatedTime: c.estimatedTime,
956
- confidence: c.confidence
957
- }))
958
- }
959
- };
960
- }
961
- async handleAnnouncementStats(params, context) {
962
- const stats = this.announcementQueue.getStats();
963
- const content = `
964
- 📊 任务广播统计:
965
-
966
- 📢 开放中: ${stats.open}
967
- ✅ 已认领: ${stats.claimed}
968
- 📋 已委托: ${stats.delegated}
969
- ⏰ 已过期: ${stats.expired}
970
- 📦 总计: ${stats.total}
971
-
972
- 💡 使用 f2a_list_announcements 查看开放广播
973
- `.trim();
974
- return { content, data: stats };
975
- }
976
585
  // ========== Helpers ==========
977
- async resolveAgent(agentRef) {
978
- const result = await this.networkClient.discoverAgents();
979
- if (!result.success)
980
- return null;
981
- const agents = result.data || [];
982
- // #索引格式
983
- if (agentRef.startsWith('#')) {
984
- const index = parseInt(agentRef.slice(1)) - 1;
985
- return agents[index] || null;
986
- }
987
- // 精确匹配
988
- const exact = agents.find(a => a.peerId === agentRef ||
989
- a.displayName === agentRef);
990
- if (exact)
991
- return exact;
992
- // 模糊匹配
993
- const fuzzy = agents.find(a => a.peerId.startsWith(agentRef) ||
994
- a.displayName.toLowerCase().includes(agentRef.toLowerCase()));
995
- return fuzzy || null;
996
- }
997
- formatBroadcastResults(results) {
998
- return results.map(r => {
999
- const icon = r.success ? '✅' : '❌';
1000
- const latency = r.latency ? ` (${r.latency}ms)` : '';
1001
- return `${icon} ${r.agent}${latency}\n ${r.success ? '完成' : `失败: ${r.error}`}`;
1002
- }).join('\n\n');
1003
- }
586
+ /**
587
+ * 合并配置(公开方法供处理器使用)
588
+ */
1004
589
  mergeConfig(config) {
1005
590
  return {
1006
591
  autoStart: config.autoStart ?? true,
@@ -1015,6 +600,9 @@ ${claims.map((c, i) => {
1015
600
  bootstrapPeers: config.bootstrapPeers,
1016
601
  dataDir: config.dataDir || './f2a-data',
1017
602
  maxQueuedTasks: config.maxQueuedTasks || 100,
603
+ pollInterval: config.pollInterval,
604
+ // 保留 webhookPush 配置(修复:之前丢失导致 webhook 推送被禁用)
605
+ webhookPush: config.webhookPush,
1018
606
  reputation: {
1019
607
  enabled: config.reputation?.enabled ?? true,
1020
608
  initialScore: config.reputation?.initialScore || 50,
@@ -1037,24 +625,72 @@ ${claims.map((c, i) => {
1037
625
  }
1038
626
  return token;
1039
627
  }
628
+ /**
629
+ * 格式化广播结果(公共方法,供测试和外部调用)
630
+ */
631
+ formatBroadcastResults(results) {
632
+ return results.map(r => {
633
+ const icon = r.success ? '✅' : '❌';
634
+ const latency = r.latency ? ` (${r.latency}ms)` : '';
635
+ return `${icon} ${r.agent}${latency}\n ${r.success ? '完成' : `失败: ${r.error}`}`;
636
+ }).join('\n\n');
637
+ }
638
+ /**
639
+ * 解析 Agent 引用(公共方法,供测试和外部调用)
640
+ */
641
+ async resolveAgent(agentRef) {
642
+ const result = await this.networkClient?.discoverAgents();
643
+ if (!result?.success)
644
+ return null;
645
+ const agents = result.data || [];
646
+ // #索引格式
647
+ if (agentRef.startsWith('#')) {
648
+ const index = parseInt(agentRef.slice(1)) - 1;
649
+ return agents[index] || null;
650
+ }
651
+ // 精确匹配
652
+ const exact = agents.find((a) => a.peerId === agentRef ||
653
+ a.displayName === agentRef);
654
+ if (exact)
655
+ return exact;
656
+ // 模糊匹配
657
+ const fuzzy = agents.find((a) => a.peerId.startsWith(agentRef) ||
658
+ a.displayName.toLowerCase().includes(agentRef.toLowerCase()));
659
+ return fuzzy || null;
660
+ }
1040
661
  /**
1041
662
  * 关闭插件,清理资源
1042
663
  */
1043
664
  async shutdown() {
1044
- console.log('[F2A Plugin] 正在关闭...');
665
+ logger_js_1.pluginLogger.info('正在关闭...');
666
+ // 停止轮询定时器
667
+ if (this.pollTimer) {
668
+ clearInterval(this.pollTimer);
669
+ this.pollTimer = undefined;
670
+ }
1045
671
  // 停止 Webhook 服务器
1046
672
  if (this.webhookServer) {
1047
673
  await this.webhookServer.stop?.();
1048
674
  }
675
+ // P1 修复:关闭前刷新信誉系统数据,确保持久化
676
+ if (this.reputationSystem) {
677
+ this.reputationSystem.flush();
678
+ logger_js_1.pluginLogger.info('信誉系统数据已保存');
679
+ }
680
+ // P1 修复:关闭 TaskGuard,停止持久化定时器并保存最终状态
681
+ task_guard_js_1.taskGuard.shutdown();
682
+ logger_js_1.pluginLogger.info('TaskGuard 已关闭');
1049
683
  // 停止 F2A Node
1050
684
  if (this.nodeManager) {
1051
685
  await this.nodeManager.stop();
1052
686
  }
1053
- // 清理任务队列
687
+ // 关闭任务队列连接(保留持久化数据,不删除任务)
688
+ // 这样重启后可以恢复未完成的任务
1054
689
  if (this.taskQueue) {
1055
- this.taskQueue.clear();
690
+ this.taskQueue.close();
691
+ logger_js_1.pluginLogger.info('任务队列已关闭,持久化数据已保留');
1056
692
  }
1057
- console.log('[F2A Plugin] 已关闭');
693
+ logger_js_1.pluginLogger.info('已关闭');
1058
694
  }
1059
695
  }
1060
696
  exports.F2AOpenClawAdapter = F2AOpenClawAdapter;