@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.
- package/dist/announcement-queue.d.ts +72 -1
- package/dist/announcement-queue.d.ts.map +1 -1
- package/dist/announcement-queue.js +145 -20
- package/dist/announcement-queue.js.map +1 -1
- package/dist/claim-handlers.d.ts +75 -0
- package/dist/claim-handlers.d.ts.map +1 -0
- package/dist/claim-handlers.js +368 -0
- package/dist/claim-handlers.js.map +1 -0
- package/dist/connector.d.ts +45 -18
- package/dist/connector.d.ts.map +1 -1
- package/dist/connector.js +219 -583
- package/dist/connector.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/logger.d.ts +28 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +44 -0
- package/dist/logger.js.map +1 -0
- package/dist/network-client.d.ts +17 -1
- package/dist/network-client.d.ts.map +1 -1
- package/dist/network-client.js +119 -23
- package/dist/network-client.js.map +1 -1
- package/dist/node-manager.d.ts +20 -0
- package/dist/node-manager.d.ts.map +1 -1
- package/dist/node-manager.js +194 -18
- package/dist/node-manager.js.map +1 -1
- package/dist/plugin.d.ts +1 -1
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +4 -5
- package/dist/plugin.js.map +1 -1
- package/dist/reputation.d.ts +22 -1
- package/dist/reputation.d.ts.map +1 -1
- package/dist/reputation.js +102 -5
- package/dist/reputation.js.map +1 -1
- package/dist/task-guard.d.ts +82 -0
- package/dist/task-guard.d.ts.map +1 -1
- package/dist/task-guard.js +392 -15
- package/dist/task-guard.js.map +1 -1
- package/dist/task-queue.d.ts +50 -7
- package/dist/task-queue.d.ts.map +1 -1
- package/dist/task-queue.js +445 -12
- package/dist/task-queue.js.map +1 -1
- package/dist/tool-handlers.d.ts +96 -0
- package/dist/tool-handlers.d.ts.map +1 -0
- package/dist/tool-handlers.js +431 -0
- package/dist/tool-handlers.js.map +1 -0
- package/dist/types.d.ts +98 -10
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +10 -0
- package/dist/types.js.map +1 -1
- package/dist/webhook-pusher.d.ts +71 -0
- package/dist/webhook-pusher.d.ts.map +1 -0
- package/dist/webhook-pusher.js +174 -0
- package/dist/webhook-pusher.js.map +1 -0
- package/dist/webhook-server.d.ts +8 -1
- package/dist/webhook-server.d.ts.map +1 -1
- package/dist/webhook-server.js +42 -7
- package/dist/webhook-server.js.map +1 -1
- 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.
|
|
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
|
-
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
978
|
-
|
|
979
|
-
|
|
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
|
-
|
|
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.
|
|
690
|
+
this.taskQueue.close();
|
|
691
|
+
logger_js_1.pluginLogger.info('任务队列已关闭,持久化数据已保留');
|
|
1056
692
|
}
|
|
1057
|
-
|
|
693
|
+
logger_js_1.pluginLogger.info('已关闭');
|
|
1058
694
|
}
|
|
1059
695
|
}
|
|
1060
696
|
exports.F2AOpenClawAdapter = F2AOpenClawAdapter;
|