@yeaft/webchat-agent 1.0.674 → 1.0.676
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/buffer.js +3 -3
- package/connection/upgrade.js +87 -23
- package/local-runtime/version.json +1 -1
- package/local-runtime/web/index.html +1 -1
- 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/unix-upgrade.js +135 -0
package/connection/buffer.js
CHANGED
|
@@ -104,14 +104,14 @@ async function sendNow(msg, queuedSocket) {
|
|
|
104
104
|
const socket = ctx.ws;
|
|
105
105
|
if (!socket || socket.readyState !== WebSocket.OPEN) return bufferMessage(msg, 'Disconnected');
|
|
106
106
|
// File transfers are request/connection scoped, unlike replayable chat output.
|
|
107
|
-
if (['file_content_chunk', 'video_chunk'].includes(msg.type) && queuedSocket !== socket) return 'dropped';
|
|
107
|
+
if (['file_content_chunk', 'video_chunk', 'upgrade_agent_ack'].includes(msg.type) && queuedSocket !== socket) return 'dropped';
|
|
108
108
|
const payload = ctx.serverEncryptionRequired && ctx.sessionKey
|
|
109
109
|
? await encrypt(msg, ctx.sessionKey) : msg;
|
|
110
110
|
if (socket !== ctx.ws || socket.readyState !== WebSocket.OPEN) {
|
|
111
111
|
return bufferMessage(msg, 'Connection changed');
|
|
112
112
|
}
|
|
113
113
|
const data = JSON.stringify(payload);
|
|
114
|
-
if (['file_content_chunk', 'video_chunk'].includes(msg.type)) return sendTransferChunk(socket, data);
|
|
114
|
+
if (['file_content_chunk', 'video_chunk', 'upgrade_agent_ack'].includes(msg.type)) return sendTransferChunk(socket, data);
|
|
115
115
|
socket.send(data);
|
|
116
116
|
return 'sent';
|
|
117
117
|
}
|
|
@@ -161,7 +161,7 @@ export async function sendToServer(msg) {
|
|
|
161
161
|
return 'dropped';
|
|
162
162
|
}
|
|
163
163
|
const promise = new Promise((resolve, reject) => {
|
|
164
|
-
const connectionScoped = ['file_content_chunk', 'video_chunk'].includes(msg.type);
|
|
164
|
+
const connectionScoped = ['file_content_chunk', 'video_chunk', 'upgrade_agent_ack'].includes(msg.type);
|
|
165
165
|
ctx.outboundSendQueue.push({ msg, bytes, resolve, reject, socket: connectionScoped ? ctx.ws : null });
|
|
166
166
|
ctx.outboundSendQueueBytes = Number(ctx.outboundSendQueueBytes || 0) + bytes;
|
|
167
167
|
});
|
package/connection/upgrade.js
CHANGED
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
} from '../upgrade-command.js';
|
|
22
22
|
import { sendToServer } from './buffer.js';
|
|
23
23
|
import { stopAgentHeartbeat } from './heartbeat.js';
|
|
24
|
+
import { createUnixUpgradeRun, getUnixUpgradeLockPath, launchUnixUpgrade, shellQuote } from '../unix-upgrade.js';
|
|
24
25
|
|
|
25
26
|
/**
|
|
26
27
|
* Resolve the local service instance id for this running agent. Each agent
|
|
@@ -63,8 +64,11 @@ export function isUpgradeAvailable(currentVersion, latestVersion) {
|
|
|
63
64
|
}
|
|
64
65
|
|
|
65
66
|
// Shared cleanup logic for restart/upgrade
|
|
66
|
-
function cleanupAndExit(exitCode) {
|
|
67
|
+
function cleanupAndExit(exitCode, { maxCleanupMs = null } = {}) {
|
|
67
68
|
setTimeout(async () => {
|
|
69
|
+
// The updater waits for this process. Do not let an unbounded async cleanup
|
|
70
|
+
// outlive the handoff and strand an on-failure service after exit(0).
|
|
71
|
+
if (maxCleanupMs) setTimeout(() => process.exit(exitCode), maxCleanupMs).unref();
|
|
68
72
|
for (const [, term] of ctx.terminals) {
|
|
69
73
|
if (term.pty) { try { term.pty.kill(); } catch {} }
|
|
70
74
|
if (term.timer) clearTimeout(term.timer);
|
|
@@ -282,7 +286,7 @@ export async function handleUpgradeAgent(correlation = {}) {
|
|
|
282
286
|
}
|
|
283
287
|
|
|
284
288
|
// 清理并退出,让升级脚本接管
|
|
285
|
-
cleanupAndExit(0);
|
|
289
|
+
cleanupAndExit(0, { maxCleanupMs: 20_000 });
|
|
286
290
|
} catch (e) {
|
|
287
291
|
console.error('[Agent] Upgrade failed:', e.message);
|
|
288
292
|
await sendUpgradeAck({ success: false, error: e.message });
|
|
@@ -416,11 +420,12 @@ export function buildUnixUpgradeScript({
|
|
|
416
420
|
safePath,
|
|
417
421
|
instanceId = DEFAULT_INSTANCE_ID,
|
|
418
422
|
isDarwin = platform() === 'darwin',
|
|
423
|
+
run,
|
|
424
|
+
lockPath,
|
|
425
|
+
nodePath = process.execPath,
|
|
426
|
+
isSystemd = existsSync(join(homedir(), '.config', 'systemd', 'user', `${getServiceName(instanceId)}.service`)),
|
|
419
427
|
}) {
|
|
420
|
-
const shPath = join(configDir, 'upgrade.sh');
|
|
421
428
|
const serviceName = getServiceName(instanceId);
|
|
422
|
-
const systemdUnitPath = join(homedir(), '.config', 'systemd', 'user', `${serviceName}.service`);
|
|
423
|
-
const isSystemd = existsSync(systemdUnitPath);
|
|
424
429
|
const plistPath = getLaunchdPlistPath(instanceId);
|
|
425
430
|
const isLaunchd = isDarwin && existsSync(plistPath);
|
|
426
431
|
const cwd = isGlobalInstall ? undefined : installDir;
|
|
@@ -428,16 +433,47 @@ export function buildUnixUpgradeScript({
|
|
|
428
433
|
const shLines = [
|
|
429
434
|
'#!/bin/bash',
|
|
430
435
|
`PID=${pid}`,
|
|
431
|
-
`PKG
|
|
432
|
-
`NPM
|
|
433
|
-
`LOGFILE
|
|
434
|
-
`export PATH
|
|
436
|
+
`PKG=${shellQuote(`${pkgName}@${targetVersion}`)}`,
|
|
437
|
+
`NPM=${shellQuote(npmPath)}`,
|
|
438
|
+
`LOGFILE=${shellQuote(join(configDir, 'logs', 'upgrade.log'))}`,
|
|
439
|
+
`export PATH=${shellQuote(safePath)}`,
|
|
435
440
|
'',
|
|
436
441
|
'# Redirect all output to log file',
|
|
437
|
-
'exec
|
|
442
|
+
'exec >> "$LOGFILE" 2>&1 || exit 1',
|
|
438
443
|
'echo "[Upgrade] Started at $(date)"',
|
|
439
444
|
'',
|
|
440
|
-
...(cwd ? [`INSTALL_DIR
|
|
445
|
+
...(cwd ? [`INSTALL_DIR=${shellQuote(cwd)}`] : []),
|
|
446
|
+
...(run ? [
|
|
447
|
+
`RUN_DIR=${shellQuote(run.dir)}`,
|
|
448
|
+
`READY=${shellQuote(run.readyPath)}`,
|
|
449
|
+
`AUTHORIZED=${shellQuote(run.authorizePath)}`,
|
|
450
|
+
`ACCEPTED=${shellQuote(run.acceptedPath)}`,
|
|
451
|
+
`CANCELLED=${shellQuote(run.cancelPath)}`,
|
|
452
|
+
`ERROR_FILE=${shellQuote(run.errorPath)}`,
|
|
453
|
+
...(lockPath ? [
|
|
454
|
+
`LOCK=${shellQuote(lockPath)}`,
|
|
455
|
+
'if ! command -v flock >/dev/null || ! exec 9>"$LOCK"; then',
|
|
456
|
+
' echo "Cannot acquire installation upgrade lock; Agent remains online" > "$ERROR_FILE"',
|
|
457
|
+
' exit 1',
|
|
458
|
+
'fi',
|
|
459
|
+
'if ! flock -n 9; then',
|
|
460
|
+
' echo "Another Agent is upgrading this shared installation; retry after it finishes" > "$ERROR_FILE"',
|
|
461
|
+
' exit 1',
|
|
462
|
+
'fi',
|
|
463
|
+
] : []),
|
|
464
|
+
'echo "$$" > "$READY" || exit 1',
|
|
465
|
+
'COUNT=0',
|
|
466
|
+
'while [ ! -f "$AUTHORIZED" ]; do',
|
|
467
|
+
' if [ -f "$CANCELLED" ] || ! kill -0 "$PID" 2>/dev/null || [ "$COUNT" -ge 60 ]; then',
|
|
468
|
+
' echo "[Upgrade] Handoff cancelled; installation unchanged"',
|
|
469
|
+
' exit 1',
|
|
470
|
+
' fi',
|
|
471
|
+
' COUNT=$((COUNT+1))',
|
|
472
|
+
' sleep 1',
|
|
473
|
+
'done',
|
|
474
|
+
'[ ! -f "$CANCELLED" ] || exit 1',
|
|
475
|
+
'touch "$ACCEPTED" || exit 1',
|
|
476
|
+
] : []),
|
|
441
477
|
'',
|
|
442
478
|
'# Wait for current process to exit',
|
|
443
479
|
'COUNT=0',
|
|
@@ -445,10 +481,11 @@ export function buildUnixUpgradeScript({
|
|
|
445
481
|
' COUNT=$((COUNT+1))',
|
|
446
482
|
' if [ $COUNT -ge 30 ]; then',
|
|
447
483
|
' echo "[Upgrade] Timeout waiting for PID $PID to exit"',
|
|
448
|
-
'
|
|
484
|
+
' exit 1',
|
|
449
485
|
' fi',
|
|
450
486
|
' sleep 2',
|
|
451
487
|
'done',
|
|
488
|
+
...(run ? ['[ ! -f "$CANCELLED" ] || exit 1'] : []),
|
|
452
489
|
'',
|
|
453
490
|
];
|
|
454
491
|
|
|
@@ -468,7 +505,10 @@ export function buildUnixUpgradeScript({
|
|
|
468
505
|
'export DBUS_SESSION_BUS_ADDRESS="${DBUS_SESSION_BUS_ADDRESS:-unix:path=/run/user/$YEAFT_UID/bus}"',
|
|
469
506
|
'',
|
|
470
507
|
'# Stop systemd service to prevent restart loop',
|
|
471
|
-
`systemctl --user stop "${serviceName}"
|
|
508
|
+
`if ! systemctl --user stop "${serviceName}"; then`,
|
|
509
|
+
' echo "[Upgrade] Could not stop selected service; refusing to modify installation"',
|
|
510
|
+
' exit 1',
|
|
511
|
+
'fi',
|
|
472
512
|
'sleep 1',
|
|
473
513
|
'',
|
|
474
514
|
);
|
|
@@ -487,10 +527,19 @@ export function buildUnixUpgradeScript({
|
|
|
487
527
|
? npmArgs.replace(/^npm /, '"$NPM" ')
|
|
488
528
|
: `cd "$INSTALL_DIR" && ${npmArgs.replace(/^npm /, '"$NPM" ')}`;
|
|
489
529
|
|
|
530
|
+
// Recheck after acquiring the shared lock. An older queued process must not
|
|
531
|
+
// replace a package that another instance has already upgraded.
|
|
532
|
+
const installedManifest = join(installDir, 'node_modules', pkgName, 'package.json');
|
|
533
|
+
const versionCheck = `try { const fs = require('node:fs'); const v = JSON.parse(fs.readFileSync(process.argv[1])).version; const a = v.split('.').map(Number); const b = process.argv[2].split('.').map(Number); let d = 0; for (let i = 0; i < 3 && !d; i++) d = a[i] - b[i]; process.exit(d >= 0 ? 0 : 1); } catch { process.exit(1); }`;
|
|
490
534
|
shLines.push(
|
|
535
|
+
`if ${shellQuote(nodePath)} -e ${shellQuote(versionCheck)} ${shellQuote(installedManifest)} ${shellQuote(targetVersion)}; then`,
|
|
536
|
+
' echo "[Upgrade] Shared installation already has this version or newer; restarting selected instance only"',
|
|
537
|
+
' EXIT_CODE=0',
|
|
538
|
+
'else',
|
|
491
539
|
'echo "[Upgrade] Installing $PKG..."',
|
|
492
540
|
npmCmd,
|
|
493
541
|
'EXIT_CODE=$?',
|
|
542
|
+
'fi',
|
|
494
543
|
'if [ $EXIT_CODE -ne 0 ]; then',
|
|
495
544
|
' echo "[Upgrade] npm install failed with exit code $EXIT_CODE"',
|
|
496
545
|
'else',
|
|
@@ -527,8 +576,8 @@ export function buildUnixUpgradeScript({
|
|
|
527
576
|
);
|
|
528
577
|
}
|
|
529
578
|
|
|
530
|
-
//
|
|
531
|
-
shLines.push('', `rm -f
|
|
579
|
+
// Keep failed handoffs for diagnosis; successful runs remove only their own files.
|
|
580
|
+
shLines.push('', run ? 'rm -rf -- "$RUN_DIR"' : `rm -f ${shellQuote(join(configDir, 'upgrade.sh'))}`);
|
|
532
581
|
|
|
533
582
|
return shLines.join('\n');
|
|
534
583
|
}
|
|
@@ -538,7 +587,16 @@ async function spawnUnixUpgradeScript(pkgName, installDir, isGlobalInstall, late
|
|
|
538
587
|
// Create logs/ too: the generated script's `exec > "$LOGFILE" 2>&1` aborts
|
|
539
588
|
// the whole upgrade silently if that directory is missing.
|
|
540
589
|
mkdirSync(join(configDir, 'logs'), { recursive: true });
|
|
541
|
-
const
|
|
590
|
+
const run = createUnixUpgradeRun(configDir);
|
|
591
|
+
const isSystemd = platform() === 'linux' && (
|
|
592
|
+
!!process.env.INVOCATION_ID
|
|
593
|
+
|| existsSync(join(homedir(), '.config', 'systemd', 'user', `${getServiceName(instanceId)}.service`))
|
|
594
|
+
);
|
|
595
|
+
// Linux flock is kernel-owned: it releases automatically even when an updater
|
|
596
|
+
// crashes. Never unlink the shared lock inode while another runner may use it.
|
|
597
|
+
const lockPath = platform() === 'linux'
|
|
598
|
+
? getUnixUpgradeLockPath(installDir, join(getConfigDir(DEFAULT_INSTANCE_ID), 'upgrade-locks'))
|
|
599
|
+
: null;
|
|
542
600
|
|
|
543
601
|
const script = buildUnixUpgradeScript({
|
|
544
602
|
pkgName,
|
|
@@ -550,14 +608,20 @@ async function spawnUnixUpgradeScript(pkgName, installDir, isGlobalInstall, late
|
|
|
550
608
|
npmPath,
|
|
551
609
|
safePath,
|
|
552
610
|
instanceId,
|
|
611
|
+
run,
|
|
612
|
+
lockPath,
|
|
613
|
+
isSystemd,
|
|
553
614
|
});
|
|
554
615
|
|
|
555
|
-
writeFileSync(
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
616
|
+
writeFileSync(run.scriptPath, script, { mode: 0o700 });
|
|
617
|
+
await launchUnixUpgrade({
|
|
618
|
+
run,
|
|
619
|
+
instanceId,
|
|
620
|
+
isSystemd,
|
|
621
|
+
env: safeEnv,
|
|
622
|
+
serviceRunnerPath: fileURLToPath(new URL('../yeaft/systemd-service-runner.js', import.meta.url)),
|
|
623
|
+
sendUpgradeAck,
|
|
624
|
+
version: latestVersion,
|
|
559
625
|
});
|
|
560
|
-
|
|
561
|
-
console.log(`[Agent] Spawned upgrade script: ${shPath}`);
|
|
562
|
-
await sendUpgradeAck({ success: true, version: latestVersion, pendingRestart: true });
|
|
626
|
+
console.log(`[Agent] Upgrade runner ready outside service cgroup: ${run.scriptPath}`);
|
|
563
627
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"1.0.
|
|
1
|
+
{"version":"1.0.676"}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
<title>Yeaft</title>
|
|
7
7
|
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'><rect width='32' height='32' rx='8' fill='%232c2c2c'/><path d='M16 22V13M16 13L10 8M16 13L22 8' stroke='white' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round' fill='none'/><circle cx='10' cy='8' r='2.4' fill='white'/><circle cx='22' cy='8' r='2.4' fill='white'/><circle cx='16' cy='22' r='2.4' fill='white'/></svg>">
|
|
8
8
|
<link rel="stylesheet" href="vendor/katex/katex.min.css?v=b0d91614">
|
|
9
|
-
<link rel="stylesheet" href="style.bundle.css?v=
|
|
9
|
+
<link rel="stylesheet" href="style.bundle.css?v=e3fe0ff3">
|
|
10
10
|
<script src="vendor.bundle.js?v=b4d2c175"></script>
|
|
11
11
|
<script defer src="jszip.min.js"></script>
|
|
12
12
|
<script defer src="docx-preview.min.js"></script>
|