@f2a/openclaw-adapter 0.1.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/README.md +510 -0
- package/dist/announcement-queue.d.ts +71 -0
- package/dist/announcement-queue.d.ts.map +1 -0
- package/dist/announcement-queue.js +181 -0
- package/dist/announcement-queue.js.map +1 -0
- package/dist/capability-detector.d.ts +21 -0
- package/dist/capability-detector.d.ts.map +1 -0
- package/dist/capability-detector.js +177 -0
- package/dist/capability-detector.js.map +1 -0
- package/dist/connector.d.ts +62 -0
- package/dist/connector.d.ts.map +1 -0
- package/dist/connector.js +1063 -0
- package/dist/connector.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +44 -0
- package/dist/index.js.map +1 -0
- package/dist/network-client.d.ts +48 -0
- package/dist/network-client.d.ts.map +1 -0
- package/dist/network-client.js +104 -0
- package/dist/network-client.js.map +1 -0
- package/dist/node-manager.d.ts +50 -0
- package/dist/node-manager.d.ts.map +1 -0
- package/dist/node-manager.js +187 -0
- package/dist/node-manager.js.map +1 -0
- package/dist/plugin.d.ts +16 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +116 -0
- package/dist/plugin.js.map +1 -0
- package/dist/reputation.d.ts +72 -0
- package/dist/reputation.d.ts.map +1 -0
- package/dist/reputation.js +215 -0
- package/dist/reputation.js.map +1 -0
- package/dist/task-guard.d.ts +77 -0
- package/dist/task-guard.d.ts.map +1 -0
- package/dist/task-guard.js +330 -0
- package/dist/task-guard.js.map +1 -0
- package/dist/task-queue.d.ts +71 -0
- package/dist/task-queue.d.ts.map +1 -0
- package/dist/task-queue.js +126 -0
- package/dist/task-queue.js.map +1 -0
- package/dist/types.d.ts +248 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/dist/webhook-server.d.ts +44 -0
- package/dist/webhook-server.d.ts.map +1 -0
- package/dist/webhook-server.js +119 -0
- package/dist/webhook-server.js.map +1 -0
- package/openclaw.plugin.json +106 -0
- package/package.json +40 -0
|
@@ -0,0 +1,1063 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* F2A OpenClaw Connector Plugin
|
|
4
|
+
* 主插件类 - 任务队列架构
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.F2AOpenClawAdapter = void 0;
|
|
8
|
+
const node_manager_js_1 = require("./node-manager.js");
|
|
9
|
+
const network_client_js_1 = require("./network-client.js");
|
|
10
|
+
const webhook_server_js_1 = require("./webhook-server.js");
|
|
11
|
+
const reputation_js_1 = require("./reputation.js");
|
|
12
|
+
const capability_detector_js_1 = require("./capability-detector.js");
|
|
13
|
+
const task_queue_js_1 = require("./task-queue.js");
|
|
14
|
+
const announcement_queue_js_1 = require("./announcement-queue.js");
|
|
15
|
+
class F2AOpenClawAdapter {
|
|
16
|
+
name = 'f2a-openclaw-adapter';
|
|
17
|
+
version = '0.2.0';
|
|
18
|
+
nodeManager;
|
|
19
|
+
networkClient;
|
|
20
|
+
webhookServer;
|
|
21
|
+
reputationSystem;
|
|
22
|
+
capabilityDetector;
|
|
23
|
+
taskQueue;
|
|
24
|
+
announcementQueue;
|
|
25
|
+
config;
|
|
26
|
+
nodeConfig;
|
|
27
|
+
capabilities = [];
|
|
28
|
+
api;
|
|
29
|
+
/**
|
|
30
|
+
* 初始化插件
|
|
31
|
+
*/
|
|
32
|
+
async initialize(config) {
|
|
33
|
+
console.log('[F2A Plugin] 初始化...');
|
|
34
|
+
// 保存 API 引用(用于触发心跳等)
|
|
35
|
+
this.api = config._api;
|
|
36
|
+
// 合并配置
|
|
37
|
+
this.config = this.mergeConfig(config);
|
|
38
|
+
this.nodeConfig = {
|
|
39
|
+
nodePath: this.config.f2aPath || './F2A',
|
|
40
|
+
controlPort: this.config.controlPort || 9001,
|
|
41
|
+
controlToken: this.config.controlToken || this.generateToken(),
|
|
42
|
+
p2pPort: this.config.p2pPort || 9000,
|
|
43
|
+
enableMDNS: this.config.enableMDNS ?? true,
|
|
44
|
+
bootstrapPeers: this.config.bootstrapPeers || []
|
|
45
|
+
};
|
|
46
|
+
// 初始化任务队列
|
|
47
|
+
this.taskQueue = new task_queue_js_1.TaskQueue({
|
|
48
|
+
maxSize: this.config.maxQueuedTasks || 100,
|
|
49
|
+
maxAgeMs: 24 * 60 * 60 * 1000 // 24小时
|
|
50
|
+
});
|
|
51
|
+
// 初始化广播队列
|
|
52
|
+
this.announcementQueue = new announcement_queue_js_1.AnnouncementQueue({
|
|
53
|
+
maxSize: 50,
|
|
54
|
+
maxAgeMs: 30 * 60 * 1000 // 30分钟
|
|
55
|
+
});
|
|
56
|
+
// 初始化组件
|
|
57
|
+
this.nodeManager = new node_manager_js_1.F2ANodeManager(this.nodeConfig);
|
|
58
|
+
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
|
|
64
|
+
}, this.config.dataDir || './f2a-data');
|
|
65
|
+
this.capabilityDetector = new capability_detector_js_1.CapabilityDetector();
|
|
66
|
+
// 启动 F2A Node
|
|
67
|
+
if (this.config.autoStart) {
|
|
68
|
+
const result = await this.nodeManager.ensureRunning();
|
|
69
|
+
if (!result.success) {
|
|
70
|
+
throw new Error(`F2A Node 启动失败: ${result.error}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// 检测能力(基于配置,不依赖 OpenClaw 会话)
|
|
74
|
+
this.capabilities = this.capabilityDetector.getDefaultCapabilities();
|
|
75
|
+
if (this.config.capabilities?.length) {
|
|
76
|
+
this.capabilities = this.capabilityDetector.mergeCustomCapabilities(this.capabilities, this.config.capabilities);
|
|
77
|
+
}
|
|
78
|
+
// 启动 Webhook 服务器
|
|
79
|
+
this.webhookServer = new webhook_server_js_1.WebhookServer(this.config.webhookPort, this.createWebhookHandler());
|
|
80
|
+
await this.webhookServer.start();
|
|
81
|
+
// 注册到 F2A Node
|
|
82
|
+
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()}`);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* 获取插件提供的 Tools
|
|
90
|
+
*/
|
|
91
|
+
getTools() {
|
|
92
|
+
return [
|
|
93
|
+
{
|
|
94
|
+
name: 'f2a_discover',
|
|
95
|
+
description: '发现 F2A 网络中的 Agents,可以按能力过滤',
|
|
96
|
+
parameters: {
|
|
97
|
+
capability: {
|
|
98
|
+
type: 'string',
|
|
99
|
+
description: '按能力过滤,如 code-generation, file-operation',
|
|
100
|
+
required: false
|
|
101
|
+
},
|
|
102
|
+
min_reputation: {
|
|
103
|
+
type: 'number',
|
|
104
|
+
description: '最低信誉分数 (0-100)',
|
|
105
|
+
required: false
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
handler: this.handleDiscover.bind(this)
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
name: 'f2a_delegate',
|
|
112
|
+
description: '委托任务给网络中的特定 Agent',
|
|
113
|
+
parameters: {
|
|
114
|
+
agent: {
|
|
115
|
+
type: 'string',
|
|
116
|
+
description: '目标 Agent ID、名称或 #索引 (如 #1)',
|
|
117
|
+
required: true
|
|
118
|
+
},
|
|
119
|
+
task: {
|
|
120
|
+
type: 'string',
|
|
121
|
+
description: '任务描述',
|
|
122
|
+
required: true
|
|
123
|
+
},
|
|
124
|
+
context: {
|
|
125
|
+
type: 'string',
|
|
126
|
+
description: '任务上下文或附件',
|
|
127
|
+
required: false
|
|
128
|
+
},
|
|
129
|
+
timeout: {
|
|
130
|
+
type: 'number',
|
|
131
|
+
description: '超时时间(毫秒)',
|
|
132
|
+
required: false
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
handler: this.handleDelegate.bind(this)
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
name: 'f2a_broadcast',
|
|
139
|
+
description: '广播任务给所有具备某能力的 Agents(并行执行)',
|
|
140
|
+
parameters: {
|
|
141
|
+
capability: {
|
|
142
|
+
type: 'string',
|
|
143
|
+
description: '所需能力',
|
|
144
|
+
required: true
|
|
145
|
+
},
|
|
146
|
+
task: {
|
|
147
|
+
type: 'string',
|
|
148
|
+
description: '任务描述',
|
|
149
|
+
required: true
|
|
150
|
+
},
|
|
151
|
+
min_responses: {
|
|
152
|
+
type: 'number',
|
|
153
|
+
description: '最少响应数',
|
|
154
|
+
required: false
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
handler: this.handleBroadcast.bind(this)
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
name: 'f2a_status',
|
|
161
|
+
description: '查看 F2A 网络状态和已连接 Peers',
|
|
162
|
+
parameters: {},
|
|
163
|
+
handler: this.handleStatus.bind(this)
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
name: 'f2a_reputation',
|
|
167
|
+
description: '查看或管理 Peer 信誉',
|
|
168
|
+
parameters: {
|
|
169
|
+
action: {
|
|
170
|
+
type: 'string',
|
|
171
|
+
description: '操作: list, view, block, unblock',
|
|
172
|
+
required: true,
|
|
173
|
+
enum: ['list', 'view', 'block', 'unblock']
|
|
174
|
+
},
|
|
175
|
+
peer_id: {
|
|
176
|
+
type: 'string',
|
|
177
|
+
description: 'Peer ID',
|
|
178
|
+
required: false
|
|
179
|
+
}
|
|
180
|
+
},
|
|
181
|
+
handler: this.handleReputation.bind(this)
|
|
182
|
+
},
|
|
183
|
+
// 新增:任务队列相关工具
|
|
184
|
+
{
|
|
185
|
+
name: 'f2a_poll_tasks',
|
|
186
|
+
description: '查询本节点收到的远程任务队列(待 OpenClaw 执行)',
|
|
187
|
+
parameters: {
|
|
188
|
+
limit: {
|
|
189
|
+
type: 'number',
|
|
190
|
+
description: '最大返回任务数',
|
|
191
|
+
required: false
|
|
192
|
+
},
|
|
193
|
+
status: {
|
|
194
|
+
type: 'string',
|
|
195
|
+
description: '任务状态过滤: pending, processing, completed, failed',
|
|
196
|
+
required: false,
|
|
197
|
+
enum: ['pending', 'processing', 'completed', 'failed']
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
handler: this.handlePollTasks.bind(this)
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
name: 'f2a_submit_result',
|
|
204
|
+
description: '提交远程任务的执行结果,发送给原节点',
|
|
205
|
+
parameters: {
|
|
206
|
+
task_id: {
|
|
207
|
+
type: 'string',
|
|
208
|
+
description: '任务ID',
|
|
209
|
+
required: true
|
|
210
|
+
},
|
|
211
|
+
result: {
|
|
212
|
+
type: 'string',
|
|
213
|
+
description: '任务执行结果',
|
|
214
|
+
required: true
|
|
215
|
+
},
|
|
216
|
+
status: {
|
|
217
|
+
type: 'string',
|
|
218
|
+
description: '执行状态: success 或 error',
|
|
219
|
+
required: true,
|
|
220
|
+
enum: ['success', 'error']
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
handler: this.handleSubmitResult.bind(this)
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
name: 'f2a_task_stats',
|
|
227
|
+
description: '查看任务队列统计信息',
|
|
228
|
+
parameters: {},
|
|
229
|
+
handler: this.handleTaskStats.bind(this)
|
|
230
|
+
},
|
|
231
|
+
// 认领模式工具
|
|
232
|
+
{
|
|
233
|
+
name: 'f2a_announce',
|
|
234
|
+
description: '广播任务到 F2A 网络,等待其他 Agent 认领(认领模式)',
|
|
235
|
+
parameters: {
|
|
236
|
+
task_type: {
|
|
237
|
+
type: 'string',
|
|
238
|
+
description: '任务类型',
|
|
239
|
+
required: true
|
|
240
|
+
},
|
|
241
|
+
description: {
|
|
242
|
+
type: 'string',
|
|
243
|
+
description: '任务描述',
|
|
244
|
+
required: true
|
|
245
|
+
},
|
|
246
|
+
required_capabilities: {
|
|
247
|
+
type: 'array',
|
|
248
|
+
description: '所需能力列表',
|
|
249
|
+
required: false
|
|
250
|
+
},
|
|
251
|
+
estimated_complexity: {
|
|
252
|
+
type: 'number',
|
|
253
|
+
description: '预估复杂度 (1-10)',
|
|
254
|
+
required: false
|
|
255
|
+
},
|
|
256
|
+
reward: {
|
|
257
|
+
type: 'number',
|
|
258
|
+
description: '任务奖励',
|
|
259
|
+
required: false
|
|
260
|
+
},
|
|
261
|
+
timeout: {
|
|
262
|
+
type: 'number',
|
|
263
|
+
description: '超时时间(毫秒)',
|
|
264
|
+
required: false
|
|
265
|
+
}
|
|
266
|
+
},
|
|
267
|
+
handler: this.handleAnnounce.bind(this)
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
name: 'f2a_list_announcements',
|
|
271
|
+
description: '查看当前开放的任务广播(可认领)',
|
|
272
|
+
parameters: {
|
|
273
|
+
capability: {
|
|
274
|
+
type: 'string',
|
|
275
|
+
description: '按能力过滤',
|
|
276
|
+
required: false
|
|
277
|
+
},
|
|
278
|
+
limit: {
|
|
279
|
+
type: 'number',
|
|
280
|
+
description: '最大返回数量',
|
|
281
|
+
required: false
|
|
282
|
+
}
|
|
283
|
+
},
|
|
284
|
+
handler: this.handleListAnnouncements.bind(this)
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
name: 'f2a_claim',
|
|
288
|
+
description: '认领一个开放的任务广播',
|
|
289
|
+
parameters: {
|
|
290
|
+
announcement_id: {
|
|
291
|
+
type: 'string',
|
|
292
|
+
description: '广播ID',
|
|
293
|
+
required: true
|
|
294
|
+
},
|
|
295
|
+
estimated_time: {
|
|
296
|
+
type: 'number',
|
|
297
|
+
description: '预计完成时间(毫秒)',
|
|
298
|
+
required: false
|
|
299
|
+
},
|
|
300
|
+
confidence: {
|
|
301
|
+
type: 'number',
|
|
302
|
+
description: '信心指数 (0-1)',
|
|
303
|
+
required: false
|
|
304
|
+
}
|
|
305
|
+
},
|
|
306
|
+
handler: this.handleClaim.bind(this)
|
|
307
|
+
},
|
|
308
|
+
{
|
|
309
|
+
name: 'f2a_manage_claims',
|
|
310
|
+
description: '管理我的任务广播的认领(接受/拒绝)',
|
|
311
|
+
parameters: {
|
|
312
|
+
announcement_id: {
|
|
313
|
+
type: 'string',
|
|
314
|
+
description: '广播ID',
|
|
315
|
+
required: true
|
|
316
|
+
},
|
|
317
|
+
action: {
|
|
318
|
+
type: 'string',
|
|
319
|
+
description: '操作: list, accept, reject',
|
|
320
|
+
required: true,
|
|
321
|
+
enum: ['list', 'accept', 'reject']
|
|
322
|
+
},
|
|
323
|
+
claim_id: {
|
|
324
|
+
type: 'string',
|
|
325
|
+
description: '认领ID(accept/reject 时需要)',
|
|
326
|
+
required: false
|
|
327
|
+
}
|
|
328
|
+
},
|
|
329
|
+
handler: this.handleManageClaims.bind(this)
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
name: 'f2a_my_claims',
|
|
333
|
+
description: '查看我提交的任务认领状态',
|
|
334
|
+
parameters: {
|
|
335
|
+
status: {
|
|
336
|
+
type: 'string',
|
|
337
|
+
description: '状态过滤: pending, accepted, rejected, all',
|
|
338
|
+
required: false,
|
|
339
|
+
enum: ['pending', 'accepted', 'rejected', 'all']
|
|
340
|
+
}
|
|
341
|
+
},
|
|
342
|
+
handler: this.handleMyClaims.bind(this)
|
|
343
|
+
},
|
|
344
|
+
{
|
|
345
|
+
name: 'f2a_announcement_stats',
|
|
346
|
+
description: '查看任务广播统计',
|
|
347
|
+
parameters: {},
|
|
348
|
+
handler: this.handleAnnouncementStats.bind(this)
|
|
349
|
+
}
|
|
350
|
+
];
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* 创建 Webhook 处理器
|
|
354
|
+
*/
|
|
355
|
+
createWebhookHandler() {
|
|
356
|
+
return {
|
|
357
|
+
onDiscover: async (payload) => {
|
|
358
|
+
// 检查请求者信誉
|
|
359
|
+
if (!this.reputationSystem.isAllowed(payload.requester)) {
|
|
360
|
+
return {
|
|
361
|
+
capabilities: [],
|
|
362
|
+
reputation: this.reputationSystem.getReputation(payload.requester).score
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
// 过滤能力
|
|
366
|
+
let caps = this.capabilities;
|
|
367
|
+
if (payload.query.capability) {
|
|
368
|
+
caps = caps.filter(c => c.name === payload.query.capability ||
|
|
369
|
+
c.tools?.includes(payload.query.capability));
|
|
370
|
+
}
|
|
371
|
+
return {
|
|
372
|
+
capabilities: caps,
|
|
373
|
+
reputation: this.reputationSystem.getReputation(payload.requester).score
|
|
374
|
+
};
|
|
375
|
+
},
|
|
376
|
+
onDelegate: async (payload) => {
|
|
377
|
+
// 安全检查
|
|
378
|
+
if (!this.reputationSystem.isAllowed(payload.from)) {
|
|
379
|
+
return {
|
|
380
|
+
accepted: false,
|
|
381
|
+
taskId: payload.taskId,
|
|
382
|
+
reason: 'Reputation too low'
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
// 检查白名单/黑名单
|
|
386
|
+
const whitelist = this.config.security?.whitelist || [];
|
|
387
|
+
const blacklist = this.config.security?.blacklist || [];
|
|
388
|
+
if (whitelist.length > 0 && !whitelist.includes(payload.from)) {
|
|
389
|
+
return {
|
|
390
|
+
accepted: false,
|
|
391
|
+
taskId: payload.taskId,
|
|
392
|
+
reason: 'Not in whitelist'
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
if (blacklist.includes(payload.from)) {
|
|
396
|
+
return {
|
|
397
|
+
accepted: false,
|
|
398
|
+
taskId: payload.taskId,
|
|
399
|
+
reason: 'In blacklist'
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
// 检查队列是否已满
|
|
403
|
+
const stats = this.taskQueue.getStats();
|
|
404
|
+
if (stats.pending >= (this.config.maxQueuedTasks || 100)) {
|
|
405
|
+
return {
|
|
406
|
+
accepted: false,
|
|
407
|
+
taskId: payload.taskId,
|
|
408
|
+
reason: 'Task queue is full'
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
// 添加任务到队列
|
|
412
|
+
try {
|
|
413
|
+
this.taskQueue.add(payload);
|
|
414
|
+
// 触发 OpenClaw 心跳,让它知道有新任务
|
|
415
|
+
this.api?.runtime?.system?.requestHeartbeatNow?.();
|
|
416
|
+
return {
|
|
417
|
+
accepted: true,
|
|
418
|
+
taskId: payload.taskId
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
catch (error) {
|
|
422
|
+
return {
|
|
423
|
+
accepted: false,
|
|
424
|
+
taskId: payload.taskId,
|
|
425
|
+
reason: error instanceof Error ? error.message : 'Failed to queue task'
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
},
|
|
429
|
+
onStatus: async () => {
|
|
430
|
+
const stats = this.taskQueue.getStats();
|
|
431
|
+
return {
|
|
432
|
+
status: 'available',
|
|
433
|
+
load: stats.pending + stats.processing,
|
|
434
|
+
queued: stats.pending,
|
|
435
|
+
processing: stats.processing
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* 注册到 F2A Node
|
|
442
|
+
*/
|
|
443
|
+
async registerToNode() {
|
|
444
|
+
await this.networkClient.registerWebhook(this.webhookServer.getUrl());
|
|
445
|
+
await this.networkClient.updateAgentInfo({
|
|
446
|
+
displayName: this.config.agentName,
|
|
447
|
+
capabilities: this.capabilities
|
|
448
|
+
});
|
|
449
|
+
}
|
|
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
|
+
// ========== 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
|
+
}
|
|
1004
|
+
mergeConfig(config) {
|
|
1005
|
+
return {
|
|
1006
|
+
autoStart: config.autoStart ?? true,
|
|
1007
|
+
webhookPort: config.webhookPort || 9002,
|
|
1008
|
+
agentName: config.agentName || 'OpenClaw Agent',
|
|
1009
|
+
capabilities: config.capabilities || [],
|
|
1010
|
+
f2aPath: config.f2aPath,
|
|
1011
|
+
controlPort: config.controlPort,
|
|
1012
|
+
controlToken: config.controlToken,
|
|
1013
|
+
p2pPort: config.p2pPort,
|
|
1014
|
+
enableMDNS: config.enableMDNS,
|
|
1015
|
+
bootstrapPeers: config.bootstrapPeers,
|
|
1016
|
+
dataDir: config.dataDir || './f2a-data',
|
|
1017
|
+
maxQueuedTasks: config.maxQueuedTasks || 100,
|
|
1018
|
+
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
|
|
1023
|
+
},
|
|
1024
|
+
security: {
|
|
1025
|
+
requireConfirmation: config.security?.requireConfirmation ?? false,
|
|
1026
|
+
whitelist: config.security?.whitelist || [],
|
|
1027
|
+
blacklist: config.security?.blacklist || [],
|
|
1028
|
+
maxTasksPerMinute: config.security?.maxTasksPerMinute || 10
|
|
1029
|
+
}
|
|
1030
|
+
};
|
|
1031
|
+
}
|
|
1032
|
+
generateToken() {
|
|
1033
|
+
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
1034
|
+
let token = 'f2a-';
|
|
1035
|
+
for (let i = 0; i < 32; i++) {
|
|
1036
|
+
token += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
1037
|
+
}
|
|
1038
|
+
return token;
|
|
1039
|
+
}
|
|
1040
|
+
/**
|
|
1041
|
+
* 关闭插件,清理资源
|
|
1042
|
+
*/
|
|
1043
|
+
async shutdown() {
|
|
1044
|
+
console.log('[F2A Plugin] 正在关闭...');
|
|
1045
|
+
// 停止 Webhook 服务器
|
|
1046
|
+
if (this.webhookServer) {
|
|
1047
|
+
await this.webhookServer.stop?.();
|
|
1048
|
+
}
|
|
1049
|
+
// 停止 F2A Node
|
|
1050
|
+
if (this.nodeManager) {
|
|
1051
|
+
await this.nodeManager.stop();
|
|
1052
|
+
}
|
|
1053
|
+
// 清理任务队列
|
|
1054
|
+
if (this.taskQueue) {
|
|
1055
|
+
this.taskQueue.clear();
|
|
1056
|
+
}
|
|
1057
|
+
console.log('[F2A Plugin] 已关闭');
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
exports.F2AOpenClawAdapter = F2AOpenClawAdapter;
|
|
1061
|
+
// 默认导出
|
|
1062
|
+
exports.default = F2AOpenClawAdapter;
|
|
1063
|
+
//# sourceMappingURL=connector.js.map
|