@yeaft/webchat-agent 0.0.168 → 0.0.170

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 (3) hide show
  1. package/connection.js +24 -12
  2. package/crew.js +26 -2
  3. package/package.json +1 -1
package/connection.js CHANGED
@@ -23,7 +23,7 @@ import {
23
23
  import {
24
24
  createCrewSession, handleCrewHumanInput, handleCrewControl,
25
25
  addRoleToSession, removeRoleFromSession,
26
- handleListCrewSessions, handleCheckCrewExists, resumeCrewSession, removeFromCrewIndex
26
+ handleListCrewSessions, handleCheckCrewExists, handleDeleteCrewDir, resumeCrewSession, removeFromCrewIndex
27
27
  } from './crew.js';
28
28
 
29
29
  // 需要在断连期间缓冲的消息类型(Claude 输出相关的关键消息)
@@ -304,6 +304,10 @@ async function handleMessage(msg) {
304
304
  await handleCheckCrewExists(msg);
305
305
  break;
306
306
 
307
+ case 'delete_crew_dir':
308
+ await handleDeleteCrewDir(msg);
309
+ break;
310
+
307
311
  case 'resume_crew_session':
308
312
  await resumeCrewSession(msg);
309
313
  break;
@@ -421,7 +425,8 @@ async function handleMessage(msg) {
421
425
 
422
426
  if (isWindows) {
423
427
  // Windows: 进程持有文件锁,npm install 无法覆盖正在运行的模块文件 (EBUSY)
424
- // 策略:生成 detached bat 脚本,等当前进程退出后再 npm install
428
+ // 策略:生成 detached bat 脚本,等当前进程退出后 pm2 stop(防止 autorestart),
429
+ // 再 npm install
425
430
  const pid = process.pid;
426
431
  const configDir = getConfigDir();
427
432
  mkdirSync(configDir, { recursive: true });
@@ -444,6 +449,13 @@ async function handleMessage(msg) {
444
449
  '',
445
450
  ':: Redirect all output to log file',
446
451
  'echo [Upgrade] Started at %date% %time% > "%LOGFILE%"',
452
+ ];
453
+
454
+ // 先等原始 agent 进程退出。
455
+ // 不能在这之前 pm2 stop,因为 pm2 stop 会发 SIGTERM 杀当前进程,
456
+ // 导致 agent 还没来得及发 upgrade_agent_ack 就被杀掉。
457
+ // PID 退出后再由 bat 脚本执行 pm2 stop 阻止 autorestart。
458
+ batLines.push(
447
459
  ':WAIT_LOOP',
448
460
  'tasklist /FI "PID eq %PID%" 2>NUL | find /I "%PID%" >NUL',
449
461
  'if errorlevel 1 goto PID_EXITED',
@@ -452,17 +464,20 @@ async function handleMessage(msg) {
452
464
  ' echo [Upgrade] Timeout waiting for PID %PID% to exit after 60s >> "%LOGFILE%"',
453
465
  ' goto PID_EXITED',
454
466
  ')',
455
- 'timeout /T 2 /NOBREAK >NUL',
467
+ 'ping -n 3 127.0.0.1 >NUL',
456
468
  'goto WAIT_LOOP',
457
469
  ':PID_EXITED',
458
- ];
470
+ );
459
471
 
460
472
  if (isPm2) {
461
- // pm2 可能已自动重启旧代码(exit 触发),先 stop 释放文件锁
473
+ // PID已退出。但如果 agent pm2 stop 失败,PM2 的 autorestart 可能
474
+ // 已在 restart_delay(5s) 后启动了新实例。这里 pm2 stop 会停掉新实例,
475
+ // 确保 npm install 时无进程锁文件。
462
476
  batLines.push(
463
- 'echo [Upgrade] Stopping pm2 agent to release file locks... >> "%LOGFILE%"',
477
+ 'echo [Upgrade] Stopping pm2 to prevent autorestart... >> "%LOGFILE%"',
464
478
  'call pm2 stop yeaft-agent >> "%LOGFILE%" 2>&1',
465
- 'timeout /T 3 /NOBREAK >NUL',
479
+ ':: Wait for pm2 to fully terminate the process and release file locks',
480
+ 'ping -n 6 127.0.0.1 >NUL',
466
481
  );
467
482
  }
468
483
 
@@ -500,11 +515,6 @@ async function handleMessage(msg) {
500
515
  child.unref();
501
516
  console.log(`[Agent] Spawned upgrade script (PID wait for ${pid}, pm2=${isPm2}, dir=${installDir}): ${batPath}`);
502
517
  sendToServer({ type: 'upgrade_agent_ack', success: true, version: latestVersion, pendingRestart: true });
503
-
504
- if (isPm2) {
505
- // PM2 环境:先同步 pm2 stop 防止 autorestart 竞态,再 exit
506
- try { execSync('pm2 stop yeaft-agent', { timeout: 5000 }); } catch {}
507
- }
508
518
  } else {
509
519
  // Linux/macOS: 生成 detached shell 脚本,先停止服务再升级再启动
510
520
  // 避免在升级过程中 systemd 不断重启已删除的旧版本
@@ -607,6 +617,8 @@ async function handleMessage(msg) {
607
617
  }
608
618
 
609
619
  // 清理并退出,让升级脚本接管
620
+ // 注意: PM2 环境下不能在进程内调用 pm2 stop(会杀死自身进程,后续代码不会执行)
621
+ // 由 bat 脚本在检测到 PID 退出后执行 pm2 stop,阻止 autorestart
610
622
  setTimeout(() => {
611
623
  for (const [, term] of ctx.terminals) {
612
624
  if (term.pty) { try { term.pty.kill(); } catch {} }
package/crew.js CHANGED
@@ -14,7 +14,7 @@
14
14
 
15
15
  import { query, Stream } from './sdk/index.js';
16
16
  import { promises as fs } from 'fs';
17
- import { join } from 'path';
17
+ import { join, isAbsolute } from 'path';
18
18
  import { homedir } from 'os';
19
19
  import { execFile as execFileCb } from 'child_process';
20
20
  import { promisify } from 'util';
@@ -383,12 +383,22 @@ export async function handleListCrewSessions(msg) {
383
383
  });
384
384
  }
385
385
 
386
+ /**
387
+ * 验证 projectDir 路径安全性:必须是绝对路径且不包含路径遍历
388
+ */
389
+ function isValidProjectDir(dir) {
390
+ if (!dir || typeof dir !== 'string') return false;
391
+ if (!isAbsolute(dir)) return false;
392
+ if (/(?:^|[\\/])\.\.(?:[\\/]|$)/.test(dir)) return false;
393
+ return true;
394
+ }
395
+
386
396
  /**
387
397
  * 检查工作目录下是否存在 .crew 目录
388
398
  */
389
399
  export async function handleCheckCrewExists(msg) {
390
400
  const { projectDir, requestId, _requestClientId } = msg;
391
- if (!projectDir) {
401
+ if (!projectDir || !isValidProjectDir(projectDir)) {
392
402
  ctx.sendToServer({
393
403
  type: 'crew_exists_result',
394
404
  requestId,
@@ -440,6 +450,20 @@ export async function handleCheckCrewExists(msg) {
440
450
  }
441
451
  }
442
452
 
453
+ /**
454
+ * 删除工作目录下的 .crew 目录
455
+ */
456
+ export async function handleDeleteCrewDir(msg) {
457
+ const { projectDir, _requestClientId } = msg;
458
+ if (!isValidProjectDir(projectDir)) return;
459
+ const crewDir = join(projectDir, '.crew');
460
+ try {
461
+ await fs.rm(crewDir, { recursive: true, force: true });
462
+ } catch {
463
+ // ignore errors (dir may not exist)
464
+ }
465
+ }
466
+
443
467
  /**
444
468
  * 恢复已停止的 crew session
445
469
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yeaft/webchat-agent",
3
- "version": "0.0.168",
3
+ "version": "0.0.170",
4
4
  "description": "Remote agent for Yeaft WebChat — connects worker machines to the central server",
5
5
  "main": "index.js",
6
6
  "type": "module",