chatccc 0.2.156 → 0.2.157

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/src/web-ui.ts +54 -41
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chatccc",
3
- "version": "0.2.156",
3
+ "version": "0.2.157",
4
4
  "description": "Feishu bot bridge for Claude Code",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
package/src/web-ui.ts CHANGED
@@ -158,6 +158,30 @@ function spawnService(): { ok: boolean; pid?: number; error?: string } {
158
158
  }
159
159
  }
160
160
 
161
+ /**
162
+ * 安排一次真正的进程重启:先回复 HTTP 请求,再 spawn 一个延迟启动的子进程,
163
+ * 然后退出当前进程。延迟是为了让当前进程完全退出并释放端口。
164
+ */
165
+ function scheduleRestart(): void {
166
+ const indexPath = join(PROJECT_ROOT, "src", "index.ts");
167
+ if (!existsSync(indexPath)) return;
168
+ if (process.platform === "win32") {
169
+ // Windows: ping 作为 sleep(ping 自己 3 次 ≈ 2 秒延迟)
170
+ spawn("cmd.exe", ["/c", "ping -n 3 127.0.0.1 > nul && npx tsx src/index.ts"], {
171
+ cwd: PROJECT_ROOT,
172
+ detached: true,
173
+ stdio: "ignore",
174
+ windowsHide: true,
175
+ }).unref();
176
+ } else {
177
+ spawn("bash", ["-c", "sleep 2 && npx tsx src/index.ts"], {
178
+ cwd: PROJECT_ROOT,
179
+ detached: true,
180
+ stdio: "ignore",
181
+ }).unref();
182
+ }
183
+ }
184
+
161
185
  function stopService(): { ok: boolean; error?: string } {
162
186
  const pid = getServicePid();
163
187
  if (!pid) return { ok: false, error: "No PID file found" };
@@ -396,23 +420,10 @@ async function handleStartService(_req: IncomingMessage, res: ServerResponse): P
396
420
  }
397
421
 
398
422
  if (path === "reload") {
399
- // service 已经在跑(通常就是当前进程自己)。仅把磁盘上刚保存的 config.json
400
- // 刷进进程内的 export let 常量,不走真重启。下次创建会话时新值即生效。
401
- if (!reloadConfigHook) {
402
- // 没注册 reload hook 视为编程错误:index.ts 必须在 main() 里调用
403
- // setReloadConfigHook();返回 500 让前端有提示,避免静默"看似生效但没生效"。
404
- jsonReply(res, 500, {
405
- ok: false,
406
- error: "reload hook 未注册(应在 main() 调用 setReloadConfigHook)",
407
- });
408
- return;
409
- }
410
- try {
411
- await reloadConfigHook();
412
- jsonReply(res, 200, { ok: true, pid: process.pid, mode: "reload" });
413
- } catch (err) {
414
- jsonReply(res, 500, { ok: false, error: (err as Error).message });
415
- }
423
+ // service 已经在跑,执行真正的进程重启让所有配置(含 WSClient domain)生效
424
+ jsonReply(res, 200, { ok: true, pid: process.pid, mode: "restart" });
425
+ scheduleRestart();
426
+ process.exit(0);
416
427
  return;
417
428
  }
418
429
 
@@ -437,14 +448,10 @@ async function handleStopService(req: IncomingMessage, res: ServerResponse): Pro
437
448
  });
438
449
  }
439
450
 
