panrouter 5.4.0 → 5.4.1

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 (2) hide show
  1. package/package.json +1 -1
  2. package/pool-worker.mjs +80 -10
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "panrouter",
3
- "version": "5.4.0",
3
+ "version": "5.4.1",
4
4
  "description": "让 Claude Code 免费使用 DeepSeek 等模型,无需 API Key",
5
5
  "type": "module",
6
6
  "bin": {
package/pool-worker.mjs CHANGED
@@ -74,6 +74,7 @@ const FINGERPRINT = getFingerprint();
74
74
  const DEVICE_TYPE = detectDeviceType();
75
75
  const SERVER_PORT = 50816;
76
76
  const PID_FILE = path.join(os.tmpdir(), "panrouter-pool-worker.pid");
77
+ const RUNTIME_DIR = path.join(HOME_DIR, ".panrouter-runtime");
77
78
 
78
79
  // ─── 重连参数 ────────────────────────────────────────────────────────────────
79
80
  const WS_RECONNECT_BASE = 1000;
@@ -177,7 +178,7 @@ function scheduleReconnect() {
177
178
 
178
179
  function handleUpgrade() {
179
180
  log("收到主控升级指令,开始升级...", "WARN");
180
- // 断开连接,不再接新任务
181
+ // 断开连接
181
182
  if (ws) {
182
183
  try { ws.close(1000, "Upgrading"); } catch {}
183
184
  ws = null;
@@ -192,8 +193,17 @@ function handleUpgrade() {
192
193
  log(`升级失败: ${e.message},仍尝试重启`, "ERR");
193
194
  }
194
195
 
195
- // 杀死当前进程,由外部进程管理器/用户重新启动
196
- // 通过 spawn 启动自身的新实例,然后退出当前进程
196
+ // 写 PID 文件让新进程可追踪(旧 PID 文件被 cleanupStaleSelf 覆盖)
197
+ try {
198
+ if (!fs.existsSync(RUNTIME_DIR)) fs.mkdirSync(RUNTIME_DIR, { recursive: true });
199
+ fs.writeFileSync(PID_FILE, String(process.pid), "utf-8");
200
+ } catch {}
201
+
202
+ // 清理旧的 server.mjs,确保新实例用最新代码
203
+ log("正在重启代理服务...");
204
+ killPort(SERVER_PORT);
205
+
206
+ // 启动新后台实例,完全脱离当前终端
197
207
  const child = spawn(process.execPath, [process.argv[1], "--pool"], {
198
208
  cwd: __dirname,
199
209
  stdio: "ignore",
@@ -202,7 +212,7 @@ function handleUpgrade() {
202
212
  });
203
213
  child.unref();
204
214
 
205
- log("新实例已启动,旧进程退出", "OFF");
215
+ log("新实例已在后台启动", "OFF");
206
216
  process.exit(0);
207
217
  }
208
218
 
@@ -296,6 +306,39 @@ function stopHeartbeat() {
296
306
  }
297
307
  }
298
308
 
309
+ // ─── 端口清理 ────────────────────────────────────────────────────────────────
310
+
311
+ function killPort(port) {
312
+ if (process.platform === 'win32') {
313
+ try {
314
+ const out = execSync(`netstat -ano | findstr :${port}`, { stdio: 'pipe', timeout: 3000 }).toString();
315
+ const lines = out.split('\n').filter(l => l.includes('LISTENING'));
316
+ for (const line of lines) {
317
+ const parts = line.trim().split(/\s+/);
318
+ const pid = parts[parts.length - 1];
319
+ if (pid && pid !== '0') {
320
+ try { execSync(`taskkill /F /PID ${pid}`, { stdio: 'pipe' }); log(`已终止端口 ${port} 上的进程 PID ${pid}`, "OK"); } catch {}
321
+ }
322
+ }
323
+ } catch {}
324
+ } else {
325
+ try {
326
+ execSync(`lsof -ti:${port} | xargs -r kill -9 2>/dev/null`, { stdio: 'pipe' });
327
+ } catch {}
328
+ }
329
+ }
330
+
331
+ function killPid(pid) {
332
+ if (!pid) return;
333
+ try {
334
+ if (process.platform === 'win32') {
335
+ execSync(`taskkill /F /PID ${pid}`, { stdio: 'pipe', timeout: 3000 });
336
+ } else {
337
+ try { process.kill(parseInt(pid), 9); } catch {}
338
+ }
339
+ } catch {}
340
+ }
341
+
299
342
  // ─── 检查端口 ────────────────────────────────────────────────────────────────
300
343
 
301
344
  function isPortOpen(port) {
@@ -310,9 +353,16 @@ function isPortOpen(port) {
310
353
  // ─── 确保 server.mjs 在运行 ──────────────────────────────────────────────
311
354
 
312
355
  async function ensureServer() {
356
+ // 不管端口是否被占用,都先杀掉确保用最新代码启动
357
+ // 这是因为 cleanupStaleSelf 可能没清干净,或者旧版 server 残留
313
358
  if (await isPortOpen(SERVER_PORT)) {
314
- log(`端口 ${SERVER_PORT} 已有服务在运行`, "OK");
315
- return true;
359
+ log(`端口 ${SERVER_PORT} 已被占用,正在释放...`, "WARN");
360
+ killPort(SERVER_PORT);
361
+ // 等端口真正释放
362
+ for (let i = 0; i < 10; i++) {
363
+ if (!(await isPortOpen(SERVER_PORT))) break;
364
+ await new Promise((r) => setTimeout(r, 500));
365
+ }
316
366
  }
317
367
 
318
368
  const serverPath = path.join(__dirname, "server.mjs");
@@ -346,10 +396,28 @@ async function ensureServer() {
346
396
 
347
397
  function writePid() {
348
398
  try {
399
+ if (!fs.existsSync(RUNTIME_DIR)) fs.mkdirSync(RUNTIME_DIR, { recursive: true });
349
400
  fs.writeFileSync(PID_FILE, String(process.pid), "utf-8");
350
401
  } catch {}
351
402
  }
352
403
 
404
+ // ─── 清理之前的 pool-worker 实例 ──────────────────────────────────────────
405
+
406
+ function cleanupStaleSelf() {
407
+ // 1. 检查 PID 文件
408
+ try {
409
+ if (fs.existsSync(PID_FILE)) {
410
+ const oldPid = fs.readFileSync(PID_FILE, "utf-8").trim();
411
+ if (oldPid && String(process.pid) !== oldPid) {
412
+ log(`发现之前的 pool-worker (PID ${oldPid}),正在清理...`, "WARN");
413
+ killPid(oldPid);
414
+ }
415
+ }
416
+ } catch {}
417
+ // 2. 清理端口 50816(确保 server.mjs 会以最新版本重启)
418
+ killPort(SERVER_PORT);
419
+ }
420
+
353
421
  // ─── 优雅退出 ────────────────────────────────────────────────────────────────
354
422
 
355
423
  function gracefulShutdown() {
@@ -370,11 +438,10 @@ function gracefulShutdown() {
370
438
  }
371
439
 
372
440
  try { fs.unlinkSync(PID_FILE); } catch {}
441
+ killPort(SERVER_PORT);
373
442
 
374
- setTimeout(() => {
375
- log("节点已安全下线,再见!", "OFF");
376
- process.exit(0);
377
- }, 500);
443
+ log("节点已安全下线,再见!", "OFF");
444
+ process.exit(0);
378
445
  }
379
446
 
380
447
  // ─── 入口 ────────────────────────────────────────────────────────────────────
@@ -385,6 +452,9 @@ export async function start() {
385
452
  log(`设备类型: ${DEVICE_TYPE}`);
386
453
  if (assignedId) log(`已注册编号: ${assignedId}`);
387
454
 
455
+ // 清理残留进程,确保 100% 干净启动
456
+ cleanupStaleSelf();
457
+
388
458
  const serverOk = await ensureServer();
389
459
  if (!serverOk) {
390
460
  log("无法启动代理服务,终止接入", "ERR");