erosolar-cli 1.7.385 ā 1.7.387
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 +213 -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') {
|
|
@@ -1089,6 +1109,8 @@ export class InteractiveShell {
|
|
|
1089
1109
|
this.statusTracker.reset();
|
|
1090
1110
|
}
|
|
1091
1111
|
setIdleStatus(detail) {
|
|
1112
|
+
// Reset streaming timer display when returning to idle so elapsed doesn't linger
|
|
1113
|
+
this.lastStreamingElapsedSeconds = null;
|
|
1092
1114
|
this.statusTracker.setBase('Ready for prompts', {
|
|
1093
1115
|
detail: this.describeStatusDetail(detail),
|
|
1094
1116
|
tone: 'success',
|
|
@@ -1255,10 +1277,18 @@ export class InteractiveShell {
|
|
|
1255
1277
|
* Log user prompt to the scroll region so it's part of the conversation flow.
|
|
1256
1278
|
*/
|
|
1257
1279
|
logUserPrompt(text) {
|
|
1258
|
-
|
|
1280
|
+
const normalized = text.trim();
|
|
1281
|
+
if (!normalized)
|
|
1259
1282
|
return;
|
|
1283
|
+
// Skip duplicate renders of the exact same prompt if they happen back-to-back.
|
|
1284
|
+
const now = Date.now();
|
|
1285
|
+
if (this.lastLoggedPrompt === normalized && now - this.lastLoggedPromptAt < 2000) {
|
|
1286
|
+
return;
|
|
1287
|
+
}
|
|
1288
|
+
this.lastLoggedPrompt = normalized;
|
|
1289
|
+
this.lastLoggedPromptAt = now;
|
|
1260
1290
|
// Format with user prompt prefix and write to scroll region
|
|
1261
|
-
const formatted = `${theme.user('>')} ${
|
|
1291
|
+
const formatted = `${theme.user('>')} ${normalized}\n`;
|
|
1262
1292
|
this.terminalInput.writeToScrollRegion(formatted);
|
|
1263
1293
|
}
|
|
1264
1294
|
requestPromptRefresh(force = false) {
|
|
@@ -1280,17 +1310,49 @@ export class InteractiveShell {
|
|
|
1280
1310
|
this.promptRefreshTimer = null;
|
|
1281
1311
|
}
|
|
1282
1312
|
}
|
|
1313
|
+
resetStreamBuffer() {
|
|
1314
|
+
this.pendingStreamBuffer = '';
|
|
1315
|
+
}
|
|
1316
|
+
captureStreamChunk(chunk) {
|
|
1317
|
+
if (!this.batchedOutputMode || !chunk) {
|
|
1318
|
+
return;
|
|
1319
|
+
}
|
|
1320
|
+
this.pendingStreamBuffer += chunk;
|
|
1321
|
+
}
|
|
1322
|
+
consumeStreamBuffer(fallback) {
|
|
1323
|
+
if (!this.batchedOutputMode) {
|
|
1324
|
+
return fallback;
|
|
1325
|
+
}
|
|
1326
|
+
const buffered = this.pendingStreamBuffer;
|
|
1327
|
+
this.pendingStreamBuffer = '';
|
|
1328
|
+
return buffered || fallback;
|
|
1329
|
+
}
|
|
1330
|
+
currentRunElapsedMs() {
|
|
1331
|
+
if (!this.streamingHeartbeatStart) {
|
|
1332
|
+
return undefined;
|
|
1333
|
+
}
|
|
1334
|
+
return Date.now() - this.streamingHeartbeatStart;
|
|
1335
|
+
}
|
|
1283
1336
|
startStreamingHeartbeat(label = 'Streaming') {
|
|
1284
1337
|
this.stopStreamingHeartbeat();
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
// Set up scroll region for streaming content
|
|
1288
|
-
this.terminalInput.enterStreamingScrollRegion();
|
|
1289
|
-
this.uiUpdates.setMode('streaming');
|
|
1338
|
+
this.resetStreamBuffer();
|
|
1339
|
+
this.lastStreamingElapsedSeconds = null;
|
|
1290
1340
|
this.streamingHeartbeatStart = Date.now();
|
|
1291
1341
|
this.streamingHeartbeatFrame = 0;
|
|
1292
1342
|
const initialFrame = STREAMING_SPINNER_FRAMES[this.streamingHeartbeatFrame];
|
|
1293
1343
|
this.streamingStatusLabel = this.buildStreamingStatus(`${initialFrame} ${label}`, 0);
|
|
1344
|
+
if (this.batchedOutputMode) {
|
|
1345
|
+
this.streamingUiActive = false;
|
|
1346
|
+
this.uiUpdates.setMode('processing');
|
|
1347
|
+
}
|
|
1348
|
+
else {
|
|
1349
|
+
// Enter global streaming mode - blocks all non-streaming UI output
|
|
1350
|
+
enterStreamingMode();
|
|
1351
|
+
// Set up scroll region for streaming content
|
|
1352
|
+
this.terminalInput.enterStreamingScrollRegion();
|
|
1353
|
+
this.streamingUiActive = true;
|
|
1354
|
+
this.uiUpdates.setMode('streaming');
|
|
1355
|
+
}
|
|
1294
1356
|
display.updateStreamingStatus(this.streamingStatusLabel);
|
|
1295
1357
|
this.refreshStatusLine(true);
|
|
1296
1358
|
// Periodically refresh the pinned input/status region while streaming so
|
|
@@ -1320,18 +1382,21 @@ export class InteractiveShell {
|
|
|
1320
1382
|
});
|
|
1321
1383
|
}
|
|
1322
1384
|
stopStreamingHeartbeat() {
|
|
1323
|
-
// Exit global streaming mode - allows UI to render again
|
|
1324
|
-
exitStreamingMode();
|
|
1325
1385
|
// Preserve final elapsed time before clearing heartbeat start
|
|
1326
1386
|
if (this.streamingHeartbeatStart) {
|
|
1327
1387
|
this.lastStreamingElapsedSeconds = Math.max(0, Math.floor((Date.now() - this.streamingHeartbeatStart) / 1000));
|
|
1328
1388
|
}
|
|
1329
|
-
|
|
1330
|
-
|
|
1389
|
+
if (this.streamingUiActive) {
|
|
1390
|
+
// Exit global streaming mode - allows UI to render again
|
|
1391
|
+
exitStreamingMode();
|
|
1392
|
+
// Exit scroll region mode
|
|
1393
|
+
this.terminalInput.exitStreamingScrollRegion();
|
|
1394
|
+
}
|
|
1331
1395
|
this.uiUpdates.stopHeartbeat('streaming');
|
|
1332
1396
|
this.streamingHeartbeatStart = null;
|
|
1333
1397
|
this.streamingHeartbeatFrame = 0;
|
|
1334
1398
|
this.streamingStatusLabel = null;
|
|
1399
|
+
this.streamingUiActive = false;
|
|
1335
1400
|
// Clear streaming label specifically (keeps override and main status if set)
|
|
1336
1401
|
this.terminalInput.setStreamingLabel(null);
|
|
1337
1402
|
// Clear streaming status from display
|
|
@@ -1499,6 +1564,10 @@ export class InteractiveShell {
|
|
|
1499
1564
|
return false;
|
|
1500
1565
|
}
|
|
1501
1566
|
switch (this.pendingInteraction.type) {
|
|
1567
|
+
case 'model-loading':
|
|
1568
|
+
display.showInfo('Still fetching model options. Please wait a moment.');
|
|
1569
|
+
this.terminalInput.render();
|
|
1570
|
+
return true;
|
|
1502
1571
|
case 'model-provider':
|
|
1503
1572
|
await this.handleModelProviderSelection(input);
|
|
1504
1573
|
return true;
|
|
@@ -3633,28 +3702,25 @@ export class InteractiveShell {
|
|
|
3633
3702
|
return lines.join('\n');
|
|
3634
3703
|
}
|
|
3635
3704
|
async showModelMenu() {
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3705
|
+
// Hold input immediately so numeric selections don't get queued as prompts while we fetch
|
|
3706
|
+
this.pendingInteraction = { type: 'model-loading' };
|
|
3707
|
+
this.showInlineMenu('Model provider', [theme.ui.muted('Fetching latest models from providers...')], 'info');
|
|
3708
|
+
try {
|
|
3709
|
+
// Fetch live models from all configured providers
|
|
3710
|
+
const providerStatuses = await quickCheckProviders();
|
|
3711
|
+
const providerOptions = this.buildProviderOptionsWithDiscovery(providerStatuses);
|
|
3712
|
+
if (!providerOptions.length) {
|
|
3713
|
+
this.pendingInteraction = null;
|
|
3714
|
+
this.showInlineMenu('Model provider', [theme.warning('No providers are available.')], 'warning');
|
|
3715
|
+
return;
|
|
3716
|
+
}
|
|
3717
|
+
this.pendingInteraction = { type: 'model-provider', options: providerOptions };
|
|
3718
|
+
this.renderProviderMenu(providerOptions);
|
|
3719
|
+
}
|
|
3720
|
+
catch (error) {
|
|
3721
|
+
this.pendingInteraction = null;
|
|
3722
|
+
this.showInlineMenu('Model provider', [theme.error('Failed to load model list. Try again in a moment.')], 'error');
|
|
3643
3723
|
}
|
|
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
3724
|
}
|
|
3659
3725
|
buildProviderOptions() {
|
|
3660
3726
|
const counts = new Map();
|
|
@@ -3744,6 +3810,21 @@ export class InteractiveShell {
|
|
|
3744
3810
|
};
|
|
3745
3811
|
});
|
|
3746
3812
|
}
|
|
3813
|
+
renderProviderMenu(options, hint) {
|
|
3814
|
+
const lines = [
|
|
3815
|
+
theme.bold('Select a provider:'),
|
|
3816
|
+
...options.map((option, index) => {
|
|
3817
|
+
const isCurrent = option.provider === this.sessionState.provider;
|
|
3818
|
+
const countLabel = `${option.modelCount} model${option.modelCount === 1 ? '' : 's'}`;
|
|
3819
|
+
const latestLabel = option.latestModel ? theme.success(` (latest: ${option.latestModel})`) : '';
|
|
3820
|
+
const label = this.colorizeDropdownLine(`${index + 1}. ${option.label} ā ${countLabel}${latestLabel}`, index);
|
|
3821
|
+
const suffix = isCurrent ? ` ${theme.primary('⢠current')}` : '';
|
|
3822
|
+
return `${label}${suffix}`;
|
|
3823
|
+
}),
|
|
3824
|
+
'Type the number of the provider to continue, or type "cancel".',
|
|
3825
|
+
];
|
|
3826
|
+
this.showInlineMenu('Model provider', lines, 'info', hint);
|
|
3827
|
+
}
|
|
3747
3828
|
showProviderModels(option) {
|
|
3748
3829
|
// Start with static presets
|
|
3749
3830
|
const staticModels = MODEL_PRESETS.filter((preset) => preset.provider === option.provider);
|
|
@@ -3783,13 +3864,17 @@ export class InteractiveShell {
|
|
|
3783
3864
|
}
|
|
3784
3865
|
}
|
|
3785
3866
|
if (!allModels.length) {
|
|
3786
|
-
|
|
3867
|
+
this.showInlineMenu('Model selection', [theme.warning(`No models available for ${option.label}.`)], 'warning');
|
|
3787
3868
|
this.pendingInteraction = null;
|
|
3788
3869
|
return;
|
|
3789
3870
|
}
|
|
3871
|
+
this.renderModelSelection(allModels, option, null);
|
|
3872
|
+
this.pendingInteraction = { type: 'model', provider: option.provider, options: allModels };
|
|
3873
|
+
}
|
|
3874
|
+
renderModelSelection(models, option, hint) {
|
|
3790
3875
|
const lines = [
|
|
3791
3876
|
theme.bold(`Select a model from ${option.label}:`),
|
|
3792
|
-
...
|
|
3877
|
+
...models.map((preset, index) => {
|
|
3793
3878
|
const isCurrent = preset.id === this.sessionState.model;
|
|
3794
3879
|
const isLatest = preset.id === option.latestModel;
|
|
3795
3880
|
const latestBadge = isLatest ? theme.success(' ā
LATEST') : '';
|
|
@@ -3800,11 +3885,23 @@ export class InteractiveShell {
|
|
|
3800
3885
|
}),
|
|
3801
3886
|
'Type the number of the model to select it, type "back" to change provider, or type "cancel".',
|
|
3802
3887
|
];
|
|
3803
|
-
|
|
3804
|
-
|
|
3888
|
+
this.showInlineMenu('Model selection', lines, 'info', hint ?? undefined);
|
|
3889
|
+
}
|
|
3890
|
+
buildProviderContext(provider, models) {
|
|
3891
|
+
return {
|
|
3892
|
+
provider,
|
|
3893
|
+
label: this.providerLabel(provider),
|
|
3894
|
+
modelCount: models.length,
|
|
3895
|
+
latestModel: models[0]?.id,
|
|
3896
|
+
discoveredModels: [],
|
|
3897
|
+
};
|
|
3805
3898
|
}
|
|
3806
3899
|
showSecretsMenu() {
|
|
3807
3900
|
const definitions = listSecretDefinitions();
|
|
3901
|
+
this.pendingInteraction = { type: 'secret-select', options: definitions };
|
|
3902
|
+
this.renderSecretsMenu(definitions);
|
|
3903
|
+
}
|
|
3904
|
+
renderSecretsMenu(definitions, hint) {
|
|
3808
3905
|
const lines = [
|
|
3809
3906
|
theme.bold('Manage Secrets:'),
|
|
3810
3907
|
...definitions.map((definition, index) => {
|
|
@@ -3816,8 +3913,18 @@ export class InteractiveShell {
|
|
|
3816
3913
|
}),
|
|
3817
3914
|
'Enter the number to update a key, or type "cancel".',
|
|
3818
3915
|
];
|
|
3819
|
-
|
|
3820
|
-
|
|
3916
|
+
this.showInlineMenu('Secrets', lines, 'info', hint);
|
|
3917
|
+
}
|
|
3918
|
+
renderSecretInput(secret, hint) {
|
|
3919
|
+
const value = getSecretValue(secret.id);
|
|
3920
|
+
const status = value ? maskSecret(value) : theme.warning('not set');
|
|
3921
|
+
const providers = secret.providers.map((id) => this.providerLabel(id)).join(', ');
|
|
3922
|
+
const lines = [
|
|
3923
|
+
`${secret.label} (${providers})`,
|
|
3924
|
+
`Current: ${status}`,
|
|
3925
|
+
'Enter a new value or type "cancel".',
|
|
3926
|
+
];
|
|
3927
|
+
this.showInlineMenu('Update secret', lines, 'info', hint);
|
|
3821
3928
|
}
|
|
3822
3929
|
showToolsMenu() {
|
|
3823
3930
|
const options = getToolToggleOptions();
|
|
@@ -4197,26 +4304,23 @@ export class InteractiveShell {
|
|
|
4197
4304
|
}
|
|
4198
4305
|
const trimmed = input.trim();
|
|
4199
4306
|
if (!trimmed) {
|
|
4200
|
-
|
|
4201
|
-
this.terminalInput.render();
|
|
4307
|
+
this.renderProviderMenu(pending.options, 'Enter a number or type cancel.');
|
|
4202
4308
|
return;
|
|
4203
4309
|
}
|
|
4204
4310
|
if (trimmed.toLowerCase() === 'cancel') {
|
|
4205
4311
|
this.pendingInteraction = null;
|
|
4206
|
-
|
|
4207
|
-
this.
|
|
4312
|
+
this.clearInlineMenu();
|
|
4313
|
+
this.updateStatusMessage('Model selection cancelled.');
|
|
4208
4314
|
return;
|
|
4209
4315
|
}
|
|
4210
4316
|
const choice = Number.parseInt(trimmed, 10);
|
|
4211
4317
|
if (!Number.isFinite(choice)) {
|
|
4212
|
-
|
|
4213
|
-
this.terminalInput.render();
|
|
4318
|
+
this.renderProviderMenu(pending.options, 'Please enter a valid number.');
|
|
4214
4319
|
return;
|
|
4215
4320
|
}
|
|
4216
4321
|
const option = pending.options[choice - 1];
|
|
4217
4322
|
if (!option) {
|
|
4218
|
-
|
|
4219
|
-
this.terminalInput.render();
|
|
4323
|
+
this.renderProviderMenu(pending.options, 'That option is not available.');
|
|
4220
4324
|
return;
|
|
4221
4325
|
}
|
|
4222
4326
|
this.showProviderModels(option);
|
|
@@ -4227,10 +4331,10 @@ export class InteractiveShell {
|
|
|
4227
4331
|
if (!pending || pending.type !== 'model') {
|
|
4228
4332
|
return;
|
|
4229
4333
|
}
|
|
4334
|
+
const providerContext = this.buildProviderContext(pending.provider, pending.options);
|
|
4230
4335
|
const trimmed = input.trim();
|
|
4231
4336
|
if (!trimmed) {
|
|
4232
|
-
|
|
4233
|
-
this.terminalInput.render();
|
|
4337
|
+
this.renderModelSelection(pending.options, providerContext, 'Enter a number, type "back", or type "cancel".');
|
|
4234
4338
|
return;
|
|
4235
4339
|
}
|
|
4236
4340
|
if (trimmed.toLowerCase() === 'back') {
|
|
@@ -4240,20 +4344,18 @@ export class InteractiveShell {
|
|
|
4240
4344
|
}
|
|
4241
4345
|
if (trimmed.toLowerCase() === 'cancel') {
|
|
4242
4346
|
this.pendingInteraction = null;
|
|
4243
|
-
|
|
4244
|
-
this.
|
|
4347
|
+
this.clearInlineMenu();
|
|
4348
|
+
this.updateStatusMessage('Model selection cancelled.');
|
|
4245
4349
|
return;
|
|
4246
4350
|
}
|
|
4247
4351
|
const choice = Number.parseInt(trimmed, 10);
|
|
4248
4352
|
if (!Number.isFinite(choice)) {
|
|
4249
|
-
|
|
4250
|
-
this.terminalInput.render();
|
|
4353
|
+
this.renderModelSelection(pending.options, providerContext, 'Please enter a valid number.');
|
|
4251
4354
|
return;
|
|
4252
4355
|
}
|
|
4253
4356
|
const preset = pending.options[choice - 1];
|
|
4254
4357
|
if (!preset) {
|
|
4255
|
-
|
|
4256
|
-
this.terminalInput.render();
|
|
4358
|
+
this.renderModelSelection(pending.options, providerContext, 'That option is not available.');
|
|
4257
4359
|
return;
|
|
4258
4360
|
}
|
|
4259
4361
|
this.pendingInteraction = null;
|
|
@@ -4277,11 +4379,14 @@ export class InteractiveShell {
|
|
|
4277
4379
|
};
|
|
4278
4380
|
this.applyPresetReasoningDefaults();
|
|
4279
4381
|
if (this.rebuildAgent()) {
|
|
4280
|
-
|
|
4382
|
+
this.showInlineMenu('Model updated', [`Switched to ${preset.label}.`], 'success');
|
|
4281
4383
|
this.refreshBannerSessionInfo();
|
|
4282
4384
|
this.persistSessionPreference();
|
|
4283
4385
|
this.resetChatBoxAfterModelSwap();
|
|
4284
4386
|
}
|
|
4387
|
+
else {
|
|
4388
|
+
this.showInlineMenu('Model updated', [`Using ${preset.label}.`], 'info');
|
|
4389
|
+
}
|
|
4285
4390
|
}
|
|
4286
4391
|
async handleSecretSelection(input) {
|
|
4287
4392
|
const pending = this.pendingInteraction;
|
|
@@ -4290,30 +4395,27 @@ export class InteractiveShell {
|
|
|
4290
4395
|
}
|
|
4291
4396
|
const trimmed = input.trim();
|
|
4292
4397
|
if (!trimmed) {
|
|
4293
|
-
|
|
4294
|
-
this.terminalInput.render();
|
|
4398
|
+
this.renderSecretsMenu(pending.options, 'Enter a number or type cancel.');
|
|
4295
4399
|
return;
|
|
4296
4400
|
}
|
|
4297
4401
|
if (trimmed.toLowerCase() === 'cancel') {
|
|
4298
4402
|
this.pendingInteraction = null;
|
|
4299
|
-
|
|
4300
|
-
this.
|
|
4403
|
+
this.clearInlineMenu();
|
|
4404
|
+
this.updateStatusMessage('Secret management cancelled.');
|
|
4301
4405
|
return;
|
|
4302
4406
|
}
|
|
4303
4407
|
const choice = Number.parseInt(trimmed, 10);
|
|
4304
4408
|
if (!Number.isFinite(choice)) {
|
|
4305
|
-
|
|
4306
|
-
this.terminalInput.render();
|
|
4409
|
+
this.renderSecretsMenu(pending.options, 'Please enter a valid number.');
|
|
4307
4410
|
return;
|
|
4308
4411
|
}
|
|
4309
4412
|
const secret = pending.options[choice - 1];
|
|
4310
4413
|
if (!secret) {
|
|
4311
|
-
|
|
4312
|
-
this.terminalInput.render();
|
|
4414
|
+
this.renderSecretsMenu(pending.options, 'That option is not available.');
|
|
4313
4415
|
return;
|
|
4314
4416
|
}
|
|
4315
|
-
display.showSystemMessage(`Enter a new value for ${secret.label} or type "cancel".`);
|
|
4316
4417
|
this.pendingInteraction = { type: 'secret-input', secret };
|
|
4418
|
+
this.renderSecretInput(secret);
|
|
4317
4419
|
this.terminalInput.render();
|
|
4318
4420
|
}
|
|
4319
4421
|
async handleSecretInput(input) {
|
|
@@ -4323,20 +4425,20 @@ export class InteractiveShell {
|
|
|
4323
4425
|
}
|
|
4324
4426
|
const trimmed = input.trim();
|
|
4325
4427
|
if (!trimmed) {
|
|
4326
|
-
|
|
4327
|
-
this.terminalInput.render();
|
|
4428
|
+
this.renderSecretInput(pending.secret, 'Enter a value or type cancel.');
|
|
4328
4429
|
return;
|
|
4329
4430
|
}
|
|
4330
4431
|
if (trimmed.toLowerCase() === 'cancel') {
|
|
4331
4432
|
this.pendingInteraction = null;
|
|
4332
4433
|
this.pendingSecretRetry = null;
|
|
4333
|
-
|
|
4434
|
+
this.clearInlineMenu();
|
|
4435
|
+
this.updateStatusMessage('Secret unchanged.');
|
|
4334
4436
|
this.terminalInput.render();
|
|
4335
4437
|
return;
|
|
4336
4438
|
}
|
|
4337
4439
|
try {
|
|
4338
4440
|
setSecretValue(pending.secret.id, trimmed);
|
|
4339
|
-
|
|
4441
|
+
this.showInlineMenu('Secret updated', [`${pending.secret.label} saved.`], 'success');
|
|
4340
4442
|
this.pendingInteraction = null;
|
|
4341
4443
|
const deferred = this.pendingSecretRetry;
|
|
4342
4444
|
this.pendingSecretRetry = null;
|
|
@@ -4351,7 +4453,7 @@ export class InteractiveShell {
|
|
|
4351
4453
|
}
|
|
4352
4454
|
catch (error) {
|
|
4353
4455
|
const message = error instanceof Error ? error.message : String(error);
|
|
4354
|
-
|
|
4456
|
+
this.showInlineMenu('Secret error', [message], 'error');
|
|
4355
4457
|
this.pendingInteraction = null;
|
|
4356
4458
|
this.pendingSecretRetry = null;
|
|
4357
4459
|
}
|
|
@@ -4370,6 +4472,7 @@ export class InteractiveShell {
|
|
|
4370
4472
|
if (!agent) {
|
|
4371
4473
|
return;
|
|
4372
4474
|
}
|
|
4475
|
+
this.logUserPrompt(request);
|
|
4373
4476
|
this.isProcessing = true;
|
|
4374
4477
|
this.uiUpdates.setMode('processing');
|
|
4375
4478
|
this.terminalInput.setStreaming(true);
|
|
@@ -4389,7 +4492,8 @@ export class InteractiveShell {
|
|
|
4389
4492
|
let responseText = '';
|
|
4390
4493
|
try {
|
|
4391
4494
|
// Start streaming - no header needed, the input area already provides context
|
|
4392
|
-
this.
|
|
4495
|
+
const heartbeatLabel = this.batchedOutputMode ? 'Processing response' : 'Streaming response';
|
|
4496
|
+
this.startStreamingHeartbeat(heartbeatLabel);
|
|
4393
4497
|
responseText = await agent.send(request, true);
|
|
4394
4498
|
await this.awaitPendingCleanup();
|
|
4395
4499
|
this.captureHistorySnapshot();
|
|
@@ -4505,7 +4609,8 @@ export class InteractiveShell {
|
|
|
4505
4609
|
this.uiAdapter.startProcessing('Continuous execution mode');
|
|
4506
4610
|
this.setProcessingStatus();
|
|
4507
4611
|
// No streaming header - just start streaming directly
|
|
4508
|
-
this.
|
|
4612
|
+
const continuousLabel = this.batchedOutputMode ? 'Processing' : 'Streaming';
|
|
4613
|
+
this.startStreamingHeartbeat(continuousLabel);
|
|
4509
4614
|
let iteration = 0;
|
|
4510
4615
|
let lastResponse = '';
|
|
4511
4616
|
let consecutiveNoProgress = 0;
|
|
@@ -4533,9 +4638,10 @@ When truly finished with ALL tasks, explicitly state "TASK_FULLY_COMPLETE".`;
|
|
|
4533
4638
|
display.showSystemMessage(`\nš Iteration ${iteration}/${MAX_ITERATIONS}`);
|
|
4534
4639
|
this.updateStatusMessage(`Working on iteration ${iteration}...`);
|
|
4535
4640
|
try {
|
|
4536
|
-
// Send the request and capture the response
|
|
4641
|
+
// Send the request and capture the response as a single block
|
|
4537
4642
|
display.showThinking('Responding...');
|
|
4538
4643
|
this.refreshStatusLine(true);
|
|
4644
|
+
this.resetStreamBuffer();
|
|
4539
4645
|
const response = await agent.send(currentPrompt, true);
|
|
4540
4646
|
await this.awaitPendingCleanup();
|
|
4541
4647
|
this.captureHistorySnapshot();
|
|
@@ -5036,6 +5142,7 @@ What's the next action?`;
|
|
|
5036
5142
|
// Send the error to the agent for fixing
|
|
5037
5143
|
display.showThinking('Analyzing build errors');
|
|
5038
5144
|
this.refreshStatusLine(true);
|
|
5145
|
+
this.resetStreamBuffer();
|
|
5039
5146
|
const response = await this.agent.send(prompt, true);
|
|
5040
5147
|
display.stopThinking();
|
|
5041
5148
|
this.refreshStatusLine(true);
|
|
@@ -5065,27 +5172,33 @@ What's the next action?`;
|
|
|
5065
5172
|
};
|
|
5066
5173
|
this.agent = this.runtimeSession.createAgent(selection, {
|
|
5067
5174
|
onStreamChunk: (chunk) => {
|
|
5175
|
+
if (this.batchedOutputMode) {
|
|
5176
|
+
this.captureStreamChunk(chunk);
|
|
5177
|
+
return;
|
|
5178
|
+
}
|
|
5068
5179
|
// Stream output using clean streamContent() - chat box floats below
|
|
5069
5180
|
this.terminalInput.streamContent(chunk);
|
|
5070
5181
|
},
|
|
5071
5182
|
onStreamFallback: (info) => this.handleStreamingFallback(info),
|
|
5072
5183
|
onAssistantMessage: (content, metadata) => {
|
|
5073
5184
|
const enriched = this.buildDisplayMetadata(metadata);
|
|
5185
|
+
const alreadyStreamedToUi = !this.batchedOutputMode && metadata.wasStreamed;
|
|
5186
|
+
const bufferedContent = this.consumeStreamBuffer(content);
|
|
5074
5187
|
// Update spinner based on message type
|
|
5075
5188
|
if (metadata.isFinal) {
|
|
5076
5189
|
// Skip display if content was already streamed to avoid double-display
|
|
5077
|
-
if (!
|
|
5078
|
-
const parsed = this.splitThinkingResponse(
|
|
5190
|
+
if (!alreadyStreamedToUi) {
|
|
5191
|
+
const parsed = this.splitThinkingResponse(bufferedContent);
|
|
5079
5192
|
if (parsed?.thinking) {
|
|
5080
5193
|
const summary = this.extractThoughtSummary(parsed.thinking);
|
|
5081
5194
|
if (summary) {
|
|
5082
5195
|
display.updateThinking(`š ${summary}`);
|
|
5083
5196
|
}
|
|
5084
|
-
display.
|
|
5197
|
+
display.showThinkingBlock(parsed.thinking, enriched.elapsedMs ?? this.currentRunElapsedMs());
|
|
5085
5198
|
}
|
|
5086
|
-
const finalContent = parsed?.response?.trim() ||
|
|
5199
|
+
const finalContent = parsed?.response?.trim() || bufferedContent.trim();
|
|
5087
5200
|
if (finalContent) {
|
|
5088
|
-
display.showAssistantMessage(finalContent, enriched);
|
|
5201
|
+
display.showAssistantMessage(finalContent, { ...enriched, isFinal: true });
|
|
5089
5202
|
}
|
|
5090
5203
|
}
|
|
5091
5204
|
// Status shown in mode controls bar - no separate status line needed
|
|
@@ -5107,8 +5220,13 @@ What's the next action?`;
|
|
|
5107
5220
|
// Stop spinner and show the narrative text directly
|
|
5108
5221
|
display.stopThinking();
|
|
5109
5222
|
// Skip display if content was already streamed to avoid double-display
|
|
5110
|
-
if (!
|
|
5111
|
-
|
|
5223
|
+
if (!alreadyStreamedToUi) {
|
|
5224
|
+
const parsed = this.splitThinkingResponse(bufferedContent);
|
|
5225
|
+
const thoughtContent = parsed?.thinking ?? parsed?.response ?? bufferedContent;
|
|
5226
|
+
const trimmed = thoughtContent.trim();
|
|
5227
|
+
if (trimmed) {
|
|
5228
|
+
display.showThinkingBlock(trimmed, enriched.elapsedMs ?? this.currentRunElapsedMs());
|
|
5229
|
+
}
|
|
5112
5230
|
}
|
|
5113
5231
|
// The isProcessing flag already shows "ā³ Processing..." - no need for duplicate status
|
|
5114
5232
|
this.requestPromptRefresh();
|
|
@@ -5667,7 +5785,12 @@ What's the next action?`;
|
|
|
5667
5785
|
const reason = info.reason ? ` (${info.reason.replace(/-/g, ' ')})` : '';
|
|
5668
5786
|
const partialNote = info.partialResponse ? ' Received partial stream before failure.' : '';
|
|
5669
5787
|
display.showWarning(`Streaming failed${reason}, retrying without streaming.${detail}${partialNote}`);
|
|
5670
|
-
this.
|
|
5788
|
+
const bufferedPartial = this.consumeStreamBuffer(info.partialResponse ?? '');
|
|
5789
|
+
if (this.batchedOutputMode && bufferedPartial.trim()) {
|
|
5790
|
+
display.showThinkingBlock(bufferedPartial.trim(), this.currentRunElapsedMs());
|
|
5791
|
+
}
|
|
5792
|
+
const fallbackLabel = this.batchedOutputMode ? 'Retrying (batched)' : 'Fallback in progress';
|
|
5793
|
+
this.startStreamingHeartbeat(fallbackLabel);
|
|
5671
5794
|
this.requestPromptRefresh(true);
|
|
5672
5795
|
}
|
|
5673
5796
|
handleProviderError(error, retryAction) {
|
|
@@ -5678,9 +5801,9 @@ What's the next action?`;
|
|
|
5678
5801
|
this.handleApiKeyIssue(apiKeyIssue, retryAction);
|
|
5679
5802
|
return true;
|
|
5680
5803
|
}
|
|
5681
|
-
handleApiKeyIssue(info, retryAction) {
|
|
5804
|
+
handleApiKeyIssue(info, retryAction, contextLabel) {
|
|
5682
5805
|
const secret = info.secret ?? null;
|
|
5683
|
-
const providerLabel = info.provider ? this.providerLabel(info.provider) : 'the selected provider';
|
|
5806
|
+
const providerLabel = contextLabel ?? (info.provider ? this.providerLabel(info.provider) : 'the selected provider');
|
|
5684
5807
|
if (!secret) {
|
|
5685
5808
|
this.pendingSecretRetry = null;
|
|
5686
5809
|
const guidance = 'Run "/secrets" to configure the required API key or export it (e.g., EXPORT KEY=value) before launching the CLI.';
|
|
@@ -5702,6 +5825,10 @@ What's the next action?`;
|
|
|
5702
5825
|
this.pendingInteraction = { type: 'secret-input', secret };
|
|
5703
5826
|
this.showSecretGuidance(secret, isMissing);
|
|
5704
5827
|
}
|
|
5828
|
+
handleToolApiKeyIssue(info, call) {
|
|
5829
|
+
const label = call?.name ? `${call.name} tool` : 'web tools';
|
|
5830
|
+
this.handleApiKeyIssue(info, undefined, label);
|
|
5831
|
+
}
|
|
5705
5832
|
showSecretGuidance(secret, promptForInput) {
|
|
5706
5833
|
const lines = [];
|
|
5707
5834
|
if (promptForInput) {
|
|
@@ -5711,7 +5838,7 @@ What's the next action?`;
|
|
|
5711
5838
|
lines.push(`Update the stored value for ${secret.label} or type "cancel".`);
|
|
5712
5839
|
}
|
|
5713
5840
|
lines.push(`Tip: run "/secrets" anytime to manage credentials or export ${secret.envVar}=<value> before launching the CLI.`);
|
|
5714
|
-
|
|
5841
|
+
this.showInlineMenu('Secrets', lines, promptForInput ? 'warning' : 'info');
|
|
5715
5842
|
}
|
|
5716
5843
|
colorizeDropdownLine(text, index) {
|
|
5717
5844
|
if (!DROPDOWN_COLORS.length) {
|