@f2a/openclaw-adapter 0.1.0 → 0.2.0

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 +46 -18
  10. package/dist/connector.d.ts.map +1 -1
  11. package/dist/connector.js +319 -592
  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 +85 -1
  34. package/dist/reputation.d.ts.map +1 -1
  35. package/dist/reputation.js +222 -9
  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 +449 -16
  40. package/dist/task-guard.js.map +1 -1
  41. package/dist/task-queue.d.ts +55 -7
  42. package/dist/task-queue.d.ts.map +1 -1
  43. package/dist/task-queue.js +477 -12
  44. package/dist/task-queue.js.map +1 -1
  45. package/dist/tool-handlers.d.ts +158 -0
  46. package/dist/tool-handlers.d.ts.map +1 -0
  47. package/dist/tool-handlers.js +724 -0
  48. package/dist/tool-handlers.js.map +1 -0
  49. package/dist/types.d.ts +112 -15
  50. package/dist/types.d.ts.map +1 -1
  51. package/dist/types.js +23 -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 +65 -7
  60. package/dist/webhook-server.js.map +1 -1
  61. package/package.json +21 -8
package/dist/connector.js CHANGED
@@ -9,12 +9,19 @@ const node_manager_js_1 = require("./node-manager.js");
9
9
  const network_client_js_1 = require("./network-client.js");
10
10
  const webhook_server_js_1 = require("./webhook-server.js");
11
11
  const reputation_js_1 = require("./reputation.js");
12
+ const types_js_1 = require("./types.js");
12
13
  const capability_detector_js_1 = require("./capability-detector.js");
13
14
  const task_queue_js_1 = require("./task-queue.js");
14
15
  const announcement_queue_js_1 = require("./announcement-queue.js");
