@yeaft/webchat-agent 1.0.251 → 1.0.253

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/cli.js CHANGED
@@ -32,7 +32,9 @@ import {
32
32
  import { applyAgentIdentityToEnv, warnDeprecatedInstanceArg } from './service/config.js';
33
33
  import {
34
34
  buildUpgradeInstallCommand,
35
+ buildUpgradeMetadataUrl,
35
36
  buildUpgradeVersionCommand,
37
+ launchWindowsUpgradeScript,
36
38
  } from './upgrade-command.js';
37
39
 
38
40
  const __dirname = dirname(fileURLToPath(import.meta.url));
@@ -58,7 +60,7 @@ if (command === 'doctor') {
58
60
  process.exit(1);
59
61
  }
60
62
  } else if (command === 'upgrade') {
61
- upgrade();
63
+ await upgrade();
62
64
  } else if (command === '--version' || command === '-v') {
63
65
  console.log(pkg.version);
64
66
  } else if (command === '--help' || command === '-h') {
@@ -464,7 +466,7 @@ function parseAndStart(args) {
464
466
 
465
467
  async function checkForUpdates() {
466
468
  try {
467
- const res = await fetch(`https://registry.npmjs.org/${pkg.name}/latest`);
469
+ const res = await fetch(buildUpgradeMetadataUrl(pkg.name));
468
470
  if (!res.ok) return;
469
471
  const data = await res.json();
470
472
  const latest = data.version;
@@ -477,12 +479,12 @@ async function checkForUpdates() {
477
479
  }
478
480
  }
479
481
 
480
- function upgrade() {
482
+ async function upgrade() {
481
483
  console.log(`Current version: ${pkg.version}`);
482
484
  console.log('Checking for updates...');
483
485
 
484
486
  try {
485
- const latest = execSync(buildUpgradeVersionCommand(pkg.name), { encoding: 'utf-8' }).trim();
487
+ const latest = execSync(buildUpgradeVersionCommand(`${pkg.name}@latest`), { encoding: 'utf-8' }).trim();
486
488
  if (latest === pkg.version) {
487
489
  console.log('Already up to date.');
488
490
  return;
@@ -493,9 +495,9 @@ function upgrade() {
493
495
  // On Windows, the current process locks its own files. npm cannot overwrite
494
496
  // them while this process is running. Spawn a detached bat script that waits
495
497
  // for us to exit, then runs npm install, then optionally restarts the service.
496
- upgradeWindows(latest);
498
+ await upgradeWindows(latest);
497
499
  } else {
498
- execSync(buildUpgradeInstallCommand(`${pkg.name}@latest`), { stdio: 'inherit' });
500
+ execSync(buildUpgradeInstallCommand(`${pkg.name}@${latest}`), { stdio: 'inherit' });
499
501
  console.log(`Successfully upgraded to ${latest}`);
500
502
 
501
503
  // If PM2 is managing yeaft-agent, restart it so the new version takes effect
@@ -517,28 +519,24 @@ function upgrade() {
517
519
  }
518
520
  }
519
521
 
520
- function upgradeWindows(latestVersion) {
522
+ async function upgradeWindows(latestVersion) {
521
523
  const configDir = join(process.env.APPDATA || join(homedir(), 'AppData', 'Roaming'), 'yeaft-agent');
522
524
  mkdirSync(configDir, { recursive: true });
523
525
  const logDir = join(configDir, 'logs');
524
526
  mkdirSync(logDir, { recursive: true });
525
527
  const batPath = join(configDir, 'upgrade-cli.bat');
526
- const vbsPath = join(configDir, 'upgrade-cli.vbs');
528
+ const handoffPath = join(configDir, 'upgrade-cli.started');
527
529
  const logPath = join(logDir, 'upgrade.log');
528
530
  const pid = process.pid;
529
531
  const pkgSpec = `${pkg.name}@${latestVersion}`;
530
532
 
531
- // --- PM2 handling: delete app before exit to prevent auto-restart ---
533
+ // Detect PM2 now, but do not delete it until the updater confirms handoff.
532
534
  let isPm2 = false;
533
535
  const ecoPath = join(configDir, 'ecosystem.config.cjs');
534
536
  try {
535
537
  const pm2List = execSync('pm2 jlist', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
536
538
  const apps = JSON.parse(pm2List);
537
539
  isPm2 = Array.isArray(apps) && apps.some(app => app.name === 'yeaft-agent');
538
- if (isPm2) {
539
- execSync('pm2 delete yeaft-agent', { stdio: 'pipe' });
540
- console.log('PM2 app deleted to prevent auto-restart during upgrade.');
541
- }
542
540
  } catch {
543
541
  // PM2 not installed or not managing yeaft-agent — continue
544
542
  }
@@ -549,12 +547,14 @@ function upgradeWindows(latestVersion) {
549
547
  `set PID=${pid}`,
550
548
  `set PKG=${pkgSpec}`,
551
549
  `set LOGFILE=${logPath}`,
550
+ `set HANDOFF=${handoffPath}`,
552
551
  `set MAX_WAIT=30`,
553
552
  `set COUNT=0`,
554
553
  '',
555
554
  ':: Change to temp dir to avoid EBUSY on cwd',
556
555
  'cd /d "%TEMP%"',
557
556
  '',
557
+ 'echo started>"%HANDOFF%"',
558
558
  'echo [Upgrade] Started at %date% %time% > "%LOGFILE%"',
559
559
  `echo [Upgrade] Version: ${pkg.version} -> ${latestVersion} >> "%LOGFILE%"`,
560
560
  `echo [Upgrade] PM2 managed: ${isPm2 ? 'yes (deleted pre-exit)' : 'no'} >> "%LOGFILE%"`,
@@ -607,28 +607,25 @@ function upgradeWindows(latestVersion) {
607
607
  '',
608
608
  'echo [Upgrade] Finished at %time% >> "%LOGFILE%"',
609
609
  ':CLEANUP',
610
- `del /F /Q "${vbsPath}" 2>NUL`,
610
+ 'del /F /Q "%HANDOFF%" 2>NUL',
611
611
  `del /F /Q "${batPath}" 2>NUL`,
612
612
  );
613
613
 
614
614
  writeFileSync(batPath, batLines.join('\r\n'));
615
615
 
616
- // Use VBScript wrapper to fully detach the bat process from the parent.
617
- // WshShell.Run with 0 (hidden window) and False (don't wait) ensures the bat
618
- // runs completely independently — survives parent exit, no console window flash.
619
- const vbsLines = [
620
- 'Set WshShell = CreateObject("WScript.Shell")',
621
- `WshShell.Run """${batPath}""", 0, False`,
622
- ];
623
- writeFileSync(vbsPath, vbsLines.join('\r\n'));
624
-
625
- spawn('wscript.exe', [vbsPath], {
626
- detached: true,
627
- stdio: 'ignore',
628
- windowsHide: true,
629
- }).unref();
616
+ const launcher = await launchWindowsUpgradeScript({
617
+ batPath,
618
+ handoffPath,
619
+ spawnProcess: spawn,
620
+ onHandoff: isPm2
621
+ ? () => {
622
+ execSync('pm2 delete yeaft-agent', { stdio: 'pipe' });
623
+ console.log('PM2 app deleted after upgrade handoff.');
624
+ }
625
+ : undefined,
626
+ });
630
627
 
631
- console.log(`Upgrade script spawned via VBScript wrapper.`);
628
+ console.log(`Upgrade script spawned via ${launcher}.`);
632
629
  console.log(`This process will exit now. The upgrade will proceed after exit.`);
633
630
  console.log(`Check upgrade log: ${logPath}`);
634
631
  process.exit(0);
@@ -1,10 +1,15 @@
1
1
  import { execFile, execFileSync, spawn } from 'child_process';
2
- import { writeFileSync, mkdirSync, existsSync, cpSync } from 'fs';
2
+ import { writeFileSync, mkdirSync, existsSync } from 'fs';
3
3
  import { join, dirname } from 'path';
4
- import { fileURLToPath } from 'url';
5
4
  import { platform, homedir } from 'os';
6
5
  import ctx from '../context.js';
7
6
  import { getConfigDir, getServiceName, getPm2AppName, getLaunchdPlistPath, DEFAULT_INSTANCE_ID } from '../service.js';
7
+ import {
8
+ buildUpgradeInstallCommand,
9
+ buildUpgradeMetadataArgs,
10
+ buildUpgradeUpdateCommand,
11
+ launchWindowsUpgradeScript,
12
+ } from '../upgrade-command.js';
8
13
  import { sendToServer } from './buffer.js';
9
14
  import { stopAgentHeartbeat } from './heartbeat.js';
10
15
 
@@ -39,24 +44,6 @@ const shellOpt = isWin ? { shell: true, windowsHide: true } : {};
39
44
  const currentPath = process.env.PATH || '/usr/bin:/bin:/usr/sbin:/sbin';
40
45
  const safePath = currentPath.includes(nodeBinDir) ? currentPath : `${nodeBinDir}:${currentPath}`;
41
46
  const safeEnv = { ...process.env, PATH: safePath };
42
- export const PUBLIC_NPM_REGISTRY = 'https://registry.npmjs.org/';
43
-
44
- /**
45
- * Build an npm metadata query that bypasses stale local metadata and registry
46
- * mirrors. Upgrade decisions must use the package actually published to the
47
- * public registry, not a cached `latest` value from a previous release.
48
- */
49
- export function buildNpmMetadataArgs(packageSpec, field) {
50
- return [
51
- 'view',
52
- packageSpec,
53
- field,
54
- `--registry=${PUBLIC_NPM_REGISTRY}`,
55
- '--prefer-online',
56
- '--prefer-offline=false',
57
- '--offline=false',
58
- ];
59
- }
60
47
 
61
48
  /** Return true only when `latestVersion` is semantically newer. */
62
49
  export function isUpgradeAvailable(currentVersion, latestVersion) {
@@ -66,9 +53,9 @@ export function isUpgradeAvailable(currentVersion, latestVersion) {
66
53
  return cmpTuple(latest, current) > 0;
67
54
  }
68
55
 
69
- /** Build the Windows worker invocation with the same registry used for metadata. */
70
- export function buildWindowsWorkerCommand(nodePath) {
71
- return `"${nodePath.replace(/\//g, '\\')}" "%WORKER%" "%PKG%" "%PKG_DIR%" "%LOGFILE%" "${PUBLIC_NPM_REGISTRY}"`;
56
+ /** Build the detached Windows npm invocation used after the Agent exits. */
57
+ export function buildWindowsUpgradeCommand() {
58
+ return `call ${buildUpgradeUpdateCommand('%PKG%')}`;
72
59
  }
73
60
 
74
61
  // Shared cleanup logic for restart/upgrade
@@ -119,7 +106,7 @@ async function fetchRequiredNodeRange(pkgName, version) {
119
106
  const stdout = await new Promise((resolve, reject) => {
120
107
  execFile(
121
108
  npmPath,
122
- buildNpmMetadataArgs(`${pkgName}@${version}`, 'engines.node'),
109
+ buildUpgradeMetadataArgs(`${pkgName}@${version}`, 'engines.node'),
123
110
  { stdio: 'pipe', env: safeEnv, ...shellOpt },
124
111
  (err, out) => { if (err) reject(err); else resolve(out.toString().trim()); },
125
112
  );
@@ -191,10 +178,10 @@ export async function handleUpgradeAgent() {
191
178
  console.log('[Agent] Upgrade requested, checking for updates...');
192
179
  try {
193
180
  const pkgName = ctx.pkgName || '@yeaft/webchat-agent';
194
- // Force a public-registry refresh so stale Windows npm metadata or a lagging
195
- // registry mirror cannot report the installed version as the latest one.
181
+ // Force an online refresh from the company-approved Yeaft registry instead
182
+ // of inheriting a blocked or stale registry from the user's npm config.
196
183
  const latestVersion = await new Promise((resolve, reject) => {
197
- execFile(npmPath, buildNpmMetadataArgs(`${pkgName}@latest`, 'version'), { stdio: 'pipe', env: safeEnv, ...shellOpt }, (err, stdout) => {
184
+ execFile(npmPath, buildUpgradeMetadataArgs(`${pkgName}@latest`, 'version'), { stdio: 'pipe', env: safeEnv, ...shellOpt }, (err, stdout) => {
198
185
  if (err) reject(err); else resolve(stdout.toString().trim());
199
186
  });
200
187
  });
@@ -260,15 +247,15 @@ export async function handleUpgradeAgent() {
260
247
  await spawnUnixUpgradeScript(pkgName, installDir, isGlobalInstall, latestVersion, instanceId);
261
248
  }
262
249
 
263
- // On PM2: delete the app BEFORE exiting so PM2 won't auto-restart the old version.
264
- // The upgrade script will re-register it with `pm2 start <ecosystem>` after replacing files.
250
+ // Windows deletes PM2 inside the verified handoff callback. Unix keeps the
251
+ // existing pre-exit behavior because its detached launcher has no marker.
265
252
  const isPm2 = !!process.env.pm_id;
266
- if (isPm2) {
253
+ if (!isWindows && isPm2) {
267
254
  try {
268
255
  execFileSync(pm2Path, ['delete', getPm2AppName(instanceId)], { stdio: 'pipe', env: safeEnv, ...shellOpt });
269
- console.log(`[Agent] PM2 app deleted to prevent auto-restart during upgrade`);
256
+ console.log('[Agent] PM2 app deleted to prevent auto-restart during upgrade');
270
257
  } catch {
271
- console.log(`[Agent] PM2 delete skipped (app may not be registered)`);
258
+ console.log('[Agent] PM2 delete skipped (app may not be registered)');
272
259
  }
273
260
  }
274
261
 
@@ -287,38 +274,29 @@ async function spawnWindowsUpgradeScript(pkgName, installDir, isGlobalInstall, l
287
274
  const logDir = join(configDir, 'logs');
288
275
  mkdirSync(logDir, { recursive: true });
289
276
  const batPath = join(configDir, 'upgrade.bat');
290
- const vbsPath = join(configDir, 'upgrade.vbs');
277
+ const handoffPath = join(configDir, 'upgrade.started');
291
278
  const logPath = join(logDir, 'upgrade.log');
292
279
  const isPm2 = !!process.env.pm_id;
293
280
  const installDirWin = installDir.replace(/\//g, '\\');
294
281
  const ecoPath = join(configDir, 'ecosystem.config.cjs').replace(/\//g, '\\');
295
282
 
296
- // Copy upgrade-worker-template.js to config dir (runs as CJS there, away from ESM context)
297
- const thisDir = dirname(fileURLToPath(import.meta.url));
298
- const workerSrc = join(thisDir, 'upgrade-worker-template.js');
299
- const workerDst = join(configDir, 'upgrade-worker.js');
300
- cpSync(workerSrc, workerDst);
301
-
302
- // Determine the target package directory inside node_modules
303
- const pkgDir = join(installDir, 'node_modules', ...pkgName.split('/')).replace(/\//g, '\\');
304
-
305
283
  const pm2Win = pm2Path.replace(/\//g, '\\');
306
284
 
307
285
  const batLines = [
308
286
  '@echo off',
309
287
  'setlocal',
310
288
  `set PID=${pid}`,
311
- `set PKG=${pkgName}@${latestVersion}`,
289
+ `set PKG=${pkgName}`,
312
290
  `set INSTALL_DIR=${installDirWin}`,
313
- `set PKG_DIR=${pkgDir}`,
314
291
  `set LOGFILE=${logPath}`,
315
- `set WORKER=${workerDst}`,
292
+ `set HANDOFF=${handoffPath}`,
316
293
  `set MAX_WAIT=30`,
317
294
  `set COUNT=0`,
318
295
  '',
319
296
  ':: Change to temp dir to avoid EBUSY on cwd',
320
297
  'cd /d "%TEMP%"',
321
298
  '',
299
+ 'echo started>"%HANDOFF%"',
322
300
  'echo [Upgrade] Started at %date% %time% > "%LOGFILE%"',
323
301
  `echo [Upgrade] Version: ${ctx.agentVersion} -> ${latestVersion} >> "%LOGFILE%"`,
324
302
  `echo [Upgrade] PM2 managed: ${isPm2 ? 'yes (deleted pre-exit)' : 'no'} >> "%LOGFILE%"`,
@@ -348,18 +326,20 @@ async function spawnWindowsUpgradeScript(pkgName, installDir, isGlobalInstall, l
348
326
  'ping -n 5 127.0.0.1 >NUL',
349
327
  );
350
328
 
351
- // Use Node.js worker for file-level upgrade (avoids EBUSY on directory rename)
329
+ // Run npm only after the Agent has released its package files. The old
330
+ // package-copy worker performed a second dependency install, hid failures,
331
+ // and bypassed npm's supported global-update transaction.
352
332
  batLines.push(
353
- 'echo [Upgrade] Running upgrade worker at %time%... >> "%LOGFILE%"',
354
- buildWindowsWorkerCommand(process.execPath),
333
+ 'echo [Upgrade] Running npm update at %time%... >> "%LOGFILE%"',
334
+ `${buildWindowsUpgradeCommand()} >> "%LOGFILE%" 2>&1`,
355
335
  'if not "%errorlevel%"=="0" (',
356
- ' echo [Upgrade] Worker failed with exit code %errorlevel% at %time% >> "%LOGFILE%"',
357
- ' goto CLEANUP',
336
+ ' echo [Upgrade] npm update failed with exit code %errorlevel% at %time% >> "%LOGFILE%"',
337
+ ' goto RESTART_SERVICE',
358
338
  ')',
359
- 'echo [Upgrade] Worker completed successfully at %time% >> "%LOGFILE%"',
339
+ 'echo [Upgrade] npm update succeeded at %time% >> "%LOGFILE%"',
360
340
  );
361
341
 
362
- batLines.push(':CLEANUP');
342
+ batLines.push(':RESTART_SERVICE');
363
343
 
364
344
  if (isPm2) {
365
345
  // Re-register and start via ecosystem config (PM2 app was deleted pre-exit)
@@ -375,33 +355,28 @@ async function spawnWindowsUpgradeScript(pkgName, installDir, isGlobalInstall, l
375
355
  );
376
356
  }
377
357
 
378
- // Clean up worker, vbs launcher, and bat script
379
358
  batLines.push(
380
359
  '',
381
360
  'echo [Upgrade] Finished at %time% >> "%LOGFILE%"',
382
- `del /F /Q "${workerDst}" 2>NUL`,
383
- `del /F /Q "${vbsPath}" 2>NUL`,
361
+ 'del /F /Q "%HANDOFF%" 2>NUL',
384
362
  `del /F /Q "${batPath}"`,
385
363
  );
386
364
 
387
365
  writeFileSync(batPath, batLines.join('\r\n'));
388
366
 
389
- // Use VBScript wrapper to fully detach the bat process from the parent.
390
- // WshShell.Run with 0 (hidden window) and False (don't wait) ensures the bat
391
- // runs completely independently — survives parent exit, no console window flash.
392
- const vbsLines = [
393
- 'Set WshShell = CreateObject("WScript.Shell")',
394
- `WshShell.Run """${batPath}""", 0, False`,
395
- ];
396
- writeFileSync(vbsPath, vbsLines.join('\r\n'));
397
-
398
- spawn('wscript.exe', [vbsPath], {
399
- detached: true,
400
- stdio: 'ignore',
401
- windowsHide: true,
402
- }).unref();
367
+ const launcher = await launchWindowsUpgradeScript({
368
+ batPath,
369
+ handoffPath,
370
+ spawnProcess: spawn,
371
+ onHandoff: isPm2
372
+ ? () => {
373
+ execFileSync(pm2Path, ['delete', getPm2AppName(instanceId)], { stdio: 'pipe', env: safeEnv, ...shellOpt });
374
+ console.log('[Agent] PM2 app deleted after upgrade handoff');
375
+ }
376
+ : undefined,
377
+ });
403
378
 
404
- console.log(`[Agent] Spawned upgrade via VBScript (PID wait for ${pid}, pm2=${isPm2}, dir=${installDir}): ${batPath}`);
379
+ console.log(`[Agent] Spawned upgrade via ${launcher} (PID wait for ${pid}, pm2=${isPm2}, dir=${installDir}): ${batPath}`);
405
380
  await sendToServer({ type: 'upgrade_agent_ack', success: true, version: latestVersion, pendingRestart: true });
406
381
  }
407
382
 
@@ -463,7 +438,6 @@ export function buildUnixUpgradeScript({
463
438
  '#!/bin/bash',
464
439
  `PID=${pid}`,
465
440
  `PKG="${pkgName}@${targetVersion}"`,
466
- `REGISTRY="${PUBLIC_NPM_REGISTRY}"`,
467
441
  `NPM="${npmPath}"`,
468
442
  `LOGFILE="${join(configDir, 'logs', 'upgrade.log')}"`,
469
443
  `export PATH="${safePath}"`,
@@ -517,9 +491,10 @@ export function buildUnixUpgradeScript({
517
491
  }
518
492
 
519
493
  // npm install (use absolute path via $NPM variable)
494
+ const npmArgs = buildUpgradeInstallCommand('"$PKG"', { global: isGlobalInstall });
520
495
  const npmCmd = isGlobalInstall
521
- ? `"$NPM" install -g "$PKG" --registry="$REGISTRY"`
522
- : `cd "$INSTALL_DIR" && "$NPM" install "$PKG" --registry="$REGISTRY"`;
496
+ ? npmArgs.replace(/^npm /, '"$NPM" ')
497
+ : `cd "$INSTALL_DIR" && ${npmArgs.replace(/^npm /, '"$NPM" ')}`;
523
498
 
524
499
  shLines.push(
525
500
  'echo "[Upgrade] Installing $PKG..."',
@@ -1 +1 @@
1
- {"version":"1.0.251"}
1
+ {"version":"1.0.253"}