@f2a/network 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (226) hide show
  1. package/README.md +278 -63
  2. package/dist/cli/commands.d.ts.map +1 -1
  3. package/dist/cli/commands.js +29 -2
  4. package/dist/cli/commands.js.map +1 -1
  5. package/dist/cli/config.d.ts +176 -0
  6. package/dist/cli/config.d.ts.map +1 -0
  7. package/dist/cli/config.js +386 -0
  8. package/dist/cli/config.js.map +1 -0
  9. package/dist/cli/daemon.d.ts +54 -0
  10. package/dist/cli/daemon.d.ts.map +1 -0
  11. package/dist/cli/daemon.js +572 -0
  12. package/dist/cli/daemon.js.map +1 -0
  13. package/dist/cli/index.js +90 -16
  14. package/dist/cli/index.js.map +1 -1
  15. package/dist/cli/init.d.ts +13 -0
  16. package/dist/cli/init.d.ts.map +1 -0
  17. package/dist/cli/init.js +352 -0
  18. package/dist/cli/init.js.map +1 -0
  19. package/dist/core/e2ee-crypto.d.ts +127 -1
  20. package/dist/core/e2ee-crypto.d.ts.map +1 -1
  21. package/dist/core/e2ee-crypto.js +446 -12
  22. package/dist/core/e2ee-crypto.js.map +1 -1
  23. package/dist/core/f2a.d.ts +2 -1
  24. package/dist/core/f2a.d.ts.map +1 -1
  25. package/dist/core/f2a.js +6 -2
  26. package/dist/core/f2a.js.map +1 -1
  27. package/dist/core/identity/encrypted-key-store.d.ts +19 -0
  28. package/dist/core/identity/encrypted-key-store.d.ts.map +1 -0
  29. package/dist/core/identity/encrypted-key-store.js +72 -0
  30. package/dist/core/identity/encrypted-key-store.js.map +1 -0
  31. package/dist/core/identity/identity-manager.d.ts +133 -0
  32. package/dist/core/identity/identity-manager.d.ts.map +1 -0
  33. package/dist/core/identity/identity-manager.js +454 -0
  34. package/dist/core/identity/identity-manager.js.map +1 -0
  35. package/dist/core/identity/index.d.ts +8 -0
  36. package/dist/core/identity/index.d.ts.map +1 -0
  37. package/dist/core/identity/index.js +7 -0
  38. package/dist/core/identity/index.js.map +1 -0
  39. package/dist/core/identity/types.d.ts +70 -0
  40. package/dist/core/identity/types.d.ts.map +1 -0
  41. package/dist/core/identity/types.js +17 -0
  42. package/dist/core/identity/types.js.map +1 -0
  43. package/dist/core/p2p-network.d.ts +26 -0
  44. package/dist/core/p2p-network.d.ts.map +1 -1
  45. package/dist/core/p2p-network.js +434 -105
  46. package/dist/core/p2p-network.js.map +1 -1
  47. package/dist/core/reputation-security.d.ts +15 -0
  48. package/dist/core/reputation-security.d.ts.map +1 -1
  49. package/dist/core/reputation-security.js +73 -3
  50. package/dist/core/reputation-security.js.map +1 -1
  51. package/dist/core/reputation.d.ts +129 -4
  52. package/dist/core/reputation.d.ts.map +1 -1
  53. package/dist/core/reputation.js +294 -1
  54. package/dist/core/reputation.js.map +1 -1
  55. package/dist/core/review-committee.d.ts +2 -2
  56. package/dist/core/review-committee.d.ts.map +1 -1
  57. package/dist/core/review-committee.js +17 -0
  58. package/dist/core/review-committee.js.map +1 -1
  59. package/dist/daemon/control-server.d.ts.map +1 -1
  60. package/dist/daemon/control-server.js +44 -1
  61. package/dist/daemon/control-server.js.map +1 -1
  62. package/dist/daemon/webhook.d.ts +3 -0
  63. package/dist/daemon/webhook.d.ts.map +1 -1
  64. package/dist/daemon/webhook.js +318 -6
  65. package/dist/daemon/webhook.js.map +1 -1
  66. package/dist/index.d.ts +3 -3
  67. package/dist/index.d.ts.map +1 -1
  68. package/dist/index.js +7 -3
  69. package/dist/index.js.map +1 -1
  70. package/dist/types/index.d.ts +4 -0
  71. package/dist/types/index.d.ts.map +1 -1
  72. package/dist/types/index.js.map +1 -1
  73. package/dist/types/result.d.ts +1 -1
  74. package/dist/types/result.d.ts.map +1 -1
  75. package/dist/types/result.js.map +1 -1
  76. package/dist/utils/crypto-utils.d.ts +17 -0
  77. package/dist/utils/crypto-utils.d.ts.map +1 -0
  78. package/dist/utils/crypto-utils.js +28 -0
  79. package/dist/utils/crypto-utils.js.map +1 -0
  80. package/dist/utils/logger.d.ts +1 -0
  81. package/dist/utils/logger.d.ts.map +1 -1
  82. package/dist/utils/logger.js +9 -3
  83. package/dist/utils/logger.js.map +1 -1
  84. package/dist/utils/rate-limiter.d.ts.map +1 -1
  85. package/dist/utils/rate-limiter.js +3 -1
  86. package/dist/utils/rate-limiter.js.map +1 -1
  87. package/dist/utils/signature.d.ts +47 -1
  88. package/dist/utils/signature.d.ts.map +1 -1
  89. package/dist/utils/signature.js +166 -11
  90. package/dist/utils/signature.js.map +1 -1
  91. package/package.json +9 -1
  92. package/.github/workflows/ci.yml +0 -113
  93. package/.github/workflows/publish.yml +0 -60
  94. package/MONOREPO.md +0 -58
  95. package/SKILL.md +0 -137
  96. package/dist/adapters/openclaw.d.ts +0 -103
  97. package/dist/adapters/openclaw.d.ts.map +0 -1
  98. package/dist/adapters/openclaw.js +0 -297
  99. package/dist/adapters/openclaw.js.map +0 -1
  100. package/dist/core/connection-manager.d.ts +0 -80
  101. package/dist/core/connection-manager.d.ts.map +0 -1
  102. package/dist/core/connection-manager.js +0 -235
  103. package/dist/core/connection-manager.js.map +0 -1
  104. package/dist/core/connection-manager.test.d.ts +0 -2
  105. package/dist/core/connection-manager.test.d.ts.map +0 -1
  106. package/dist/core/connection-manager.test.js +0 -52
  107. package/dist/core/connection-manager.test.js.map +0 -1
  108. package/dist/core/identity.d.ts +0 -47
  109. package/dist/core/identity.d.ts.map +0 -1
  110. package/dist/core/identity.js +0 -130
  111. package/dist/core/identity.js.map +0 -1
  112. package/dist/core/identity.test.d.ts +0 -2
  113. package/dist/core/identity.test.d.ts.map +0 -1
  114. package/dist/core/identity.test.js +0 -43
  115. package/dist/core/identity.test.js.map +0 -1
  116. package/dist/core/serverless.d.ts +0 -155
  117. package/dist/core/serverless.d.ts.map +0 -1
  118. package/dist/core/serverless.js +0 -615
  119. package/dist/core/serverless.js.map +0 -1
  120. package/dist/daemon/webhook.test.d.ts +0 -2
  121. package/dist/daemon/webhook.test.d.ts.map +0 -1
  122. package/dist/daemon/webhook.test.js +0 -24
  123. package/dist/daemon/webhook.test.js.map +0 -1
  124. package/dist/protocol/messages.d.ts +0 -739
  125. package/dist/protocol/messages.d.ts.map +0 -1
  126. package/dist/protocol/messages.js +0 -188
  127. package/dist/protocol/messages.js.map +0 -1
  128. package/dist/protocol/messages.test.d.ts +0 -2
  129. package/dist/protocol/messages.test.d.ts.map +0 -1
  130. package/dist/protocol/messages.test.js +0 -55
  131. package/dist/protocol/messages.test.js.map +0 -1
  132. package/docs/F2A-PROTOCOL.md +0 -61
  133. package/docs/MOBILE_BOOTSTRAP_DESIGN.md +0 -126
  134. package/docs/a2a-lessons.md +0 -316
  135. package/docs/middleware-guide.md +0 -448
  136. package/docs/readme-update-checklist.md +0 -90
  137. package/docs/reputation-guide.md +0 -396
  138. package/docs/rfcs/001-reputation-system.md +0 -712
  139. package/docs/security-design.md +0 -247
  140. package/install.sh +0 -231
  141. package/packages/openclaw-adapter/README.md +0 -510
  142. package/packages/openclaw-adapter/openclaw.plugin.json +0 -106
  143. package/packages/openclaw-adapter/package.json +0 -40
  144. package/packages/openclaw-adapter/src/announcement-queue.test.ts +0 -449
  145. package/packages/openclaw-adapter/src/announcement-queue.ts +0 -403
  146. package/packages/openclaw-adapter/src/capability-detector.test.ts +0 -99
  147. package/packages/openclaw-adapter/src/capability-detector.ts +0 -183
  148. package/packages/openclaw-adapter/src/claim-handlers.test.ts +0 -974
  149. package/packages/openclaw-adapter/src/claim-handlers.ts +0 -482
  150. package/packages/openclaw-adapter/src/connector.business.test.ts +0 -583
  151. package/packages/openclaw-adapter/src/connector.ts +0 -795
  152. package/packages/openclaw-adapter/src/index.test.ts +0 -82
  153. package/packages/openclaw-adapter/src/index.ts +0 -18
  154. package/packages/openclaw-adapter/src/integration.e2e.test.ts +0 -829
  155. package/packages/openclaw-adapter/src/logger.ts +0 -51
  156. package/packages/openclaw-adapter/src/network-client.test.ts +0 -266
  157. package/packages/openclaw-adapter/src/network-client.ts +0 -251
  158. package/packages/openclaw-adapter/src/network-recovery.test.ts +0 -465
  159. package/packages/openclaw-adapter/src/node-manager.test.ts +0 -136
  160. package/packages/openclaw-adapter/src/node-manager.ts +0 -429
  161. package/packages/openclaw-adapter/src/plugin.test.ts +0 -439
  162. package/packages/openclaw-adapter/src/plugin.ts +0 -104
  163. package/packages/openclaw-adapter/src/reputation.test.ts +0 -221
  164. package/packages/openclaw-adapter/src/reputation.ts +0 -368
  165. package/packages/openclaw-adapter/src/task-guard.test.ts +0 -502
  166. package/packages/openclaw-adapter/src/task-guard.ts +0 -860
  167. package/packages/openclaw-adapter/src/task-queue.concurrency.test.ts +0 -462
  168. package/packages/openclaw-adapter/src/task-queue.edge-cases.test.ts +0 -284
  169. package/packages/openclaw-adapter/src/task-queue.persistence.test.ts +0 -408
  170. package/packages/openclaw-adapter/src/task-queue.ts +0 -668
  171. package/packages/openclaw-adapter/src/tool-handlers.test.ts +0 -906
  172. package/packages/openclaw-adapter/src/tool-handlers.ts +0 -574
  173. package/packages/openclaw-adapter/src/types.ts +0 -361
  174. package/packages/openclaw-adapter/src/webhook-pusher.test.ts +0 -188
  175. package/packages/openclaw-adapter/src/webhook-pusher.ts +0 -220
  176. package/packages/openclaw-adapter/src/webhook-server.test.ts +0 -580
  177. package/packages/openclaw-adapter/src/webhook-server.ts +0 -202
  178. package/packages/openclaw-adapter/tsconfig.json +0 -20
  179. package/src/cli/commands.test.ts +0 -157
  180. package/src/cli/commands.ts +0 -129
  181. package/src/cli/index.test.ts +0 -77
  182. package/src/cli/index.ts +0 -234
  183. package/src/core/autonomous-economy.test.ts +0 -291
  184. package/src/core/autonomous-economy.ts +0 -428
  185. package/src/core/e2ee-crypto.test.ts +0 -125
  186. package/src/core/e2ee-crypto.ts +0 -246
  187. package/src/core/f2a.test.ts +0 -269
  188. package/src/core/f2a.ts +0 -618
  189. package/src/core/p2p-network.test.ts +0 -199
  190. package/src/core/p2p-network.ts +0 -1432
  191. package/src/core/reputation-security.test.ts +0 -403
  192. package/src/core/reputation-security.ts +0 -562
  193. package/src/core/reputation.test.ts +0 -260
  194. package/src/core/reputation.ts +0 -576
  195. package/src/core/review-committee.test.ts +0 -380
  196. package/src/core/review-committee.ts +0 -401
  197. package/src/core/token-manager.test.ts +0 -133
  198. package/src/core/token-manager.ts +0 -140
  199. package/src/daemon/control-server.test.ts +0 -216
  200. package/src/daemon/control-server.ts +0 -292
  201. package/src/daemon/index.test.ts +0 -85
  202. package/src/daemon/index.ts +0 -89
  203. package/src/daemon/main.ts +0 -44
  204. package/src/daemon/start.ts +0 -29
  205. package/src/daemon/webhook.test.ts +0 -68
  206. package/src/daemon/webhook.ts +0 -105
  207. package/src/index.test.ts +0 -436
  208. package/src/index.ts +0 -72
  209. package/src/types/index.test.ts +0 -87
  210. package/src/types/index.ts +0 -341
  211. package/src/types/result.ts +0 -68
  212. package/src/utils/benchmark.ts +0 -237
  213. package/src/utils/logger.ts +0 -331
  214. package/src/utils/middleware.ts +0 -229
  215. package/src/utils/rate-limiter.ts +0 -207
  216. package/src/utils/signature.ts +0 -136
  217. package/src/utils/validation.ts +0 -186
  218. package/tests/docker/Dockerfile.node +0 -23
  219. package/tests/docker/Dockerfile.runner +0 -18
  220. package/tests/docker/docker-compose.test.yml +0 -73
  221. package/tests/integration/message-passing.test.ts +0 -109
  222. package/tests/integration/multi-node.test.ts +0 -92
  223. package/tests/integration/p2p-connection.test.ts +0 -83
  224. package/tests/integration/test-config.ts +0 -32
  225. package/tsconfig.json +0 -21
  226. package/vitest.config.ts +0 -26
