@yeaft/webchat-agent 1.0.119 → 1.0.121

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.
@@ -8,6 +8,7 @@ const os = require('os');
8
8
  const PKG = process.argv[2];
9
9
  const TARGET = process.argv[3];
10
10
  const LOGFILE = process.argv[4];
11
+ const REGISTRY = process.argv[5];
11
12
 
12
13
  function log(msg) {
13
14
  const line = '[Upgrade-Worker] ' + msg;
@@ -75,7 +76,13 @@ try {
75
76
  const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'yeaft-upgrade-'));
76
77
  log('Temp dir: ' + tmpDir);
77
78
 
78
- const packOutput = execFileSync('npm', ['pack', PKG, '--pack-destination', tmpDir], {
79
+ const packOutput = execFileSync('npm', [
80
+ 'pack',
81
+ PKG,
82
+ '--pack-destination',
83
+ tmpDir,
84
+ `--registry=${REGISTRY}`,
85
+ ], {
79
86
  shell: process.platform === 'win32', encoding: 'utf8', cwd: tmpDir, timeout: 120000
80
87
  }).trim();
81
88
  const tgzName = packOutput.split('\n').pop().trim();
@@ -39,6 +39,37 @@ const shellOpt = isWin ? { shell: true, windowsHide: true } : {};
39
39
  const currentPath = process.env.PATH || '/usr/bin:/bin:/usr/sbin:/sbin';
40
40
  const safePath = currentPath.includes(nodeBinDir) ? currentPath : `${nodeBinDir}:${currentPath}`;
41
41
  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
+
61
+ /** Return true only when `latestVersion` is semantically newer. */
62
+ export function isUpgradeAvailable(currentVersion, latestVersion) {
63
+ const current = parseSemver(currentVersion);
64
+ const latest = parseSemver(latestVersion);
65
+ if (!current || !latest) return currentVersion !== latestVersion;
66
+ return cmpTuple(latest, current) > 0;
67
+ }
68
+
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}"`;
72
+ }
42
73
 
43
74
  // Shared cleanup logic for restart/upgrade
