@f2a/network 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (234) hide show
  1. package/.github/workflows/ci.yml +113 -0
  2. package/.github/workflows/publish.yml +60 -0
  3. package/LICENSE +21 -0
  4. package/MONOREPO.md +58 -0
  5. package/README.md +280 -0
  6. package/SKILL.md +137 -0
  7. package/dist/adapters/openclaw.d.ts +103 -0
  8. package/dist/adapters/openclaw.d.ts.map +1 -0
  9. package/dist/adapters/openclaw.js +297 -0
  10. package/dist/adapters/openclaw.js.map +1 -0
  11. package/dist/cli/commands.d.ts +17 -0
  12. package/dist/cli/commands.d.ts.map +1 -0
  13. package/dist/cli/commands.js +107 -0
  14. package/dist/cli/commands.js.map +1 -0
  15. package/dist/cli/index.d.ts +6 -0
  16. package/dist/cli/index.d.ts.map +1 -0
  17. package/dist/cli/index.js +203 -0
  18. package/dist/cli/index.js.map +1 -0
  19. package/dist/core/autonomous-economy.d.ts +136 -0
  20. package/dist/core/autonomous-economy.d.ts.map +1 -0
  21. package/dist/core/autonomous-economy.js +255 -0
  22. package/dist/core/autonomous-economy.js.map +1 -0
  23. package/dist/core/connection-manager.d.ts +80 -0
  24. package/dist/core/connection-manager.d.ts.map +1 -0
  25. package/dist/core/connection-manager.js +235 -0
  26. package/dist/core/connection-manager.js.map +1 -0
  27. package/dist/core/connection-manager.test.d.ts +2 -0
  28. package/dist/core/connection-manager.test.d.ts.map +1 -0
  29. package/dist/core/connection-manager.test.js +52 -0
  30. package/dist/core/connection-manager.test.js.map +1 -0
  31. package/dist/core/e2ee-crypto.d.ts +90 -0
  32. package/dist/core/e2ee-crypto.d.ts.map +1 -0
  33. package/dist/core/e2ee-crypto.js +190 -0
  34. package/dist/core/e2ee-crypto.js.map +1 -0
  35. package/dist/core/f2a.d.ts +126 -0
  36. package/dist/core/f2a.d.ts.map +1 -0
  37. package/dist/core/f2a.js +425 -0
  38. package/dist/core/f2a.js.map +1 -0
  39. package/dist/core/identity.d.ts +47 -0
  40. package/dist/core/identity.d.ts.map +1 -0
  41. package/dist/core/identity.js +130 -0
  42. package/dist/core/identity.js.map +1 -0
  43. package/dist/core/identity.test.d.ts +2 -0
  44. package/dist/core/identity.test.d.ts.map +1 -0
  45. package/dist/core/identity.test.js +43 -0
  46. package/dist/core/identity.test.js.map +1 -0
  47. package/dist/core/p2p-network.d.ts +242 -0
  48. package/dist/core/p2p-network.d.ts.map +1 -0
  49. package/dist/core/p2p-network.js +1182 -0
  50. package/dist/core/p2p-network.js.map +1 -0
  51. package/dist/core/reputation-security.d.ts +168 -0
  52. package/dist/core/reputation-security.d.ts.map +1 -0
  53. package/dist/core/reputation-security.js +369 -0
  54. package/dist/core/reputation-security.js.map +1 -0
  55. package/dist/core/reputation.d.ts +179 -0
  56. package/dist/core/reputation.d.ts.map +1 -0
  57. package/dist/core/reputation.js +472 -0
  58. package/dist/core/reputation.js.map +1 -0
  59. package/dist/core/review-committee.d.ts +130 -0
  60. package/dist/core/review-committee.d.ts.map +1 -0
  61. package/dist/core/review-committee.js +251 -0
  62. package/dist/core/review-committee.js.map +1 -0
  63. package/dist/core/serverless.d.ts +155 -0
  64. package/dist/core/serverless.d.ts.map +1 -0
  65. package/dist/core/serverless.js +615 -0
  66. package/dist/core/serverless.js.map +1 -0
  67. package/dist/core/token-manager.d.ts +42 -0
  68. package/dist/core/token-manager.d.ts.map +1 -0
  69. package/dist/core/token-manager.js +122 -0
  70. package/dist/core/token-manager.js.map +1 -0
  71. package/dist/daemon/control-server.d.ts +55 -0
  72. package/dist/daemon/control-server.d.ts.map +1 -0
  73. package/dist/daemon/control-server.js +262 -0
  74. package/dist/daemon/control-server.js.map +1 -0
  75. package/dist/daemon/index.d.ts +35 -0
  76. package/dist/daemon/index.d.ts.map +1 -0
  77. package/dist/daemon/index.js +69 -0
  78. package/dist/daemon/index.js.map +1 -0
  79. package/dist/daemon/main.d.ts +6 -0
  80. package/dist/daemon/main.d.ts.map +1 -0
  81. package/dist/daemon/main.js +38 -0
  82. package/dist/daemon/main.js.map +1 -0
  83. package/dist/daemon/start.d.ts +6 -0
  84. package/dist/daemon/start.d.ts.map +1 -0
  85. package/dist/daemon/start.js +25 -0
  86. package/dist/daemon/start.js.map +1 -0
  87. package/dist/daemon/webhook.d.ts +30 -0
  88. package/dist/daemon/webhook.d.ts.map +1 -0
  89. package/dist/daemon/webhook.js +86 -0
  90. package/dist/daemon/webhook.js.map +1 -0
  91. package/dist/daemon/webhook.test.d.ts +2 -0
  92. package/dist/daemon/webhook.test.d.ts.map +1 -0
  93. package/dist/daemon/webhook.test.js +24 -0
  94. package/dist/daemon/webhook.test.js.map +1 -0
  95. package/dist/index.d.ts +24 -0
  96. package/dist/index.d.ts.map +1 -0
  97. package/dist/index.js +25 -0
  98. package/dist/index.js.map +1 -0
  99. package/dist/protocol/messages.d.ts +739 -0
  100. package/dist/protocol/messages.d.ts.map +1 -0
  101. package/dist/protocol/messages.js +188 -0
  102. package/dist/protocol/messages.js.map +1 -0
  103. package/dist/protocol/messages.test.d.ts +2 -0
  104. package/dist/protocol/messages.test.d.ts.map +1 -0
  105. package/dist/protocol/messages.test.js +55 -0
  106. package/dist/protocol/messages.test.js.map +1 -0
  107. package/dist/types/index.d.ts +247 -0
  108. package/dist/types/index.d.ts.map +1 -0
  109. package/dist/types/index.js +10 -0
  110. package/dist/types/index.js.map +1 -0
  111. package/dist/types/result.d.ts +28 -0
  112. package/dist/types/result.d.ts.map +1 -0
  113. package/dist/types/result.js +16 -0
  114. package/dist/types/result.js.map +1 -0
  115. package/dist/utils/benchmark.d.ts +67 -0
  116. package/dist/utils/benchmark.d.ts.map +1 -0
  117. package/dist/utils/benchmark.js +179 -0
  118. package/dist/utils/benchmark.js.map +1 -0
  119. package/dist/utils/logger.d.ts +105 -0
  120. package/dist/utils/logger.d.ts.map +1 -0
  121. package/dist/utils/logger.js +275 -0
  122. package/dist/utils/logger.js.map +1 -0
  123. package/dist/utils/middleware.d.ts +85 -0
  124. package/dist/utils/middleware.d.ts.map +1 -0
  125. package/dist/utils/middleware.js +173 -0
  126. package/dist/utils/middleware.js.map +1 -0
  127. package/dist/utils/rate-limiter.d.ts +71 -0
  128. package/dist/utils/rate-limiter.d.ts.map +1 -0
  129. package/dist/utils/rate-limiter.js +160 -0
  130. package/dist/utils/rate-limiter.js.map +1 -0
  131. package/dist/utils/signature.d.ts +57 -0
  132. package/dist/utils/signature.d.ts.map +1 -0
  133. package/dist/utils/signature.js +102 -0
  134. package/dist/utils/signature.js.map +1 -0
  135. package/dist/utils/validation.d.ts +504 -0
  136. package/dist/utils/validation.d.ts.map +1 -0
  137. package/dist/utils/validation.js +159 -0
  138. package/dist/utils/validation.js.map +1 -0
  139. package/docs/F2A-PROTOCOL.md +61 -0
  140. package/docs/MOBILE_BOOTSTRAP_DESIGN.md +126 -0
  141. package/docs/a2a-lessons.md +316 -0
  142. package/docs/middleware-guide.md +448 -0
  143. package/docs/readme-update-checklist.md +90 -0
  144. package/docs/reputation-guide.md +396 -0
  145. package/docs/rfcs/001-reputation-system.md +712 -0
  146. package/docs/security-design.md +247 -0
  147. package/install.sh +231 -0
  148. package/package.json +64 -0
  149. package/packages/openclaw-adapter/README.md +510 -0
  150. package/packages/openclaw-adapter/openclaw.plugin.json +106 -0
  151. package/packages/openclaw-adapter/package.json +40 -0
  152. package/packages/openclaw-adapter/src/announcement-queue.test.ts +449 -0
  153. package/packages/openclaw-adapter/src/announcement-queue.ts +403 -0
  154. package/packages/openclaw-adapter/src/capability-detector.test.ts +99 -0
  155. package/packages/openclaw-adapter/src/capability-detector.ts +183 -0
  156. package/packages/openclaw-adapter/src/claim-handlers.test.ts +974 -0
  157. package/packages/openclaw-adapter/src/claim-handlers.ts +482 -0
  158. package/packages/openclaw-adapter/src/connector.business.test.ts +583 -0
  159. package/packages/openclaw-adapter/src/connector.ts +795 -0
  160. package/packages/openclaw-adapter/src/index.test.ts +82 -0
  161. package/packages/openclaw-adapter/src/index.ts +18 -0
  162. package/packages/openclaw-adapter/src/integration.e2e.test.ts +829 -0
  163. package/packages/openclaw-adapter/src/logger.ts +51 -0
  164. package/packages/openclaw-adapter/src/network-client.test.ts +266 -0
  165. package/packages/openclaw-adapter/src/network-client.ts +251 -0
  166. package/packages/openclaw-adapter/src/network-recovery.test.ts +465 -0
  167. package/packages/openclaw-adapter/src/node-manager.test.ts +136 -0
  168. package/packages/openclaw-adapter/src/node-manager.ts +429 -0
  169. package/packages/openclaw-adapter/src/plugin.test.ts +439 -0
  170. package/packages/openclaw-adapter/src/plugin.ts +104 -0
  171. package/packages/openclaw-adapter/src/reputation.test.ts +221 -0
  172. package/packages/openclaw-adapter/src/reputation.ts +368 -0
  173. package/packages/openclaw-adapter/src/task-guard.test.ts +502 -0
  174. package/packages/openclaw-adapter/src/task-guard.ts +860 -0
  175. package/packages/openclaw-adapter/src/task-queue.concurrency.test.ts +462 -0
  176. package/packages/openclaw-adapter/src/task-queue.edge-cases.test.ts +284 -0
  177. package/packages/openclaw-adapter/src/task-queue.persistence.test.ts +408 -0
  178. package/packages/openclaw-adapter/src/task-queue.ts +668 -0
  179. package/packages/openclaw-adapter/src/tool-handlers.test.ts +906 -0
  180. package/packages/openclaw-adapter/src/tool-handlers.ts +574 -0
  181. package/packages/openclaw-adapter/src/types.ts +361 -0
  182. package/packages/openclaw-adapter/src/webhook-pusher.test.ts +188 -0
  183. package/packages/openclaw-adapter/src/webhook-pusher.ts +220 -0
  184. package/packages/openclaw-adapter/src/webhook-server.test.ts +580 -0
  185. package/packages/openclaw-adapter/src/webhook-server.ts +202 -0
  186. package/packages/openclaw-adapter/tsconfig.json +20 -0
  187. package/src/cli/commands.test.ts +157 -0
  188. package/src/cli/commands.ts +129 -0
  189. package/src/cli/index.test.ts +77 -0
  190. package/src/cli/index.ts +234 -0
  191. package/src/core/autonomous-economy.test.ts +291 -0
  192. package/src/core/autonomous-economy.ts +428 -0
  193. package/src/core/e2ee-crypto.test.ts +125 -0
  194. package/src/core/e2ee-crypto.ts +246 -0
  195. package/src/core/f2a.test.ts +269 -0
  196. package/src/core/f2a.ts +618 -0
  197. package/src/core/p2p-network.test.ts +199 -0
  198. package/src/core/p2p-network.ts +1432 -0
  199. package/src/core/reputation-security.test.ts +403 -0
  200. package/src/core/reputation-security.ts +562 -0
  201. package/src/core/reputation.test.ts +260 -0
  202. package/src/core/reputation.ts +576 -0
  203. package/src/core/review-committee.test.ts +380 -0
  204. package/src/core/review-committee.ts +401 -0
  205. package/src/core/token-manager.test.ts +133 -0
  206. package/src/core/token-manager.ts +140 -0
  207. package/src/daemon/control-server.test.ts +216 -0
  208. package/src/daemon/control-server.ts +292 -0
  209. package/src/daemon/index.test.ts +85 -0
  210. package/src/daemon/index.ts +89 -0
  211. package/src/daemon/main.ts +44 -0
  212. package/src/daemon/start.ts +29 -0
  213. package/src/daemon/webhook.test.ts +68 -0
  214. package/src/daemon/webhook.ts +105 -0
  215. package/src/index.test.ts +436 -0
  216. package/src/index.ts +72 -0
  217. package/src/types/index.test.ts +87 -0
  218. package/src/types/index.ts +341 -0
  219. package/src/types/result.ts +68 -0
  220. package/src/utils/benchmark.ts +237 -0
  221. package/src/utils/logger.ts +331 -0
  222. package/src/utils/middleware.ts +229 -0
  223. package/src/utils/rate-limiter.ts +207 -0
  224. package/src/utils/signature.ts +136 -0
  225. package/src/utils/validation.ts +186 -0
  226. package/tests/docker/Dockerfile.node +23 -0
  227. package/tests/docker/Dockerfile.runner +18 -0
  228. package/tests/docker/docker-compose.test.yml +73 -0
  229. package/tests/integration/message-passing.test.ts +109 -0
  230. package/tests/integration/multi-node.test.ts +92 -0
  231. package/tests/integration/p2p-connection.test.ts +83 -0
  232. package/tests/integration/test-config.ts +32 -0
  233. package/tsconfig.json +21 -0
  234. package/vitest.config.ts +26 -0
