@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.
- package/connection.js +24 -12
- package/crew.js +26 -2
- 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
|
|
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
|
-
'
|
|
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
|
-
//
|
|
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
|
|
477
|
+
'echo [Upgrade] Stopping pm2 to prevent autorestart... >> "%LOGFILE%"',
|
|
464
478
|
'call pm2 stop yeaft-agent >> "%LOGFILE%" 2>&1',
|
|
465
|
-
'
|
|
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
|
*/
|