@yeaft/webchat-agent 0.0.20 → 0.0.21
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 +122 -9
- package/package.json +1 -1
package/connection.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import WebSocket from 'ws';
|
|
2
|
-
import { execSync, execFile } from 'child_process';
|
|
2
|
+
import { execSync, execFile, spawn } from 'child_process';
|
|
3
|
+
import { writeFileSync, mkdirSync } from 'fs';
|
|
4
|
+
import { join } from 'path';
|
|
5
|
+
import { platform } from 'os';
|
|
3
6
|
import ctx from './context.js';
|
|
7
|
+
import { getConfigDir } from './service.js';
|
|
4
8
|
import { encrypt, decrypt, isEncrypted, decodeKey } from './encryption.js';
|
|
5
9
|
import { handleTerminalCreate, handleTerminalInput, handleTerminalResize, handleTerminalClose } from './terminal.js';
|
|
6
10
|
import { handleProxyHttpRequest, handleProxyWsOpen, handleProxyWsMessage, handleProxyWsClose } from './proxy.js';
|
|
@@ -321,15 +325,124 @@ async function handleMessage(msg) {
|
|
|
321
325
|
return;
|
|
322
326
|
}
|
|
323
327
|
console.log(`[Agent] Upgrading from ${ctx.agentVersion} to ${latestVersion}...`);
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
+
|
|
329
|
+
// 检测安装方式:npm install 的路径包含 node_modules,源码运行则不包含
|
|
330
|
+
const scriptPath = (process.argv[1] || '').replace(/\\/g, '/');
|
|
331
|
+
const nmIndex = scriptPath.lastIndexOf('/node_modules/');
|
|
332
|
+
const isNpmInstall = nmIndex !== -1;
|
|
333
|
+
|
|
334
|
+
if (!isNpmInstall) {
|
|
335
|
+
// 源码运行不支持远程升级(代码在 git repo 中,需要手动 git pull)
|
|
336
|
+
console.log('[Agent] Source-based install detected, remote upgrade not supported.');
|
|
337
|
+
sendToServer({ type: 'upgrade_agent_ack', success: false, error: 'Source-based install: please use git pull to upgrade' });
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// 提取 node_modules 的父目录(即 npm install 执行时的项目目录或全局 prefix)
|
|
342
|
+
// 例如 /usr/lib/node_modules/@yeaft/webchat-agent/cli.js → /usr/lib
|
|
343
|
+
// 例如 C:/Users/x/myproject/node_modules/@yeaft/webchat-agent/cli.js → C:/Users/x/myproject
|
|
344
|
+
const installDir = scriptPath.substring(0, nmIndex);
|
|
345
|
+
|
|
346
|
+
// 判断全局安装 vs 局部安装:全局安装的 installDir 就是 npm global prefix
|
|
347
|
+
const isGlobalInstall = await new Promise((resolve) => {
|
|
348
|
+
execFile('npm', ['prefix', '-g'], { shell: true }, (err, stdout) => {
|
|
349
|
+
if (err) { resolve(false); return; }
|
|
350
|
+
const globalPrefix = stdout.toString().trim().replace(/\\/g, '/');
|
|
351
|
+
resolve(installDir === globalPrefix);
|
|
328
352
|
});
|
|
329
353
|
});
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
//
|
|
354
|
+
|
|
355
|
+
const isWindows = platform() === 'win32';
|
|
356
|
+
// 全局安装用 npm install -g,局部安装在 installDir 下 npm install
|
|
357
|
+
const npmArgs = isGlobalInstall
|
|
358
|
+
? ['install', '-g', `${pkgName}@${latestVersion}`]
|
|
359
|
+
: ['install', `${pkgName}@${latestVersion}`];
|
|
360
|
+
|
|
361
|
+
if (isWindows) {
|
|
362
|
+
// Windows: 进程持有文件锁,npm install 无法覆盖正在运行的模块文件 (EBUSY)
|
|
363
|
+
// 策略:生成 detached bat 脚本,等当前进程退出后再 npm install
|
|
364
|
+
const pid = process.pid;
|
|
365
|
+
const configDir = getConfigDir();
|
|
366
|
+
mkdirSync(configDir, { recursive: true });
|
|
367
|
+
const batPath = join(configDir, 'upgrade.bat');
|
|
368
|
+
const isPm2 = !!process.env.pm_id;
|
|
369
|
+
const installDirWin = installDir.replace(/\//g, '\\');
|
|
370
|
+
|
|
371
|
+
const batLines = [
|
|
372
|
+
'@echo off',
|
|
373
|
+
'setlocal',
|
|
374
|
+
`set PID=${pid}`,
|
|
375
|
+
`set PKG=${pkgName}@${latestVersion}`,
|
|
376
|
+
`set INSTALL_DIR=${installDirWin}`,
|
|
377
|
+
`set MAX_WAIT=30`,
|
|
378
|
+
`set COUNT=0`,
|
|
379
|
+
':WAIT_LOOP',
|
|
380
|
+
'tasklist /FI "PID eq %PID%" 2>NUL | find /I "%PID%" >NUL',
|
|
381
|
+
'if errorlevel 1 goto PID_EXITED',
|
|
382
|
+
'set /A COUNT+=1',
|
|
383
|
+
'if %COUNT% GEQ %MAX_WAIT% (',
|
|
384
|
+
' echo [Upgrade] Timeout waiting for PID %PID% to exit after 60s',
|
|
385
|
+
' goto PID_EXITED',
|
|
386
|
+
')',
|
|
387
|
+
'timeout /T 2 /NOBREAK >NUL',
|
|
388
|
+
'goto WAIT_LOOP',
|
|
389
|
+
':PID_EXITED',
|
|
390
|
+
];
|
|
391
|
+
|
|
392
|
+
if (isPm2) {
|
|
393
|
+
// pm2 可能已自动重启旧代码(exit 1 触发),先 stop 释放文件锁
|
|
394
|
+
batLines.push(
|
|
395
|
+
'echo [Upgrade] Stopping pm2 agent to release file locks...',
|
|
396
|
+
'call pm2 stop yeaft-agent 2>NUL',
|
|
397
|
+
'timeout /T 3 /NOBREAK >NUL',
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const npmBatCmd = isGlobalInstall
|
|
402
|
+
? 'call npm install -g %PKG%'
|
|
403
|
+
: 'cd /d "%INSTALL_DIR%" && call npm install %PKG%';
|
|
404
|
+
|
|
405
|
+
batLines.push(
|
|
406
|
+
'echo [Upgrade] Installing %PKG%...',
|
|
407
|
+
npmBatCmd,
|
|
408
|
+
'if errorlevel 1 (',
|
|
409
|
+
' echo [Upgrade] npm install failed with exit code %errorlevel%',
|
|
410
|
+
') else (',
|
|
411
|
+
' echo [Upgrade] Successfully installed %PKG%',
|
|
412
|
+
')',
|
|
413
|
+
);
|
|
414
|
+
|
|
415
|
+
if (isPm2) {
|
|
416
|
+
batLines.push(
|
|
417
|
+
'echo [Upgrade] Starting agent via pm2...',
|
|
418
|
+
'call pm2 start yeaft-agent',
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
batLines.push(`del /F /Q "${batPath}"`);
|
|
423
|
+
|
|
424
|
+
writeFileSync(batPath, batLines.join('\r\n'));
|
|
425
|
+
const child = spawn('cmd.exe', ['/c', batPath], {
|
|
426
|
+
detached: true,
|
|
427
|
+
stdio: 'ignore',
|
|
428
|
+
windowsHide: true
|
|
429
|
+
});
|
|
430
|
+
child.unref();
|
|
431
|
+
console.log(`[Agent] Spawned upgrade script (PID wait for ${pid}, pm2=${isPm2}, dir=${installDir}): ${batPath}`);
|
|
432
|
+
sendToServer({ type: 'upgrade_agent_ack', success: true, version: latestVersion, pendingRestart: true });
|
|
433
|
+
} else {
|
|
434
|
+
// Linux/macOS: rename 不受文件锁影响,直接升级
|
|
435
|
+
const cwd = isGlobalInstall ? undefined : installDir;
|
|
436
|
+
await new Promise((resolve, reject) => {
|
|
437
|
+
execFile('npm', npmArgs, { cwd, stdio: 'pipe', shell: true }, (err) => {
|
|
438
|
+
if (err) reject(err); else resolve();
|
|
439
|
+
});
|
|
440
|
+
});
|
|
441
|
+
console.log('[Agent] Upgrade successful, restarting...');
|
|
442
|
+
sendToServer({ type: 'upgrade_agent_ack', success: true, version: latestVersion });
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// 清理并退出
|
|
333
446
|
setTimeout(() => {
|
|
334
447
|
for (const [, term] of ctx.terminals) {
|
|
335
448
|
if (term.pty) { try { term.pty.kill(); } catch {} }
|
|
@@ -347,7 +460,7 @@ async function handleMessage(msg) {
|
|
|
347
460
|
ctx.ws.close();
|
|
348
461
|
}
|
|
349
462
|
clearTimeout(ctx.reconnectTimer);
|
|
350
|
-
console.log('[Agent] Cleanup done, exiting
|
|
463
|
+
console.log('[Agent] Cleanup done, exiting...');
|
|
351
464
|
process.exit(1);
|
|
352
465
|
}, 500);
|
|
353
466
|
} catch (e) {
|