erosolar-cli 1.7.385 ā 1.7.386
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/dist/core/secretStore.d.ts +1 -0
- package/dist/core/secretStore.d.ts.map +1 -1
- package/dist/core/secretStore.js +3 -0
- package/dist/core/secretStore.js.map +1 -1
- package/dist/core/toolRuntime.d.ts +6 -0
- package/dist/core/toolRuntime.d.ts.map +1 -1
- package/dist/core/toolRuntime.js +7 -0
- package/dist/core/toolRuntime.js.map +1 -1
- package/dist/runtime/agentHost.d.ts +3 -1
- package/dist/runtime/agentHost.d.ts.map +1 -1
- package/dist/runtime/agentHost.js +3 -0
- package/dist/runtime/agentHost.js.map +1 -1
- package/dist/runtime/agentSession.d.ts +2 -1
- package/dist/runtime/agentSession.d.ts.map +1 -1
- package/dist/runtime/agentSession.js +1 -0
- package/dist/runtime/agentSession.js.map +1 -1
- package/dist/runtime/universal.d.ts +2 -1
- package/dist/runtime/universal.d.ts.map +1 -1
- package/dist/runtime/universal.js +1 -0
- package/dist/runtime/universal.js.map +1 -1
- package/dist/shell/interactiveShell.d.ts +22 -0
- package/dist/shell/interactiveShell.d.ts.map +1 -1
- package/dist/shell/interactiveShell.js +211 -86
- package/dist/shell/interactiveShell.js.map +1 -1
- package/dist/shell/shellApp.d.ts.map +1 -1
- package/dist/shell/shellApp.js +5 -0
- package/dist/shell/shellApp.js.map +1 -1
- package/dist/shell/systemPrompt.d.ts.map +1 -1
- package/dist/shell/systemPrompt.js +4 -0
- package/dist/shell/systemPrompt.js.map +1 -1
- package/dist/shell/terminalInput.d.ts +23 -0
- package/dist/shell/terminalInput.d.ts.map +1 -1
- package/dist/shell/terminalInput.js +167 -26
- package/dist/shell/terminalInput.js.map +1 -1
- package/dist/shell/terminalInputAdapter.d.ts +9 -0
- package/dist/shell/terminalInputAdapter.d.ts.map +1 -1
- package/dist/shell/terminalInputAdapter.js +8 -2
- package/dist/shell/terminalInputAdapter.js.map +1 -1
- package/dist/tools/fileTools.d.ts.map +1 -1
- package/dist/tools/fileTools.js +29 -5
- package/dist/tools/fileTools.js.map +1 -1
- package/dist/tools/grepTools.d.ts.map +1 -1
- package/dist/tools/grepTools.js +22 -4
- package/dist/tools/grepTools.js.map +1 -1
- package/dist/tools/searchTools.d.ts.map +1 -1
- package/dist/tools/searchTools.js +47 -13
- package/dist/tools/searchTools.js.map +1 -1
- package/dist/tools/webTools.d.ts.map +1 -1
- package/dist/tools/webTools.js +36 -9
- package/dist/tools/webTools.js.map +1 -1
- package/dist/ui/display.d.ts +1 -0
- package/dist/ui/display.d.ts.map +1 -1
- package/dist/ui/display.js +50 -11
- package/dist/ui/display.js.map +1 -1
- package/dist/ui/layout.js +8 -7
- package/dist/ui/layout.js.map +1 -1
- package/dist/ui/unified/layout.js +2 -2
- package/dist/ui/unified/layout.js.map +1 -1
- package/package.json +1 -1
|
@@ -102,6 +102,8 @@ export class InteractiveShell {
|
|
|
102
102
|
pendingCleanup = null;
|
|
103
103
|
cleanupInProgress = false;
|
|
104
104
|
slashPreviewVisible = false;
|
|
105
|
+
lastLoggedPrompt = null;
|
|
106
|
+
lastLoggedPromptAt = 0;
|
|
105
107
|
skillRepository;
|
|
106
108
|
skillToolHandlers = new Map();
|
|
107
109
|
thinkingMode = 'balanced';
|
|
@@ -151,9 +153,12 @@ export class InteractiveShell {
|
|
|
151
153
|
autoBuildInFlight = false;
|
|
152
154
|
lastAutoBuildRun = null;
|
|
153
155
|
// Streaming UX tracking
|
|
156
|
+
batchedOutputMode;
|
|
154
157
|
streamingHeartbeatStart = null;
|
|
155
158
|
streamingHeartbeatFrame = 0;
|
|
156
159
|
streamingStatusLabel = null;
|
|
160
|
+
streamingUiActive = false;
|
|
161
|
+
pendingStreamBuffer = '';
|
|
157
162
|
lastStreamingElapsedSeconds = null; // Preserve final elapsed time
|
|
158
163
|
statusLineState = null;
|
|
159
164
|
statusMessageOverride = null;
|
|
@@ -175,6 +180,8 @@ export class InteractiveShell {
|
|
|
175
180
|
this.sessionRestoreConfig = config.sessionRestore ?? { mode: 'none' };
|
|
176
181
|
this._enabledPlugins = config.enabledPlugins ?? [];
|
|
177
182
|
this.version = config.version ?? '0.0.0';
|
|
183
|
+
const streamingEnv = process.env['EROSOLAR_STREAMING_UI']?.toLowerCase();
|
|
184
|
+
this.batchedOutputMode = streamingEnv !== 'on' && streamingEnv !== 'true' && streamingEnv !== '1';
|
|
178
185
|
// Alternate screen disabled - use terminal-native mode for proper scrollback and text selection
|
|
179
186
|
this.alternateScreenEnabled = false;
|
|
180
187
|
this.initializeSessionHistory();
|
|
@@ -277,7 +284,7 @@ export class InteractiveShell {
|
|
|
277
284
|
}
|
|
278
285
|
// Stream banner first - this sets up scroll region dynamically
|
|
279
286
|
const banner = this.buildBanner();
|
|
280
|
-
this.terminalInput.streamContent(banner + '\n
|
|
287
|
+
this.terminalInput.streamContent(banner + '\n');
|
|
281
288
|
// Render chat box after banner is streamed
|
|
282
289
|
this.refreshControlBar();
|
|
283
290
|
this.terminalInput.forceRender();
|
|
@@ -371,7 +378,6 @@ export class InteractiveShell {
|
|
|
371
378
|
void maybeOfferCliUpdate(this.version);
|
|
372
379
|
}
|
|
373
380
|
if (initialPrompt) {
|
|
374
|
-
this.logUserPrompt(initialPrompt);
|
|
375
381
|
await this.processInputBlock(initialPrompt);
|
|
376
382
|
return;
|
|
377
383
|
}
|
|
@@ -399,7 +405,6 @@ export class InteractiveShell {
|
|
|
399
405
|
}
|
|
400
406
|
// DON'T clear the input here - keep it visible while streaming.
|
|
401
407
|
// The input will be cleared after streaming completes in the finally block.
|
|
402
|
-
this.logUserPrompt(approved);
|
|
403
408
|
void this.processInputBlock(approved).catch((err) => {
|
|
404
409
|
display.showError(err instanceof Error ? err.message : String(err), err);
|
|
405
410
|
});
|
|
@@ -779,6 +784,21 @@ export class InteractiveShell {
|
|
|
779
784
|
// During streaming we still want the spinner prefix; when idle force a fast refresh.
|
|
780
785
|
this.refreshStatusLine(!this.isProcessing);
|
|
781
786
|
}
|
|
787
|
+
/**
|
|
788
|
+
* Inline menu/panel helpers (rendered in TerminalInput, not in scrollback).
|
|
789
|
+
*/
|
|
790
|
+
showInlineMenu(title, lines, tone = 'info', hint) {
|
|
791
|
+
const merged = [...lines];
|
|
792
|
+
if (hint) {
|
|
793
|
+
merged.push('', tone === 'error' ? theme.error(hint) : theme.warning(hint));
|
|
794
|
+
}
|
|
795
|
+
this.terminalInput.setInlinePanel({ title, lines: merged, tone });
|
|
796
|
+
this.terminalInput.render();
|
|
797
|
+
}
|
|
798
|
+
clearInlineMenu() {
|
|
799
|
+
this.terminalInput.setInlinePanel(null);
|
|
800
|
+
this.terminalInput.render();
|
|
801
|
+
}
|
|
782
802
|
async handleToolSettingsInput(input) {
|
|
783
803
|
const pending = this.pendingInteraction;
|
|
784
804
|
if (!pending || pending.type !== 'tool-settings') {
|
|
@@ -1255,10 +1275,18 @@ export class InteractiveShell {
|
|
|
1255
1275
|
* Log user prompt to the scroll region so it's part of the conversation flow.
|
|
1256
1276
|
*/
|
|
1257
1277
|
logUserPrompt(text) {
|
|
1258
|
-
|
|
1278
|
+
const normalized = text.trim();
|
|
1279
|
+
if (!normalized)
|
|
1259
1280
|
return;
|
|
1281
|
+
// Skip duplicate renders of the exact same prompt if they happen back-to-back.
|
|
1282
|
+
const now = Date.now();
|
|
1283
|
+
if (this.lastLoggedPrompt === normalized && now - this.lastLoggedPromptAt < 2000) {
|
|
1284
|
+
return;
|
|
1285
|
+
}
|
|
1286
|
+
this.lastLoggedPrompt = normalized;
|
|
1287
|
+
this.lastLoggedPromptAt = now;
|
|
1260
1288
|
// Format with user prompt prefix and write to scroll region
|
|
1261
|
-
const formatted = `${theme.user('>')} ${
|
|
1289
|
+
const formatted = `${theme.user('>')} ${normalized}\n`;
|
|
1262
1290
|
this.terminalInput.writeToScrollRegion(formatted);
|
|
1263
1291
|
}
|
|
1264
1292
|
requestPromptRefresh(force = false) {
|
|
@@ -1280,17 +1308,49 @@ export class InteractiveShell {
|
|
|
1280
1308
|
this.promptRefreshTimer = null;
|
|
1281
1309
|
}
|
|
1282
1310
|
}
|
|
1311
|
+
resetStreamBuffer() {
|
|
1312
|
+
this.pendingStreamBuffer = '';
|
|
1313
|
+
}
|
|
1314
|
+
captureStreamChunk(chunk) {
|
|
1315
|
+
if (!this.batchedOutputMode || !chunk) {
|
|
1316
|
+
return;
|
|
1317
|
+
}
|
|
1318
|
+
this.pendingStreamBuffer += chunk;
|
|
1319
|
+
}
|
|
1320
|
+
consumeStreamBuffer(fallback) {
|
|
1321
|
+
if (!this.batchedOutputMode) {
|
|
1322
|
+
return fallback;
|
|
1323
|
+
}
|
|
1324
|
+
const buffered = this.pendingStreamBuffer;
|
|
1325
|
+
this.pendingStreamBuffer = '';
|
|
1326
|
+
return buffered || fallback;
|
|
1327
|
+
}
|
|
1328
|
+
currentRunElapsedMs() {
|
|
1329
|
+
if (!this.streamingHeartbeatStart) {
|
|
1330
|
+
return undefined;
|
|
1331
|
+
}
|
|
1332
|
+
return Date.now() - this.streamingHeartbeatStart;
|
|
1333
|
+
}
|
|
1283
1334
|
startStreamingHeartbeat(label = 'Streaming') {
|
|
1284
1335
|
this.stopStreamingHeartbeat();
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
// Set up scroll region for streaming content
|
|
1288
|
-
this.terminalInput.enterStreamingScrollRegion();
|
|
1289
|
-
this.uiUpdates.setMode('streaming');
|
|
1336
|
+
this.resetStreamBuffer();
|
|
1337
|
+
this.lastStreamingElapsedSeconds = null;
|
|
1290
1338
|
this.streamingHeartbeatStart = Date.now();
|
|
1291
1339
|
this.streamingHeartbeatFrame = 0;
|
|
1292
1340
|
const initialFrame = STREAMING_SPINNER_FRAMES[this.streamingHeartbeatFrame];
|
|
1293
1341
|
this.streamingStatusLabel = this.buildStreamingStatus(`${initialFrame} ${label}`, 0);
|
|
1342
|
+
if (this.batchedOutputMode) {
|
|
1343
|
+
this.streamingUiActive = false;
|
|
1344
|
+
this.uiUpdates.setMode('processing');
|
|
1345
|
+
}
|
|
1346
|
+
else {
|
|
1347
|
+
// Enter global streaming mode - blocks all non-streaming UI output
|
|
1348
|
+
enterStreamingMode();
|
|
1349
|
+
// Set up scroll region for streaming content
|
|
1350
|
+
this.terminalInput.enterStreamingScrollRegion();
|
|
1351
|
+
this.streamingUiActive = true;
|
|
1352
|
+
this.uiUpdates.setMode('streaming');
|
|
1353
|
+
}
|
|
1294
1354
|
display.updateStreamingStatus(this.streamingStatusLabel);
|
|
1295
1355
|
this.refreshStatusLine(true);
|
|
1296
1356
|
// Periodically refresh the pinned input/status region while streaming so
|
|
@@ -1320,18 +1380,21 @@ export class InteractiveShell {
|
|
|
1320
1380
|
});
|
|
1321
1381
|
}
|
|
1322
1382
|
stopStreamingHeartbeat() {
|
|
1323
|
-
// Exit global streaming mode - allows UI to render again
|
|
1324
|
-
exitStreamingMode();
|
|
1325
1383
|
// Preserve final elapsed time before clearing heartbeat start
|
|
1326
1384
|
if (this.streamingHeartbeatStart) {
|
|
1327
1385
|
this.lastStreamingElapsedSeconds = Math.max(0, Math.floor((Date.now() - this.streamingHeartbeatStart) / 1000));
|
|
1328
1386
|
}
|
|
1329
|
-
|
|
1330
|
-
|
|
1387
|
+
if (this.streamingUiActive) {
|
|
1388
|
+
// Exit global streaming mode - allows UI to render again
|
|
1389
|
+
exitStreamingMode();
|
|
1390
|
+
// Exit scroll region mode
|
|
1391
|
+
this.terminalInput.exitStreamingScrollRegion();
|
|
1392
|
+
}
|
|
1331
1393
|
this.uiUpdates.stopHeartbeat('streaming');
|
|
1332
1394
|
this.streamingHeartbeatStart = null;
|
|
1333
1395
|
this.streamingHeartbeatFrame = 0;
|
|
1334
1396
|
this.streamingStatusLabel = null;
|
|
1397
|
+
this.streamingUiActive = false;
|
|
1335
1398
|
// Clear streaming label specifically (keeps override and main status if set)
|
|
1336
1399
|
this.terminalInput.setStreamingLabel(null);
|
|
1337
1400
|
// Clear streaming status from display
|
|
@@ -1499,6 +1562,10 @@ export class InteractiveShell {
|
|
|
1499
1562
|
return false;
|
|
1500
1563
|
}
|
|
1501
1564
|
switch (this.pendingInteraction.type) {
|
|
1565
|
+
case 'model-loading':
|
|
1566
|
+
display.showInfo('Still fetching model options. Please wait a moment.');
|
|
1567
|
+
this.terminalInput.render();
|
|
1568
|
+
return true;
|
|
1502
1569
|
case 'model-provider':
|
|
1503
1570
|
await this.handleModelProviderSelection(input);
|
|
1504
1571
|
return true;
|
|
@@ -3633,28 +3700,25 @@ export class InteractiveShell {
|
|
|
3633
3700
|
return lines.join('\n');
|
|
3634
3701
|
}
|
|
3635
3702
|
async showModelMenu() {
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3703
|
+
// Hold input immediately so numeric selections don't get queued as prompts while we fetch
|
|
3704
|
+
this.pendingInteraction = { type: 'model-loading' };
|
|
3705
|
+
this.showInlineMenu('Model provider', [theme.ui.muted('Fetching latest models from providers...')], 'info');
|
|
3706
|
+
try {
|
|
3707
|
+
// Fetch live models from all configured providers
|
|
3708
|
+
const providerStatuses = await quickCheckProviders();
|
|
3709
|
+
const providerOptions = this.buildProviderOptionsWithDiscovery(providerStatuses);
|
|
3710
|
+
if (!providerOptions.length) {
|
|
3711
|
+
this.pendingInteraction = null;
|
|
3712
|
+
this.showInlineMenu('Model provider', [theme.warning('No providers are available.')], 'warning');
|
|
3713
|
+
return;
|
|
3714
|
+
}
|
|
3715
|
+
this.pendingInteraction = { type: 'model-provider', options: providerOptions };
|
|
3716
|
+
this.renderProviderMenu(providerOptions);
|
|
3717
|
+
}
|
|
3718
|
+
catch (error) {
|
|
3719
|
+
this.pendingInteraction = null;
|
|
3720
|
+
this.showInlineMenu('Model provider', [theme.error('Failed to load model list. Try again in a moment.')], 'error');
|
|
3643
3721
|
}
|
|
3644
|
-
const lines = [
|
|
3645
|
-
theme.bold('Select a provider:'),
|
|
3646
|
-
...providerOptions.map((option, index) => {
|
|
3647
|
-
const isCurrent = option.provider === this.sessionState.provider;
|
|
3648
|
-
const countLabel = `${option.modelCount} model${option.modelCount === 1 ? '' : 's'}`;
|
|
3649
|
-
const latestLabel = option.latestModel ? theme.success(` (latest: ${option.latestModel})`) : '';
|
|
3650
|
-
const label = this.colorizeDropdownLine(`${index + 1}. ${option.label} ā ${countLabel}${latestLabel}`, index);
|
|
3651
|
-
const suffix = isCurrent ? ` ${theme.primary('⢠current')}` : '';
|
|
3652
|
-
return `${label}${suffix}`;
|
|
3653
|
-
}),
|
|
3654
|
-
'Type the number of the provider to continue, or type "cancel".',
|
|
3655
|
-
];
|
|
3656
|
-
display.showSystemMessage(lines.join('\n'));
|
|
3657
|
-
this.pendingInteraction = { type: 'model-provider', options: providerOptions };
|
|
3658
3722
|
}
|
|
3659
3723
|
buildProviderOptions() {
|
|
3660
3724
|
const counts = new Map();
|
|
@@ -3744,6 +3808,21 @@ export class InteractiveShell {
|
|
|
3744
3808
|
};
|
|
3745
3809
|
});
|
|
3746
3810
|
}
|
|
3811
|
+
renderProviderMenu(options, hint) {
|
|
3812
|
+
const lines = [
|
|
3813
|
+
theme.bold('Select a provider:'),
|
|
3814
|
+
...options.map((option, index) => {
|
|
3815
|
+
const isCurrent = option.provider === this.sessionState.provider;
|
|
3816
|
+
const countLabel = `${option.modelCount} model${option.modelCount === 1 ? '' : 's'}`;
|
|
3817
|
+
const latestLabel = option.latestModel ? theme.success(` (latest: ${option.latestModel})`) : '';
|
|
3818
|
+
const label = this.colorizeDropdownLine(`${index + 1}. ${option.label} ā ${countLabel}${latestLabel}`, index);
|
|
3819
|
+
const suffix = isCurrent ? ` ${theme.primary('⢠current')}` : '';
|
|
3820
|
+
return `${label}${suffix}`;
|
|
3821
|
+
}),
|
|
3822
|
+
'Type the number of the provider to continue, or type "cancel".',
|
|
3823
|
+
];
|
|
3824
|
+
this.showInlineMenu('Model provider', lines, 'info', hint);
|
|
3825
|
+
}
|
|
3747
3826
|
showProviderModels(option) {
|
|
3748
3827
|
// Start with static presets
|
|
3749
3828
|
const staticModels = MODEL_PRESETS.filter((preset) => preset.provider === option.provider);
|
|
@@ -3783,13 +3862,17 @@ export class InteractiveShell {
|
|
|
3783
3862
|
}
|
|
3784
3863
|
}
|
|
3785
3864
|
if (!allModels.length) {
|
|
3786
|
-
|
|
3865
|
+
this.showInlineMenu('Model selection', [theme.warning(`No models available for ${option.label}.`)], 'warning');
|
|
3787
3866
|
this.pendingInteraction = null;
|
|
3788
3867
|
return;
|
|
3789
3868
|
}
|
|
3869
|
+
this.renderModelSelection(allModels, option, null);
|
|
3870
|
+
this.pendingInteraction = { type: 'model', provider: option.provider, options: allModels };
|
|
3871
|
+
}
|
|
3872
|
+
renderModelSelection(models, option, hint) {
|
|
3790
3873
|
const lines = [
|
|
3791
3874
|
theme.bold(`Select a model from ${option.label}:`),
|
|
3792
|
-
...
|
|
3875
|
+
...models.map((preset, index) => {
|
|
3793
3876
|
const isCurrent = preset.id === this.sessionState.model;
|
|
3794
3877
|
const isLatest = preset.id === option.latestModel;
|
|
3795
3878
|
const latestBadge = isLatest ? theme.success(' ā
LATEST') : '';
|
|
@@ -3800,11 +3883,23 @@ export class InteractiveShell {
|
|
|
3800
3883
|
}),
|
|
3801
3884
|
'Type the number of the model to select it, type "back" to change provider, or type "cancel".',
|
|
3802
3885
|
];
|
|
3803
|
-
|
|
3804
|
-
|
|
3886
|
+
this.showInlineMenu('Model selection', lines, 'info', hint ?? undefined);
|
|
3887
|
+
}
|
|
3888
|
+
buildProviderContext(provider, models) {
|
|
3889
|
+
return {
|
|
3890
|
+
provider,
|
|
3891
|
+
label: this.providerLabel(provider),
|
|
3892
|
+
modelCount: models.length,
|
|
3893
|
+
latestModel: models[0]?.id,
|
|
3894
|
+
discoveredModels: [],
|
|
3895
|
+
};
|
|
3805
3896
|
}
|
|
3806
3897
|
showSecretsMenu() {
|
|
3807
3898
|
const definitions = listSecretDefinitions();
|
|
3899
|
+
this.pendingInteraction = { type: 'secret-select', options: definitions };
|
|
3900
|
+
this.renderSecretsMenu(definitions);
|
|
3901
|
+
}
|
|
3902
|
+
renderSecretsMenu(definitions, hint) {
|
|
3808
3903
|
const lines = [
|
|
3809
3904
|
theme.bold('Manage Secrets:'),
|
|
3810
3905
|
...definitions.map((definition, index) => {
|
|
@@ -3816,8 +3911,18 @@ export class InteractiveShell {
|
|
|
3816
3911
|
}),
|
|
3817
3912
|
'Enter the number to update a key, or type "cancel".',
|
|
3818
3913
|
];
|
|
3819
|
-
|
|
3820
|
-
|
|
3914
|
+
this.showInlineMenu('Secrets', lines, 'info', hint);
|
|
3915
|
+
}
|
|
3916
|
+
renderSecretInput(secret, hint) {
|
|
3917
|
+
const value = getSecretValue(secret.id);
|
|
3918
|
+
const status = value ? maskSecret(value) : theme.warning('not set');
|
|
3919
|
+
const providers = secret.providers.map((id) => this.providerLabel(id)).join(', ');
|
|
3920
|
+
const lines = [
|
|
3921
|
+
`${secret.label} (${providers})`,
|
|
3922
|
+
`Current: ${status}`,
|
|
3923
|
+
'Enter a new value or type "cancel".',
|
|
3924
|
+
];
|
|
3925
|
+
this.showInlineMenu('Update secret', lines, 'info', hint);
|
|
3821
3926
|
}
|
|
3822
3927
|
showToolsMenu() {
|
|
3823
3928
|
const options = getToolToggleOptions();
|
|
@@ -4197,26 +4302,23 @@ export class InteractiveShell {
|
|
|
4197
4302
|
}
|
|
4198
4303
|
const trimmed = input.trim();
|
|
4199
4304
|
if (!trimmed) {
|
|
4200
|
-
|
|
4201
|
-
this.terminalInput.render();
|
|
4305
|
+
this.renderProviderMenu(pending.options, 'Enter a number or type cancel.');
|
|
4202
4306
|
return;
|
|
4203
4307
|
}
|
|
4204
4308
|
if (trimmed.toLowerCase() === 'cancel') {
|
|
4205
4309
|
this.pendingInteraction = null;
|
|
4206
|
-
|
|
4207
|
-
this.
|
|
4310
|
+
this.clearInlineMenu();
|
|
4311
|
+
this.updateStatusMessage('Model selection cancelled.');
|
|
4208
4312
|
return;
|
|
4209
4313
|
}
|
|
4210
4314
|
const choice = Number.parseInt(trimmed, 10);
|
|
4211
4315
|
if (!Number.isFinite(choice)) {
|
|
4212
|
-
|
|
4213
|
-
this.terminalInput.render();
|
|
4316
|
+
this.renderProviderMenu(pending.options, 'Please enter a valid number.');
|
|
4214
4317
|
return;
|
|
4215
4318
|
}
|
|
4216
4319
|
const option = pending.options[choice - 1];
|
|
4217
4320
|
if (!option) {
|
|
4218
|
-
|
|
4219
|
-
this.terminalInput.render();
|
|
4321
|
+
this.renderProviderMenu(pending.options, 'That option is not available.');
|
|
4220
4322
|
return;
|
|
4221
4323
|
}
|
|
4222
4324
|
this.showProviderModels(option);
|
|
@@ -4227,10 +4329,10 @@ export class InteractiveShell {
|
|
|
4227
4329
|
if (!pending || pending.type !== 'model') {
|
|
4228
4330
|
return;
|
|
4229
4331
|
}
|
|
4332
|
+
const providerContext = this.buildProviderContext(pending.provider, pending.options);
|
|
4230
4333
|
const trimmed = input.trim();
|
|
4231
4334
|
if (!trimmed) {
|
|
4232
|
-
|
|
4233
|
-
this.terminalInput.render();
|
|
4335
|
+
this.renderModelSelection(pending.options, providerContext, 'Enter a number, type "back", or type "cancel".');
|
|
4234
4336
|
return;
|
|
4235
4337
|
}
|
|
4236
4338
|
if (trimmed.toLowerCase() === 'back') {
|
|
@@ -4240,20 +4342,18 @@ export class InteractiveShell {
|
|
|
4240
4342
|
}
|
|
4241
4343
|
if (trimmed.toLowerCase() === 'cancel') {
|
|
4242
4344
|
this.pendingInteraction = null;
|
|
4243
|
-
|
|
4244
|
-
this.
|
|
4345
|
+
this.clearInlineMenu();
|
|
4346
|
+
this.updateStatusMessage('Model selection cancelled.');
|
|
4245
4347
|
return;
|
|
4246
4348
|
}
|
|
4247
4349
|
const choice = Number.parseInt(trimmed, 10);
|
|
4248
4350
|
if (!Number.isFinite(choice)) {
|
|
4249
|
-
|
|
4250
|
-
this.terminalInput.render();
|
|
4351
|
+
this.renderModelSelection(pending.options, providerContext, 'Please enter a valid number.');
|
|
4251
4352
|
return;
|
|
4252
4353
|
}
|
|
4253
4354
|
const preset = pending.options[choice - 1];
|
|
4254
4355
|
if (!preset) {
|
|
4255
|
-
|
|
4256
|
-
this.terminalInput.render();
|
|
4356
|
+
this.renderModelSelection(pending.options, providerContext, 'That option is not available.');
|
|
4257
4357
|
return;
|
|
4258
4358
|
}
|
|
4259
4359
|
this.pendingInteraction = null;
|
|
@@ -4277,11 +4377,14 @@ export class InteractiveShell {
|
|
|
4277
4377
|
};
|
|
4278
4378
|
this.applyPresetReasoningDefaults();
|
|
4279
4379
|
if (this.rebuildAgent()) {
|
|
4280
|
-
|
|
4380
|
+
this.showInlineMenu('Model updated', [`Switched to ${preset.label}.`], 'success');
|
|
4281
4381
|
this.refreshBannerSessionInfo();
|
|
4282
4382
|
this.persistSessionPreference();
|
|
4283
4383
|
this.resetChatBoxAfterModelSwap();
|
|
4284
4384
|
}
|
|
4385
|
+
else {
|
|
4386
|
+
this.showInlineMenu('Model updated', [`Using ${preset.label}.`], 'info');
|
|
4387
|
+
}
|
|
4285
4388
|
}
|
|
4286
4389
|
async handleSecretSelection(input) {
|
|
4287
4390
|
const pending = this.pendingInteraction;
|
|
@@ -4290,30 +4393,27 @@ export class InteractiveShell {
|
|
|
4290
4393
|
}
|
|
4291
4394
|
const trimmed = input.trim();
|
|
4292
4395
|
if (!trimmed) {
|
|
4293
|
-
|
|
4294
|
-
this.terminalInput.render();
|
|
4396
|
+
this.renderSecretsMenu(pending.options, 'Enter a number or type cancel.');
|
|
4295
4397
|
return;
|
|
4296
4398
|
}
|
|
4297
4399
|
if (trimmed.toLowerCase() === 'cancel') {
|
|
4298
4400
|
this.pendingInteraction = null;
|
|
4299
|
-
|
|
4300
|
-
this.
|
|
4401
|
+
this.clearInlineMenu();
|
|
4402
|
+
this.updateStatusMessage('Secret management cancelled.');
|
|
4301
4403
|
return;
|
|
4302
4404
|
}
|
|
4303
4405
|
const choice = Number.parseInt(trimmed, 10);
|
|
4304
4406
|
if (!Number.isFinite(choice)) {
|
|
4305
|
-
|
|
4306
|
-
this.terminalInput.render();
|
|
4407
|
+
this.renderSecretsMenu(pending.options, 'Please enter a valid number.');
|
|
4307
4408
|
return;
|
|
4308
4409
|
}
|
|
4309
4410
|
const secret = pending.options[choice - 1];
|
|
4310
4411
|
if (!secret) {
|
|
4311
|
-
|
|
4312
|
-
this.terminalInput.render();
|
|
4412
|
+
this.renderSecretsMenu(pending.options, 'That option is not available.');
|
|
4313
4413
|
return;
|
|
4314
4414
|
}
|
|
4315
|
-
display.showSystemMessage(`Enter a new value for ${secret.label} or type "cancel".`);
|
|
4316
4415
|
this.pendingInteraction = { type: 'secret-input', secret };
|
|
4416
|
+
this.renderSecretInput(secret);
|
|
4317
4417
|
this.terminalInput.render();
|
|
4318
4418
|
}
|
|
4319
4419
|
async handleSecretInput(input) {
|
|
@@ -4323,20 +4423,20 @@ export class InteractiveShell {
|
|
|
4323
4423
|
}
|
|
4324
4424
|
const trimmed = input.trim();
|
|
4325
4425
|
if (!trimmed) {
|
|
4326
|
-
|
|
4327
|
-
this.terminalInput.render();
|
|
4426
|
+
this.renderSecretInput(pending.secret, 'Enter a value or type cancel.');
|
|
4328
4427
|
return;
|
|
4329
4428
|
}
|
|
4330
4429
|
if (trimmed.toLowerCase() === 'cancel') {
|
|
4331
4430
|
this.pendingInteraction = null;
|
|
4332
4431
|
this.pendingSecretRetry = null;
|
|
4333
|
-
|
|
4432
|
+
this.clearInlineMenu();
|
|
4433
|
+
this.updateStatusMessage('Secret unchanged.');
|
|
4334
4434
|
this.terminalInput.render();
|
|
4335
4435
|
return;
|
|
4336
4436
|
}
|
|
4337
4437
|
try {
|
|
4338
4438
|
setSecretValue(pending.secret.id, trimmed);
|
|
4339
|
-
|
|
4439
|
+
this.showInlineMenu('Secret updated', [`${pending.secret.label} saved.`], 'success');
|
|
4340
4440
|
this.pendingInteraction = null;
|
|
4341
4441
|
const deferred = this.pendingSecretRetry;
|
|
4342
4442
|
this.pendingSecretRetry = null;
|
|
@@ -4351,7 +4451,7 @@ export class InteractiveShell {
|
|
|
4351
4451
|
}
|
|
4352
4452
|
catch (error) {
|
|
4353
4453
|
const message = error instanceof Error ? error.message : String(error);
|
|
4354
|
-
|
|
4454
|
+
this.showInlineMenu('Secret error', [message], 'error');
|
|
4355
4455
|
this.pendingInteraction = null;
|
|
4356
4456
|
this.pendingSecretRetry = null;
|
|
4357
4457
|
}
|
|
@@ -4370,6 +4470,7 @@ export class InteractiveShell {
|
|
|
4370
4470
|
if (!agent) {
|
|
4371
4471
|
return;
|
|
4372
4472
|
}
|
|
4473
|
+
this.logUserPrompt(request);
|
|
4373
4474
|
this.isProcessing = true;
|
|
4374
4475
|
this.uiUpdates.setMode('processing');
|
|
4375
4476
|
this.terminalInput.setStreaming(true);
|
|
@@ -4389,7 +4490,8 @@ export class InteractiveShell {
|
|
|
4389
4490
|
let responseText = '';
|
|
4390
4491
|
try {
|
|
4391
4492
|
// Start streaming - no header needed, the input area already provides context
|
|
4392
|
-
this.
|
|
4493
|
+
const heartbeatLabel = this.batchedOutputMode ? 'Processing response' : 'Streaming response';
|
|
4494
|
+
this.startStreamingHeartbeat(heartbeatLabel);
|
|
4393
4495
|
responseText = await agent.send(request, true);
|
|
4394
4496
|
await this.awaitPendingCleanup();
|
|
4395
4497
|
this.captureHistorySnapshot();
|
|
@@ -4505,7 +4607,8 @@ export class InteractiveShell {
|
|
|
4505
4607
|
this.uiAdapter.startProcessing('Continuous execution mode');
|
|
4506
4608
|
this.setProcessingStatus();
|
|
4507
4609
|
// No streaming header - just start streaming directly
|
|
4508
|
-
this.
|
|
4610
|
+
const continuousLabel = this.batchedOutputMode ? 'Processing' : 'Streaming';
|
|
4611
|
+
this.startStreamingHeartbeat(continuousLabel);
|
|
4509
4612
|
let iteration = 0;
|
|
4510
4613
|
let lastResponse = '';
|
|
4511
4614
|
let consecutiveNoProgress = 0;
|
|
@@ -4533,9 +4636,10 @@ When truly finished with ALL tasks, explicitly state "TASK_FULLY_COMPLETE".`;
|
|
|
4533
4636
|
display.showSystemMessage(`\nš Iteration ${iteration}/${MAX_ITERATIONS}`);
|
|
4534
4637
|
this.updateStatusMessage(`Working on iteration ${iteration}...`);
|
|
4535
4638
|
try {
|
|
4536
|
-
// Send the request and capture the response
|
|
4639
|
+
// Send the request and capture the response as a single block
|
|
4537
4640
|
display.showThinking('Responding...');
|
|
4538
4641
|
this.refreshStatusLine(true);
|
|
4642
|
+
this.resetStreamBuffer();
|
|
4539
4643
|
const response = await agent.send(currentPrompt, true);
|
|
4540
4644
|
await this.awaitPendingCleanup();
|
|
4541
4645
|
this.captureHistorySnapshot();
|
|
@@ -5036,6 +5140,7 @@ What's the next action?`;
|
|
|
5036
5140
|
// Send the error to the agent for fixing
|
|
5037
5141
|
display.showThinking('Analyzing build errors');
|
|
5038
5142
|
this.refreshStatusLine(true);
|
|
5143
|
+
this.resetStreamBuffer();
|
|
5039
5144
|
const response = await this.agent.send(prompt, true);
|
|
5040
5145
|
display.stopThinking();
|
|
5041
5146
|
this.refreshStatusLine(true);
|
|
@@ -5065,27 +5170,33 @@ What's the next action?`;
|
|
|
5065
5170
|
};
|
|
5066
5171
|
this.agent = this.runtimeSession.createAgent(selection, {
|
|
5067
5172
|
onStreamChunk: (chunk) => {
|
|
5173
|
+
if (this.batchedOutputMode) {
|
|
5174
|
+
this.captureStreamChunk(chunk);
|
|
5175
|
+
return;
|
|
5176
|
+
}
|
|
5068
5177
|
// Stream output using clean streamContent() - chat box floats below
|
|
5069
5178
|
this.terminalInput.streamContent(chunk);
|
|
5070
5179
|
},
|
|
5071
5180
|
onStreamFallback: (info) => this.handleStreamingFallback(info),
|
|
5072
5181
|
onAssistantMessage: (content, metadata) => {
|
|
5073
5182
|
const enriched = this.buildDisplayMetadata(metadata);
|
|
5183
|
+
const alreadyStreamedToUi = !this.batchedOutputMode && metadata.wasStreamed;
|
|
5184
|
+
const bufferedContent = this.consumeStreamBuffer(content);
|
|
5074
5185
|
// Update spinner based on message type
|
|
5075
5186
|
if (metadata.isFinal) {
|
|
5076
5187
|
// Skip display if content was already streamed to avoid double-display
|
|
5077
|
-
if (!
|
|
5078
|
-
const parsed = this.splitThinkingResponse(
|
|
5188
|
+
if (!alreadyStreamedToUi) {
|
|
5189
|
+
const parsed = this.splitThinkingResponse(bufferedContent);
|
|
5079
5190
|
if (parsed?.thinking) {
|
|
5080
5191
|
const summary = this.extractThoughtSummary(parsed.thinking);
|
|
5081
5192
|
if (summary) {
|
|
5082
5193
|
display.updateThinking(`š ${summary}`);
|
|
5083
5194
|
}
|
|
5084
|
-
display.
|
|
5195
|
+
display.showThinkingBlock(parsed.thinking, enriched.elapsedMs ?? this.currentRunElapsedMs());
|
|
5085
5196
|
}
|
|
5086
|
-
const finalContent = parsed?.response?.trim() ||
|
|
5197
|
+
const finalContent = parsed?.response?.trim() || bufferedContent.trim();
|
|
5087
5198
|
if (finalContent) {
|
|
5088
|
-
display.showAssistantMessage(finalContent, enriched);
|
|
5199
|
+
display.showAssistantMessage(finalContent, { ...enriched, isFinal: true });
|
|
5089
5200
|
}
|
|
5090
5201
|
}
|
|
5091
5202
|
// Status shown in mode controls bar - no separate status line needed
|
|
@@ -5107,8 +5218,13 @@ What's the next action?`;
|
|
|
5107
5218
|
// Stop spinner and show the narrative text directly
|
|
5108
5219
|
display.stopThinking();
|
|
5109
5220
|
// Skip display if content was already streamed to avoid double-display
|
|
5110
|
-
if (!
|
|
5111
|
-
|
|
5221
|
+
if (!alreadyStreamedToUi) {
|
|
5222
|
+
const parsed = this.splitThinkingResponse(bufferedContent);
|
|
5223
|
+
const thoughtContent = parsed?.thinking ?? parsed?.response ?? bufferedContent;
|
|
5224
|
+
const trimmed = thoughtContent.trim();
|
|
5225
|
+
if (trimmed) {
|
|
5226
|
+
display.showThinkingBlock(trimmed, enriched.elapsedMs ?? this.currentRunElapsedMs());
|
|
5227
|
+
}
|
|
5112
5228
|
}
|
|
5113
5229
|
// The isProcessing flag already shows "ā³ Processing..." - no need for duplicate status
|
|
5114
5230
|
this.requestPromptRefresh();
|
|
@@ -5667,7 +5783,12 @@ What's the next action?`;
|
|
|
5667
5783
|
const reason = info.reason ? ` (${info.reason.replace(/-/g, ' ')})` : '';
|
|
5668
5784
|
const partialNote = info.partialResponse ? ' Received partial stream before failure.' : '';
|
|
5669
5785
|
display.showWarning(`Streaming failed${reason}, retrying without streaming.${detail}${partialNote}`);
|
|
5670
|
-
this.
|
|
5786
|
+
const bufferedPartial = this.consumeStreamBuffer(info.partialResponse ?? '');
|
|
5787
|
+
if (this.batchedOutputMode && bufferedPartial.trim()) {
|
|
5788
|
+
display.showThinkingBlock(bufferedPartial.trim(), this.currentRunElapsedMs());
|
|
5789
|
+
}
|
|
5790
|
+
const fallbackLabel = this.batchedOutputMode ? 'Retrying (batched)' : 'Fallback in progress';
|
|
5791
|
+
this.startStreamingHeartbeat(fallbackLabel);
|
|
5671
5792
|
this.requestPromptRefresh(true);
|
|
5672
5793
|
}
|
|
5673
5794
|
handleProviderError(error, retryAction) {
|
|
@@ -5678,9 +5799,9 @@ What's the next action?`;
|
|
|
5678
5799
|
this.handleApiKeyIssue(apiKeyIssue, retryAction);
|
|
5679
5800
|
return true;
|
|
5680
5801
|
}
|
|
5681
|
-
handleApiKeyIssue(info, retryAction) {
|
|
5802
|
+
handleApiKeyIssue(info, retryAction, contextLabel) {
|
|
5682
5803
|
const secret = info.secret ?? null;
|
|
5683
|
-
const providerLabel = info.provider ? this.providerLabel(info.provider) : 'the selected provider';
|
|
5804
|
+
const providerLabel = contextLabel ?? (info.provider ? this.providerLabel(info.provider) : 'the selected provider');
|
|
5684
5805
|
if (!secret) {
|
|
5685
5806
|
this.pendingSecretRetry = null;
|
|
5686
5807
|
const guidance = 'Run "/secrets" to configure the required API key or export it (e.g., EXPORT KEY=value) before launching the CLI.';
|
|
@@ -5702,6 +5823,10 @@ What's the next action?`;
|
|
|
5702
5823
|
this.pendingInteraction = { type: 'secret-input', secret };
|
|
5703
5824
|
this.showSecretGuidance(secret, isMissing);
|
|
5704
5825
|
}
|
|
5826
|
+
handleToolApiKeyIssue(info, call) {
|
|
5827
|
+
const label = call?.name ? `${call.name} tool` : 'web tools';
|
|
5828
|
+
this.handleApiKeyIssue(info, undefined, label);
|
|
5829
|
+
}
|
|
5705
5830
|
showSecretGuidance(secret, promptForInput) {
|
|
5706
5831
|
const lines = [];
|
|
5707
5832
|
if (promptForInput) {
|
|
@@ -5711,7 +5836,7 @@ What's the next action?`;
|
|
|
5711
5836
|
lines.push(`Update the stored value for ${secret.label} or type "cancel".`);
|
|
5712
5837
|
}
|
|
5713
5838
|
lines.push(`Tip: run "/secrets" anytime to manage credentials or export ${secret.envVar}=<value> before launching the CLI.`);
|
|
5714
|
-
|
|
5839
|
+
this.showInlineMenu('Secrets', lines, promptForInput ? 'warning' : 'info');
|
|
5715
5840
|
}
|
|
5716
5841
|
colorizeDropdownLine(text, index) {
|
|
5717
5842
|
if (!DROPDOWN_COLORS.length) {
|