panrouter 5.4.0 → 5.4.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 (2) hide show
  1. package/package.json +1 -1
  2. package/pool-worker.mjs +81 -14
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "panrouter",
3
- "version": "5.4.0",
3
+ "version": "5.4.2",
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,17 +193,23 @@ 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
- stdio: "ignore",
200
- detached: true,
201
- windowsHide: true,
209
+ stdio: "inherit",
202
210
  });
203
- child.unref();
204
211
 
205
- log("新实例已启动,旧进程退出", "OFF");
212
+ log("新实例已在后台启动", "OFF");
206
213
  process.exit(0);
207
214
  }
208
215
 
@@ -296,6 +303,39 @@ function stopHeartbeat() {
296
303
  }
297
304
  }
298
305
 
306
+ // ─── 端口清理 ────────────────────────────────────────────────────────────────
307
+
308
+ function killPort(port) {
309
+ if (process.platform === 'win32') {
310
+ try {
311
+ const out = execSync(`netstat -ano | findstr :${port}`, { stdio: 'pipe', timeout: 3000 }).toString();
312
+ const lines = out.split('\n').filter(l => l.includes('LISTENING'));
313
+ for (const line of lines) {
314
+ const parts = line.trim().split(/\s+/);
315
+ const pid = parts[parts.length - 1];
316
+ if (pid && pid !== '0') {
317
+ try { execSync(`taskkill /F /PID ${pid}`, { stdio: 'pipe' }); log(`已终止端口 ${port} 上的进程 PID ${pid}`, "OK"); } catch {}
318
+ }
319
+ }
320
+ } catch {}
321
+ } else {
322
+ try {
323
+ execSync(`lsof -ti:${port} | xargs -r kill -9 2>/dev/null`, { stdio: 'pipe' });
324
+ } catch {}
325
+ }
326
+ }
327
+
328
+ function killPid(pid) {
329
+ if (!pid) return;
330
+ try {
331
+ if (process.platform === 'win32') {
332
+ execSync(`taskkill /F /PID ${pid}`, { stdio: 'pipe', timeout: 3000 });
333
+ } else {
334
+ try { process.kill(parseInt(pid), 9); } catch {}
335
+ }
336
+ } catch {}
337
+ }
338
+
299
339
  // ─── 检查端口 ────────────────────────────────────────────────────────────────
300
340
 
301
341
  function isPortOpen(port) {
@@ -310,9 +350,16 @@ function isPortOpen(port) {
310
350
  // ─── 确保 server.mjs 在运行 ──────────────────────────────────────────────
311
351
 
312
352
  async function ensureServer() {
353
+ // 不管端口是否被占用,都先杀掉确保用最新代码启动
354
+ // 这是因为 cleanupStaleSelf 可能没清干净,或者旧版 server 残留
313
355
  if (await isPortOpen(SERVER_PORT)) {
314
- log(`端口 ${SERVER_PORT} 已有服务在运行`, "OK");
315
- return true;
356
+ log(`端口 ${SERVER_PORT} 已被占用,正在释放...`, "WARN");
357
+ killPort(SERVER_PORT);
358
+ // 等端口真正释放
359
+ for (let i = 0; i < 10; i++) {
360
+ if (!(await isPortOpen(SERVER_PORT))) break;
361
+ await new Promise((r) => setTimeout(r, 500));
362
+ }
316
363
  }
317
364
 
318
365
  const serverPath = path.join(__dirname, "server.mjs");
@@ -346,10 +393,28 @@ async function ensureServer() {
346
393
 
347
394
  function writePid() {
348
395
  try {
396
+ if (!fs.existsSync(RUNTIME_DIR)) fs.mkdirSync(RUNTIME_DIR, { recursive: true });
349
397
  fs.writeFileSync(PID_FILE, String(process.pid), "utf-8");
350
398
  } catch {}
351
399
  }
352
400
 
401
+ // ─── 清理之前的 pool-worker 实例 ──────────────────────────────────────────
402
+
403
+ function cleanupStaleSelf() {
404
+ // 1. 检查 PID 文件
405
+ try {
406
+ if (fs.existsSync(PID_FILE)) {
407
+ const oldPid = fs.readFileSync(PID_FILE, "utf-8").trim();
408
+ if (oldPid && String(process.pid) !== oldPid) {
409
+ log(`发现之前的 pool-worker (PID ${oldPid}),正在清理...`, "WARN");
410
+ killPid(oldPid);
411
+ }
412
+ }
413
+ } catch {}
414
+ // 2. 清理端口 50816(确保 server.mjs 会以最新版本重启)
415
+ killPort(SERVER_PORT);
416
+ }
417
+
353
418
  // ─── 优雅退出 ────────────────────────────────────────────────────────────────
354
419
 
355
420
  function gracefulShutdown() {
@@ -370,11 +435,10 @@ function gracefulShutdown() {
370
435
  }
371
436
 
372
437
  try { fs.unlinkSync(PID_FILE); } catch {}
438
+ killPort(SERVER_PORT);
373
439
 
374
- setTimeout(() => {
375
- log("节点已安全下线,再见!", "OFF");
376
- process.exit(0);
377
- }, 500);
440
+ log("节点已安全下线,再见!", "OFF");
441
+ process.exit(0);
378
442
  }
379
443
 
380
444
  // ─── 入口 ────────────────────────────────────────────────────────────────────
@@ -385,6 +449,9 @@ export async function start() {
385
449
  log(`设备类型: ${DEVICE_TYPE}`);
386
450
  if (assignedId) log(`已注册编号: ${assignedId}`);
387
451
 
452
+ // 清理残留进程,确保 100% 干净启动
453
+ cleanupStaleSelf();
454
+
388
455
  const serverOk = await ensureServer();
389
456
  if (!serverOk) {
390
457
  log("无法启动代理服务,终止接入", "ERR");