44
75
  function cleanupAndExit(exitCode) {
@@ -82,7 +113,7 @@ async function fetchRequiredNodeRange(pkgName, version) {
82
113
  const stdout = await new Promise((resolve, reject) => {
83
114
  execFile(
84
115
  npmPath,
85
- ['view', `${pkgName}@${version}`, 'engines.node'],
116
+ buildNpmMetadataArgs(`${pkgName}@${version}`, 'engines.node'),
86
117
  { stdio: 'pipe', env: safeEnv, ...shellOpt },
87
118
  (err, out) => { if (err) reject(err); else resolve(out.toString().trim()); },
88
119
  );
@@ -154,14 +185,15 @@ export async function handleUpgradeAgent() {
154
185
  console.log('[Agent] Upgrade requested, checking for updates...');
155
186
  try {
156
187
  const pkgName = ctx.pkgName || '@yeaft/webchat-agent';
157
- // Check latest version (async to avoid blocking heartbeat)
188
+ // Force a public-registry refresh so stale Windows npm metadata or a lagging
189
+ // registry mirror cannot report the installed version as the latest one.
158
190
  const latestVersion = await new Promise((resolve, reject) => {
159
- execFile(npmPath, ['view', pkgName, 'version'], { stdio: 'pipe', env: safeEnv, ...shellOpt }, (err, stdout) => {
191
+ execFile(npmPath, buildNpmMetadataArgs(`${pkgName}@latest`, 'version'), { stdio: 'pipe', env: safeEnv, ...shellOpt }, (err, stdout) => {
160
192
  if (err) reject(err); else resolve(stdout.toString().trim());
161
193
  });
162
194
  });
163
- if (latestVersion === ctx.agentVersion) {
164
- console.log(`[Agent] Already at latest version (${ctx.agentVersion}), skipping upgrade.`);
195
+ if (!isUpgradeAvailable(ctx.agentVersion, latestVersion)) {
196
+ console.log(`[Agent] No newer version than ${ctx.agentVersion} is available (registry: ${latestVersion}), skipping upgrade.`);
165
197
  await sendToServer({ type: 'upgrade_agent_ack', success: true, alreadyLatest: true, version: ctx.agentVersion });
166
198
  return;
167
199
  }
@@ -270,7 +302,7 @@ async function spawnWindowsUpgradeScript(pkgName, installDir, isGlobalInstall, l
270
302
  '@echo off',
271
303
  'setlocal',
272
304
  `set PID=${pid}`,
273
- `set PKG=${pkgName}@latest`,
305
+ `set PKG=${pkgName}@${latestVersion}`,
274
306
  `set INSTALL_DIR=${installDirWin}`,
275
307
  `set PKG_DIR=${pkgDir}`,
276
308
  `set LOGFILE=${logPath}`,
@@ -313,7 +345,7 @@ async function spawnWindowsUpgradeScript(pkgName, installDir, isGlobalInstall, l
313
345
  // Use Node.js worker for file-level upgrade (avoids EBUSY on directory rename)
314
346
  batLines.push(
315
347
  'echo [Upgrade] Running upgrade worker at %time%... >> "%LOGFILE%"',
316
- `"${process.execPath.replace(/\//g, '\\')}" "%WORKER%" "%PKG%" "%PKG_DIR%" "%LOGFILE%"`,
348
+ buildWindowsWorkerCommand(process.execPath),
317
349
  'if not "%errorlevel%"=="0" (',
318
350
  ' echo [Upgrade] Worker failed with exit code %errorlevel% at %time% >> "%LOGFILE%"',
319
351
  ' goto CLEANUP',
@@ -390,6 +422,7 @@ async function spawnWindowsUpgradeScript(pkgName, installDir, isGlobalInstall, l
390
422
  *
391
423
  * @param {object} opts
392
424
  * @param {string} opts.pkgName npm package name
425
+ * @param {string} opts.targetVersion exact version selected by the metadata check
393
426
  * @param {string} opts.installDir install dir (local install) — used as cwd
394
427
  * @param {boolean} opts.isGlobalInstall global vs local npm install
395
428
  * @param {number} opts.pid pid of the exiting agent to wait on
@@ -402,6 +435,7 @@ async function spawnWindowsUpgradeScript(pkgName, installDir, isGlobalInstall, l
402
435
  */
403
436
  export function buildUnixUpgradeScript({
404
437
  pkgName,
438
+ targetVersion,
405
439
  installDir,
406
440
  isGlobalInstall,
407
441
  pid,
@@ -422,7 +456,8 @@ export function buildUnixUpgradeScript({
422
456
  const shLines = [
423
457
  '#!/bin/bash',
424
458
  `PID=${pid}`,
425
- `PKG="${pkgName}@latest"`,
459
+ `PKG="${pkgName}@${targetVersion}"`,
460
+ `REGISTRY="${PUBLIC_NPM_REGISTRY}"`,
426
461
  `NPM="${npmPath}"`,
427
462
  `LOGFILE="${join(configDir, 'logs', 'upgrade.log')}"`,
428
463
  `export PATH="${safePath}"`,
@@ -477,8 +512,8 @@ export function buildUnixUpgradeScript({
477
512
 
478
513
  // npm install (use absolute path via $NPM variable)
479
514
  const npmCmd = isGlobalInstall
480
- ? `"$NPM" install -g "$PKG"`
481
- : `cd "$INSTALL_DIR" && "$NPM" install "$PKG"`;
515
+ ? `"$NPM" install -g "$PKG" --registry="$REGISTRY"`
516
+ : `cd "$INSTALL_DIR" && "$NPM" install "$PKG" --registry="$REGISTRY"`;
482
517
 
483
518
  shLines.push(
484
519
  'echo "[Upgrade] Installing $PKG..."',
@@ -535,6 +570,7 @@ async function spawnUnixUpgradeScript(pkgName, installDir, isGlobalInstall, late
535
570
 
536
571
  const script = buildUnixUpgradeScript({
537
572
  pkgName,
573
+ targetVersion: latestVersion,
538
574
  installDir,
539
575
  isGlobalInstall,
540
576
  pid: process.pid,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yeaft/webchat-agent",
3
- "version": "1.0.119",
3
+ "version": "1.0.121",
4
4
  "description": "Remote worker agent for Yeaft Web Code Agent — connects the native Yeaft engine, CLI providers, and workbench tools",
5
5
  "main": "index.js",
6
6
  "type": "module",
package/yeaft/engine.js CHANGED
@@ -23,7 +23,7 @@ import { join, resolve as resolvePath } from 'path';
23
23
  import { buildSystemPrompt, buildWorkerPrompt } from './prompts.js';
24
24
  import { getRuntimePlatformInfo } from './runtime-platform.js';
25
25
  import { LLMContextError, LLMAbortError, LLMRateLimitError, LLMServerError, LLMStreamIdleTimeoutError } from './llm/adapter.js';
26
- import { runMemoryPreflow, buildRelevantScopes } from './sessions/pre-flow.js';
26
+ import { runMemoryPreflow, buildRelevantScopes, memoryScopeLabel } from './sessions/pre-flow.js';
27
27
  import { readProjectDoc, pickProjectDocFile, DEFAULT_PROJECT_DOC_MAX_BYTES } from './sessions/project-doc.js';
28
28
  import { partitionMessages } from './compact/partition.js';
29
29
  import { runCompact as runCompactOrchestrator } from './compact/orchestrator.js';
@@ -905,19 +905,19 @@ export class Engine {
905
905
  if (snap.resident.length > 0) {
906
906
  parts.push(zh ? '### 常驻记忆' : '### Resident');
907
907
  for (const r of snap.resident) {
908
- parts.push(`- **${r.scope}**: ${r.summary}`);
908
+ parts.push(`- **${memoryScopeLabel(r.scope)}**: ${r.summary}`);
909
909
  }
910
910
  }
911
911
  if (snap.recent.length > 0) {
912
912
  parts.push(zh ? '### 最近记忆' : '### Recent');
913
913
  for (const s of snap.recent) {
914
- parts.push(`- (${s.scope}) ${(s.body || '').trim()}`);
914
+ parts.push(`- (${memoryScopeLabel(s.scope)}) ${(s.body || '').trim()}`);
915
915
  }
916
916
  }
917
917
  if (snap.onDemand.length > 0) {
918
918
  parts.push(zh ? '### 按需记忆' : '### OnDemand');
919
919
  for (const s of snap.onDemand) {
920
- parts.push(`- (${s.scope}) ${(s.body || '').trim()}`);
920
+ parts.push(`- (${memoryScopeLabel(s.scope)}) ${(s.body || '').trim()}`);
921
921
  }
922
922
  }
923
923
  return parts.join('\n');
@@ -157,6 +157,24 @@ export function selectRespondingVps(input) {
157
157
  }
158
158
 
159
159
 
160
+ /**
161
+ * Turn an internal memory scope into the compact label shown to the model.
162
+ * The active-scope prompt already carries the current session id, so repeating
163
+ * the full storage path on every memory entry only wastes context. Internal
164
+ * entries and debug events keep the original scope for ACLs and diagnostics.
165
+ *
166
+ * @param {string} scope
167
+ * @returns {string}
168
+ */
169
+ export function memoryScopeLabel(scope) {
170
+ const value = typeof scope === 'string' && scope ? scope : 'unknown';
171
+ const sessionTopic = /^(?:sessions|session|group)\/[^/]+\/topic\/(.+)$/.exec(value);
172
+ if (sessionTopic) return `topic: ${sessionTopic[1]}`;
173
+ if (/^(?:sessions|session|group)\/[^/]+$/.test(value)) return 'session';
174
+ if (value.startsWith('topic/')) return `topic: ${value.slice(6)}`;
175
+ return value;
176
+ }
177
+
160
178
  /**
161
179
  * Build the heading for a single scope's formatted memory block.
162
180
  *
@@ -167,6 +185,8 @@ export function selectRespondingVps(input) {
167
185
  * @returns {string}
168
186
  */
169
187
  function scopeHeading(scope) {
188
+ const label = memoryScopeLabel(scope);
189
+ if (label.startsWith('topic: ')) return `## Memory: Topic ${label.slice(7)}`;
170
190
  if (scope === 'user') return '## Memory: User';
171
191
  // Nested chat scopes first.
172
192
  let m = /^chat\/([^/]+)\/vp\/(.+)$/.exec(scope);
@@ -180,10 +200,6 @@ function scopeHeading(scope) {
180
200
  if (m) return `## Memory: Session ${m[1]} (user)`;
181
201
  m = /^session\/([^/]+)\/feature\/(.+)$/.exec(scope);
182
202
  if (m) return `## Memory: Feature ${m[2]}`;
183
- m = /^sessions\/([^/]+)\/topic\/(.+)$/.exec(scope);
184
- if (m) return `## Memory: Topic ${m[2]}`;
185
- m = /^session\/([^/]+)\/topic\/(.+)$/.exec(scope);
186
- if (m) return `## Memory: Topic ${m[2]}`;
187
203
  // Legacy nested group scopes (un-migrated data).
188
204
  m = /^group\/([^/]+)\/vp\/(.+)$/.exec(scope);
189
205
  if (m) return `## Memory: VP ${m[2]}`;
@@ -191,8 +207,6 @@ function scopeHeading(scope) {
191
207
  if (m) return `## Memory: Session ${m[1]} (user)`;
192
208
  m = /^group\/([^/]+)\/feature\/(.+)$/.exec(scope);
193
209
  if (m) return `## Memory: Feature ${m[2]}`;
194
- m = /^group\/([^/]+)\/topic\/(.+)$/.exec(scope);
195
- if (m) return `## Memory: Topic ${m[2]}`;
196
210
  if (scope.startsWith('session/')) return `## Memory: Session ${scope.slice(8)}`;
197
211
  if (scope.startsWith('group/')) return `## Memory: Session ${scope.slice(6)}`;
198
212
  if (scope.startsWith('vp/')) return `## Memory: VP ${scope.slice(3)}`;