@yeaft/webchat-agent 1.0.330 → 1.0.332
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 +71 -101
- package/connection/upgrade.js +45 -97
- package/local-runtime/server/db/session-ui-metadata-db.js +37 -33
- package/local-runtime/server/db/yeaft-project-db.js +15 -4
- package/local-runtime/server/handlers/client-conversation.js +41 -37
- package/local-runtime/server/session-catalog.js +13 -1
- package/local-runtime/version.json +1 -1
- package/local-runtime/web/app.bundle.js +101 -93
- package/local-runtime/web/app.bundle.js.gz +0 -0
- package/local-runtime/web/index.html +2 -2
- package/local-runtime/web/style.bundle.css +1 -1
- package/local-runtime/web/style.bundle.css.gz +0 -0
- package/package.json +1 -1
- package/upgrade-command.js +62 -39
- package/windows-upgrade-runner.js +196 -0
package/cli.js
CHANGED
|
@@ -6,13 +6,13 @@
|
|
|
6
6
|
import { assertNodeVersion } from './check-node-version.js';
|
|
7
7
|
assertNodeVersion({ component: '@yeaft/webchat-agent' });
|
|
8
8
|
|
|
9
|
-
import { execSync, spawn } from 'child_process';
|
|
9
|
+
import { execFileSync, execSync, spawn } from 'child_process';
|
|
10
10
|
import { createInterface } from 'readline/promises';
|
|
11
11
|
import { stdin as input, stdout as output } from 'process';
|
|
12
|
-
import { readFileSync,
|
|
12
|
+
import { readFileSync, mkdirSync } from 'fs';
|
|
13
13
|
import { dirname, join } from 'path';
|
|
14
14
|
import { fileURLToPath } from 'url';
|
|
15
|
-
import { platform
|
|
15
|
+
import { platform } from 'os';
|
|
16
16
|
import {
|
|
17
17
|
addOrUpdateProvider,
|
|
18
18
|
formatLlmConfig,
|
|
@@ -29,12 +29,22 @@ import {
|
|
|
29
29
|
discoverOpenAICompatibleModels,
|
|
30
30
|
GITHUB_COPILOT_PROVIDER,
|
|
31
31
|
} from './llm-model-discovery.js';
|
|
32
|
-
import {
|
|
32
|
+
import {
|
|
33
|
+
DEFAULT_INSTANCE_ID,
|
|
34
|
+
applyAgentIdentityToEnv,
|
|
35
|
+
getConfigDir,
|
|
36
|
+
getPm2AppName,
|
|
37
|
+
resolveServiceInstanceId,
|
|
38
|
+
warnDeprecatedInstanceArg,
|
|
39
|
+
} from './service/config.js';
|
|
33
40
|
import {
|
|
34
41
|
buildUpgradeInstallCommand,
|
|
35
42
|
buildUpgradeMetadataUrl,
|
|
36
43
|
buildUpgradeVersionCommand,
|
|
37
44
|
launchWindowsUpgradeScript,
|
|
45
|
+
prepareWindowsUpgradeRunner,
|
|
46
|
+
resolveWindowsNpmCliPath,
|
|
47
|
+
resolveWindowsPm2CliPath,
|
|
38
48
|
} from './upgrade-command.js';
|
|
39
49
|
|
|
40
50
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
@@ -60,7 +70,7 @@ if (command === 'doctor') {
|
|
|
60
70
|
process.exit(1);
|
|
61
71
|
}
|
|
62
72
|
} else if (command === 'upgrade') {
|
|
63
|
-
await upgrade();
|
|
73
|
+
await upgrade(subArgs);
|
|
64
74
|
} else if (command === '--version' || command === '-v') {
|
|
65
75
|
console.log(pkg.version);
|
|
66
76
|
} else if (command === '--help' || command === '-h') {
|
|
@@ -88,7 +98,7 @@ function printHelp() {
|
|
|
88
98
|
yeaft-agent logs [options] View service logs (follow mode)
|
|
89
99
|
yeaft-agent doctor Diagnose service configuration
|
|
90
100
|
yeaft-agent llm <command> Configure local Yeaft LLM providers/models
|
|
91
|
-
yeaft-agent upgrade
|
|
101
|
+
yeaft-agent upgrade [--name <id>] Upgrade and restart the selected service
|
|
92
102
|
yeaft-agent --version Show version
|
|
93
103
|
|
|
94
104
|
Options:
|
|
@@ -486,7 +496,9 @@ async function checkForUpdates() {
|
|
|
486
496
|
}
|
|
487
497
|
}
|
|
488
498
|
|
|
489
|
-
async function upgrade() {
|
|
499
|
+
async function upgrade(args = []) {
|
|
500
|
+
warnDeprecatedInstanceArg(args);
|
|
501
|
+
const instanceId = resolveServiceInstanceId(args, process.env, { management: true });
|
|
490
502
|
console.log(`Current version: ${pkg.version}`);
|
|
491
503
|
console.log('Checking for updates...');
|
|
492
504
|
|
|
@@ -500,9 +512,9 @@ async function upgrade() {
|
|
|
500
512
|
|
|
501
513
|
if (platform() === 'win32') {
|
|
502
514
|
// On Windows, the current process locks its own files. npm cannot overwrite
|
|
503
|
-
// them while this process is running. Spawn a detached
|
|
504
|
-
// for us to exit, then runs npm install
|
|
505
|
-
await upgradeWindows(latest);
|
|
515
|
+
// them while this process is running. Spawn a detached Node runner that waits
|
|
516
|
+
// for us to exit, then runs npm install and optionally restarts the service.
|
|
517
|
+
await upgradeWindows(latest, instanceId);
|
|
506
518
|
} else {
|
|
507
519
|
execSync(buildUpgradeInstallCommand(`${pkg.name}@${latest}`), { stdio: 'inherit' });
|
|
508
520
|
console.log(`Successfully upgraded to ${latest}`);
|
|
@@ -526,114 +538,72 @@ async function upgrade() {
|
|
|
526
538
|
}
|
|
527
539
|
}
|
|
528
540
|
|
|
529
|
-
async function upgradeWindows(latestVersion) {
|
|
530
|
-
const configDir =
|
|
531
|
-
|
|
541
|
+
async function upgradeWindows(latestVersion, instanceId = DEFAULT_INSTANCE_ID) {
|
|
542
|
+
const configDir = getConfigDir(instanceId);
|
|
543
|
+
const upgradeDir = join(configDir, 'upgrade-runtime');
|
|
532
544
|
const logDir = join(configDir, 'logs');
|
|
533
545
|
mkdirSync(logDir, { recursive: true });
|
|
534
|
-
|
|
535
|
-
const
|
|
546
|
+
|
|
547
|
+
const runnerPath = join(upgradeDir, 'windows-upgrade-runner.js');
|
|
548
|
+
const commandPath = join(upgradeDir, 'upgrade-command.js');
|
|
549
|
+
const payloadPath = join(upgradeDir, 'payload.json');
|
|
550
|
+
const handoffPath = join(upgradeDir, 'started');
|
|
536
551
|
const logPath = join(logDir, 'upgrade.log');
|
|
537
|
-
const
|
|
538
|
-
const
|
|
552
|
+
const ecosystemPath = join(configDir, 'ecosystem.config.cjs');
|
|
553
|
+
const pm2AppName = getPm2AppName(instanceId);
|
|
554
|
+
const npmCliPath = resolveWindowsNpmCliPath(process.execPath);
|
|
555
|
+
if (!npmCliPath) throw new Error('npm JavaScript CLI entry point could not be resolved');
|
|
556
|
+
const pm2CliPath = resolveWindowsPm2CliPath(process.execPath);
|
|
539
557
|
|
|
540
|
-
// Detect PM2 now, but do not delete it until the updater confirms handoff.
|
|
541
558
|
let isPm2 = false;
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
} catch {
|
|
548
|
-
// PM2 not installed or not managing yeaft-agent — continue
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
const batLines = [
|
|
552
|
-
'@echo off',
|
|
553
|
-
'setlocal',
|
|
554
|
-
`set PID=${pid}`,
|
|
555
|
-
`set PKG=${pkgSpec}`,
|
|
556
|
-
`set LOGFILE=${logPath}`,
|
|
557
|
-
`set HANDOFF=${handoffPath}`,
|
|
558
|
-
`set MAX_WAIT=30`,
|
|
559
|
-
`set COUNT=0`,
|
|
560
|
-
'',
|
|
561
|
-
':: Change to temp dir to avoid EBUSY on cwd',
|
|
562
|
-
'cd /d "%TEMP%"',
|
|
563
|
-
'',
|
|
564
|
-
'echo started>"%HANDOFF%"',
|
|
565
|
-
'echo [Upgrade] Started at %date% %time% > "%LOGFILE%"',
|
|
566
|
-
`echo [Upgrade] Version: ${pkg.version} -> ${latestVersion} >> "%LOGFILE%"`,
|
|
567
|
-
`echo [Upgrade] PM2 managed: ${isPm2 ? 'yes (deleted pre-exit)' : 'no'} >> "%LOGFILE%"`,
|
|
568
|
-
'echo [Upgrade] Waiting for CLI process (PID %PID%) to exit... >> "%LOGFILE%"',
|
|
569
|
-
'',
|
|
570
|
-
':WAIT_LOOP',
|
|
571
|
-
'tasklist /FI "PID eq %PID%" /NH 2>NUL | findstr /C:"%PID%" >NUL',
|
|
572
|
-
'if errorlevel 1 goto PID_EXITED',
|
|
573
|
-
'set /A COUNT+=1',
|
|
574
|
-
'if %COUNT% GEQ %MAX_WAIT% (',
|
|
575
|
-
' echo [Upgrade] Timeout waiting for PID %PID% to exit after %MAX_WAIT%s >> "%LOGFILE%"',
|
|
576
|
-
' goto PID_EXITED',
|
|
577
|
-
')',
|
|
578
|
-
'ping -n 3 127.0.0.1 >NUL',
|
|
579
|
-
'goto WAIT_LOOP',
|
|
580
|
-
':PID_EXITED',
|
|
581
|
-
'',
|
|
582
|
-
':: Extra wait for file locks to release',
|
|
583
|
-
'echo [Upgrade] Process exited at %time%, waiting for file locks... >> "%LOGFILE%"',
|
|
584
|
-
'ping -n 5 127.0.0.1 >NUL',
|
|
585
|
-
'',
|
|
586
|
-
'echo [Upgrade] Running npm install -g %PKG%... >> "%LOGFILE%"',
|
|
587
|
-
`call ${buildUpgradeInstallCommand('%PKG%')} >> "%LOGFILE%" 2>&1`,
|
|
588
|
-
'if not "%errorlevel%"=="0" (',
|
|
589
|
-
' echo [Upgrade] npm install failed with exit code %errorlevel% at %time% >> "%LOGFILE%"',
|
|
590
|
-
' goto PM2_RESTART',
|
|
591
|
-
')',
|
|
592
|
-
'echo [Upgrade] npm install succeeded at %time% >> "%LOGFILE%"',
|
|
593
|
-
];
|
|
594
|
-
|
|
595
|
-
// PM2 re-registration after successful upgrade
|
|
596
|
-
batLines.push(
|
|
597
|
-
'',
|
|
598
|
-
':PM2_RESTART',
|
|
599
|
-
);
|
|
600
|
-
if (isPm2) {
|
|
601
|
-
batLines.push(
|
|
602
|
-
'echo [Upgrade] Re-registering agent via PM2... >> "%LOGFILE%"',
|
|
603
|
-
`if exist "${ecoPath}" (`,
|
|
604
|
-
` call pm2 start "${ecoPath}" >> "%LOGFILE%" 2>&1`,
|
|
605
|
-
' call pm2 save >> "%LOGFILE%" 2>&1',
|
|
606
|
-
' echo [Upgrade] PM2 app re-registered at %time% >> "%LOGFILE%"',
|
|
607
|
-
') else (',
|
|
608
|
-
' echo [Upgrade] WARNING: ecosystem.config.cjs not found, PM2 not restarted >> "%LOGFILE%"',
|
|
609
|
-
')',
|
|
610
|
-
);
|
|
559
|
+
if (pm2CliPath) {
|
|
560
|
+
try {
|
|
561
|
+
const apps = JSON.parse(execFileSync(process.execPath, [pm2CliPath, 'jlist'], { encoding: 'utf8' }));
|
|
562
|
+
isPm2 = Array.isArray(apps) && apps.some(app => app.name === pm2AppName);
|
|
563
|
+
} catch {}
|
|
611
564
|
}
|
|
612
565
|
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
566
|
+
const payload = {
|
|
567
|
+
parentPid: process.pid,
|
|
568
|
+
packageSpec: `${pkg.name}@${latestVersion}`,
|
|
569
|
+
globalInstall: true,
|
|
570
|
+
installDir: dirname(dirname(__dirname)),
|
|
571
|
+
logPath,
|
|
572
|
+
handoffPath,
|
|
573
|
+
runnerPath,
|
|
574
|
+
commandPath,
|
|
575
|
+
payloadPath,
|
|
576
|
+
nodePath: process.execPath,
|
|
577
|
+
npmCliPath,
|
|
578
|
+
pm2CliPath: isPm2 ? pm2CliPath : null,
|
|
579
|
+
ecosystemPath: isPm2 ? ecosystemPath : null,
|
|
580
|
+
};
|
|
581
|
+
prepareWindowsUpgradeRunner({
|
|
582
|
+
sourceRunnerPath: join(__dirname, 'windows-upgrade-runner.js'),
|
|
583
|
+
sourceCommandPath: join(__dirname, 'upgrade-command.js'),
|
|
584
|
+
runnerPath,
|
|
585
|
+
commandPath,
|
|
586
|
+
payloadPath,
|
|
587
|
+
payload,
|
|
588
|
+
});
|
|
622
589
|
|
|
623
590
|
const launcher = await launchWindowsUpgradeScript({
|
|
624
|
-
|
|
591
|
+
nodePath: process.execPath,
|
|
592
|
+
runnerPath,
|
|
593
|
+
payloadPath,
|
|
594
|
+
logPath,
|
|
625
595
|
handoffPath,
|
|
626
596
|
spawnProcess: spawn,
|
|
627
597
|
onHandoff: isPm2
|
|
628
598
|
? () => {
|
|
629
|
-
|
|
630
|
-
console.log(
|
|
599
|
+
execFileSync(process.execPath, [pm2CliPath, 'delete', pm2AppName], { stdio: 'pipe' });
|
|
600
|
+
console.log(`PM2 app ${pm2AppName} deleted after upgrade handoff.`);
|
|
631
601
|
}
|
|
632
602
|
: undefined,
|
|
633
603
|
});
|
|
634
604
|
|
|
635
|
-
console.log(`Upgrade
|
|
636
|
-
console.log(
|
|
605
|
+
console.log(`Upgrade runner spawned via ${launcher}.`);
|
|
606
|
+
console.log('This process will exit now. The upgrade will proceed after exit.');
|
|
637
607
|
console.log(`Check upgrade log: ${logPath}`);
|
|
638
608
|
process.exit(0);
|
|
639
609
|
}
|
package/connection/upgrade.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { execFile, execFileSync, spawn } from 'child_process';
|
|
2
2
|
import { writeFileSync, mkdirSync, existsSync } from 'fs';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
3
4
|
import { join, dirname } from 'path';
|
|
4
5
|
import { platform, homedir } from 'os';
|
|
5
6
|
import ctx from '../context.js';
|
|
@@ -7,8 +8,10 @@ import { getConfigDir, getServiceName, getPm2AppName, getLaunchdPlistPath, DEFAU
|
|
|
7
8
|
import {
|
|
8
9
|
buildUpgradeInstallCommand,
|
|
9
10
|
buildUpgradeMetadataArgs,
|
|
10
|
-
buildUpgradeUpdateCommand,
|
|
11
11
|
launchWindowsUpgradeScript,
|
|
12
|
+
prepareWindowsUpgradeRunner,
|
|
13
|
+
resolveWindowsNpmCliPath,
|
|
14
|
+
resolveWindowsPm2CliPath,
|
|
12
15
|
} from '../upgrade-command.js';
|
|
13
16
|
import { sendToServer } from './buffer.js';
|
|
14
17
|
import { stopAgentHeartbeat } from './heartbeat.js';
|
|
@@ -53,11 +56,6 @@ export function isUpgradeAvailable(currentVersion, latestVersion) {
|
|
|
53
56
|
return cmpTuple(latest, current) > 0;
|
|
54
57
|
}
|
|
55
58
|
|
|
56
|
-
/** Build the detached Windows npm invocation used after the Agent exits. */
|
|
57
|
-
export function buildWindowsUpgradeCommand() {
|
|
58
|
-
return `call ${buildUpgradeUpdateCommand('%PKG%')}`;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
59
|
// Shared cleanup logic for restart/upgrade
|
|
62
60
|
function cleanupAndExit(exitCode) {
|
|
63
61
|
setTimeout(async () => {
|
|
@@ -268,115 +266,65 @@ export async function handleUpgradeAgent() {
|
|
|
268
266
|
}
|
|
269
267
|
|
|
270
268
|
async function spawnWindowsUpgradeScript(pkgName, installDir, isGlobalInstall, latestVersion, instanceId = DEFAULT_INSTANCE_ID) {
|
|
271
|
-
const pid = process.pid;
|
|
272
269
|
const configDir = getConfigDir(instanceId);
|
|
273
|
-
|
|
270
|
+
const upgradeDir = join(configDir, 'upgrade-runtime');
|
|
274
271
|
const logDir = join(configDir, 'logs');
|
|
275
272
|
mkdirSync(logDir, { recursive: true });
|
|
276
|
-
|
|
277
|
-
const
|
|
273
|
+
|
|
274
|
+
const runnerPath = join(upgradeDir, 'windows-upgrade-runner.js');
|
|
275
|
+
const commandPath = join(upgradeDir, 'upgrade-command.js');
|
|
276
|
+
const payloadPath = join(upgradeDir, 'payload.json');
|
|
277
|
+
const handoffPath = join(upgradeDir, 'started');
|
|
278
278
|
const logPath = join(logDir, 'upgrade.log');
|
|
279
|
+
const ecosystemPath = join(configDir, 'ecosystem.config.cjs');
|
|
280
|
+
const npmCliPath = resolveWindowsNpmCliPath(process.execPath);
|
|
281
|
+
if (!npmCliPath) throw new Error('npm JavaScript CLI entry point could not be resolved');
|
|
279
282
|
const isPm2 = !!process.env.pm_id;
|
|
280
|
-
const
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
const pm2Win = pm2Path.replace(/\//g, '\\');
|
|
284
|
-
|
|
285
|
-
const batLines = [
|
|
286
|
-
'@echo off',
|
|
287
|
-
'setlocal',
|
|
288
|
-
`set PID=${pid}`,
|
|
289
|
-
`set PKG=${pkgName}`,
|
|
290
|
-
`set INSTALL_DIR=${installDirWin}`,
|
|
291
|
-
`set LOGFILE=${logPath}`,
|
|
292
|
-
`set HANDOFF=${handoffPath}`,
|
|
293
|
-
`set MAX_WAIT=30`,
|
|
294
|
-
`set COUNT=0`,
|
|
295
|
-
'',
|
|
296
|
-
':: Change to temp dir to avoid EBUSY on cwd',
|
|
297
|
-
'cd /d "%TEMP%"',
|
|
298
|
-
'',
|
|
299
|
-
'echo started>"%HANDOFF%"',
|
|
300
|
-
'echo [Upgrade] Started at %date% %time% > "%LOGFILE%"',
|
|
301
|
-
`echo [Upgrade] Version: ${ctx.agentVersion} -> ${latestVersion} >> "%LOGFILE%"`,
|
|
302
|
-
`echo [Upgrade] PM2 managed: ${isPm2 ? 'yes (deleted pre-exit)' : 'no'} >> "%LOGFILE%"`,
|
|
303
|
-
`echo [Upgrade] Install dir: ${installDirWin} >> "%LOGFILE%"`,
|
|
304
|
-
];
|
|
305
|
-
|
|
306
|
-
// Wait for old process to exit (PM2 already deleted before exit, so no auto-restart race)
|
|
307
|
-
batLines.push(
|
|
308
|
-
'echo [Upgrade] Waiting for PID %PID% to exit... >> "%LOGFILE%"',
|
|
309
|
-
':WAIT_LOOP',
|
|
310
|
-
'tasklist /FI "PID eq %PID%" /NH 2>NUL | findstr /C:"%PID%" >NUL',
|
|
311
|
-
'if errorlevel 1 goto PID_EXITED',
|
|
312
|
-
'set /A COUNT+=1',
|
|
313
|
-
'if %COUNT% GEQ %MAX_WAIT% (',
|
|
314
|
-
' echo [Upgrade] Timeout waiting for PID %PID% to exit after %MAX_WAIT%s >> "%LOGFILE%"',
|
|
315
|
-
' goto PID_EXITED',
|
|
316
|
-
')',
|
|
317
|
-
'ping -n 3 127.0.0.1 >NUL',
|
|
318
|
-
'goto WAIT_LOOP',
|
|
319
|
-
':PID_EXITED',
|
|
320
|
-
);
|
|
321
|
-
|
|
322
|
-
// No need to pm2 stop — PM2 app was already deleted before process exit.
|
|
323
|
-
// Wait extra time for file locks to fully release.
|
|
324
|
-
batLines.push(
|
|
325
|
-
'echo [Upgrade] Process exited at %time%, waiting for file locks... >> "%LOGFILE%"',
|
|
326
|
-
'ping -n 5 127.0.0.1 >NUL',
|
|
327
|
-
);
|
|
328
|
-
|
|
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.
|
|
332
|
-
batLines.push(
|
|
333
|
-
'echo [Upgrade] Running npm update at %time%... >> "%LOGFILE%"',
|
|
334
|
-
`${buildWindowsUpgradeCommand()} >> "%LOGFILE%" 2>&1`,
|
|
335
|
-
'if not "%errorlevel%"=="0" (',
|
|
336
|
-
' echo [Upgrade] npm update failed with exit code %errorlevel% at %time% >> "%LOGFILE%"',
|
|
337
|
-
' goto RESTART_SERVICE',
|
|
338
|
-
')',
|
|
339
|
-
'echo [Upgrade] npm update succeeded at %time% >> "%LOGFILE%"',
|
|
340
|
-
);
|
|
341
|
-
|
|
342
|
-
batLines.push(':RESTART_SERVICE');
|
|
343
|
-
|
|
344
|
-
if (isPm2) {
|
|
345
|
-
// Re-register and start via ecosystem config (PM2 app was deleted pre-exit)
|
|
346
|
-
batLines.push(
|
|
347
|
-
'echo [Upgrade] Re-registering agent via PM2... >> "%LOGFILE%"',
|
|
348
|
-
`if exist "${ecoPath}" (`,
|
|
349
|
-
` call "${pm2Win}" start "${ecoPath}" >> "%LOGFILE%" 2>&1`,
|
|
350
|
-
` call "${pm2Win}" save >> "%LOGFILE%" 2>&1`,
|
|
351
|
-
' echo [Upgrade] PM2 app re-registered at %time% >> "%LOGFILE%"',
|
|
352
|
-
') else (',
|
|
353
|
-
' echo [Upgrade] WARNING: ecosystem.config.cjs not found, PM2 not restarted >> "%LOGFILE%"',
|
|
354
|
-
')',
|
|
355
|
-
);
|
|
283
|
+
const pm2CliPath = isPm2 ? resolveWindowsPm2CliPath(process.execPath) : null;
|
|
284
|
+
if (isPm2 && !pm2CliPath) {
|
|
285
|
+
throw new Error('PM2 manages this Agent, but its JavaScript CLI entry point could not be resolved');
|
|
356
286
|
}
|
|
357
287
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
288
|
+
const payload = {
|
|
289
|
+
parentPid: process.pid,
|
|
290
|
+
packageSpec: `${pkgName}@${latestVersion}`,
|
|
291
|
+
globalInstall: isGlobalInstall,
|
|
292
|
+
installDir,
|
|
293
|
+
logPath,
|
|
294
|
+
handoffPath,
|
|
295
|
+
runnerPath,
|
|
296
|
+
commandPath,
|
|
297
|
+
payloadPath,
|
|
298
|
+
nodePath: process.execPath,
|
|
299
|
+
npmCliPath,
|
|
300
|
+
pm2CliPath,
|
|
301
|
+
ecosystemPath: isPm2 ? ecosystemPath : null,
|
|
302
|
+
};
|
|
303
|
+
prepareWindowsUpgradeRunner({
|
|
304
|
+
sourceRunnerPath: fileURLToPath(new URL('../windows-upgrade-runner.js', import.meta.url)),
|
|
305
|
+
sourceCommandPath: fileURLToPath(new URL('../upgrade-command.js', import.meta.url)),
|
|
306
|
+
runnerPath,
|
|
307
|
+
commandPath,
|
|
308
|
+
payloadPath,
|
|
309
|
+
payload,
|
|
310
|
+
});
|
|
366
311
|
|
|
367
312
|
const launcher = await launchWindowsUpgradeScript({
|
|
368
|
-
|
|
313
|
+
nodePath: process.execPath,
|
|
314
|
+
runnerPath,
|
|
315
|
+
payloadPath,
|
|
316
|
+
logPath,
|
|
369
317
|
handoffPath,
|
|
370
318
|
spawnProcess: spawn,
|
|
371
319
|
onHandoff: isPm2
|
|
372
320
|
? () => {
|
|
373
|
-
execFileSync(
|
|
321
|
+
execFileSync(process.execPath, [pm2CliPath, 'delete', getPm2AppName(instanceId)], { stdio: 'pipe', env: safeEnv });
|
|
374
322
|
console.log('[Agent] PM2 app deleted after upgrade handoff');
|
|
375
323
|
}
|
|
376
324
|
: undefined,
|
|
377
325
|
});
|
|
378
326
|
|
|
379
|
-
console.log(`[Agent] Spawned upgrade via ${launcher} (
|
|
327
|
+
console.log(`[Agent] Spawned Windows upgrade runner via ${launcher} (pm2=${isPm2}, dir=${installDir})`);
|
|
380
328
|
await sendToServer({ type: 'upgrade_agent_ack', success: true, version: latestVersion, pendingRestart: true });
|
|
381
329
|
}
|
|
382
330
|
|
|
@@ -1,5 +1,41 @@
|
|
|
1
1
|
import { stmts, transaction } from './connection.js';
|
|
2
2
|
|
|
3
|
+
export function applySessionUiMetadataUpdates(userId, updates, now = Date.now()) {
|
|
4
|
+
if (!userId || !Array.isArray(updates) || updates.length === 0) return false;
|
|
5
|
+
for (const update of updates) {
|
|
6
|
+
if (!update?.catalogKey) throw new Error('Catalog metadata update requires catalogKey');
|
|
7
|
+
stmts.upsertSessionUiMetadata.run(
|
|
8
|
+
userId,
|
|
9
|
+
update.catalogKey,
|
|
10
|
+
update.pinned === true ? 1 : 0,
|
|
11
|
+
Number.isFinite(update.sortRank) ? update.sortRank : null,
|
|
12
|
+
now,
|
|
13
|
+
);
|
|
14
|
+
if (update.runtimeProvider === 'yeaft') {
|
|
15
|
+
const result = stmts.setYeaftSessionPinnedForAgent.run(
|
|
16
|
+
update.pinned === true ? 1 : 0,
|
|
17
|
+
now,
|
|
18
|
+
update.sessionId,
|
|
19
|
+
userId,
|
|
20
|
+
update.agentId,
|
|
21
|
+
);
|
|
22
|
+
if (result.changes !== 1) throw new Error('Yeaft Session identity changed during metadata update');
|
|
23
|
+
} else if (update.runtimeProvider === 'claude-code' || update.runtimeProvider === 'copilot') {
|
|
24
|
+
const result = stmts.updateSessionPinnedForRoute.run(
|
|
25
|
+
update.pinned === true ? 1 : 0,
|
|
26
|
+
now,
|
|
27
|
+
update.sessionId,
|
|
28
|
+
update.agentId,
|
|
29
|
+
userId,
|
|
30
|
+
);
|
|
31
|
+
if (result.changes !== 1) throw new Error('Chat Session identity changed during metadata update');
|
|
32
|
+
} else {
|
|
33
|
+
throw new Error('Unknown Session runtime provider during metadata update');
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
|
|
3
39
|
function mapRow(row) {
|
|
4
40
|
if (!row) return null;
|
|
5
41
|
return {
|
|
@@ -36,39 +72,7 @@ export const sessionUiMetadataDb = {
|
|
|
36
72
|
|
|
37
73
|
applyBatch(userId, updates) {
|
|
38
74
|
if (!userId || !Array.isArray(updates) || updates.length === 0) return false;
|
|
39
|
-
return transaction(() =>
|
|
40
|
-
const now = Date.now();
|
|
41
|
-
for (const update of updates) {
|
|
42
|
-
if (!update?.catalogKey) throw new Error('Catalog metadata update requires catalogKey');
|
|
43
|
-
stmts.upsertSessionUiMetadata.run(
|
|
44
|
-
userId,
|
|
45
|
-
update.catalogKey,
|
|
46
|
-
update.pinned === true ? 1 : 0,
|
|
47
|
-
Number.isFinite(update.sortRank) ? update.sortRank : null,
|
|
48
|
-
now,
|
|
49
|
-
);
|
|
50
|
-
if (update.runtimeProvider === 'yeaft') {
|
|
51
|
-
const result = stmts.setYeaftSessionPinnedForAgent.run(
|
|
52
|
-
update.pinned === true ? 1 : 0,
|
|
53
|
-
now,
|
|
54
|
-
update.sessionId,
|
|
55
|
-
userId,
|
|
56
|
-
update.agentId,
|
|
57
|
-
);
|
|
58
|
-
if (result.changes !== 1) throw new Error('Yeaft Session identity changed during metadata update');
|
|
59
|
-
} else if (update.runtimeProvider === 'claude-code' || update.runtimeProvider === 'copilot') {
|
|
60
|
-
const result = stmts.updateSessionPinnedForRoute.run(
|
|
61
|
-
update.pinned === true ? 1 : 0,
|
|
62
|
-
now,
|
|
63
|
-
update.sessionId,
|
|
64
|
-
update.agentId,
|
|
65
|
-
userId,
|
|
66
|
-
);
|
|
67
|
-
if (result.changes !== 1) throw new Error('Chat Session identity changed during metadata update');
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
return true;
|
|
71
|
-
})();
|
|
75
|
+
return transaction(() => applySessionUiMetadataUpdates(userId, updates))();
|
|
72
76
|
},
|
|
73
77
|
|
|
74
78
|
delete(userId, catalogKey) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { randomUUID } from 'crypto';
|
|
2
2
|
import { stmts, transaction } from './connection.js';
|
|
3
|
+
import { applySessionUiMetadataUpdates } from './session-ui-metadata-db.js';
|
|
3
4
|
|
|
4
5
|
export class YeaftProjectDbError extends Error {
|
|
5
6
|
constructor(code, message) {
|
|
@@ -137,7 +138,12 @@ export const yeaftProjectDb = {
|
|
|
137
138
|
return { projectId: id };
|
|
138
139
|
},
|
|
139
140
|
|
|
140
|
-
moveSession(userId, {
|
|
141
|
+
moveSession(userId, {
|
|
142
|
+
agentId,
|
|
143
|
+
sessionId,
|
|
144
|
+
projectId = null,
|
|
145
|
+
catalogUpdates = null,
|
|
146
|
+
} = {}) {
|
|
141
147
|
const ownerId = requireUserId(userId);
|
|
142
148
|
const targetAgentId = requireId(agentId, 'invalid_agent_id', 'Agent id');
|
|
143
149
|
const targetSessionId = requireId(sessionId, 'invalid_session_id', 'Session id');
|
|
@@ -145,8 +151,12 @@ export const yeaftProjectDb = {
|
|
|
145
151
|
? null
|
|
146
152
|
: requireId(projectId, 'invalid_project_id', 'Project id');
|
|
147
153
|
if (targetProjectId) requireProject(ownerId, targetProjectId);
|
|
154
|
+
if (catalogUpdates !== null && (!Array.isArray(catalogUpdates) || catalogUpdates.length === 0)) {
|
|
155
|
+
throw new YeaftProjectDbError('invalid_catalog_order', 'Complete catalog order is required');
|
|
156
|
+
}
|
|
148
157
|
|
|
149
|
-
transaction(() => {
|
|
158
|
+
return transaction(() => {
|
|
159
|
+
const now = Date.now();
|
|
150
160
|
stmts.deleteYeaftProjectSessionMembership.run(ownerId, targetAgentId, targetSessionId);
|
|
151
161
|
if (targetProjectId) {
|
|
152
162
|
stmts.insertYeaftProjectSessionMembership.run(
|
|
@@ -154,11 +164,12 @@ export const yeaftProjectDb = {
|
|
|
154
164
|
targetProjectId,
|
|
155
165
|
targetAgentId,
|
|
156
166
|
targetSessionId,
|
|
157
|
-
|
|
167
|
+
now,
|
|
158
168
|
);
|
|
159
169
|
}
|
|
170
|
+
if (catalogUpdates) applySessionUiMetadataUpdates(ownerId, catalogUpdates, now);
|
|
171
|
+
return { agentId: targetAgentId, sessionId: targetSessionId, projectId: targetProjectId };
|
|
160
172
|
})();
|
|
161
|
-
return { agentId: targetAgentId, sessionId: targetSessionId, projectId: targetProjectId };
|
|
162
173
|
},
|
|
163
174
|
|
|
164
175
|
reconcileAgentSessions(userId, agentId, sessionIds) {
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
import { agents, pendingFiles, trackUserTurn, webClients } from '../context.js';
|
|
12
12
|
import {
|
|
13
13
|
sendToWebClient, forwardToAgent,
|
|
14
|
-
broadcastAgentList, broadcastSessionCatalog,
|
|
14
|
+
broadcastAgentList, broadcastSessionCatalog, buildSessionCatalog,
|
|
15
15
|
verifyConversationOwnership, verifyAgentOwnership
|
|
16
16
|
} from '../ws-utils.js';
|
|
17
17
|
import { routeSessionPin } from './session-pin-router.js';
|
|
@@ -100,6 +100,35 @@ function persistSessionPin(userId, routeRef, pinned) {
|
|
|
100
100
|
}]);
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
+
function catalogOrderUpdates(client, items) {
|
|
104
|
+
if (!client?.userId || !Array.isArray(items) || items.length === 0) return null;
|
|
105
|
+
const canonical = buildSessionCatalog(client.userId, client.role);
|
|
106
|
+
if (canonical.length !== items.length) return null;
|
|
107
|
+
const canonicalByKey = new Map(canonical.map(row => [row.catalogKey, row]));
|
|
108
|
+
if (canonicalByKey.size !== canonical.length) return null;
|
|
109
|
+
|
|
110
|
+
const seen = new Set();
|
|
111
|
+
const updates = [];
|
|
112
|
+
for (const [sortRank, item] of items.entries()) {
|
|
113
|
+
const row = canonicalByKey.get(item?.catalogKey);
|
|
114
|
+
const routeRef = item?.routeRef;
|
|
115
|
+
if (!row || seen.has(item.catalogKey)
|
|
116
|
+
|| routeRef?.runtimeProvider !== row.routeRef?.runtimeProvider
|
|
117
|
+
|| routeRef?.agentId !== row.routeRef?.agentId
|
|
118
|
+
|| routeRef?.sessionId !== row.routeRef?.sessionId) return null;
|
|
119
|
+
seen.add(item.catalogKey);
|
|
120
|
+
updates.push({
|
|
121
|
+
catalogKey: row.catalogKey,
|
|
122
|
+
runtimeProvider: row.routeRef.runtimeProvider,
|
|
123
|
+
agentId: row.routeRef.agentId,
|
|
124
|
+
sessionId: row.routeRef.sessionId,
|
|
125
|
+
pinned: row.pinned === true,
|
|
126
|
+
sortRank,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
return seen.size === canonical.length ? updates : null;
|
|
130
|
+
}
|
|
131
|
+
|
|
103
132
|
export function groupOnlineYeaftSessions(rows, agentRegistry = agents) {
|
|
104
133
|
const byAgent = {};
|
|
105
134
|
for (const row of Array.isArray(rows) ? rows : []) {
|
|
@@ -522,43 +551,9 @@ export async function handleClientConversation(clientId, client, msg, checkAgent
|
|
|
522
551
|
|
|
523
552
|
case 'reorder_session_catalog': {
|
|
524
553
|
if (!client.userId || !Array.isArray(msg.sessions)) break;
|
|
525
|
-
const updates =
|
|
526
|
-
for (const [index, item] of msg.sessions.entries()) {
|
|
527
|
-
const routeRef = item?.routeRef;
|
|
528
|
-
if (!item?.catalogKey || !routeRef?.runtimeProvider) { updates.length = 0; break; }
|
|
529
|
-
const { runtimeProvider, agentId, sessionId } = routeRef;
|
|
530
|
-
let expectedCatalogKey = null;
|
|
531
|
-
if (runtimeProvider === 'yeaft') {
|
|
532
|
-
if (!agentId || !sessionId || !yeaftSessionDb.getForAgent(client.userId, agentId, sessionId)) {
|
|
533
|
-
updates.length = 0;
|
|
534
|
-
break;
|
|
535
|
-
}
|
|
536
|
-
expectedCatalogKey = yeaftCatalogKey(agentId, sessionId);
|
|
537
|
-
} else if (runtimeProvider === 'claude-code' || runtimeProvider === 'copilot') {
|
|
538
|
-
if (!agentId || !sessionId
|
|
539
|
-
|| (!CONFIG.skipAuth && !verifyConversationOwnership(sessionId, client.userId, client.role))) {
|
|
540
|
-
updates.length = 0;
|
|
541
|
-
break;
|
|
542
|
-
}
|
|
543
|
-
const row = sessionDb.get(sessionId);
|
|
544
|
-
if (!row || row.agent_id !== agentId || (row.provider || 'claude-code') !== runtimeProvider) {
|
|
545
|
-
updates.length = 0;
|
|
546
|
-
break;
|
|
547
|
-
}
|
|
548
|
-
expectedCatalogKey = chatCatalogKey(sessionId);
|
|
549
|
-
}
|
|
550
|
-
if (expectedCatalogKey !== item.catalogKey) { updates.length = 0; break; }
|
|
551
|
-
updates.push({
|
|
552
|
-
catalogKey: expectedCatalogKey,
|
|
553
|
-
runtimeProvider,
|
|
554
|
-
agentId,
|
|
555
|
-
sessionId,
|
|
556
|
-
pinned: item.pinned === true,
|
|
557
|
-
sortRank: index,
|
|
558
|
-
});
|
|
559
|
-
}
|
|
554
|
+
const updates = catalogOrderUpdates(client, msg.sessions);
|
|
560
555
|
let persisted = false;
|
|
561
|
-
if (updates
|
|
556
|
+
if (updates) {
|
|
562
557
|
try {
|
|
563
558
|
persisted = sessionUiMetadataDb.applyBatch(client.userId, updates);
|
|
564
559
|
if (persisted) await broadcastSessionCatalog(client.userId);
|
|
@@ -1340,10 +1335,19 @@ export async function handleClientConversation(clientId, client, msg, checkAgent
|
|
|
1340
1335
|
error.code = 'session_archived';
|
|
1341
1336
|
throw error;
|
|
1342
1337
|
}
|
|
1338
|
+
const catalogUpdates = msg.catalogOrder === undefined
|
|
1339
|
+
? null
|
|
1340
|
+
: catalogOrderUpdates(client, msg.catalogOrder);
|
|
1341
|
+
if (msg.catalogOrder !== undefined && !catalogUpdates) {
|
|
1342
|
+
const error = new Error('Complete canonical catalog order is required');
|
|
1343
|
+
error.code = 'invalid_catalog_order';
|
|
1344
|
+
throw error;
|
|
1345
|
+
}
|
|
1343
1346
|
result = yeaftProjectDb.moveSession(client.userId, {
|
|
1344
1347
|
agentId,
|
|
1345
1348
|
sessionId,
|
|
1346
1349
|
projectId: msg.projectId || null,
|
|
1350
|
+
catalogUpdates,
|
|
1347
1351
|
});
|
|
1348
1352
|
} else {
|
|
1349
1353
|
throw new Error('Unknown Project operation');
|