440
- async function handleRestartService(req: IncomingMessage, res: ServerResponse): Promise<void> {
451
+ async function handleRestartService(_req: IncomingMessage, res: ServerResponse): Promise<void> {
441
452
  jsonReply(res, 200, { ok: true, message: "Restarting..." });
442
- setImmediate(() => {
443
- stopService();
444
- setTimeout(() => {
445
- spawnService();
446
- }, 1000);
447
- });
453
+ scheduleRestart();
454
+ process.exit(0);
448
455
  }
449
456
 
450
457
  async function handleValidate(req: IncomingMessage, res: ServerResponse): Promise<void> {
@@ -1309,20 +1316,20 @@ async function saveAndStart() {
1309
1316
  document.getElementById('btn-save-start').innerHTML = '<span class="spinner"></span> 应用中...';
1310
1317
  var result = await api('/api/start', 'POST');
1311
1318
  if (result.ok) {
1312
- // 后端按 mode 区分场景,前端给出更贴切的 toast:
1313
- // - inplace:setup → service 首次激活,进程内启动飞书 service
1314
- // - reload :service 已经在跑,仅刷新进程内 config(不真重启)
1315
- // - spawn :旧 service 已退出,spawn 新子进程
1316
1319
  var msg;
1317
- if (result.mode === 'reload') {
1318
- msg = '配置已保存并生效(无须重启)';
1320
+ if (result.mode === 'restart') {
1321
+ msg = '配置已保存,服务正在重启…';
1319
1322
  } else if (result.mode === 'inplace') {
1320
1323
  msg = '服务已启动! PID: ' + result.pid;
1321
1324
  } else {
1322
1325
  msg = '服务已启动! PID: ' + (result.pid || '?');
1323
1326
  }
1324
1327
  toast(msg);
1325
- setTimeout(function(){ location.reload(); }, 1500);
1328
+ if (result.mode === 'restart') {
1329
+ pollUntilRunning();
1330
+ } else {
1331
+ setTimeout(function(){ location.reload(); }, 1500);
1332
+ }
1326
1333
  } else {
1327
1334
  toast('保存失败: ' + (result.error || '未知错误'), 'error');
1328
1335
  document.getElementById('btn-save-start').disabled = false;
@@ -1458,16 +1465,21 @@ async function restartService() {
1458
1465
  if (!confirm('确定要重启服务吗?')) return;
1459
1466
  document.getElementById('btn-restart').disabled = true;
1460
1467
  document.getElementById('btn-restart').textContent = '重启中...';
1461
- await api('/api/start', 'POST');
1462
- // Wait and refresh
1463
- setTimeout(async function(){
1464
- var s = await api('/api/status');
1465
- state.running = s.running;
1466
- state.pid = s.pid;
1467
- updateDashboardUI();
1468
- document.getElementById('btn-restart').disabled = false;
1469
- document.getElementById('btn-restart').textContent = '重启';
1470
- }, 2000);
1468
+ await api('/api/restart', 'POST');
1469
+ pollUntilRunning();
1470
+ }
1471
+
1472
+ async function pollUntilRunning() {
1473
+ for (var i = 0; i < 30; i++) {
1474
+ await new Promise(function(r) { setTimeout(r, 1000); });
1475
+ try {
1476
+ var s = await api('/api/status');
1477
+ if (s.running) { location.reload(); return; }
1478
+ } catch(e) {}
1479
+ }
1480
+ toast('重启超时,请在终端手动运行 chatccc', 'error');
1481
+ document.getElementById('btn-restart').disabled = false;
1482
+ document.getElementById('btn-restart').textContent = '重启';
1471
1483
  }
1472
1484
 
1473
1485
  // ---- Edit Modal ----
@@ -1602,6 +1614,7 @@ async function handleRequest(req: IncomingMessage, res: ServerResponse): Promise
1602
1614
  if (url === "/api/status" && method === "GET") return handleGetStatus(req, res);
1603
1615
  if (url === "/api/start" && method === "POST") return handleStartService(req, res);
1604
1616
  if (url === "/api/stop" && method === "POST") return handleStopService(req, res);
1617
+ if (url === "/api/restart" && method === "POST") return handleRestartService(req, res);
1605
1618
  if (url === "/api/validate" && method === "POST") return handleValidate(req, res);
1606
1619
  if (url === "/api/ilink/forget" && method === "POST") return handleForgetIlink(req, res);
1607
1620