@@ -0,0 +1,795 @@
1
+ /**
2
+ * F2A OpenClaw Connector Plugin
3
+ * 主插件类 - 任务队列架构
4
+ */
5
+
6
+ import type {
7
+ OpenClawPlugin,
8
+ OpenClawPluginApi,
9
+ Tool,
10
+ SessionContext,
11
+ ToolResult,
12
+ F2ANodeConfig,
13
+ F2APluginConfig,
14
+ AgentInfo,
15
+ AgentCapability,
16
+ DiscoverWebhookPayload,
17
+ DelegateWebhookPayload,
18
+ } from './types.js';
19
+ import { F2ANodeManager } from './node-manager.js';
20
+ import { F2ANetworkClient } from './network-client.js';
21
+ import { WebhookServer, WebhookHandler } from './webhook-server.js';
22
+ import { ReputationSystem } from './reputation.js';
23
+ import { CapabilityDetector } from './capability-detector.js';
24
+ import { TaskQueue } from './task-queue.js';
25
+ import { AnnouncementQueue } from './announcement-queue.js';
26
+ import { WebhookPusher } from './webhook-pusher.js';
27
+ import { taskGuard, TaskGuardContext } from './task-guard.js';
28
+ import { ToolHandlers } from './tool-handlers.js';
29
+ import { ClaimHandlers } from './claim-handlers.js';
30
+ import { pluginLogger as logger } from './logger.js';
31
+
32
+ /** 广播结果类型 */
33
+ interface BroadcastResult {
34
+ agent: string;
35
+ success: boolean;
36
+ error?: string;
37
+ latency?: number;
38
+ }
39
+
40
+ export class F2AOpenClawAdapter implements OpenClawPlugin {
41
+ name = 'f2a-openclaw-adapter';
42
+ version = '0.3.0';
43
+
44
+ private nodeManager!: F2ANodeManager;
45
+ private networkClient!: F2ANetworkClient;
46
+ private webhookServer!: WebhookServer;
47
+ private reputationSystem!: ReputationSystem;
48
+ private capabilityDetector!: CapabilityDetector;
49
+ private taskQueue!: TaskQueue;
50
+ private announcementQueue!: AnnouncementQueue;
51
+ private webhookPusher?: WebhookPusher;
52
+
53
+ // 处理器实例(延迟初始化)
54
+ private _toolHandlers?: ToolHandlers;
55
+ private _claimHandlers?: ClaimHandlers;
56
+
57
+ private config!: F2APluginConfig;
58
+ private nodeConfig!: F2ANodeConfig;
59
+ private capabilities: AgentCapability[] = [];
60
+ private api?: OpenClawPluginApi;
61
+ private pollTimer?: NodeJS.Timeout;
62
+
63
+ /**
64
+ * 获取工具处理器(延迟初始化,支持未初始化时调用getTools)
65
+ */
66
+ private get toolHandlers(): ToolHandlers {
67
+ if (!this._toolHandlers) {
68
+ this._toolHandlers = new ToolHandlers(this);
69
+ }
70
+ return this._toolHandlers;
71
+ }
72
+
73
+ /**
74
+ * 获取认领处理器(延迟初始化)
75
+ */
76
+ private get claimHandlers(): ClaimHandlers {
77
+ if (!this._claimHandlers) {
78
+ this._claimHandlers = new ClaimHandlers(this);
79
+ }
80
+ return this._claimHandlers;
81
+ }
82
+
83
+ /**
84
+ * 初始化插件
85
+ */
86
+ async initialize(config: Record<string, unknown> & { _api?: OpenClawPluginApi }): Promise<void> {
87
+ logger.info('初始化...');
88
+
89
+ // 保存 API 引用(用于触发心跳等)
90
+ this.api = config._api;
91
+
92
+ // 合并配置
93
+ this.config = this.mergeConfig(config);
94
+ this.nodeConfig = {
95
+ nodePath: this.config.f2aPath || './F2A',
96
+ controlPort: this.config.controlPort || 9001,
97
+ controlToken: this.config.controlToken || this.generateToken(),
98
+ p2pPort: this.config.p2pPort || 9000,
99
+ enableMDNS: this.config.enableMDNS ?? true,
100
+ bootstrapPeers: this.config.bootstrapPeers || []
101
+ };
102
+
103
+ // 初始化任务队列(带持久化)
104
+ const dataDir = this.config.dataDir || './f2a-data';
105
+ this.taskQueue = new TaskQueue({
106
+ maxSize: this.config.maxQueuedTasks || 100,
107
+ maxAgeMs: 24 * 60 * 60 * 1000, // 24小时
108
+ persistDir: dataDir,
109
+ persistEnabled: true
110
+ });
111
+
112
+ // 初始化 Webhook 推送器
113
+ if (this.config.webhookPush?.enabled !== false && this.config.webhookPush?.url) {
114
+ this.webhookPusher = new WebhookPusher(this.config.webhookPush);
115
+ logger.info('Webhook 推送已启用');
116
+ }
117
+
118
+ // 初始化广播队列
119
+ this.announcementQueue = new AnnouncementQueue({
120
+ maxSize: 50,
121
+ maxAgeMs: 30 * 60 * 1000 // 30分钟
122
+ });
123
+
124
+ // 初始化组件
125
+ this.nodeManager = new F2ANodeManager(this.nodeConfig);
126
+ this.networkClient = new F2ANetworkClient(this.nodeConfig);
127
+ this.reputationSystem = new ReputationSystem(
128
+ this.config.reputation || {
129
+ enabled: true,
130
+ initialScore: 50,
131
+ minScoreForService: 20,
132
+ decayRate: 0.01
133
+ },
134
+ this.config.dataDir || './f2a-data'
135
+ );
136
+ this.capabilityDetector = new CapabilityDetector();
137
+
138
+ // 处理器使用 getter 延迟初始化,无需在此显式创建
139
+
140
+ // 启动 F2A Node
141
+ if (this.config.autoStart) {
142
+ const result = await this.nodeManager.ensureRunning();
143
+ if (!result.success) {
144
+ throw new Error(`F2A Node 启动失败: ${result.error}`);
145
+ }
146
+ }
147
+
148
+ // 检测能力(基于配置,不依赖 OpenClaw 会话)
149
+ this.capabilities = this.capabilityDetector.getDefaultCapabilities();
150
+ if (this.config.capabilities?.length) {
151
+ this.capabilities = this.capabilityDetector.mergeCustomCapabilities(
152
+ this.capabilities,
153
+ this.config.capabilities
154
+ );
155
+ }
156
+
157
+ // 启动 Webhook 服务器
158
+ this.webhookServer = new WebhookServer(
159
+ this.config.webhookPort,
160
+ this.createWebhookHandler()
161
+ );
162
+ await this.webhookServer.start();
163
+
164
+ // 注册到 F2A Node
165
+ await this.registerToNode();
166
+
167
+ logger.info('初始化完成');
168
+ logger.info(`Agent 名称: ${this.config.agentName}`);
169
+ logger.info(`能力数: ${this.capabilities.length}`);
170
+ logger.info(`Webhook: ${this.webhookServer.getUrl()}`);
171
+
172
+ // 启动兜底轮询(降低到 60 秒)
173
+ this.startFallbackPolling();
174
+ }
175
+
176
+ /**
177
+ * 兜底轮询
178
+ * 当 webhook 推送失败时,轮询确保任务不会丢失
179
+ */
180
+ private startFallbackPolling(): void {
181
+ const interval = this.config.pollInterval || 60000; // 默认 60 秒
182
+
183
+ this.pollTimer = setInterval(async () => {
184
+ // P1 修复:定期检查并重置超时的 processing 任务,防止僵尸任务
185
+ this.resetTimedOutProcessingTasks();
186
+
187
+ if (!this.webhookPusher) {
188
+ // 没有配置 webhook,不轮询(保持原有轮询模式)
189
+ return;
190
+ }
191
+
192
+ try {
193
+ // 获取未推送的任务
194
+ const pending = this.taskQueue.getWebhookPending();
195
+
196
+ if (pending.length > 0) {
197
+ logger.info(`兜底轮询: ${pending.length} 个待推送任务`);
198
+
199
+ for (const task of pending) {
200
+ const result = await this.webhookPusher.pushTask(task);
201
+ if (result.success) {
202
+ this.taskQueue.markWebhookPushed(task.taskId);
203
+ }
204
+ }
205
+ }
206
+ } catch (error) {
207
+ logger.error('兜底轮询失败:', error);
208
+ }
209
+ }, interval);
210
+ }
211
+
212
+ /**
213
+ * P1 修复:重置超时的 processing 任务
214
+ * 如果任务在 processing 状态停留超过超时时间,将其重置为 pending
215
+ * 防止因处理失败导致的僵尸任务
216
+ */
217
+ private resetTimedOutProcessingTasks(): void {
218
+ const stats = this.taskQueue.getStats();
219
+ if (stats.processing === 0) {
220
+ return; // 没有处理中的任务,无需检查
221
+ }
222
+
223
+ const allTasks = this.taskQueue.getAll();
224
+ const now = Date.now();
225
+ const processingTimeout = this.config.processingTimeoutMs || 5 * 60 * 1000; // 默认 5 分钟
226
+
227
+ for (const task of allTasks) {
228
+ if (task.status === 'processing') {
229
+ const taskTimeout = task.timeout || 30000; // 使用任务自身的超时或默认 30 秒
230
+ const maxAllowedTime = Math.max(taskTimeout * 2, processingTimeout); // 至少 2 倍任务超时或 processingTimeout
231
+ const processingTime = now - (task.updatedAt || task.createdAt);
232
+
233
+ if (processingTime > maxAllowedTime) {
234
+ logger.warn(`检测到僵尸任务 ${task.taskId.slice(0, 8)}... (processing ${Math.round(processingTime / 1000)}s),重置为 pending`);
235
+ // 将任务重置为 pending 状态
236
+ this.taskQueue.resetProcessingTask(task.taskId);
237
+ }
238
+ }
239
+ }
240
+ }
241
+
242
+ /**
243
+ * 获取插件提供的 Tools
244
+ */
245
+ getTools(): Tool[] {
246
+ return [
247
+ {
248
+ name: 'f2a_discover',
249
+ description: '发现 F2A 网络中的 Agents,可以按能力过滤',
250
+ parameters: {
251
+ capability: {
252
+ type: 'string',
253
+ description: '按能力过滤,如 code-generation, file-operation',
254
+ required: false
255
+ },
256
+ min_reputation: {
257
+ type: 'number',
258
+ description: '最低信誉分数 (0-100)',
259
+ required: false
260
+ }
261
+ },
262
+ handler: this.toolHandlers.handleDiscover.bind(this.toolHandlers)
263
+ },
264
+ {
265
+ name: 'f2a_delegate',
266
+ description: '委托任务给网络中的特定 Agent',
267
+ parameters: {
268
+ agent: {
269
+ type: 'string',
270
+ description: '目标 Agent ID、名称或 #索引 (如 #1)',
271
+ required: true
272
+ },
273
+ task: {
274
+ type: 'string',
275
+ description: '任务描述',
276
+ required: true
277
+ },
278
+ context: {
279
+ type: 'string',
280
+ description: '任务上下文或附件',
281
+ required: false
282
+ },
283
+ timeout: {
284
+ type: 'number',
285
+ description: '超时时间(毫秒)',
286
+ required: false
287
+ }
288
+ },
289
+ handler: this.toolHandlers.handleDelegate.bind(this.toolHandlers)
290
+ },
291
+ {
292
+ name: 'f2a_broadcast',
293
+ description: '广播任务给所有具备某能力的 Agents(并行执行)',
294
+ parameters: {
295
+ capability: {
296
+ type: 'string',
297
+ description: '所需能力',
298
+ required: true
299
+ },
300
+ task: {
301
+ type: 'string',
302
+ description: '任务描述',
303
+ required: true
304
+ },
305
+ min_responses: {
306
+ type: 'number',
307
+ description: '最少响应数',
308
+ required: false
309
+ }
310
+ },
311
+ handler: this.toolHandlers.handleBroadcast.bind(this.toolHandlers)
312
+ },
313
+ {
314
+ name: 'f2a_status',
315
+ description: '查看 F2A 网络状态和已连接 Peers',
316
+ parameters: {},
317
+ handler: this.toolHandlers.handleStatus.bind(this.toolHandlers)
318
+ },
319
+ {
320
+ name: 'f2a_reputation',
321
+ description: '查看或管理 Peer 信誉',
322
+ parameters: {
323
+ action: {
324
+ type: 'string',
325
+ description: '操作: list, view, block, unblock',
326
+ required: true,
327
+ enum: ['list', 'view', 'block', 'unblock']
328
+ },
329
+ peer_id: {
330
+ type: 'string',
331
+ description: 'Peer ID',
332
+ required: false
333
+ }
334
+ },
335
+ handler: this.toolHandlers.handleReputation.bind(this.toolHandlers)
336
+ },
337
+ // 新增:任务队列相关工具
338
+ {
339
+ name: 'f2a_poll_tasks',
340
+ description: '查询本节点收到的远程任务队列(待 OpenClaw 执行)',
341
+ parameters: {
342
+ limit: {
343
+ type: 'number',
344
+ description: '最大返回任务数',
345
+ required: false
346
+ },
347
+ status: {
348
+ type: 'string',
349
+ description: '任务状态过滤: pending, processing, completed, failed',
350
+ required: false,
351
+ enum: ['pending', 'processing', 'completed', 'failed']
352
+ }
353
+ },
354
+ handler: this.toolHandlers.handlePollTasks.bind(this.toolHandlers)
355
+ },
356
+ {
357
+ name: 'f2a_submit_result',
358
+ description: '提交远程任务的执行结果,发送给原节点',
359
+ parameters: {
360
+ task_id: {
361
+ type: 'string',
362
+ description: '任务ID',
363
+ required: true
364
+ },
365
+ result: {
366
+ type: 'string',
367
+ description: '任务执行结果',
368
+ required: true
369
+ },
370
+ status: {
371
+ type: 'string',
372
+ description: '执行状态: success 或 error',
373
+ required: true,
374
+ enum: ['success', 'error']
375
+ }
376
+ },
377
+ handler: this.toolHandlers.handleSubmitResult.bind(this.toolHandlers)
378
+ },
379
+ {
380
+ name: 'f2a_task_stats',
381
+ description: '查看任务队列统计信息',
382
+ parameters: {},
383
+ handler: this.toolHandlers.handleTaskStats.bind(this.toolHandlers)
384
+ },
385
+ // 认领模式工具
386
+ {
387
+ name: 'f2a_announce',
388
+ description: '广播任务到 F2A 网络,等待其他 Agent 认领(认领模式)',
389
+ parameters: {
390
+ task_type: {
391
+ type: 'string',
392
+ description: '任务类型',
393
+ required: true
394
+ },
395
+ description: {
396
+ type: 'string',
397
+ description: '任务描述',
398
+ required: true
399
+ },
400
+ required_capabilities: {
401
+ type: 'array',
402
+ description: '所需能力列表',
403
+ required: false
404
+ },
405
+ estimated_complexity: {
406
+ type: 'number',
407
+ description: '预估复杂度 (1-10)',
408
+ required: false
409
+ },
410
+ reward: {
411
+ type: 'number',
412
+ description: '任务奖励',
413
+ required: false
414
+ },
415
+ timeout: {
416
+ type: 'number',
417
+ description: '超时时间(毫秒)',
418
+ required: false
419
+ }
420
+ },
421
+ handler: this.claimHandlers.handleAnnounce.bind(this.claimHandlers)
422
+ },
423
+ {
424
+ name: 'f2a_list_announcements',
425
+ description: '查看当前开放的任务广播(可认领)',
426
+ parameters: {
427
+ capability: {
428
+ type: 'string',
429
+ description: '按能力过滤',
430
+ required: false
431
+ },
432
+ limit: {
433
+ type: 'number',
434
+ description: '最大返回数量',
435
+ required: false
436
+ }
437
+ },
438
+ handler: this.claimHandlers.handleListAnnouncements.bind(this.claimHandlers)
439
+ },
440
+ {
441
+ name: 'f2a_claim',
442
+ description: '认领一个开放的任务广播',
443
+ parameters: {
444
+ announcement_id: {
445
+ type: 'string',
446
+ description: '广播ID',
447
+ required: true
448
+ },
449
+ estimated_time: {
450
+ type: 'number',
451
+ description: '预计完成时间(毫秒)',
452
+ required: false
453
+ },
454
+ confidence: {
455
+ type: 'number',
456
+ description: '信心指数 (0-1)',
457
+ required: false
458
+ }
459
+ },
460
+ handler: this.claimHandlers.handleClaim.bind(this.claimHandlers)
461
+ },
462
+ {
463
+ name: 'f2a_manage_claims',
464
+ description: '管理我的任务广播的认领(接受/拒绝)',
465
+ parameters: {
466
+ announcement_id: {
467
+ type: 'string',
468
+ description: '广播ID',
469
+ required: true
470
+ },
471
+ action: {
472
+ type: 'string',
473
+ description: '操作: list, accept, reject',
474
+ required: true,
475
+ enum: ['list', 'accept', 'reject']
476
+ },
477
+ claim_id: {
478
+ type: 'string',
479
+ description: '认领ID(accept/reject 时需要)',
480
+ required: false
481
+ }
482
+ },
483
+ handler: this.claimHandlers.handleManageClaims.bind(this.claimHandlers)
484
+ },
485
+ {
486
+ name: 'f2a_my_claims',
487
+ description: '查看我提交的任务认领状态',
488
+ parameters: {
489
+ status: {
490
+ type: 'string',
491
+ description: '状态过滤: pending, accepted, rejected, all',
492
+ required: false,
493
+ enum: ['pending', 'accepted', 'rejected', 'all']
494
+ }
495
+ },
496
+ handler: this.claimHandlers.handleMyClaims.bind(this.claimHandlers)
497
+ },
498
+ {
499
+ name: 'f2a_announcement_stats',
500
+ description: '查看任务广播统计',
501
+ parameters: {},
502
+ handler: this.claimHandlers.handleAnnouncementStats.bind(this.claimHandlers)
503
+ }
504
+ ];
505
+ }
506
+
507
+ /**
508
+ * 创建 Webhook 处理器
509
+ */
510
+ private createWebhookHandler(): WebhookHandler {
511
+ return {
512
+ onDiscover: async (payload: DiscoverWebhookPayload) => {
513
+ // 检查请求者信誉
514
+ if (!this.reputationSystem.isAllowed(payload.requester)) {
515
+ return {
516
+ capabilities: [],
517
+ reputation: this.reputationSystem.getReputation(payload.requester).score
518
+ };
519
+ }
520
+
521
+ // 过滤能力
522
+ let caps = this.capabilities;
523
+ if (payload.query.capability) {
524
+ caps = caps.filter(c =>
525
+ c.name === payload.query.capability ||
526
+ c.tools?.includes(payload.query.capability!)
527
+ );
528
+ }
529
+
530
+ return {
531
+ capabilities: caps,
532
+ reputation: this.reputationSystem.getReputation(payload.requester).score
533
+ };
534
+ },
535
+
536
+ onDelegate: async (payload: DelegateWebhookPayload) => {
537
+ // 安全检查
538
+ if (!this.reputationSystem.isAllowed(payload.from)) {
539
+ return {
540
+ accepted: false,
541
+ taskId: payload.taskId,
542
+ reason: 'Reputation too low'
543
+ };
544
+ }
545
+
546
+ // 检查白名单/黑名单
547
+ const whitelist = this.config.security?.whitelist || [];
548
+ const blacklist = this.config.security?.blacklist || [];
549
+ const isWhitelisted = whitelist.length > 0 && whitelist.includes(payload.from);
550
+ const isBlacklisted = blacklist.includes(payload.from);
551
+
552
+ if (whitelist.length > 0 && !isWhitelisted) {
553
+ return {
554
+ accepted: false,
555
+ taskId: payload.taskId,
556
+ reason: 'Not in whitelist'
557
+ };
558
+ }
559
+
560
+ if (isBlacklisted) {
561
+ return {
562
+ accepted: false,
563
+ taskId: payload.taskId,
564
+ reason: 'In blacklist'
565
+ };
566
+ }
567
+
568
+ // TaskGuard 安全检查
569
+ const requesterReputation = this.reputationSystem.getReputation(payload.from);
570
+ const taskGuardContext: Partial<TaskGuardContext> = {
571
+ requesterReputation,
572
+ isWhitelisted,
573
+ isBlacklisted,
574
+ recentTaskCount: 0 // Will be tracked internally by TaskGuard
575
+ };
576
+
577
+ const taskGuardReport = taskGuard.check(payload, taskGuardContext);
578
+
579
+ if (!taskGuardReport.passed) {
580
+ // 任务被阻止
581
+ const blockReasons = taskGuardReport.blocks.map(b => b.message).join('; ');
582
+ logger.warn(`TaskGuard 阻止任务 ${payload.taskId}: ${blockReasons}`);
583
+ return {
584
+ accepted: false,
585
+ taskId: payload.taskId,
586
+ reason: `TaskGuard blocked: ${blockReasons}`
587
+ };
588
+ }
589
+
590
+ if (taskGuardReport.requiresConfirmation) {
591
+ // 任务需要确认(警告但不阻止)
592
+ const warnReasons = taskGuardReport.warnings.map(w => w.message).join('; ');
593
+ logger.warn(`TaskGuard 警告 ${payload.taskId}: ${warnReasons}`);
594
+ // 未来可以扩展为请求用户确认
595
+ // 目前记录警告但继续处理任务
596
+ }
597
+
598
+ // 检查队列是否已满
599
+ const stats = this.taskQueue.getStats();
600
+ if (stats.pending >= (this.config.maxQueuedTasks || 100)) {
601
+ return {
602
+ accepted: false,
603
+ taskId: payload.taskId,
604
+ reason: 'Task queue is full'
605
+ };
606
+ }
607
+
608
+ // 添加任务到队列
609
+ try {
610
+ const task = this.taskQueue.add(payload);
611
+
612
+ // 优先使用 webhook 推送
613
+ if (this.webhookPusher) {
614
+ const result = await this.webhookPusher.pushTask(task);
615
+ if (result.success) {
616
+ this.taskQueue.markWebhookPushed(task.taskId);
617
+ logger.info(`任务 ${task.taskId} 已通过 webhook 推送 (${result.latency}ms)`);
618
+ } else {
619
+ logger.info(`Webhook 推送失败: ${result.error},任务将在轮询时处理`);
620
+ }
621
+ }
622
+
623
+ // 触发 OpenClaw 心跳,让它知道有新任务
624
+ this.api?.runtime?.system?.requestHeartbeatNow?.();
625
+
626
+ return {
627
+ accepted: true,
628
+ taskId: payload.taskId
629
+ };
630
+ } catch (error) {
631
+ return {
632
+ accepted: false,
633
+ taskId: payload.taskId,
634
+ reason: error instanceof Error ? error.message : 'Failed to queue task'
635
+ };
636
+ }
637
+ },
638
+
639
+ onStatus: async () => {
640
+ const stats = this.taskQueue.getStats();
641
+ return {
642
+ status: 'available',
643
+ load: stats.pending + stats.processing,
644
+ queued: stats.pending,
645
+ processing: stats.processing
646
+ };
647
+ }
648
+ };
649
+ }
650
+
651
+ /**
652
+ * 注册到 F2A Node
653
+ */
654
+ private async registerToNode(): Promise<void> {
655
+ await this.networkClient.registerWebhook(this.webhookServer.getUrl());
656
+
657
+ await this.networkClient.updateAgentInfo({
658
+ displayName: this.config.agentName,
659
+ capabilities: this.capabilities
660
+ });
661
+ }
662
+
663
+ // ========== Helpers ==========
664
+
665
+ /**
666
+ * 合并配置(公开方法供处理器使用)
667
+ */
668
+ mergeConfig(config: Record<string, unknown> & { _api?: unknown }): F2APluginConfig {
669
+ return {
670
+ autoStart: (config.autoStart as boolean) ?? true,
671
+ webhookPort: (config.webhookPort as number) || 9002,
672
+ agentName: (config.agentName as string) || 'OpenClaw Agent',
673
+ capabilities: (config.capabilities as string[]) || [],
674
+ f2aPath: config.f2aPath as string | undefined,
675
+ controlPort: config.controlPort as number | undefined,
676
+ controlToken: config.controlToken as string | undefined,
677
+ p2pPort: config.p2pPort as number | undefined,
678
+ enableMDNS: config.enableMDNS as boolean | undefined,
679
+ bootstrapPeers: config.bootstrapPeers as string[] | undefined,
680
+ dataDir: (config.dataDir as string) || './f2a-data',
681
+ maxQueuedTasks: (config.maxQueuedTasks as number) || 100,
682
+ pollInterval: config.pollInterval as number | undefined,
683
+ // 保留 webhookPush 配置(修复:之前丢失导致 webhook 推送被禁用)
684
+ webhookPush: config.webhookPush as { enabled?: boolean; url: string; token: string; timeout?: number } | undefined,
685
+ reputation: {
686
+ enabled: ((config.reputation as Record<string, unknown>)?.enabled as boolean) ?? true,
687
+ initialScore: ((config.reputation as Record<string, unknown>)?.initialScore as number) || 50,
688
+ minScoreForService: ((config.reputation as Record<string, unknown>)?.minScoreForService as number) || 20,
689
+ decayRate: ((config.reputation as Record<string, unknown>)?.decayRate as number) || 0.01
690
+ },
691
+ security: {
692
+ requireConfirmation: ((config.security as Record<string, unknown>)?.requireConfirmation as boolean) ?? false,
693
+ whitelist: ((config.security as Record<string, unknown>)?.whitelist as string[]) || [],
694
+ blacklist: ((config.security as Record<string, unknown>)?.blacklist as string[]) || [],
695
+ maxTasksPerMinute: ((config.security as Record<string, unknown>)?.maxTasksPerMinute as number) || 10
696
+ }
697
+ };
698
+ }
699
+
700
+ private generateToken(): string {
701
+ const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
702
+ let token = 'f2a-';
703
+ for (let i = 0; i < 32; i++) {
704
+ token += chars.charAt(Math.floor(Math.random() * chars.length));
705
+ }
706
+ return token;
707
+ }
708
+
709
+ /**
710
+ * 格式化广播结果(公共方法,供测试和外部调用)
711
+ */
712
+ formatBroadcastResults(results: BroadcastResult[]): string {
713
+ return results.map(r => {
714
+ const icon = r.success ? '✅' : '❌';
715
+ const latency = r.latency ? ` (${r.latency}ms)` : '';
716
+ return `${icon} ${r.agent}${latency}\n ${r.success ? '完成' : `失败: ${r.error}`}`;
717
+ }).join('\n\n');
718
+ }
719
+
720
+ /**
721
+ * 解析 Agent 引用(公共方法,供测试和外部调用)
722
+ */
723
+ async resolveAgent(agentRef: string): Promise<AgentInfo | null> {
724
+ const result = await this.networkClient?.discoverAgents();
725
+ if (!result?.success) return null;
726
+
727
+ const agents = result.data || [];
728
+
729
+ // #索引格式
730
+ if (agentRef.startsWith('#')) {
731
+ const index = parseInt(agentRef.slice(1)) - 1;
732
+ return agents[index] || null;
733
+ }
734
+
735
+ // 精确匹配
736
+ const exact = agents.find((a: AgentInfo) =>
737
+ a.peerId === agentRef ||
738
+ a.displayName === agentRef
739
+ );
740
+ if (exact) return exact;
741
+
742
+ // 模糊匹配
743
+ const fuzzy = agents.find((a: AgentInfo) =>
744
+ a.peerId.startsWith(agentRef) ||
745
+ a.displayName.toLowerCase().includes(agentRef.toLowerCase())
746
+ );
747
+
748
+ return fuzzy || null;
749
+ }
750
+
751
+ /**
752
+ * 关闭插件,清理资源
753
+ */
754
+ async shutdown(): Promise<void> {
755
+ logger.info('正在关闭...');
756
+
757
+ // 停止轮询定时器
758
+ if (this.pollTimer) {
759
+ clearInterval(this.pollTimer);
760
+ this.pollTimer = undefined;
761
+ }
762
+
763
+ // 停止 Webhook 服务器
764
+ if (this.webhookServer) {
765
+ await this.webhookServer.stop?.();
766
+ }
767
+
768
+ // P1 修复:关闭前刷新信誉系统数据,确保持久化
769
+ if (this.reputationSystem) {
770
+ this.reputationSystem.flush();
771
+ logger.info('信誉系统数据已保存');
772
+ }
773
+
774
+ // P1 修复:关闭 TaskGuard,停止持久化定时器并保存最终状态
775
+ taskGuard.shutdown();
776
+ logger.info('TaskGuard 已关闭');
777
+
778
+ // 停止 F2A Node
779
+ if (this.nodeManager) {
780
+ await this.nodeManager.stop();
781
+ }
782
+
783
+ // 关闭任务队列连接(保留持久化数据,不删除任务)
784
+ // 这样重启后可以恢复未完成的任务
785
+ if (this.taskQueue) {
786
+ this.taskQueue.close();
787
+ logger.info('任务队列已关闭,持久化数据已保留');
788
+ }
789
+
790
+ logger.info('已关闭');
791
+ }
792
+ }
793
+
794
+ // 默认导出
795
+ export default F2AOpenClawAdapter;