16
+ const webhook_pusher_js_1 = require("./webhook-pusher.js");
17
+ const task_guard_js_1 = require("./task-guard.js");
18
+ const tool_handlers_js_1 = require("./tool-handlers.js");
19
+ const claim_handlers_js_1 = require("./claim-handlers.js");
20
+ const logger_js_1 = require("./logger.js");
21
+ const network_1 = require("@f2a/network");
15
22
  class F2AOpenClawAdapter {
16
23
  name = 'f2a-openclaw-adapter';
17
- version = '0.2.0';
24
+ version = '0.3.0';
18
25
  nodeManager;
19
26
  networkClient;
20
27
  webhookServer;
@@ -22,15 +29,39 @@ class F2AOpenClawAdapter {
22
29
  capabilityDetector;
23
30
  taskQueue;
24
31
  announcementQueue;
32
+ webhookPusher;
33
+ reviewCommittee;
34
+ // 处理器实例(延迟初始化)
35
+ _toolHandlers;
36
+ _claimHandlers;
25
37
  config;
26
38
  nodeConfig;
27
39
  capabilities = [];
28
40
  api;
41
+ pollTimer;
42
+ /**
43
+ * 获取工具处理器(延迟初始化,支持未初始化时调用getTools)
44
+ */
45
+ get toolHandlers() {
46
+ if (!this._toolHandlers) {
47
+ this._toolHandlers = new tool_handlers_js_1.ToolHandlers(this);
48
+ }
49
+ return this._toolHandlers;
50
+ }
51
+ /**
52
+ * 获取认领处理器(延迟初始化)
53
+ */
54
+ get claimHandlers() {
55
+ if (!this._claimHandlers) {
56
+ this._claimHandlers = new claim_handlers_js_1.ClaimHandlers(this);
57
+ }
58
+ return this._claimHandlers;
59
+ }
29
60
  /**
30
61
  * 初始化插件
31
62
  */
32
63
  async initialize(config) {
33
- console.log('[F2A Plugin] 初始化...');
64
+ logger_js_1.pluginLogger.info('初始化...');
34
65
  // 保存 API 引用(用于触发心跳等)
35
66
  this.api = config._api;
36
67
  // 合并配置
@@ -43,11 +74,19 @@ class F2AOpenClawAdapter {
43
74
  enableMDNS: this.config.enableMDNS ?? true,
44
75
  bootstrapPeers: this.config.bootstrapPeers || []
45
76
  };
46
- // 初始化任务队列
77
+ // 初始化任务队列(带持久化)
78
+ const dataDir = this.config.dataDir || './f2a-data';
47
79
  this.taskQueue = new task_queue_js_1.TaskQueue({
48
80
  maxSize: this.config.maxQueuedTasks || 100,
49
- maxAgeMs: 24 * 60 * 60 * 1000 // 24小时
81
+ maxAgeMs: 24 * 60 * 60 * 1000, // 24小时
82
+ persistDir: dataDir,
83
+ persistEnabled: true
50
84
  });
85
+ // 初始化 Webhook 推送器
86
+ if (this.config.webhookPush?.enabled !== false && this.config.webhookPush?.url) {
87
+ this.webhookPusher = new webhook_pusher_js_1.WebhookPusher(this.config.webhookPush);
88
+ logger_js_1.pluginLogger.info('Webhook 推送已启用');
89
+ }
51
90
  // 初始化广播队列
52
91
  this.announcementQueue = new announcement_queue_js_1.AnnouncementQueue({
53
92
  maxSize: 50,
@@ -56,13 +95,23 @@ class F2AOpenClawAdapter {
56
95
  // 初始化组件
57
96
  this.nodeManager = new node_manager_js_1.F2ANodeManager(this.nodeConfig);
58
97
  this.networkClient = new network_client_js_1.F2ANetworkClient(this.nodeConfig);
59
- this.reputationSystem = new reputation_js_1.ReputationSystem(this.config.reputation || {
60
- enabled: true,
61
- initialScore: 50,
62
- minScoreForService: 20,
63
- decayRate: 0.01
98
+ // 使用程序内部控制的经济参数,防止用户作弊
99
+ this.reputationSystem = new reputation_js_1.ReputationSystem({
100
+ enabled: types_js_1.INTERNAL_REPUTATION_CONFIG.enabled,
101
+ initialScore: types_js_1.INTERNAL_REPUTATION_CONFIG.initialScore,
102
+ minScoreForService: types_js_1.INTERNAL_REPUTATION_CONFIG.minScoreForService,
103
+ decayRate: types_js_1.INTERNAL_REPUTATION_CONFIG.decayRate,
64
104
  }, this.config.dataDir || './f2a-data');
65
105
  this.capabilityDetector = new capability_detector_js_1.CapabilityDetector();
106
+ // 初始化评审委员会(使用适配器包装 ReputationSystem)
107
+ const reputationAdapter = new reputation_js_1.ReputationManagerAdapter(this.reputationSystem);
108
+ this.reviewCommittee = new network_1.ReviewCommittee(reputationAdapter, {
109
+ minReviewers: 1,
110
+ maxReviewers: 5,
111
+ minReputation: types_js_1.INTERNAL_REPUTATION_CONFIG.minScoreForReview,
112
+ reviewTimeout: 5 * 60 * 1000 // 5 分钟
113
+ });
114
+ // 处理器使用 getter 延迟初始化,无需在此显式创建
66
115
  // 启动 F2A Node
67
116
  if (this.config.autoStart) {
68
117
  const result = await this.nodeManager.ensureRunning();
@@ -76,14 +125,73 @@ class F2AOpenClawAdapter {
76
125
  this.capabilities = this.capabilityDetector.mergeCustomCapabilities(this.capabilities, this.config.capabilities);
77
126
  }
78
127
  // 启动 Webhook 服务器
79
- this.webhookServer = new webhook_server_js_1.WebhookServer(this.config.webhookPort, this.createWebhookHandler());
128
+ this.webhookServer = new webhook_server_js_1.WebhookServer(this.config.webhookPort || 0, this.createWebhookHandler());
80
129
  await this.webhookServer.start();
81
130
  // 注册到 F2A Node
82
131
  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()}`);
132
+ logger_js_1.pluginLogger.info('初始化完成');
133
+ logger_js_1.pluginLogger.info(`Agent 名称: ${this.config.agentName}`);
134
+ logger_js_1.pluginLogger.info(`能力数: ${this.capabilities.length}`);
135
+ logger_js_1.pluginLogger.info(`Webhook: ${this.webhookServer.getUrl()}`);
136
+ // 启动兜底轮询(降低到 60 秒)
137
+ this.startFallbackPolling();
138
+ }
139
+ /**
140
+ * 兜底轮询
141
+ * 当 webhook 推送失败时,轮询确保任务不会丢失
142
+ */
143
+ startFallbackPolling() {
144
+ const interval = this.config.pollInterval || 60000; // 默认 60 秒
145
+ this.pollTimer = setInterval(async () => {
146
+ // P1 修复:定期检查并重置超时的 processing 任务,防止僵尸任务
147
+ this.resetTimedOutProcessingTasks();
148
+ if (!this.webhookPusher) {
149
+ // 没有配置 webhook,不轮询(保持原有轮询模式)
150
+ return;
151
+ }
152
+ try {
153
+ // 获取未推送的任务
154
+ const pending = this.taskQueue.getWebhookPending();
155
+ if (pending.length > 0) {
156
+ logger_js_1.pluginLogger.info(`兜底轮询: ${pending.length} 个待推送任务`);
157
+ for (const task of pending) {
158
+ const result = await this.webhookPusher.pushTask(task);
159
+ if (result.success) {
160
+ this.taskQueue.markWebhookPushed(task.taskId);
161
+ }
162
+ }
163
+ }
164
+ }
165
+ catch (error) {
166
+ logger_js_1.pluginLogger.error('兜底轮询失败:', error);
167
+ }
168
+ }, interval);
169
+ }
170
+ /**
171
+ * P1 修复:重置超时的 processing 任务
172
+ * 如果任务在 processing 状态停留超过超时时间,将其重置为 pending
173
+ * 防止因处理失败导致的僵尸任务
174
+ */
175
+ resetTimedOutProcessingTasks() {
176
+ const stats = this.taskQueue.getStats();
177
+ if (stats.processing === 0) {
178
+ return; // 没有处理中的任务,无需检查
179
+ }
180
+ const allTasks = this.taskQueue.getAll();
181
+ const now = Date.now();
182
+ const processingTimeout = this.config.processingTimeoutMs || 5 * 60 * 1000; // 默认 5 分钟
183
+ for (const task of allTasks) {
184
+ if (task.status === 'processing') {
185
+ const taskTimeout = task.timeout || 30000; // 使用任务自身的超时或默认 30 秒
186
+ const maxAllowedTime = Math.max(taskTimeout * 2, processingTimeout); // 至少 2 倍任务超时或 processingTimeout
187
+ const processingTime = now - (task.updatedAt || task.createdAt);
188
+ if (processingTime > maxAllowedTime) {
189
+ logger_js_1.pluginLogger.warn(`检测到僵尸任务 ${task.taskId.slice(0, 8)}... (processing ${Math.round(processingTime / 1000)}s),重置为 pending`);
190
+ // 将任务重置为 pending 状态
191
+ this.taskQueue.resetProcessingTask(task.taskId);
192
+ }
193
+ }
194
+ }
87
195
  }
88
196
  /**
89
197
  * 获取插件提供的 Tools
@@ -105,7 +213,7 @@ class F2AOpenClawAdapter {
105
213
  required: false
106
214
  }
107
215
  },
108
- handler: this.handleDiscover.bind(this)
216
+ handler: this.toolHandlers.handleDiscover.bind(this.toolHandlers)
109
217
  },
110
218
  {
111
219
  name: 'f2a_delegate',
@@ -132,7 +240,7 @@ class F2AOpenClawAdapter {
132
240
  required: false
133
241
  }
134
242
  },
135
- handler: this.handleDelegate.bind(this)
243
+ handler: this.toolHandlers.handleDelegate.bind(this.toolHandlers)
136
244
  },
137
245
  {
138
246
  name: 'f2a_broadcast',
@@ -154,13 +262,13 @@ class F2AOpenClawAdapter {
154
262
  required: false
155
263
  }
156
264
  },
157
- handler: this.handleBroadcast.bind(this)
265
+ handler: this.toolHandlers.handleBroadcast.bind(this.toolHandlers)
158
266
  },
159
267
  {
160
268
  name: 'f2a_status',
161
269
  description: '查看 F2A 网络状态和已连接 Peers',
162
270
  parameters: {},
163
- handler: this.handleStatus.bind(this)
271
+ handler: this.toolHandlers.handleStatus.bind(this.toolHandlers)
164
272
  },
165
273
  {
166
274
  name: 'f2a_reputation',
@@ -178,7 +286,7 @@ class F2AOpenClawAdapter {
178
286
  required: false
179
287
  }
180
288
  },
181
- handler: this.handleReputation.bind(this)
289
+ handler: this.toolHandlers.handleReputation.bind(this.toolHandlers)
182
290
  },
183
291
  // 新增:任务队列相关工具
184
292
  {
@@ -197,7 +305,7 @@ class F2AOpenClawAdapter {
197
305
  enum: ['pending', 'processing', 'completed', 'failed']
198
306
  }
199
307
  },
200
- handler: this.handlePollTasks.bind(this)
308
+ handler: this.toolHandlers.handlePollTasks.bind(this.toolHandlers)
201
309
  },
202
310
  {
203
311
  name: 'f2a_submit_result',
@@ -220,13 +328,13 @@ class F2AOpenClawAdapter {
220
328
  enum: ['success', 'error']
221
329
  }
222
330
  },
223
- handler: this.handleSubmitResult.bind(this)
331
+ handler: this.toolHandlers.handleSubmitResult.bind(this.toolHandlers)
224
332
  },
225
333
  {
226
334
  name: 'f2a_task_stats',
227
335
  description: '查看任务队列统计信息',
228
336
  parameters: {},
229
- handler: this.handleTaskStats.bind(this)
337
+ handler: this.toolHandlers.handleTaskStats.bind(this.toolHandlers)
230
338
  },
231
339
  // 认领模式工具
232
340
  {
@@ -264,7 +372,7 @@ class F2AOpenClawAdapter {
264
372
  required: false
265
373
  }
266
374
  },
267
- handler: this.handleAnnounce.bind(this)
375
+ handler: this.claimHandlers.handleAnnounce.bind(this.claimHandlers)
268
376
  },
269
377
  {
270
378
  name: 'f2a_list_announcements',
@@ -281,7 +389,7 @@ class F2AOpenClawAdapter {
281
389
  required: false
282
390
  }
283
391
  },
284
- handler: this.handleListAnnouncements.bind(this)
392
+ handler: this.claimHandlers.handleListAnnouncements.bind(this.claimHandlers)
285
393
  },
286
394
  {
287
395
  name: 'f2a_claim',
@@ -303,7 +411,7 @@ class F2AOpenClawAdapter {
303
411
  required: false
304
412
  }
305
413
  },
306
- handler: this.handleClaim.bind(this)
414
+ handler: this.claimHandlers.handleClaim.bind(this.claimHandlers)
307
415
  },
308
416
  {
309
417
  name: 'f2a_manage_claims',
@@ -326,7 +434,7 @@ class F2AOpenClawAdapter {
326
434
  required: false
327
435
  }
328
436
  },
329
- handler: this.handleManageClaims.bind(this)
437
+ handler: this.claimHandlers.handleManageClaims.bind(this.claimHandlers)
330
438
  },
331
439
  {
332
440
  name: 'f2a_my_claims',
@@ -339,13 +447,92 @@ class F2AOpenClawAdapter {
339
447
  enum: ['pending', 'accepted', 'rejected', 'all']
340
448
  }
341
449
  },
342
- handler: this.handleMyClaims.bind(this)
450
+ handler: this.claimHandlers.handleMyClaims.bind(this.claimHandlers)
343
451
  },
344
452
  {
345
453
  name: 'f2a_announcement_stats',
346
454
  description: '查看任务广播统计',
347
455
  parameters: {},
348
- handler: this.handleAnnouncementStats.bind(this)
456
+ handler: this.claimHandlers.handleAnnouncementStats.bind(this.claimHandlers)
457
+ },
458
+ // 任务评估相关工具
459
+ {
460
+ name: 'f2a_estimate_task',
461
+ description: '评估任务成本(工作量、复杂度、预估时间)',
462
+ parameters: {
463
+ task_type: {
464
+ type: 'string',
465
+ description: '任务类型',
466
+ required: true
467
+ },
468
+ description: {
469
+ type: 'string',
470
+ description: '任务描述',
471
+ required: true
472
+ },
473
+ required_capabilities: {
474
+ type: 'array',
475
+ description: '所需能力列表',
476
+ required: false
477
+ }
478
+ },
479
+ handler: this.toolHandlers.handleEstimateTask.bind(this.toolHandlers)
480
+ },
481
+ {
482
+ name: 'f2a_review_task',
483
+ description: '作为评审者评审任务的工作量和价值',
484
+ parameters: {
485
+ task_id: {
486
+ type: 'string',
487
+ description: '任务ID',
488
+ required: true
489
+ },
490
+ workload: {
491
+ type: 'number',
492
+ description: '工作量评估 (0-100)',
493
+ required: true
494
+ },
495
+ value: {
496
+ type: 'number',
497
+ description: '价值评估 (-100 ~ 100)',
498
+ required: true
499
+ },
500
+ risk_flags: {
501
+ type: 'array',
502
+ description: '风险标记: dangerous, malicious, spam, invalid',
503
+ required: false
504
+ },
505
+ comment: {
506
+ type: 'string',
507
+ description: '评审意见',
508
+ required: false
509
+ }
510
+ },
511
+ handler: this.toolHandlers.handleReviewTask.bind(this.toolHandlers)
512
+ },
513
+ {
514
+ name: 'f2a_get_reviews',
515
+ description: '获取任务的评审汇总结果',
516
+ parameters: {
517
+ task_id: {
518
+ type: 'string',
519
+ description: '任务ID',
520
+ required: true
521
+ }
522
+ },
523
+ handler: this.toolHandlers.handleGetReviews.bind(this.toolHandlers)
524
+ },
525
+ {
526
+ name: 'f2a_get_capabilities',
527
+ description: '获取指定 Agent 的能力列表',
528
+ parameters: {
529
+ peer_id: {
530
+ type: 'string',
531
+ description: 'Agent 的 Peer ID 或名称',
532
+ required: false
533
+ }
534
+ },
535
+ handler: this.toolHandlers.handleGetCapabilities.bind(this.toolHandlers)
349
536
  }
350
537
  ];
351
538
  }
@@ -385,20 +572,48 @@ class F2AOpenClawAdapter {
385
572
  // 检查白名单/黑名单
386
573
  const whitelist = this.config.security?.whitelist || [];
387
574
  const blacklist = this.config.security?.blacklist || [];
388
- if (whitelist.length > 0 && !whitelist.includes(payload.from)) {
575
+ const isWhitelisted = whitelist.length > 0 && whitelist.includes(payload.from);
576
+ const isBlacklisted = blacklist.includes(payload.from);
577
+ if (whitelist.length > 0 && !isWhitelisted) {
389
578
  return {
390
579
  accepted: false,
391
580
  taskId: payload.taskId,
392
581
  reason: 'Not in whitelist'
393
582
  };
394
583
  }
395
- if (blacklist.includes(payload.from)) {
584
+ if (isBlacklisted) {
396
585
  return {
397
586
  accepted: false,
398
587
  taskId: payload.taskId,
399
588
  reason: 'In blacklist'
400
589
  };
401
590
  }
591
+ // TaskGuard 安全检查
592
+ const requesterReputation = this.reputationSystem.getReputation(payload.from);
593
+ const taskGuardContext = {
594
+ requesterReputation,
595
+ isWhitelisted,
596
+ isBlacklisted,
597
+ recentTaskCount: 0 // Will be tracked internally by TaskGuard
598
+ };
599
+ const taskGuardReport = task_guard_js_1.taskGuard.check(payload, taskGuardContext);
600
+ if (!taskGuardReport.passed) {
601
+ // 任务被阻止
602
+ const blockReasons = taskGuardReport.blocks.map(b => b.message).join('; ');
603
+ logger_js_1.pluginLogger.warn(`TaskGuard 阻止任务 ${payload.taskId}: ${blockReasons}`);
604
+ return {
605
+ accepted: false,
606
+ taskId: payload.taskId,
607
+ reason: `TaskGuard blocked: ${blockReasons}`
608
+ };
609
+ }
610
+ if (taskGuardReport.requiresConfirmation) {
611
+ // 任务需要确认(警告但不阻止)
612
+ const warnReasons = taskGuardReport.warnings.map(w => w.message).join('; ');
613
+ logger_js_1.pluginLogger.warn(`TaskGuard 警告 ${payload.taskId}: ${warnReasons}`);
614
+ // 未来可以扩展为请求用户确认
615
+ // 目前记录警告但继续处理任务
616
+ }
402
617
  // 检查队列是否已满
403
618
  const stats = this.taskQueue.getStats();
404
619
  if (stats.pending >= (this.config.maxQueuedTasks || 100)) {
@@ -410,7 +625,18 @@ class F2AOpenClawAdapter {
410
625
  }
411
626
  // 添加任务到队列
412
627
  try {
413
- this.taskQueue.add(payload);
628
+ const task = this.taskQueue.add(payload);
629
+ // 优先使用 webhook 推送
630
+ if (this.webhookPusher) {
631
+ const result = await this.webhookPusher.pushTask(task);
632
+ if (result.success) {
633
+ this.taskQueue.markWebhookPushed(task.taskId);
634
+ logger_js_1.pluginLogger.info(`任务 ${task.taskId} 已通过 webhook 推送 (${result.latency}ms)`);
635
+ }
636
+ else {
637
+ logger_js_1.pluginLogger.info(`Webhook 推送失败: ${result.error},任务将在轮询时处理`);
638
+ }
639
+ }
414
640
  // 触发 OpenClaw 心跳,让它知道有新任务
415
641
  this.api?.runtime?.system?.requestHeartbeatNow?.();
416
642
  return {
@@ -447,560 +673,10 @@ class F2AOpenClawAdapter {
447
673
  capabilities: this.capabilities
448
674
  });
449
675
  }
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
676
  // ========== 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
- }
677
+ /**
678
+ * 合并配置(公开方法供处理器使用)
679
+ */
1004
680
  mergeConfig(config) {
1005
681
  return {
1006
682
  autoStart: config.autoStart ?? true,
@@ -1015,11 +691,14 @@ ${claims.map((c, i) => {
1015
691
  bootstrapPeers: config.bootstrapPeers,
1016
692
  dataDir: config.dataDir || './f2a-data',
1017
693
  maxQueuedTasks: config.maxQueuedTasks || 100,
694
+ pollInterval: config.pollInterval,
695
+ // 保留 webhookPush 配置(修复:之前丢失导致 webhook 推送被禁用)
696
+ webhookPush: config.webhookPush,
1018
697
  reputation: {
1019
- enabled: config.reputation?.enabled ?? true,
1020
- initialScore: config.reputation?.initialScore || 50,
1021
- minScoreForService: config.reputation?.minScoreForService || 20,
1022
- decayRate: config.reputation?.decayRate || 0.01
698
+ enabled: config.reputation?.enabled ?? types_js_1.INTERNAL_REPUTATION_CONFIG.enabled,
699
+ initialScore: config.reputation?.initialScore || types_js_1.INTERNAL_REPUTATION_CONFIG.initialScore,
700
+ minScoreForService: config.reputation?.minScoreForService || types_js_1.INTERNAL_REPUTATION_CONFIG.minScoreForService,
701
+ decayRate: config.reputation?.decayRate || types_js_1.INTERNAL_REPUTATION_CONFIG.decayRate
1023
702
  },
1024
703
  security: {
1025
704
  requireConfirmation: config.security?.requireConfirmation ?? false,
@@ -1037,24 +716,72 @@ ${claims.map((c, i) => {
1037
716
  }
1038
717
  return token;
1039
718
  }
719
+ /**
720
+ * 格式化广播结果(公共方法,供测试和外部调用)
721
+ */
722
+ formatBroadcastResults(results) {
723
+ return results.map(r => {
724
+ const icon = r.success ? '✅' : '❌';
725
+ const latency = r.latency ? ` (${r.latency}ms)` : '';
726
+ return `${icon} ${r.agent}${latency}\n ${r.success ? '完成' : `失败: ${r.error}`}`;
727
+ }).join('\n\n');
728
+ }
729
+ /**
730
+ * 解析 Agent 引用(公共方法,供测试和外部调用)
731
+ */
732
+ async resolveAgent(agentRef) {
733
+ const result = await this.networkClient?.discoverAgents();
734
+ if (!result?.success)
735
+ return null;
736
+ const agents = result.data || [];
737
+ // #索引格式
738
+ if (agentRef.startsWith('#')) {
739
+ const index = parseInt(agentRef.slice(1)) - 1;
740
+ return agents[index] || null;
741
+ }
742
+ // 精确匹配
743
+ const exact = agents.find((a) => a.peerId === agentRef ||
744
+ a.displayName === agentRef);
745
+ if (exact)
746
+ return exact;
747
+ // 模糊匹配
748
+ const fuzzy = agents.find((a) => a.peerId.startsWith(agentRef) ||
749
+ a.displayName.toLowerCase().includes(agentRef.toLowerCase()));
750
+ return fuzzy || null;
751
+ }
1040
752
  /**
1041
753
  * 关闭插件,清理资源
1042
754
  */
1043
755
  async shutdown() {
1044
- console.log('[F2A Plugin] 正在关闭...');
756
+ logger_js_1.pluginLogger.info('正在关闭...');
757
+ // 停止轮询定时器
758
+ if (this.pollTimer) {
759
+ clearInterval(this.pollTimer);
760
+ this.pollTimer = undefined;
761
+ }
1045
762
  // 停止 Webhook 服务器
1046
763
  if (this.webhookServer) {
1047
764
  await this.webhookServer.stop?.();
1048
765
  }
766
+ // P1 修复:关闭前刷新信誉系统数据,确保持久化
767
+ if (this.reputationSystem) {
768
+ this.reputationSystem.flush();
769
+ logger_js_1.pluginLogger.info('信誉系统数据已保存');
770
+ }
771
+ // P1 修复:关闭 TaskGuard,停止持久化定时器并保存最终状态
772
+ task_guard_js_1.taskGuard.shutdown();
773
+ logger_js_1.pluginLogger.info('TaskGuard 已关闭');
1049
774
  // 停止 F2A Node
1050
775
  if (this.nodeManager) {
1051
776
  await this.nodeManager.stop();
1052
777
  }
1053
- // 清理任务队列
778
+ // 关闭任务队列连接(保留持久化数据,不删除任务)
779
+ // 这样重启后可以恢复未完成的任务
1054
780
  if (this.taskQueue) {
1055
- this.taskQueue.clear();
781
+ this.taskQueue.close();
782
+ logger_js_1.pluginLogger.info('任务队列已关闭,持久化数据已保留');
1056
783
  }
1057
- console.log('[F2A Plugin] 已关闭');
784
+ logger_js_1.pluginLogger.info('已关闭');
1058
785
  }
1059
786
  }
1060
787
  exports.F2AOpenClawAdapter = F2AOpenClawAdapter;