@yeaft/webchat-agent 1.0.338 → 1.0.339

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/index.js CHANGED
@@ -14,7 +14,12 @@ import { getDefaultAgentName, getDefaultYeaftDir, resolveRuntimeIdentity, getCon
14
14
  import { loadNodePty } from './terminal.js';
15
15
  import { connect } from './connection.js';
16
16
  import { loadMcpServers } from './mcp.js';
17
- import { ensureManagedCliTools, summarizeManagedCliResults } from './yeaft/managed-cli.js';
17
+ import {
18
+ ensureManagedCliTools,
19
+ prepareManagedCliToolEnvironment,
20
+ runAfterManagedCliRuntimeCleanup,
21
+ summarizeManagedCliResults,
22
+ } from './yeaft/managed-cli.js';
18
23
 
19
24
  const execAsync = promisify(exec);
20
25
 
@@ -310,30 +315,32 @@ async function ensureYeaftSkills() {
310
315
  }
311
316
 
312
317
  // 优雅退出
313
- async function cleanup() {
314
- // 清理所有终端
315
- for (const [, term] of ctx.terminals) {
316
- if (term.pty) {
317
- try { term.pty.kill(); } catch {}
318
+ function cleanup() {
319
+ return runAfterManagedCliRuntimeCleanup(async () => {
320
+ // 清理所有终端
321
+ for (const [, term] of ctx.terminals) {
322
+ if (term.pty) {
323
+ try { term.pty.kill(); } catch {}
324
+ }
325
+ if (term.timer) clearTimeout(term.timer);
318
326
  }
319
- if (term.timer) clearTimeout(term.timer);
320
- }
321
- ctx.terminals.clear();
327
+ ctx.terminals.clear();
322
328
 
323
- for (const [, state] of ctx.conversations) {
324
- if (state.abortController) {
325
- state.abortController.abort();
326
- }
327
- if (state.inputStream) {
328
- state.inputStream.done();
329
+ for (const [, state] of ctx.conversations) {
330
+ if (state.abortController) {
331
+ state.abortController.abort();
332
+ }
333
+ if (state.inputStream) {
334
+ state.inputStream.done();
335
+ }
329
336
  }
330
- }
331
- ctx.conversations.clear();
332
- try {
333
- const { shutdownWorkCenter } = await import('./yeaft/work-center/bridge.js');
334
- await shutdownWorkCenter();
335
- } catch {}
336
- if (ctx.ws) ctx.ws.close();
337
+ ctx.conversations.clear();
338
+ try {
339
+ const { shutdownWorkCenter } = await import('./yeaft/work-center/bridge.js');
340
+ await shutdownWorkCenter();
341
+ } catch {}
342
+ if (ctx.ws) ctx.ws.close();
343
+ });
337
344
  }
338
345
 
339
346
  process.on('SIGINT', async () => {
@@ -362,6 +369,16 @@ process.on('SIGTERM', async () => {
362
369
  });
363
370
  }
364
371
  ctx.managedCliReady = managedCliReady;
372
+ try {
373
+ const rgEnvironment = await prepareManagedCliToolEnvironment(managedCliReady, 'rg', {
374
+ yeaftDir: YEAFT_DIR,
375
+ });
376
+ if (rgEnvironment.activated) {
377
+ console.log(`[Startup] managed rg available to child processes: ${rgEnvironment.command}`);
378
+ }
379
+ } catch (error) {
380
+ console.warn(`[Startup] managed rg environment setup failed; using built-in fallback: ${error?.message || error}`);
381
+ }
365
382
  ctx.agentCapabilities = await detectCapabilities();
366
383
  // Prime the models.dev community catalog so the Yeaft engine's *synchronous*
367
384
  // hot path (engine.js / config.js / cli.js all read context-window inline)
@@ -1 +1 @@
1
- {"version":"1.0.338"}
1
+ {"version":"1.0.339"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yeaft/webchat-agent",
3
- "version": "1.0.338",
3
+ "version": "1.0.339",
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/cli.js CHANGED
@@ -40,7 +40,12 @@ import { loadSessionConfig, resolveSessionConfig } from './sessions/session-conf
40
40
  import { validateSessionId } from './sessions/ids.js';
41
41
  import { createJsonlWriter, JsonlInput, runStreamTurn, runStreamSessionTurn } from './stdio-protocol.js';
42
42
  import { createCliSessionRunner } from './cli-session-runner.js';
43
- import { ensureManagedCliTools, summarizeManagedCliResults } from './managed-cli.js';
43
+ import {
44
+ cleanupManagedCliRuntimePaths,
45
+ ensureManagedCliTools,
46
+ prepareManagedCliToolEnvironment,
47
+ summarizeManagedCliResults,
48
+ } from './managed-cli.js';
44
49
 
45
50
  // ─── Argument parsing ──────────────────────────────────────────
46
51
 
@@ -1033,6 +1038,26 @@ async function runOnce(config, args) {
1033
1038
 
1034
1039
  // ─── Main ──────────────────────────────────────────────────────
1035
1040
 
1041
+ function installManagedCliSignalCleanup() {
1042
+ if (process.platform === 'win32') return () => {};
1043
+ const signals = ['SIGINT', 'SIGTERM'];
1044
+ const handlers = new Map();
1045
+ const dispose = () => {
1046
+ for (const [signal, handler] of handlers) process.off(signal, handler);
1047
+ handlers.clear();
1048
+ };
1049
+ for (const signal of signals) {
1050
+ const handler = () => {
1051
+ cleanupManagedCliRuntimePaths();
1052
+ dispose();
1053
+ process.kill(process.pid, signal);
1054
+ };
1055
+ handlers.set(signal, handler);
1056
+ process.once(signal, handler);
1057
+ }
1058
+ return dispose;
1059
+ }
1060
+
1036
1061
  async function main() {
1037
1062
  const args = parseArgs(process.argv);
1038
1063
 
@@ -1071,7 +1096,7 @@ async function main() {
1071
1096
  return;
1072
1097
  }
1073
1098
 
1074
- const prepareManagedCli = () => {
1099
+ const prepareManagedCli = async () => {
1075
1100
  args.managedCliReady = ensureManagedCliTools({ yeaftDir: config.dir });
1076
1101
  args.managedCliReady.then(results => {
1077
1102
  if (results.some(result => result.status === 'installed')) {
@@ -1080,11 +1105,18 @@ async function main() {
1080
1105
  }).catch(error => {
1081
1106
  console.error(`[Yeaft] managed CLI setup failed; using built-in fallbacks: ${error?.message || error}`);
1082
1107
  });
1108
+ try {
1109
+ await prepareManagedCliToolEnvironment(args.managedCliReady, 'rg', {
1110
+ yeaftDir: config.dir,
1111
+ });
1112
+ } catch (error) {
1113
+ console.error(`[Yeaft] managed rg environment setup failed; using built-in fallback: ${error?.message || error}`);
1114
+ }
1083
1115
  };
1084
1116
 
1085
1117
  // Handle interactive mode
1086
1118
  if (args.interactive) {
1087
- prepareManagedCli();
1119
+ await prepareManagedCli();
1088
1120
  await runREPL(config, args);
1089
1121
  return;
1090
1122
  }
@@ -1096,7 +1128,7 @@ async function main() {
1096
1128
  if (!args.prompt && args.inputFormat !== 'stream-json' && process.stdin.isTTY) {
1097
1129
  throw new Error('stream-json output requires a prompt, piped stdin, or --input-format stream-json');
1098
1130
  }
1099
- prepareManagedCli();
1131
+ await prepareManagedCli();
1100
1132
  await runStreamJson(config, args);
1101
1133
  return;
1102
1134
  }
@@ -1106,7 +1138,7 @@ async function main() {
1106
1138
 
1107
1139
  // Handle prompt (from args or stdin)
1108
1140
  if (args.prompt) {
1109
- prepareManagedCli();
1141
+ await prepareManagedCli();
1110
1142
  await runOnce(config, args);
1111
1143
  return;
1112
1144
  }
@@ -1119,7 +1151,7 @@ async function main() {
1119
1151
  }
1120
1152
  args.prompt = input.trim();
1121
1153
  if (args.prompt) {
1122
- prepareManagedCli();
1154
+ await prepareManagedCli();
1123
1155
  await runOnce(config, args);
1124
1156
  return;
1125
1157
  }
@@ -1159,8 +1191,14 @@ async function main() {
1159
1191
 
1160
1192
  const isDirectRun = process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url);
1161
1193
  if (isDirectRun) {
1194
+ const disposeSignalCleanup = installManagedCliSignalCleanup();
1195
+ const cleanupManagedCli = () => {
1196
+ disposeSignalCleanup();
1197
+ cleanupManagedCliRuntimePaths();
1198
+ };
1162
1199
  main().catch(err => {
1163
1200
  console.error(err);
1201
+ cleanupManagedCli();
1164
1202
  process.exit(1);
1165
- });
1203
+ }).finally(cleanupManagedCli);
1166
1204
  }
@@ -3,10 +3,12 @@ import { spawnSync } from 'node:child_process';
3
3
  import {
4
4
  accessSync,
5
5
  chmodSync,
6
+ copyFileSync,
6
7
  constants,
7
8
  existsSync,
8
9
  lstatSync,
9
10
  mkdirSync,
11
+ mkdtempSync,
10
12
  readFileSync,
11
13
  realpathSync,
12
14
  renameSync,
@@ -15,7 +17,7 @@ import {
15
17
  unlinkSync,
16
18
  writeFileSync,
17
19
  } from 'node:fs';
18
- import { homedir } from 'node:os';
20
+ import { homedir, tmpdir } from 'node:os';
19
21
  import { basename, delimiter, dirname, join, resolve } from 'node:path';
20
22
  import { gunzipSync, inflateRawSync } from 'node:zlib';
21
23
 
@@ -28,6 +30,8 @@ const LOCK_STALE_MS = 2 * 60 * 1000;
28
30
  const LOCK_WAIT_MS = 15_000;
29
31
  const STATE_FILE = 'managed-cli.json';
30
32
  const installFlights = new Map();
33
+ const runtimePathDirectories = new Set();
34
+ let runtimePathCleanupRegistered = false;
31
35
 
32
36
  const TOOL_SPECS = Object.freeze({
33
37
  rg: {
@@ -78,8 +82,11 @@ export function managedCliBinDir(yeaftDir = DEFAULT_ROOT) {
78
82
  return join(resolve(yeaftDir), 'bin');
79
83
  }
80
84
 
81
- export function prependManagedCliBinToPath(yeaftDir = DEFAULT_ROOT, env = process.env, platform = process.platform) {
82
- const binDir = managedCliBinDir(yeaftDir);
85
+ function prependDirectoryToPath(
86
+ binDir,
87
+ env = process.env,
88
+ platform = process.platform,
89
+ ) {
83
90
  const current = typeof env.PATH === 'string'
84
91
  ? env.PATH
85
92
  : (typeof env.Path === 'string' ? env.Path : '');
@@ -93,6 +100,10 @@ export function prependManagedCliBinToPath(yeaftDir = DEFAULT_ROOT, env = proces
93
100
  return binDir;
94
101
  }
95
102
 
103
+ export function prependManagedCliBinToPath(yeaftDir = DEFAULT_ROOT, env = process.env, platform = process.platform) {
104
+ return prependDirectoryToPath(managedCliBinDir(yeaftDir), env, platform);
105
+ }
106
+
96
107
  function canExecute(path, platform) {
97
108
  try {
98
109
  accessSync(path, platform === 'win32' ? constants.F_OK : constants.X_OK);
@@ -155,10 +166,12 @@ function inspectManagedBinary(name, { yeaftDir, platform, arch }) {
155
166
  return { path, exists: true, valid: false };
156
167
  }
157
168
  try {
169
+ const binarySha256 = hashFile(path);
158
170
  return {
159
171
  path,
160
172
  exists: true,
161
- valid: hashFile(path) === installation.binarySha256,
173
+ valid: binarySha256 === installation.binarySha256,
174
+ binarySha256,
162
175
  };
163
176
  } catch {
164
177
  return { path, exists: true, valid: false };
@@ -608,6 +621,91 @@ export function managedCliToolReady(ready, name) {
608
621
  return ready?.toolReady?.[name] || ready || Promise.resolve([]);
609
622
  }
610
623
 
624
+ function removeRuntimePathDirectory(path) {
625
+ try { chmodSync(path, 0o700); } catch {}
626
+ try { rmSync(path, { recursive: true, force: true }); } catch {}
627
+ if (!existsSync(path)) runtimePathDirectories.delete(path);
628
+ }
629
+
630
+ export function cleanupManagedCliRuntimePaths() {
631
+ for (const directory of [...runtimePathDirectories]) {
632
+ removeRuntimePathDirectory(directory);
633
+ }
634
+ }
635
+
636
+ /**
637
+ * Run shutdown work only after managed CLI runtime paths are synchronously
638
+ * cleaned. The returned task may remain pending without delaying that cleanup.
639
+ */
640
+ export function runAfterManagedCliRuntimeCleanup(task) {
641
+ cleanupManagedCliRuntimePaths();
642
+ return task();
643
+ }
644
+
645
+ function registerRuntimePathDirectory(path) {
646
+ runtimePathDirectories.add(path);
647
+ if (runtimePathCleanupRegistered) return;
648
+ runtimePathCleanupRegistered = true;
649
+ process.once('exit', cleanupManagedCliRuntimePaths);
650
+ }
651
+
652
+ function createIsolatedManagedCommand(name, managed, platform) {
653
+ const binDir = mkdtempSync(join(tmpdir(), 'yeaft-managed-cli-'));
654
+ const command = join(binDir, executableName(name, platform));
655
+ const temporary = `${command}.tmp`;
656
+ try {
657
+ copyFileSync(managed.path, temporary, constants.COPYFILE_EXCL);
658
+ if (platform !== 'win32') chmodSync(temporary, 0o500);
659
+ if (hashFile(temporary) !== managed.binarySha256) {
660
+ throw new Error(`managed ${name} changed while preparing its runtime command`);
661
+ }
662
+ renameSync(temporary, command);
663
+ const verification = spawnSync(command, ['--version'], {
664
+ encoding: 'utf8',
665
+ timeout: 5000,
666
+ windowsHide: true,
667
+ });
668
+ if (verification.error || verification.status !== 0
669
+ || !versionOutputMatches(name, TOOL_SPECS[name].version, verification.stdout)) {
670
+ throw new Error(`isolated managed ${name} failed its version check`);
671
+ }
672
+ if (platform !== 'win32') chmodSync(binDir, 0o500);
673
+ registerRuntimePathDirectory(binDir);
674
+ return { binDir, command };
675
+ } catch (error) {
676
+ rmSync(temporary, { force: true });
677
+ removeRuntimePathDirectory(binDir);
678
+ throw error;
679
+ }
680
+ }
681
+
682
+ /**
683
+ * Wait for one managed command and expose it to child processes through an
684
+ * isolated PATH directory. The directory contains only the checksum-verified
685
+ * command, so unrelated files in the shared managed bin directory stay hidden.
686
+ */
687
+ export async function prepareManagedCliToolEnvironment(
688
+ ready,
689
+ name,
690
+ options = {},
691
+ ) {
692
+ const yeaftDir = resolve(options.yeaftDir || DEFAULT_ROOT);
693
+ const platform = options.platform || process.platform;
694
+ const arch = options.arch || process.arch;
695
+ const env = options.env || process.env;
696
+ await managedCliToolReady(ready, name);
697
+
698
+ const managed = inspectManagedBinary(name, { yeaftDir, platform, arch });
699
+ if (!managed.valid) {
700
+ const command = resolveExternalCommand(name, { yeaftDir, platform, env });
701
+ return { name, activated: false, command };
702
+ }
703
+
704
+ const isolated = createIsolatedManagedCommand(name, managed, platform);
705
+ prependDirectoryToPath(isolated.binDir, env, platform);
706
+ return { name, activated: true, ...isolated };
707
+ }
708
+
611
709
  export function summarizeManagedCliResults(results) {
612
710
  return (results || []).map(result => {
613
711
  const detail = result.path || result.reason || `${result.platform || ''}-${result.arch || ''}`;