@yeaft/webchat-agent 1.0.338 → 1.0.340
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 +39 -22
- package/local-runtime/version.json +1 -1
- package/package.json +1 -1
- package/yeaft/cli.js +45 -7
- package/yeaft/engine.js +21 -17
- package/yeaft/managed-cli.js +102 -4
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 {
|
|
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
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
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
|
-
|
|
320
|
-
}
|
|
321
|
-
ctx.terminals.clear();
|
|
327
|
+
ctx.terminals.clear();
|
|
322
328
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
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
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
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.
|
|
1
|
+
{"version":"1.0.340"}
|
package/package.json
CHANGED
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 {
|
|
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
|
}
|
package/yeaft/engine.js
CHANGED
|
@@ -3437,24 +3437,28 @@ export class Engine {
|
|
|
3437
3437
|
|
|
3438
3438
|
// If new user input was appended while this loop was streaming and
|
|
3439
3439
|
// there are no tools to force another loop, splice it now and continue
|
|
3440
|
-
// instead of ending the thread.
|
|
3441
|
-
//
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3440
|
+
// instead of ending the thread. A tool response must execute first: an
|
|
3441
|
+
// append between assistant(toolCalls) and its tool results would break
|
|
3442
|
+
// the provider protocol and discard the entire tool batch. The regular
|
|
3443
|
+
// pre-stream drain on the next iteration appends input after those results.
|
|
3444
|
+
if (toolCalls.length === 0) {
|
|
3445
|
+
const appendedAfterAssistant = this.#drainPendingUserMessages(drainPendingUserMessages);
|
|
3446
|
+
if (appendedAfterAssistant.length > 0) {
|
|
3447
|
+
for (const item of appendedAfterAssistant) {
|
|
3448
|
+
this.#persistAppendedUserMessage(item, runtimeSessionId);
|
|
3449
|
+
conversationMessages.push({ role: 'user', content: item.content });
|
|
3450
|
+
yield {
|
|
3451
|
+
type: 'user_append',
|
|
3452
|
+
turnId: queryTurnId,
|
|
3453
|
+
loopNumber: turnNumber,
|
|
3454
|
+
threadId,
|
|
3455
|
+
preview: String(item.preview || '').slice(0, 200),
|
|
3456
|
+
internal: Boolean(item.internal),
|
|
3457
|
+
};
|
|
3458
|
+
}
|
|
3459
|
+
yield { type: 'turn_end', turnNumber, stopReason: 'user_append_continue', threadId };
|
|
3460
|
+
continue;
|
|
3455
3461
|
}
|
|
3456
|
-
yield { type: 'turn_end', turnNumber, stopReason: 'user_append_continue', threadId };
|
|
3457
|
-
continue;
|
|
3458
3462
|
}
|
|
3459
3463
|
|
|
3460
3464
|
// If no tool calls, we're done — UNLESS we still own a pending
|
package/yeaft/managed-cli.js
CHANGED
|
@@ -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
|
-
|
|
82
|
-
|
|
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:
|
|
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 || ''}`;
|