@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.
- 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 +46 -18
- package/dist/connector.d.ts.map +1 -1
- package/dist/connector.js +319 -592
- 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 +85 -1
- package/dist/reputation.d.ts.map +1 -1
- package/dist/reputation.js +222 -9
- 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 +449 -16
- package/dist/task-guard.js.map +1 -1
- package/dist/task-queue.d.ts +55 -7
- package/dist/task-queue.d.ts.map +1 -1
- package/dist/task-queue.js +477 -12
- package/dist/task-queue.js.map +1 -1
- package/dist/tool-handlers.d.ts +158 -0
- package/dist/tool-handlers.d.ts.map +1 -0
- package/dist/tool-handlers.js +724 -0
- package/dist/tool-handlers.js.map +1 -0
- package/dist/types.d.ts +112 -15
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +23 -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 +65 -7
- package/dist/webhook-server.js.map +1 -1
- 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.
|
|
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
|
-
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
-
}
|
|
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 ??
|
|
1020
|
-
initialScore: config.reputation?.initialScore ||
|
|
1021
|
-
minScoreForService: config.reputation?.minScoreForService ||
|
|
1022
|
-
decayRate: config.reputation?.decayRate ||
|
|
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
|
-
|
|
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.
|
|
781
|
+
this.taskQueue.close();
|
|
782
|
+
logger_js_1.pluginLogger.info('任务队列已关闭,持久化数据已保留');
|
|
1056
783
|
}
|
|
1057
|
-
|
|
784
|
+
logger_js_1.pluginLogger.info('已关闭');
|
|
1058
785
|
}
|
|
1059
786
|
}
|
|
1060
787
|
exports.F2AOpenClawAdapter = F2AOpenClawAdapter;
|