@@ -1,429 +0,0 @@
1
- /**
2
- * F2A Node Manager
3
- * 管理 F2A Network 服务的生命周期
4
- */
5
-
6
- import { spawn, ChildProcess } from 'child_process';
7
- import { existsSync, readFileSync, writeFileSync, unlinkSync } from 'fs';
8
- import { join } from 'path';
9
- import { promisify } from 'util';
10
- import type { F2ANodeConfig, Result } from './types.js';
11
- import { nodeLogger as logger } from './logger.js';
12
-
13
- const sleep = promisify(setTimeout);
14
-
15
- // PID 文件路径
16
- const PID_FILE_NAME = 'f2a-node.pid';
17
-
18
- /** 健康检查重启配置 */
19
- interface HealthCheckRestartConfig {
20
- /** 最大连续重启次数 */
21
- maxRestarts: number;
22
- /** 重启计数重置时间窗口(毫秒) */
23
- resetWindowMs: number;
24
- /** 冷却期基础时间(毫秒) */
25
- cooldownBaseMs: number;
26
- /** 冷却期最大时间(毫秒) */
27
- cooldownMaxMs: number;
28
- }
29
-
30
- /** 默认重启配置 */
31
- const DEFAULT_RESTART_CONFIG: HealthCheckRestartConfig = {
32
- maxRestarts: 3, // 最多连续重启 3 次
33
- resetWindowMs: 60000, // 1 分钟内重置计数
34
- cooldownBaseMs: 5000, // 冷却期基础 5 秒
35
- cooldownMaxMs: 60000 // 冷却期最大 60 秒
36
- };
37
-
38
- export class F2ANodeManager {
39
- private process: ChildProcess | null = null;
40
- private config: F2ANodeConfig;
41
- private healthCheckInterval?: NodeJS.Timeout;
42
- private pidFilePath: string;
43
-
44
- // P1 修复:健康检查重启限制
45
- private restartConfig: HealthCheckRestartConfig;
46
- private consecutiveRestarts: number = 0;
47
- private lastRestartTime: number = 0;
48
- private isRestarting: boolean = false;
49
-
50
- constructor(config: Partial<F2ANodeConfig>) {
51
- this.config = {
52
- nodePath: config.nodePath || './F2A',
53
- controlPort: config.controlPort || 9001,
54
- controlToken: config.controlToken || this.generateToken(),
55
- p2pPort: config.p2pPort || 9000,
56
- enableMDNS: config.enableMDNS ?? true,
57
- bootstrapPeers: config.bootstrapPeers || []
58
- };
59
- this.pidFilePath = join(this.config.nodePath, PID_FILE_NAME);
60
- this.restartConfig = { ...DEFAULT_RESTART_CONFIG };
61
-
62
- // 启动时清理孤儿进程
63
- this.cleanupOrphanProcesses();
64
- }
65
-
66
- /**
67
- * 清理孤儿进程
68
- * 检查 PID 文件中记录的进程是否仍在运行,如果是则尝试清理
69
- */
70
- private cleanupOrphanProcesses(): void {
71
- if (!existsSync(this.pidFilePath)) {
72
- return;
73
- }
74
-
75
- try {
76
- const pidStr = readFileSync(this.pidFilePath, 'utf-8').trim();
77
- const pid = parseInt(pidStr, 10);
78
-
79
- if (isNaN(pid)) {
80
- // PID 文件无效,删除
81
- unlinkSync(this.pidFilePath);
82
- return;
83
- }
84
-
85
- // 检查进程是否存在
86
- try {
87
- process.kill(pid, 0); // 不实际发送信号,只检查进程是否存在
88
- // 进程存在,尝试终止
89
- logger.info('发现孤儿进程,尝试终止: pid=%d', pid);
90
- try {
91
- process.kill(pid, 'SIGTERM');
92
- // 等待进程终止
93
- setTimeout(() => {
94
- try {
95
- process.kill(pid, 0); // 检查是否还在运行
96
- process.kill(pid, 'SIGKILL'); // 强制终止
97
- } catch {
98
- // 进程已终止
99
- }
100
- }, 3000);
101
- } catch (killError) {
102
- // 无法终止,可能是权限问题
103
- logger.warn('无法终止孤儿进程: pid=%d, error=%s', pid, killError);
104
- }
105
- } catch {
106
- // 进程不存在,只删除 PID 文件
107
- }
108
-
109
- // 删除 PID 文件
110
- unlinkSync(this.pidFilePath);
111
- logger.info('孤儿进程清理完成');
112
- } catch (error) {
113
- logger.warn('清理孤儿进程失败: error=%s', error);
114
- }
115
- }
116
-
117
- /**
118
- * 保存 PID 到文件
119
- */
120
- private savePid(pid: number): void {
121
- try {
122
- writeFileSync(this.pidFilePath, String(pid), { mode: 0o644 });
123
- logger.info('PID 文件已保存: path=%s', this.pidFilePath);
124
- } catch (error) {
125
- logger.warn('保存 PID 文件失败: error=%s', error);
126
- }
127
- }
128
-
129
- /**
130
- * 删除 PID 文件
131
- */
132
- private removePidFile(): void {
133
- try {
134
- if (existsSync(this.pidFilePath)) {
135
- unlinkSync(this.pidFilePath);
136
- }
137
- } catch (error) {
138
- logger.warn('删除 PID 文件失败: error=%s', error);
139
- }
140
- }
141
-
142
- /**
143
- * 确保 F2A Node 在运行
144
- */
145
- async ensureRunning(): Promise<Result<void>> {
146
- if (await this.isRunning()) {
147
- logger.info('Node 已在运行');
148
- return { success: true };
149
- }
150
-
151
- return this.start();
152
- }
153
-
154
- /**
155
- * 启动 F2A Node
156
- */
157
- async start(): Promise<Result<void>> {
158
- const daemonPath = join(this.config.nodePath, 'dist/daemon/index.js');
159
-
160
- if (!existsSync(daemonPath)) {
161
- return {
162
- success: false,
163
- error: `F2A Node 未找到: ${daemonPath}\n请先运行: cd ${this.config.nodePath} && npm install && npm run build`
164
- };
165
- }
166
-
167
- logger.info('启动 Node...');
168
- logger.info('Control Port: %d', this.config.controlPort);
169
- logger.info('P2P Port: %d', this.config.p2pPort);
170
-
171
- try {
172
- this.process = spawn('node', [daemonPath], {
173
- cwd: this.config.nodePath,
174
- env: {
175
- ...process.env,
176
- F2A_CONTROL_PORT: String(this.config.controlPort),
177
- F2A_CONTROL_TOKEN: this.config.controlToken,
178
- F2A_P2P_PORT: String(this.config.p2pPort),
179
- F2A_ENABLE_MDNS: String(this.config.enableMDNS),
180
- F2A_BOOTSTRAP_PEERS: JSON.stringify(this.config.bootstrapPeers)
181
- },
182
- detached: true,
183
- stdio: ['ignore', 'pipe', 'pipe']
184
- });
185
-
186
- // 记录子进程 PID
187
- const pid = this.process.pid;
188
- if (pid) {
189
- this.savePid(pid);
190
- }
191
-
192
- // 监听进程退出事件
193
- this.process.on('exit', (code, signal) => {
194
- logger.info('Node 进程退出: code=%s, signal=%s', code, signal);
195
- this.removePidFile();
196
- this.process = null;
197
- });
198
-
199
- this.process.on('error', (err) => {
200
- logger.error('Node 进程错误: error=%s', err);
201
- this.removePidFile();
202
- });
203
-
204
- this.process.unref();
205
-
206
- // 记录日志
207
- this.process.stdout?.on('data', (data) => {
208
- logger.info('Node stdout: %s', data.toString().trim());
209
- });
210
-
211
- this.process.stderr?.on('data', (data) => {
212
- logger.error('Node stderr: %s', data.toString().trim());
213
- });
214
-
215
- // 等待启动完成
216
- await this.waitForReady(30000);
217
-
218
- // 启动健康检查
219
- this.startHealthCheck();
220
-
221
- // P1 修复:成功启动后重置重启计数器
222
- this.consecutiveRestarts = 0;
223
-
224
- logger.info('Node 启动成功');
225
- return { success: true };
226
-
227
- } catch (error) {
228
- const errorMsg = error instanceof Error ? error.message : String(error);
229
- this.removePidFile();
230
- return { success: false, error: errorMsg };
231
- }
232
- }
233
-
234
- /**
235
- * 停止 F2A Node
236
- */
237
- async stop(): Promise<void> {
238
- if (this.healthCheckInterval) {
239
- clearInterval(this.healthCheckInterval);
240
- }
241
-
242
- if (this.process) {
243
- logger.info('停止 Node...');
244
-
245
- // 尝试优雅关闭
246
- this.process.kill('SIGTERM');
247
-
248
- // 等待 5 秒
249
- await sleep(5000);
250
-
251
- // 如果还在运行,强制关闭
252
- if (this.process.exitCode === null) {
253
- this.process.kill('SIGKILL');
254
- }
255
-
256
- this.process = null;
257
- } else {
258
- // 没有当前进程引用,但可能存在孤儿进程
259
- // 尝试从 PID 文件读取并终止
260
- if (existsSync(this.pidFilePath)) {
261
- try {
262
- const pidStr = readFileSync(this.pidFilePath, 'utf-8').trim();
263
- const pid = parseInt(pidStr, 10);
264
- if (!isNaN(pid)) {
265
- logger.info('尝试终止残留进程: pid=%d', pid);
266
- try {
267
- process.kill(pid, 'SIGTERM');
268
- await sleep(3000);
269
- try {
270
- process.kill(pid, 0);
271
- process.kill(pid, 'SIGKILL');
272
- } catch {
273
- // 进程已终止
274
- }
275
- } catch {
276
- // 进程不存在或无权限
277
- }
278
- }
279
- } catch (error) {
280
- logger.warn('清理残留进程失败: error=%s', error);
281
- }
282
- }
283
- }
284
-
285
- // 清理 PID 文件
286
- this.removePidFile();
287
- }
288
-
289
- /**
290
- * 检查 Node 是否运行中
291
- */
292
- async isRunning(): Promise<boolean> {
293
- try {
294
- const response = await fetch(`http://localhost:${this.config.controlPort}/health`, {
295
- headers: {
296
- 'Authorization': `Bearer ${this.config.controlToken}`
297
- }
298
- });
299
- return response.ok;
300
- } catch {
301
- return false;
302
- }
303
- }
304
-
305
- /**
306
- * 获取 Node 状态
307
- */
308
- async getStatus(): Promise<Result<{
309
- running: boolean;
310
- peerId?: string;
311
- connectedPeers?: number;
312
- uptime?: number;
313
- }>> {
314
- try {
315
- const response = await fetch(`http://localhost:${this.config.controlPort}/status`, {
316
- headers: {
317
- 'Authorization': `Bearer ${this.config.controlToken}`
318
- }
319
- });
320
-
321
- if (!response.ok) {
322
- return { success: false, error: 'Node 未响应' };
323
- }
324
-
325
- const data = await response.json() as { running: boolean; peerId?: string; connectedPeers?: number; uptime?: number };
326
- return { success: true, data };
327
-
328
- } catch (error) {
329
- return {
330
- success: false,
331
- error: error instanceof Error ? error.message : String(error)
332
- };
333
- }
334
- }
335
-
336
- /**
337
- * 等待 Node 就绪
338
- */
339
- private async waitForReady(timeout: number): Promise<void> {
340
- const start = Date.now();
341
-
342
- while (Date.now() - start < timeout) {
343
- if (await this.isRunning()) {
344
- return;
345
- }
346
- await sleep(500);
347
- }
348
-
349
- throw new Error('Node 启动超时');
350
- }
351
-
352
- /**
353
- * 启动健康检查
354
- *
355
- * P1 修复:添加重启次数限制和冷却期,防止无限重启循环
356
- */
357
- private startHealthCheck(): void {
358
- this.healthCheckInterval = setInterval(async () => {
359
- // 如果正在重启,跳过本次检查
360
- if (this.isRestarting) {
361
- return;
362
- }
363
-
364
- const isHealthy = await this.isRunning();
365
- if (!isHealthy && this.process) {
366
- // 检查是否达到重启限制
367
- const now = Date.now();
368
-
369
- // 如果距离上次重启超过重置窗口,重置计数
370
- if (now - this.lastRestartTime > this.restartConfig.resetWindowMs) {
371
- this.consecutiveRestarts = 0;
372
- }
373
-
374
- // 检查是否达到最大重启次数
375
- if (this.consecutiveRestarts >= this.restartConfig.maxRestarts) {
376
- logger.error(
377
- 'Node 健康检查失败,已达到最大重启次数: maxRestarts=%d, resetWindowMs=%d',
378
- this.restartConfig.maxRestarts,
379
- Math.round(this.restartConfig.resetWindowMs / 1000)
380
- );
381
- return;
382
- }
383
-
384
- // 计算冷却期(指数退避)
385
- const cooldownMs = Math.min(
386
- this.restartConfig.cooldownBaseMs * Math.pow(2, this.consecutiveRestarts),
387
- this.restartConfig.cooldownMaxMs
388
- );
389
-
390
- logger.warn(
391
- 'Node 健康检查失败,尝试重启: attempt=%d/%d, cooldownMs=%d',
392
- this.consecutiveRestarts + 1,
393
- this.restartConfig.maxRestarts,
394
- Math.round(cooldownMs / 1000)
395
- );
396
-
397
- this.isRestarting = true;
398
- this.consecutiveRestarts++;
399
- this.lastRestartTime = now;
400
-
401
- try {
402
- await this.stop();
403
- await sleep(cooldownMs);
404
- await this.start();
405
- } catch (error) {
406
- logger.error('重启失败: error=%s', error);
407
- } finally {
408
- this.isRestarting = false;
409
- }
410
- }
411
- }, 30000); // 每 30 秒检查一次
412
- }
413
-
414
- /**
415
- * 生成随机 Token
416
- */
417
- private generateToken(): string {
418
- const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
419
- let token = 'f2a-';
420
- for (let i = 0; i < 32; i++) {
421
- token += chars.charAt(Math.floor(Math.random() * chars.length));
422
- }
423
- return token;
424
- }
425
-
426
- getConfig(): F2ANodeConfig {
427
- return { ...this.config };
428
- }
429
- }