erosolar-cli 2.1.168 → 2.1.170
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/README.md +1 -1
- package/agents/erosolar-code.rules.json +2 -2
- package/agents/general.rules.json +21 -3
- package/dist/capabilities/statusCapability.js +2 -2
- package/dist/capabilities/statusCapability.js.map +1 -1
- package/dist/contracts/agent-schemas.json +5 -5
- package/dist/core/agent.d.ts +83 -24
- package/dist/core/agent.d.ts.map +1 -1
- package/dist/core/agent.js +499 -248
- package/dist/core/agent.js.map +1 -1
- package/dist/core/preferences.d.ts +1 -0
- package/dist/core/preferences.d.ts.map +1 -1
- package/dist/core/preferences.js +8 -1
- package/dist/core/preferences.js.map +1 -1
- package/dist/core/reliabilityPrompt.d.ts +9 -0
- package/dist/core/reliabilityPrompt.d.ts.map +1 -0
- package/dist/core/reliabilityPrompt.js +31 -0
- package/dist/core/reliabilityPrompt.js.map +1 -0
- package/dist/core/schemaValidator.js +3 -3
- package/dist/core/schemaValidator.js.map +1 -1
- package/dist/core/toolPreconditions.d.ts +0 -11
- package/dist/core/toolPreconditions.d.ts.map +1 -1
- package/dist/core/toolPreconditions.js +33 -164
- package/dist/core/toolPreconditions.js.map +1 -1
- package/dist/core/toolRuntime.d.ts.map +1 -1
- package/dist/core/toolRuntime.js +9 -114
- package/dist/core/toolRuntime.js.map +1 -1
- package/dist/core/updateChecker.d.ts +61 -1
- package/dist/core/updateChecker.d.ts.map +1 -1
- package/dist/core/updateChecker.js +147 -3
- package/dist/core/updateChecker.js.map +1 -1
- package/dist/headless/headlessApp.d.ts.map +1 -1
- package/dist/headless/headlessApp.js +0 -39
- package/dist/headless/headlessApp.js.map +1 -1
- package/dist/plugins/tools/nodeDefaults.d.ts.map +1 -1
- package/dist/plugins/tools/nodeDefaults.js +0 -2
- package/dist/plugins/tools/nodeDefaults.js.map +1 -1
- package/dist/providers/openaiResponsesProvider.d.ts.map +1 -1
- package/dist/providers/openaiResponsesProvider.js +79 -74
- package/dist/providers/openaiResponsesProvider.js.map +1 -1
- package/dist/runtime/agentController.d.ts.map +1 -1
- package/dist/runtime/agentController.js +6 -3
- package/dist/runtime/agentController.js.map +1 -1
- package/dist/runtime/agentSession.d.ts +0 -2
- package/dist/runtime/agentSession.d.ts.map +1 -1
- package/dist/runtime/agentSession.js +2 -2
- package/dist/runtime/agentSession.js.map +1 -1
- package/dist/shell/interactiveShell.d.ts +11 -12
- package/dist/shell/interactiveShell.d.ts.map +1 -1
- package/dist/shell/interactiveShell.js +269 -193
- package/dist/shell/interactiveShell.js.map +1 -1
- package/dist/shell/systemPrompt.d.ts.map +1 -1
- package/dist/shell/systemPrompt.js +4 -15
- package/dist/shell/systemPrompt.js.map +1 -1
- package/dist/subagents/taskRunner.js +2 -1
- package/dist/subagents/taskRunner.js.map +1 -1
- package/dist/tools/bashTools.d.ts.map +1 -1
- package/dist/tools/bashTools.js +101 -8
- package/dist/tools/bashTools.js.map +1 -1
- package/dist/tools/diffUtils.d.ts +8 -2
- package/dist/tools/diffUtils.d.ts.map +1 -1
- package/dist/tools/diffUtils.js +72 -13
- package/dist/tools/diffUtils.js.map +1 -1
- package/dist/tools/grepTools.d.ts.map +1 -1
- package/dist/tools/grepTools.js +10 -2
- package/dist/tools/grepTools.js.map +1 -1
- package/dist/tools/searchTools.d.ts.map +1 -1
- package/dist/tools/searchTools.js +4 -2
- package/dist/tools/searchTools.js.map +1 -1
- package/dist/ui/PromptController.d.ts +2 -3
- package/dist/ui/PromptController.d.ts.map +1 -1
- package/dist/ui/PromptController.js +2 -3
- package/dist/ui/PromptController.js.map +1 -1
- package/dist/ui/ShellUIAdapter.d.ts +71 -18
- package/dist/ui/ShellUIAdapter.d.ts.map +1 -1
- package/dist/ui/ShellUIAdapter.js +237 -139
- package/dist/ui/ShellUIAdapter.js.map +1 -1
- package/dist/ui/UnifiedUIController.d.ts +0 -1
- package/dist/ui/UnifiedUIController.d.ts.map +1 -1
- package/dist/ui/UnifiedUIController.js +0 -1
- package/dist/ui/UnifiedUIController.js.map +1 -1
- package/dist/ui/UnifiedUIRenderer.d.ts +122 -7
- package/dist/ui/UnifiedUIRenderer.d.ts.map +1 -1
- package/dist/ui/UnifiedUIRenderer.js +823 -130
- package/dist/ui/UnifiedUIRenderer.js.map +1 -1
- package/dist/ui/animatedStatus.d.ts +129 -0
- package/dist/ui/animatedStatus.d.ts.map +1 -0
- package/dist/ui/animatedStatus.js +384 -0
- package/dist/ui/animatedStatus.js.map +1 -0
- package/dist/ui/display.d.ts +13 -48
- package/dist/ui/display.d.ts.map +1 -1
- package/dist/ui/display.js +22 -105
- package/dist/ui/display.js.map +1 -1
- package/dist/ui/shortcutsHelp.d.ts.map +1 -1
- package/dist/ui/shortcutsHelp.js +0 -1
- package/dist/ui/shortcutsHelp.js.map +1 -1
- package/dist/ui/unified/index.d.ts +1 -1
- package/dist/ui/unified/index.d.ts.map +1 -1
- package/dist/ui/unified/index.js +0 -2
- package/dist/ui/unified/index.js.map +1 -1
- package/package.json +1 -2
- package/dist/StringUtils.d.ts +0 -8
- package/dist/StringUtils.d.ts.map +0 -1
- package/dist/StringUtils.js +0 -11
- package/dist/StringUtils.js.map +0 -1
- package/dist/core/aiFlowSupervisor.d.ts +0 -44
- package/dist/core/aiFlowSupervisor.d.ts.map +0 -1
- package/dist/core/aiFlowSupervisor.js +0 -299
- package/dist/core/aiFlowSupervisor.js.map +0 -1
- package/dist/core/cliTestHarness.d.ts +0 -200
- package/dist/core/cliTestHarness.d.ts.map +0 -1
- package/dist/core/cliTestHarness.js +0 -549
- package/dist/core/cliTestHarness.js.map +0 -1
- package/dist/core/testUtils.d.ts +0 -121
- package/dist/core/testUtils.d.ts.map +0 -1
- package/dist/core/testUtils.js +0 -235
- package/dist/core/testUtils.js.map +0 -1
- package/dist/core/toolValidation.d.ts +0 -116
- package/dist/core/toolValidation.d.ts.map +0 -1
- package/dist/core/toolValidation.js +0 -282
- package/dist/core/toolValidation.js.map +0 -1
- package/dist/ui/compactRenderer.d.ts +0 -139
- package/dist/ui/compactRenderer.d.ts.map +0 -1
- package/dist/ui/compactRenderer.js +0 -398
- package/dist/ui/compactRenderer.js.map +0 -1
- package/dist/ui/streamingFormatter.d.ts +0 -30
- package/dist/ui/streamingFormatter.d.ts.map +0 -1
- package/dist/ui/streamingFormatter.js +0 -91
- package/dist/ui/streamingFormatter.js.map +0 -1
- package/dist/utils/errorUtils.d.ts +0 -16
- package/dist/utils/errorUtils.d.ts.map +0 -1
- package/dist/utils/errorUtils.js +0 -66
- package/dist/utils/errorUtils.js.map +0 -1
|
@@ -7,7 +7,6 @@ import { join, resolve } from 'node:path';
|
|
|
7
7
|
import { display } from '../ui/display.js';
|
|
8
8
|
import { theme } from '../ui/theme.js';
|
|
9
9
|
import { getTerminalColumns } from '../ui/layout.js';
|
|
10
|
-
import { StreamingResponseFormatter } from '../ui/streamingFormatter.js';
|
|
11
10
|
import { getContextWindowTokens } from '../core/contextWindow.js';
|
|
12
11
|
import { ensureSecretForProvider, getSecretDefinitionForProvider, getSecretValue, listSecretDefinitions, maskSecret, setSecretValue, } from '../core/secretStore.js';
|
|
13
12
|
import { saveActiveProfilePreference, saveModelPreference, loadToolSettings, saveToolSettings, clearToolSettings, clearActiveProfilePreference, loadSessionPreferences, saveSessionPreferences, loadFeatureFlags, saveFeatureFlags, toggleFeatureFlag, FEATURE_FLAG_INFO, } from '../core/preferences.js';
|
|
@@ -19,7 +18,6 @@ import { detectNetworkError } from '../core/errors/networkErrors.js';
|
|
|
19
18
|
import { buildWorkspaceContext } from '../workspace.js';
|
|
20
19
|
import { buildInteractiveSystemPrompt } from './systemPrompt.js';
|
|
21
20
|
import { getTaskCompletionDetector, resetTaskCompletionDetector, } from './taskCompletionDetector.js';
|
|
22
|
-
import { assessAiFlow } from '../core/aiFlowSupervisor.js';
|
|
23
21
|
import { discoverAllModels, quickCheckProviders, getCachedDiscoveredModels, sortModelsByPriority } from '../core/modelDiscovery.js';
|
|
24
22
|
import { getModels, getSlashCommands, getProviders } from '../core/agentSchemaLoader.js';
|
|
25
23
|
import { loadMcpServers } from '../mcp/config.js';
|
|
@@ -136,7 +134,6 @@ export class InteractiveShell {
|
|
|
136
134
|
lastContextWarningLevel = null;
|
|
137
135
|
sessionPreferences;
|
|
138
136
|
autosaveEnabled;
|
|
139
|
-
autoContinueEnabled;
|
|
140
137
|
verificationEnabled = false;
|
|
141
138
|
criticalApprovalMode = 'auto';
|
|
142
139
|
editGuardMode = 'display-edits';
|
|
@@ -182,7 +179,6 @@ export class InteractiveShell {
|
|
|
182
179
|
streamingOutputSuppressed = false;
|
|
183
180
|
aiRuntimeStart = null;
|
|
184
181
|
aiRuntimeTotalMs = 0;
|
|
185
|
-
streamingFormatter = null;
|
|
186
182
|
streamingThoughtBuffer = '';
|
|
187
183
|
statusLineState = null;
|
|
188
184
|
statusMessageOverride = null;
|
|
@@ -207,7 +203,6 @@ export class InteractiveShell {
|
|
|
207
203
|
this.sessionPreferences = loadSessionPreferences();
|
|
208
204
|
this.thinkingMode = this.sessionPreferences.thinkingMode;
|
|
209
205
|
this.autosaveEnabled = this.sessionPreferences.autosave;
|
|
210
|
-
this.autoContinueEnabled = this.sessionPreferences.autoContinue;
|
|
211
206
|
this.criticalApprovalMode = this.sessionPreferences.criticalApprovalMode;
|
|
212
207
|
const featureFlags = loadFeatureFlags();
|
|
213
208
|
this.verificationEnabled = featureFlags.verification === true;
|
|
@@ -284,6 +279,10 @@ export class InteractiveShell {
|
|
|
284
279
|
this.uiAdapter.setToolStatusCallback((status) => {
|
|
285
280
|
this.updateStatusMessage(status ?? null);
|
|
286
281
|
});
|
|
282
|
+
// Set up activity callback to update activity line with streaming bash output
|
|
283
|
+
this.uiAdapter.setActivityCallback((activity) => {
|
|
284
|
+
this.renderer?.setActivity(activity);
|
|
285
|
+
});
|
|
287
286
|
this.skillRepository = new SkillRepository({
|
|
288
287
|
workingDir: this.workingDir,
|
|
289
288
|
env: process.env,
|
|
@@ -297,13 +296,14 @@ export class InteractiveShell {
|
|
|
297
296
|
onQueue: (text) => this.handleQueuedInput(text),
|
|
298
297
|
onCtrlC: ({ hadBuffer }) => this.handleCtrlCPress(hadBuffer),
|
|
299
298
|
onInterrupt: () => this.handleInterrupt(),
|
|
299
|
+
onExit: () => this.handleExit(),
|
|
300
300
|
onChange: ({ text, cursor }) => this.handleInputChange(text, cursor, 'renderer'),
|
|
301
301
|
onEditModeChange: (mode) => this.handleEditModeChange(mode),
|
|
302
302
|
onToggleVerify: () => this.toggleVerificationMode(),
|
|
303
|
-
onToggleAutoContinue: () => this.toggleAutoContinueMode(),
|
|
304
303
|
onToggleThinking: () => this.cycleThinkingMode(),
|
|
305
304
|
onClearContext: () => this.handleClearContext(),
|
|
306
305
|
onToggleCriticalApproval: () => this.toggleCriticalApprovalMode('shortcut'),
|
|
306
|
+
onExpandToolResult: () => this.expandLastToolResult(),
|
|
307
307
|
});
|
|
308
308
|
// Share renderer with Display so all output flows through the unified queue
|
|
309
309
|
this.renderer = this.terminalInput.getRenderer();
|
|
@@ -447,18 +447,21 @@ export class InteractiveShell {
|
|
|
447
447
|
const top = `╭${'─'.repeat(labelLeft)}${label}${'─'.repeat(labelRight)}╮`;
|
|
448
448
|
const bottom = `╰${'─'.repeat(contentWidth)}╯`;
|
|
449
449
|
const userName = process.env['USER'] || 'there';
|
|
450
|
-
const logo =
|
|
450
|
+
const logo = '⟣╍◎╍⟢';
|
|
451
451
|
const versionLabel = version ? clamp(`${brand} · v${version}`) : brand;
|
|
452
452
|
const modelLine = clamp(model);
|
|
453
453
|
const workspaceLine = clamp(workspace);
|
|
454
|
-
|
|
454
|
+
// Compact welcome line with logo on same line
|
|
455
|
+
const welcomeLine = clamp(`${logo} Welcome back ${userName}! ${logo}`);
|
|
456
|
+
// Quick reference hints
|
|
457
|
+
const hintsLine = clamp('/model · /secrets · /help · ? shortcuts');
|
|
455
458
|
const lines = [
|
|
456
459
|
frame(top),
|
|
457
460
|
frame(`│${padCenter(welcomeLine, contentWidth)}│`),
|
|
458
|
-
frame(`│${padCenter(logo, contentWidth)}│`),
|
|
459
461
|
frame(`│${padCenter(modelLine, contentWidth)}│`),
|
|
460
462
|
frame(`│${padCenter(versionLabel, contentWidth)}│`),
|
|
461
463
|
frame(`│${padCenter(workspaceLine, contentWidth)}│`),
|
|
464
|
+
frame(`│${padCenter(hintsLine, contentWidth)}│`),
|
|
462
465
|
frame(bottom),
|
|
463
466
|
].join('\n');
|
|
464
467
|
if (this.renderer) {
|
|
@@ -469,8 +472,6 @@ export class InteractiveShell {
|
|
|
469
472
|
else {
|
|
470
473
|
display.stream(`\n${lines}\n`);
|
|
471
474
|
}
|
|
472
|
-
// Check for updates asynchronously (non-blocking)
|
|
473
|
-
void this.checkAndShowUpdates();
|
|
474
475
|
// Keep UI pinned; no scrollback banners
|
|
475
476
|
this.requestPromptRefresh(true);
|
|
476
477
|
}
|
|
@@ -508,22 +509,32 @@ export class InteractiveShell {
|
|
|
508
509
|
}
|
|
509
510
|
async checkAndShowUpdates() {
|
|
510
511
|
try {
|
|
511
|
-
const { checkForUpdates,
|
|
512
|
+
const { checkForUpdates, getUpdateDecision, formatUpdateBanner, performBackgroundUpdate, readAutoUpdateState, shouldShowUpdateNotification, } = await import('../core/updateChecker.js');
|
|
512
513
|
const currentVersion = this.version || this.getPackageInfo().version || '1.7.458';
|
|
513
514
|
const updateInfo = await checkForUpdates(currentVersion);
|
|
514
515
|
if (!updateInfo || !updateInfo.updateAvailable) {
|
|
515
516
|
return;
|
|
516
517
|
}
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
518
|
+
// Check if we should show notification (respects quiet period)
|
|
519
|
+
const state = readAutoUpdateState();
|
|
520
|
+
if (!shouldShowUpdateNotification(state)) {
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
// Get user's preference-based decision (no interactive prompt)
|
|
524
|
+
const sessionPrefs = loadSessionPreferences();
|
|
525
|
+
const decision = getUpdateDecision(sessionPrefs.autoUpdate);
|
|
526
|
+
if (decision === 'skip') {
|
|
527
|
+
// User chose to always skip - silently ignore updates
|
|
522
528
|
return;
|
|
523
529
|
}
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
530
|
+
// Show non-interactive banner notification
|
|
531
|
+
this.streamEventBlock(formatUpdateBanner(updateInfo, decision));
|
|
532
|
+
if (decision === 'auto') {
|
|
533
|
+
// User chose to always update - perform background update
|
|
534
|
+
await performBackgroundUpdate(updateInfo, (message) => this.streamEventBlock(message));
|
|
535
|
+
}
|
|
536
|
+
// If decision === 'ask', we just show the banner with instructions
|
|
537
|
+
// User can run /update check or /update auto to update
|
|
527
538
|
}
|
|
528
539
|
catch {
|
|
529
540
|
// Silently fail - don't interrupt user experience
|
|
@@ -607,6 +618,8 @@ export class InteractiveShell {
|
|
|
607
618
|
this.pushUiEvent('raw', block);
|
|
608
619
|
}
|
|
609
620
|
async start(initialPrompt) {
|
|
621
|
+
// Check for updates BEFORE terminal input starts (to avoid keypress conflicts)
|
|
622
|
+
await this.checkAndShowUpdates();
|
|
610
623
|
// Initialize the renderer before emitting the banner so we don't render the prompt twice
|
|
611
624
|
this.terminalInput.start();
|
|
612
625
|
this.resetRendererStreamingMode();
|
|
@@ -615,8 +628,9 @@ export class InteractiveShell {
|
|
|
615
628
|
this.refreshControlBar();
|
|
616
629
|
// Now sync renderer and control bar state
|
|
617
630
|
this.syncRendererInput();
|
|
618
|
-
//
|
|
619
|
-
|
|
631
|
+
// CRITICAL: Force prompt render after banner - flushEvents should have done this,
|
|
632
|
+
// but explicitly ensure the prompt area is visible
|
|
633
|
+
this.renderer?.render();
|
|
620
634
|
if (initialPrompt) {
|
|
621
635
|
await this.enqueuePromptForProcessing(initialPrompt, 'programmatic');
|
|
622
636
|
return;
|
|
@@ -624,7 +638,7 @@ export class InteractiveShell {
|
|
|
624
638
|
this.showLaunchCommandPalette();
|
|
625
639
|
// Ensure the terminal input is visible
|
|
626
640
|
this.syncRendererInput();
|
|
627
|
-
this.
|
|
641
|
+
this.renderer?.render();
|
|
628
642
|
}
|
|
629
643
|
showLaunchCommandPalette() {
|
|
630
644
|
// Disabled: Quick commands palette takes up too much space
|
|
@@ -692,6 +706,10 @@ export class InteractiveShell {
|
|
|
692
706
|
*/
|
|
693
707
|
async runPromptJob(job) {
|
|
694
708
|
try {
|
|
709
|
+
// Show thinking indicator instead of "processing queued prompt"
|
|
710
|
+
display.showThinking('Thinking…');
|
|
711
|
+
// Re-echo the prompt to make it clear what's being processed
|
|
712
|
+
this.renderer?.emitPrompt(job.text);
|
|
695
713
|
await this.processInputBlock(job.text);
|
|
696
714
|
job.resolve();
|
|
697
715
|
}
|
|
@@ -787,7 +805,6 @@ export class InteractiveShell {
|
|
|
787
805
|
'/output-style',
|
|
788
806
|
// Mode toggles
|
|
789
807
|
'/thinking',
|
|
790
|
-
'/autocontinue',
|
|
791
808
|
// Discovery and plugins
|
|
792
809
|
'/local', '/discover',
|
|
793
810
|
'/plugins',
|
|
@@ -877,25 +894,22 @@ export class InteractiveShell {
|
|
|
877
894
|
: '🛡️ High-impact actions now require your approval. (Ctrl+Shift+A to toggle; use /approvals ask to persist)';
|
|
878
895
|
display.showSystemMessage(message);
|
|
879
896
|
}
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
this.agent.setAutoContinue(this.autoContinueEnabled);
|
|
889
|
-
}
|
|
890
|
-
this.refreshControlBar();
|
|
891
|
-
if (!changed && source === 'shortcut') {
|
|
897
|
+
/**
|
|
898
|
+
* Expand the last tool result (Ctrl+O shortcut).
|
|
899
|
+
* Shows the full output from the most recent tool call.
|
|
900
|
+
*/
|
|
901
|
+
expandLastToolResult() {
|
|
902
|
+
const result = this.uiAdapter.getLastToolResult();
|
|
903
|
+
if (!result) {
|
|
904
|
+
display.showInfo('No tool result to expand. Run a tool first, then press Ctrl+O.');
|
|
892
905
|
return;
|
|
893
906
|
}
|
|
894
|
-
|
|
895
|
-
const
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
907
|
+
// Format the expanded output with tool name header
|
|
908
|
+
const header = `${theme.info('━━━')} ${theme.tool(result.toolName)} ${theme.ui.muted('output')} ${theme.info('━━━')}`;
|
|
909
|
+
const content = result.output.trim() || '(empty result)';
|
|
910
|
+
const footer = theme.ui.muted('━'.repeat(Math.min(60, header.length)));
|
|
911
|
+
// Display the expanded result
|
|
912
|
+
display.stream(`\n${header}\n${content}\n${footer}\n\n`);
|
|
899
913
|
}
|
|
900
914
|
/**
|
|
901
915
|
* Cycle through thinking modes (Tab shortcut).
|
|
@@ -1049,6 +1063,13 @@ export class InteractiveShell {
|
|
|
1049
1063
|
}
|
|
1050
1064
|
this.ctrlCHandledThisPress = false;
|
|
1051
1065
|
}
|
|
1066
|
+
/**
|
|
1067
|
+
* Handle exit request (Ctrl+C with empty buffer in idle mode)
|
|
1068
|
+
*/
|
|
1069
|
+
handleExit() {
|
|
1070
|
+
display.showSystemMessage('\nGoodbye!\n');
|
|
1071
|
+
this.shutdown();
|
|
1072
|
+
}
|
|
1052
1073
|
/**
|
|
1053
1074
|
* Gracefully tear down the shell and exit
|
|
1054
1075
|
*/
|
|
@@ -1618,9 +1639,7 @@ export class InteractiveShell {
|
|
|
1618
1639
|
refreshControlBar() {
|
|
1619
1640
|
this.terminalInput.setModeToggles({
|
|
1620
1641
|
verificationEnabled: this.verificationEnabled,
|
|
1621
|
-
autoContinueEnabled: this.autoContinueEnabled,
|
|
1622
1642
|
verificationHotkey: 'ctrl+shift+v',
|
|
1623
|
-
autoContinueHotkey: 'ctrl+shift+c',
|
|
1624
1643
|
thinkingModeLabel: (this.thinkingMode || 'off').toString(),
|
|
1625
1644
|
thinkingHotkey: 'tab',
|
|
1626
1645
|
criticalApprovalMode: this.criticalApprovalMode,
|
|
@@ -1711,15 +1730,14 @@ export class InteractiveShell {
|
|
|
1711
1730
|
*/
|
|
1712
1731
|
refreshStatusLine(forceRender = false) {
|
|
1713
1732
|
const elapsedSeconds = this.getAiRuntimeSeconds();
|
|
1714
|
-
const thinkingMs = display.isSpinnerActive() ? display.getThinkingElapsedMs() : null;
|
|
1715
1733
|
const tokensUsed = this.latestTokenUsage.used;
|
|
1716
1734
|
const tokenLimit = this.latestTokenUsage.limit ?? this.activeContextWindowTokens;
|
|
1717
1735
|
this.terminalInput.setMetaStatus({
|
|
1718
1736
|
elapsedSeconds,
|
|
1719
1737
|
tokensUsed,
|
|
1720
1738
|
tokenLimit,
|
|
1721
|
-
thinkingMs,
|
|
1722
|
-
thinkingHasContent:
|
|
1739
|
+
thinkingMs: null,
|
|
1740
|
+
thinkingHasContent: false,
|
|
1723
1741
|
});
|
|
1724
1742
|
// Keep model/provider visible in the controls bar
|
|
1725
1743
|
this.terminalInput.setModelContext({
|
|
@@ -1895,12 +1913,16 @@ export class InteractiveShell {
|
|
|
1895
1913
|
enterStreamingMode();
|
|
1896
1914
|
// Set up scroll region for streaming content
|
|
1897
1915
|
this.uiUpdates.setMode('streaming');
|
|
1916
|
+
// Show activity status with animated spinner - use provided label or default
|
|
1917
|
+
const activityLabel = label || 'Thinking';
|
|
1918
|
+
this.renderer?.setActivity(activityLabel);
|
|
1898
1919
|
this.streamingHeartbeatStart = Date.now();
|
|
1899
1920
|
this.streamingContentSeen = false;
|
|
1900
1921
|
this.streamingStatusText = null;
|
|
1901
1922
|
this.streamingStatusLastUpdate = null;
|
|
1902
|
-
|
|
1903
|
-
|
|
1923
|
+
this.streamingTokenCount = 0; // Reset token count for new prompt
|
|
1924
|
+
// Show raw streaming output in real-time (Claude Code style)
|
|
1925
|
+
this.streamingOutputSuppressed = false;
|
|
1904
1926
|
const initialLabel = this.isMeaningfulStreamingSnippet(label)
|
|
1905
1927
|
? this.truncateStreamingLabel(label)
|
|
1906
1928
|
: this.getStreamingFallbackLabel();
|
|
@@ -1944,6 +1966,8 @@ export class InteractiveShell {
|
|
|
1944
1966
|
this.streamingStatusLastUpdate = null;
|
|
1945
1967
|
this.streamingStatusText = null;
|
|
1946
1968
|
this.streamingOutputSuppressed = false;
|
|
1969
|
+
// Clear activity status when streaming ends
|
|
1970
|
+
this.renderer?.setActivity(null);
|
|
1947
1971
|
// Emit a streaming note for stop/quit so the status stays inside the stream
|
|
1948
1972
|
if (reason === 'stop' || reason === 'quit') {
|
|
1949
1973
|
const note = reason === 'quit' ? 'Session closed.' : 'Stream stopped.';
|
|
@@ -1960,11 +1984,15 @@ export class InteractiveShell {
|
|
|
1960
1984
|
// Buffer for accumulating partial <thinking> tags during streaming
|
|
1961
1985
|
thinkingTagBuffer = '';
|
|
1962
1986
|
insideThinkingBlock = false;
|
|
1987
|
+
streamingTokenCount = 0;
|
|
1963
1988
|
handleStreamChunk(chunk, type = 'content') {
|
|
1964
1989
|
if (!chunk) {
|
|
1965
1990
|
return;
|
|
1966
1991
|
}
|
|
1967
1992
|
const isReasoning = type === 'reasoning';
|
|
1993
|
+
// Approximate token count (roughly 4 chars per token)
|
|
1994
|
+
this.streamingTokenCount += Math.ceil(chunk.length / 4);
|
|
1995
|
+
this.renderer?.updateStreamingTokens(this.streamingTokenCount);
|
|
1968
1996
|
// Keep pinned status updated for all streaming chunks
|
|
1969
1997
|
this.updateStreamingStatusFromChunk(chunk);
|
|
1970
1998
|
// Handle <thinking> tags as separate events in the queue
|
|
@@ -1973,37 +2001,32 @@ export class InteractiveShell {
|
|
|
1973
2001
|
if (!processed.contentChunk && !processed.thinkingChunk) {
|
|
1974
2002
|
return;
|
|
1975
2003
|
}
|
|
1976
|
-
//
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
}
|
|
2004
|
+
// Don't emit thinking blocks during streaming - they will be rendered
|
|
2005
|
+
// as a complete block by onAssistantMessage when the response is final.
|
|
2006
|
+
// This prevents duplicate display.
|
|
1980
2007
|
// Process regular content (skip if no content after extracting thinking)
|
|
1981
2008
|
const contentChunk = processed.contentChunk;
|
|
1982
2009
|
if (!contentChunk) {
|
|
1983
2010
|
return;
|
|
1984
2011
|
}
|
|
1985
2012
|
// Suppress raw streaming output in scrollback; keep only status + final message.
|
|
1986
|
-
// Reasoning
|
|
1987
|
-
if (this.streamingOutputSuppressed
|
|
2013
|
+
// Reasoning tokens should also be suppressed and shown only in the indicator.
|
|
2014
|
+
if (this.streamingOutputSuppressed) {
|
|
1988
2015
|
this.captureStreamingThought(contentChunk);
|
|
1989
2016
|
this.streamingContentSeen = true;
|
|
2017
|
+
// Update thinking indicator with a snippet of the content
|
|
2018
|
+
const prefix = isReasoning ? '○ ' : '';
|
|
2019
|
+
const snippet = contentChunk.replace(/\s+/g, ' ').trim().slice(0, 60);
|
|
2020
|
+
if (snippet) {
|
|
2021
|
+
display.updateThinking(prefix + snippet + (contentChunk.length > 60 ? '…' : ''));
|
|
2022
|
+
}
|
|
1990
2023
|
return;
|
|
1991
2024
|
}
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
this.pushUiEvent('streaming', this.streamingFormatter.header());
|
|
1995
|
-
}
|
|
1996
|
-
this.streamingFormatter.setMode(isReasoning ? 'reasoning' : 'content');
|
|
1997
|
-
const formatted = isReasoning
|
|
1998
|
-
? this.streamingFormatter.formatReasoningChunk(contentChunk)
|
|
1999
|
-
: this.streamingFormatter.formatChunk(contentChunk);
|
|
2025
|
+
// Buffer all streaming content - rendered as complete block via onAssistantMessage
|
|
2026
|
+
// This prevents fragmented display and ensures thinking tags are properly processed
|
|
2000
2027
|
this.captureStreamingThought(contentChunk);
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
this.streamingContentSeen = true;
|
|
2004
|
-
}
|
|
2005
|
-
this.pushUiEvent('streaming', formatted);
|
|
2006
|
-
}
|
|
2028
|
+
// Activity: show what the model is doing (stable, informative)
|
|
2029
|
+
this.renderer?.setActivity(type === 'reasoning' ? 'Reasoning' : 'Writing');
|
|
2007
2030
|
}
|
|
2008
2031
|
/**
|
|
2009
2032
|
* Process streaming content to extract <thinking> blocks as separate events.
|
|
@@ -2051,22 +2074,12 @@ export class InteractiveShell {
|
|
|
2051
2074
|
}
|
|
2052
2075
|
return { thinkingChunk: thinkingContent, contentChunk: remainingContent };
|
|
2053
2076
|
}
|
|
2054
|
-
finishStreamingFormatter(
|
|
2055
|
-
|
|
2056
|
-
return;
|
|
2057
|
-
}
|
|
2058
|
-
const closing = this.streamingFormatter.finish({
|
|
2059
|
-
note,
|
|
2060
|
-
mode: options?.mode ?? 'complete',
|
|
2061
|
-
});
|
|
2062
|
-
if (closing) {
|
|
2063
|
-
this.pushUiEvent('streaming', closing);
|
|
2064
|
-
}
|
|
2077
|
+
finishStreamingFormatter(_note, options) {
|
|
2078
|
+
// Flush any remaining buffered thoughts
|
|
2065
2079
|
if (this.streamingThoughtBuffer.trim()) {
|
|
2066
2080
|
this.ui.controller.recordAssistantThought(this.streamingThoughtBuffer.trim());
|
|
2067
2081
|
}
|
|
2068
2082
|
this.streamingThoughtBuffer = '';
|
|
2069
|
-
this.streamingFormatter = null;
|
|
2070
2083
|
if (options?.refreshPrompt ?? true) {
|
|
2071
2084
|
this.requestPromptRefresh(true);
|
|
2072
2085
|
}
|
|
@@ -2405,9 +2418,6 @@ export class InteractiveShell {
|
|
|
2405
2418
|
case '/thinking':
|
|
2406
2419
|
this.handleThinkingCommand(input);
|
|
2407
2420
|
break;
|
|
2408
|
-
case '/autocontinue':
|
|
2409
|
-
this.handleAutoContinueCommand(input);
|
|
2410
|
-
break;
|
|
2411
2421
|
case '/shortcuts':
|
|
2412
2422
|
case '/keys':
|
|
2413
2423
|
this.handleShortcutsCommand();
|
|
@@ -2511,6 +2521,9 @@ export class InteractiveShell {
|
|
|
2511
2521
|
case '/permissions':
|
|
2512
2522
|
this.handlePermissionsCommand();
|
|
2513
2523
|
break;
|
|
2524
|
+
case '/update':
|
|
2525
|
+
await this.handleUpdateCommand(input);
|
|
2526
|
+
break;
|
|
2514
2527
|
case '/init':
|
|
2515
2528
|
this.handleInitCommand(input);
|
|
2516
2529
|
break;
|
|
@@ -2568,7 +2581,6 @@ export class InteractiveShell {
|
|
|
2568
2581
|
theme.bold(' Mode Toggles'),
|
|
2569
2582
|
` ${theme.info('Shift+Tab')} ${theme.ui.muted('Toggle edit mode (auto/ask)')}`,
|
|
2570
2583
|
` ${theme.info('Option+V')} ${theme.ui.muted('Toggle verification')}`,
|
|
2571
|
-
` ${theme.info('Option+C')} ${theme.ui.muted('Toggle auto-continue')}`,
|
|
2572
2584
|
` ${theme.info('Ctrl+Shift+A')} ${theme.ui.muted('Toggle approvals for high-impact actions')}`,
|
|
2573
2585
|
` ${theme.info('Option+T')} ${theme.ui.muted('Cycle thinking mode')}`,
|
|
2574
2586
|
` ${theme.info('Option+E')} ${theme.ui.muted('Toggle edit permission mode')}`,
|
|
@@ -4282,20 +4294,6 @@ export class InteractiveShell {
|
|
|
4282
4294
|
clearAutosaveSnapshot(this.profile);
|
|
4283
4295
|
display.showInfo('Cleared autosave history.');
|
|
4284
4296
|
}
|
|
4285
|
-
handleAutoContinueCommand(input) {
|
|
4286
|
-
const tokens = input.split(/\s+/).slice(1);
|
|
4287
|
-
const value = tokens[0]?.toLowerCase();
|
|
4288
|
-
if (!value) {
|
|
4289
|
-
// Show current status
|
|
4290
|
-
display.showInfo(`Auto-continue is ${this.autoContinueEnabled ? 'enabled' : 'disabled'}. Use /autocontinue on|off or Ctrl+Shift+C to toggle.`);
|
|
4291
|
-
return;
|
|
4292
|
-
}
|
|
4293
|
-
if (value !== 'on' && value !== 'off') {
|
|
4294
|
-
display.showWarning('Usage: /autocontinue on|off');
|
|
4295
|
-
return;
|
|
4296
|
-
}
|
|
4297
|
-
this.setAutoContinueMode(value === 'on', 'command');
|
|
4298
|
-
}
|
|
4299
4297
|
// ==================== Erosolar-CLI Style Commands ====================
|
|
4300
4298
|
async handleRewindCommand(_input) {
|
|
4301
4299
|
const lines = [];
|
|
@@ -4527,7 +4525,6 @@ export class InteractiveShell {
|
|
|
4527
4525
|
lines.push(`${theme.primary('Provider/Model')}: ${theme.info(`${this.providerLabel(this.sessionState.provider)} · ${this.sessionState.model}`)}`);
|
|
4528
4526
|
lines.push(`${theme.primary('Workspace')}: ${theme.ui.muted(this.abbreviatePath(this.workingDir))}`);
|
|
4529
4527
|
lines.push(`${theme.primary('Autosave')}: ${this.autosaveEnabled ? theme.success('on') : theme.ui.muted('off')}`);
|
|
4530
|
-
lines.push(`${theme.primary('Auto-continue')}: ${this.autoContinueEnabled ? theme.success('on') : theme.ui.muted('off')}`);
|
|
4531
4528
|
lines.push(`${theme.primary('Verification')}: ${this.verificationEnabled ? theme.success('on') : theme.ui.muted('off')}`);
|
|
4532
4529
|
lines.push(`${theme.primary('Approvals')}: ${theme.ui.muted(this.describeEditGuardMode())}`);
|
|
4533
4530
|
lines.push(`${theme.primary('Critical approvals')}: ${this.criticalApprovalMode === 'approval' ? theme.warning('ask') : theme.ui.muted('auto')}`);
|
|
@@ -4683,6 +4680,67 @@ export class InteractiveShell {
|
|
|
4683
4680
|
lines.push(theme.ui.muted('Use /tools to manage which tool suites are enabled.'));
|
|
4684
4681
|
display.showSystemMessage(lines.join('\n'));
|
|
4685
4682
|
}
|
|
4683
|
+
async handleUpdateCommand(input) {
|
|
4684
|
+
const tokens = input.split(/\s+/).slice(1);
|
|
4685
|
+
const subcommand = tokens[0]?.toLowerCase();
|
|
4686
|
+
const prefs = loadSessionPreferences();
|
|
4687
|
+
const currentPref = prefs.autoUpdate;
|
|
4688
|
+
const prefLabel = currentPref === true ? 'always update' : currentPref === false ? 'always skip' : 'notify only';
|
|
4689
|
+
// Show status or help
|
|
4690
|
+
if (!subcommand || subcommand === 'status') {
|
|
4691
|
+
const lines = [];
|
|
4692
|
+
lines.push(theme.bold('Update Settings'));
|
|
4693
|
+
lines.push('');
|
|
4694
|
+
lines.push(`Current preference: ${theme.info(prefLabel)}`);
|
|
4695
|
+
lines.push('');
|
|
4696
|
+
lines.push(theme.secondary('Commands:'));
|
|
4697
|
+
lines.push(' /update check - Check for updates and install immediately');
|
|
4698
|
+
lines.push(' /update auto - Always auto-update in background');
|
|
4699
|
+
lines.push(' /update skip - Never auto-update (silent)');
|
|
4700
|
+
lines.push(' /update notify - Show notification only (default)');
|
|
4701
|
+
display.showSystemMessage(lines.join('\n'));
|
|
4702
|
+
return;
|
|
4703
|
+
}
|
|
4704
|
+
if (subcommand === 'check') {
|
|
4705
|
+
// Force check and perform update immediately (non-interactive)
|
|
4706
|
+
try {
|
|
4707
|
+
const { checkForUpdates, performUpdate } = await import('../core/updateChecker.js');
|
|
4708
|
+
const currentVersion = this.version || this.getPackageInfo().version || '1.7.458';
|
|
4709
|
+
const updateInfo = await checkForUpdates(currentVersion);
|
|
4710
|
+
if (!updateInfo) {
|
|
4711
|
+
display.showWarning('Unable to check for updates (network issue or timeout).');
|
|
4712
|
+
return;
|
|
4713
|
+
}
|
|
4714
|
+
if (!updateInfo.updateAvailable) {
|
|
4715
|
+
display.showSuccess(`You're on the latest version (v${updateInfo.current}).`);
|
|
4716
|
+
return;
|
|
4717
|
+
}
|
|
4718
|
+
// Show update info and perform update immediately (no interactive prompt)
|
|
4719
|
+
display.showInfo(`Update available: v${updateInfo.current} → v${updateInfo.latest}`);
|
|
4720
|
+
await performUpdate(updateInfo, (msg) => this.streamEventBlock(msg));
|
|
4721
|
+
}
|
|
4722
|
+
catch (error) {
|
|
4723
|
+
display.showError(`Update check failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
4724
|
+
}
|
|
4725
|
+
return;
|
|
4726
|
+
}
|
|
4727
|
+
if (subcommand === 'auto' || subcommand === 'always') {
|
|
4728
|
+
saveSessionPreferences({ autoUpdate: true });
|
|
4729
|
+
display.showSuccess('Auto-update enabled. Updates will install automatically.');
|
|
4730
|
+
return;
|
|
4731
|
+
}
|
|
4732
|
+
if (subcommand === 'skip' || subcommand === 'never' || subcommand === 'off') {
|
|
4733
|
+
saveSessionPreferences({ autoUpdate: false });
|
|
4734
|
+
display.showInfo('Auto-update disabled. Updates will be skipped silently.');
|
|
4735
|
+
return;
|
|
4736
|
+
}
|
|
4737
|
+
if (subcommand === 'ask' || subcommand === 'prompt' || subcommand === 'reset' || subcommand === 'notify') {
|
|
4738
|
+
saveSessionPreferences({ autoUpdate: null });
|
|
4739
|
+
display.showInfo('Update preference reset. You will see a notification when updates are available.');
|
|
4740
|
+
return;
|
|
4741
|
+
}
|
|
4742
|
+
display.showWarning('Usage: /update [check|auto|skip|notify|status]');
|
|
4743
|
+
}
|
|
4686
4744
|
handleInitCommand(input) {
|
|
4687
4745
|
const tokens = input.split(/\s+/).slice(1);
|
|
4688
4746
|
const confirm = tokens[0]?.toLowerCase() === 'confirm';
|
|
@@ -5707,6 +5765,7 @@ export class InteractiveShell {
|
|
|
5707
5765
|
}
|
|
5708
5766
|
this.isProcessing = true;
|
|
5709
5767
|
this.uiUpdates.setMode('processing');
|
|
5768
|
+
this.streamingTokenCount = 0; // Reset token counter for new request
|
|
5710
5769
|
this.terminalInput.setStreaming(true);
|
|
5711
5770
|
// Keep the persistent input/control bar active as we transition into streaming.
|
|
5712
5771
|
this.syncRendererInput();
|
|
@@ -5721,6 +5780,7 @@ export class InteractiveShell {
|
|
|
5721
5780
|
this.currentTaskType = classifyTaskType(request);
|
|
5722
5781
|
this.currentToolCalls = [];
|
|
5723
5782
|
this.clearToolUsageMeta();
|
|
5783
|
+
this.renderer?.setActivity('Starting...');
|
|
5724
5784
|
this.uiAdapter.startProcessing('Working on your request');
|
|
5725
5785
|
this.setProcessingStatus();
|
|
5726
5786
|
this.beginAiRuntime();
|
|
@@ -5776,8 +5836,6 @@ export class InteractiveShell {
|
|
|
5776
5836
|
clearActionHistory();
|
|
5777
5837
|
this.lastFailure = null;
|
|
5778
5838
|
}
|
|
5779
|
-
// Run post-hoc AI flow sanity check to catch "looks right but wrong" responses
|
|
5780
|
-
this.analyzeAiFlowForRun(request, responseText, requestStartTime);
|
|
5781
5839
|
}
|
|
5782
5840
|
catch (error) {
|
|
5783
5841
|
const handled = this.handleProviderError(error, () => this.processRequest(request));
|
|
@@ -5848,6 +5906,7 @@ export class InteractiveShell {
|
|
|
5848
5906
|
this.clearToolUsageMeta();
|
|
5849
5907
|
this.isProcessing = true;
|
|
5850
5908
|
this.uiUpdates.setMode('processing');
|
|
5909
|
+
this.streamingTokenCount = 0; // Reset token counter for new request
|
|
5851
5910
|
this.terminalInput.setStreaming(true);
|
|
5852
5911
|
if (this.suppressNextNetworkReset) {
|
|
5853
5912
|
this.suppressNextNetworkReset = false;
|
|
@@ -6255,43 +6314,6 @@ What's the next action?`;
|
|
|
6255
6314
|
const parts = candidates.filter((part) => typeof part === 'string' && part.trim().length > 0);
|
|
6256
6315
|
return parts.join('\n').trim();
|
|
6257
6316
|
}
|
|
6258
|
-
/**
|
|
6259
|
-
* Post-run sanity check to catch hallucinated "looks right" answers.
|
|
6260
|
-
* Compares the prompt/response against per-run tool usage and surfaces risks.
|
|
6261
|
-
*/
|
|
6262
|
-
analyzeAiFlowForRun(prompt, responseText, startedAt) {
|
|
6263
|
-
const toolHistory = this.runtimeSession.toolRuntime.getToolHistory?.();
|
|
6264
|
-
if (!toolHistory) {
|
|
6265
|
-
return;
|
|
6266
|
-
}
|
|
6267
|
-
const diffSnapshots = this.runtimeSession.toolRuntime.getDiffSnapshots?.() ?? [];
|
|
6268
|
-
const recentDiffs = diffSnapshots
|
|
6269
|
-
.filter((snapshot) => snapshot.timestamp >= startedAt)
|
|
6270
|
-
.map(({ command, output }) => ({ command, output }));
|
|
6271
|
-
const assessment = assessAiFlow({
|
|
6272
|
-
prompt,
|
|
6273
|
-
response: responseText,
|
|
6274
|
-
startedAt,
|
|
6275
|
-
toolHistory,
|
|
6276
|
-
diffSnapshots: recentDiffs,
|
|
6277
|
-
});
|
|
6278
|
-
this.reportAiFlowAssessment(assessment);
|
|
6279
|
-
}
|
|
6280
|
-
reportAiFlowAssessment(assessment) {
|
|
6281
|
-
if (!assessment || assessment.risks.length === 0) {
|
|
6282
|
-
return;
|
|
6283
|
-
}
|
|
6284
|
-
display.showSystemMessage('⚠️ AI flow check: potential unverified completion detected.');
|
|
6285
|
-
for (const risk of assessment.risks) {
|
|
6286
|
-
const detail = risk.details ? ` — ${risk.details}` : '';
|
|
6287
|
-
display.showWarning(`${risk.summary}${detail}`);
|
|
6288
|
-
}
|
|
6289
|
-
if (assessment.recommendations.length > 0) {
|
|
6290
|
-
const uniqueRecommendations = Array.from(new Set(assessment.recommendations));
|
|
6291
|
-
const advice = uniqueRecommendations.slice(0, 3).map((rec, idx) => `${idx + 1}. ${rec}`).join('\n');
|
|
6292
|
-
display.showSystemMessage(`Follow-ups to de-risk this run:\n${advice}`);
|
|
6293
|
-
}
|
|
6294
|
-
}
|
|
6295
6317
|
runAutoQualityChecks(trigger, assistantResponse, verificationContext) {
|
|
6296
6318
|
if (!this.verificationEnabled) {
|
|
6297
6319
|
return;
|
|
@@ -6672,21 +6694,41 @@ Return ONLY JSON array:
|
|
|
6672
6694
|
maxTokens: this.sessionState.maxTokens,
|
|
6673
6695
|
systemPrompt: this.buildSystemPrompt(),
|
|
6674
6696
|
reasoningEffort: this.sessionState.reasoningEffort,
|
|
6675
|
-
autoContinue: this.autoContinueEnabled,
|
|
6676
6697
|
};
|
|
6677
6698
|
this.agent = this.runtimeSession.createAgent(selection, {
|
|
6699
|
+
onRequestReceived: (requestPreview) => {
|
|
6700
|
+
const normalized = requestPreview?.trim();
|
|
6701
|
+
const activity = normalized ? `Working: ${normalized}` : 'Working';
|
|
6702
|
+
this.renderer?.setActivity(activity);
|
|
6703
|
+
},
|
|
6704
|
+
onBeforeFirstToolCall: (toolNames) => {
|
|
6705
|
+
const primaryTool = toolNames[0];
|
|
6706
|
+
if (primaryTool) {
|
|
6707
|
+
this.renderer?.setActivity(`Running ${primaryTool}`);
|
|
6708
|
+
}
|
|
6709
|
+
return undefined;
|
|
6710
|
+
},
|
|
6678
6711
|
onStreamChunk: (chunk, type) => {
|
|
6679
6712
|
this.handleStreamChunk(chunk, type ?? 'content');
|
|
6680
6713
|
},
|
|
6681
|
-
onStreamFallback: (info) => this.handleStreamingFallback(info),
|
|
6682
6714
|
onAssistantMessage: (content, metadata) => {
|
|
6683
6715
|
const enriched = this.buildDisplayMetadata(metadata);
|
|
6684
6716
|
const streamedVisible = metadata.wasStreamed && this.streamingContentSeen && !this.streamingOutputSuppressed;
|
|
6685
6717
|
let renderedFinal = false;
|
|
6718
|
+
// Update streaming token count from usage info (more accurate than chunk counting)
|
|
6719
|
+
if (metadata.usage) {
|
|
6720
|
+
const totalTokens = this.totalTokens(metadata.usage);
|
|
6721
|
+
if (totalTokens !== null && totalTokens > this.streamingTokenCount) {
|
|
6722
|
+
this.streamingTokenCount = totalTokens;
|
|
6723
|
+
this.renderer?.updateStreamingTokens(this.streamingTokenCount);
|
|
6724
|
+
}
|
|
6725
|
+
}
|
|
6686
6726
|
// Update spinner based on message type
|
|
6687
6727
|
if (metadata.isFinal) {
|
|
6688
6728
|
const parsed = this.splitThinkingResponse(content);
|
|
6689
|
-
|
|
6729
|
+
// If we successfully parsed thinking, use the parsed response (may be empty)
|
|
6730
|
+
// Don't fall back to original content with raw <thinking> tags
|
|
6731
|
+
const finalContent = parsed ? parsed.response?.trim() : content;
|
|
6690
6732
|
const thoughtContent = parsed?.thinking?.trim() || null;
|
|
6691
6733
|
// Show the response if it wasn't already rendered during streaming
|
|
6692
6734
|
if (!streamedVisible) {
|
|
@@ -6789,12 +6831,6 @@ Return ONLY JSON array:
|
|
|
6789
6831
|
this.updateStatusMessage('Retrying with reduced context...');
|
|
6790
6832
|
this.syncRendererInput();
|
|
6791
6833
|
},
|
|
6792
|
-
onAutoContinue: (attempt, maxAttempts, _message) => {
|
|
6793
|
-
// Show auto-continue progress in UI
|
|
6794
|
-
display.showSystemMessage(`🔄 Auto-continue (${attempt}/${maxAttempts}): Model expressed intent, prompting to act...`);
|
|
6795
|
-
this.updateStatusMessage('Auto-continuing...');
|
|
6796
|
-
this.syncRendererInput();
|
|
6797
|
-
},
|
|
6798
6834
|
onCancelled: () => {
|
|
6799
6835
|
// Update UI to show operation was cancelled
|
|
6800
6836
|
display.showWarning('Operation cancelled.');
|
|
@@ -6803,10 +6839,42 @@ Return ONLY JSON array:
|
|
|
6803
6839
|
this.updateStatusMessage(null);
|
|
6804
6840
|
this.terminalInput.setStreaming(false);
|
|
6805
6841
|
},
|
|
6842
|
+
onToolExecution: (toolName, isStart, args) => {
|
|
6843
|
+
// Update activity status to show what tool is being executed
|
|
6844
|
+
if (isStart) {
|
|
6845
|
+
// Show more specific activity for long-running tools
|
|
6846
|
+
let activity = `Running ${toolName}`;
|
|
6847
|
+
if (toolName === 'execute_bash' && args?.['command']) {
|
|
6848
|
+
const cmd = String(args['command']).slice(0, 40);
|
|
6849
|
+
activity = `$ ${cmd}${String(args['command']).length > 40 ? '...' : ''}`;
|
|
6850
|
+
}
|
|
6851
|
+
else if (toolName === 'read_file' && args?.['file_path']) {
|
|
6852
|
+
const path = String(args['file_path']).split('/').pop() || args['file_path'];
|
|
6853
|
+
activity = `Reading ${path}`;
|
|
6854
|
+
}
|
|
6855
|
+
this.renderer?.setActivity(activity);
|
|
6856
|
+
// Estimate tokens for tool call (~50 tokens per call)
|
|
6857
|
+
this.streamingTokenCount += 50;
|
|
6858
|
+
this.renderer?.updateStreamingTokens(this.streamingTokenCount);
|
|
6859
|
+
}
|
|
6860
|
+
else {
|
|
6861
|
+
// Tool finished - estimate result tokens (~100 per result)
|
|
6862
|
+
this.streamingTokenCount += 100;
|
|
6863
|
+
this.renderer?.updateStreamingTokens(this.streamingTokenCount);
|
|
6864
|
+
// Reset to thinking state while model generates next response
|
|
6865
|
+
this.renderer?.setActivity('Thinking');
|
|
6866
|
+
}
|
|
6867
|
+
},
|
|
6806
6868
|
onVerificationNeeded: (response, context) => {
|
|
6807
6869
|
this.lastAssistantResponse = response;
|
|
6808
6870
|
void this.runAutoQualityChecks('verification', response, context);
|
|
6809
6871
|
},
|
|
6872
|
+
// Retry notification for transient errors
|
|
6873
|
+
onRetrying: (attempt, maxAttempts, error) => {
|
|
6874
|
+
const shortError = error.message.slice(0, 100);
|
|
6875
|
+
display.showSystemMessage(`⚡ Retry ${attempt}/${maxAttempts}: ${shortError}${error.message.length > 100 ? '...' : ''}`);
|
|
6876
|
+
this.renderer?.setActivity(`Retrying (${attempt}/${maxAttempts})...`);
|
|
6877
|
+
},
|
|
6810
6878
|
});
|
|
6811
6879
|
// Register global AI enhancer for explore tool - uses active model by default
|
|
6812
6880
|
this.registerExploreAIEnhancer();
|
|
@@ -6901,20 +6969,30 @@ Return ONLY JSON array:
|
|
|
6901
6969
|
return lines.join('\n').trim();
|
|
6902
6970
|
}
|
|
6903
6971
|
buildThinkingDirective() {
|
|
6972
|
+
// Base requirement: ALWAYS think before acting (applies to all modes)
|
|
6973
|
+
const baseRequirement = [
|
|
6974
|
+
'CRITICAL: Before calling ANY tool, ALWAYS output a <thinking>...</thinking> block explaining:',
|
|
6975
|
+
'1. What you understand the user is asking',
|
|
6976
|
+
'2. Your approach/plan to solve it',
|
|
6977
|
+
'3. Which tools you will use and why',
|
|
6978
|
+
'This thinking block MUST appear before your first tool call in every response.',
|
|
6979
|
+
].join('\n');
|
|
6904
6980
|
switch (this.thinkingMode) {
|
|
6905
6981
|
case 'extended':
|
|
6906
6982
|
return [
|
|
6907
|
-
|
|
6908
|
-
'
|
|
6909
|
-
'
|
|
6910
|
-
'
|
|
6911
|
-
'<response
|
|
6912
|
-
'Final answer with actionable next steps and any code/commands requested.',
|
|
6913
|
-
'</response>',
|
|
6983
|
+
baseRequirement,
|
|
6984
|
+
'',
|
|
6985
|
+
'Extended thinking mode: Use detailed multi-step reasoning in your <thinking> block.',
|
|
6986
|
+
'Reference tool runs/files when relevant. Keep secrets redacted.',
|
|
6987
|
+
'After thinking, wrap your final answer in <response>...</response>.',
|
|
6914
6988
|
].join('\n');
|
|
6915
6989
|
case 'balanced':
|
|
6916
6990
|
default:
|
|
6917
|
-
return
|
|
6991
|
+
return [
|
|
6992
|
+
baseRequirement,
|
|
6993
|
+
'',
|
|
6994
|
+
'Balanced mode: Keep thinking concise (2-4 sentences) but always present.',
|
|
6995
|
+
].join('\n');
|
|
6918
6996
|
}
|
|
6919
6997
|
}
|
|
6920
6998
|
buildDisplayMetadata(metadata) {
|
|
@@ -7005,13 +7083,14 @@ Return ONLY JSON array:
|
|
|
7005
7083
|
});
|
|
7006
7084
|
cleanupOverlayActive = true;
|
|
7007
7085
|
const triggerReason = trigger?.reason ?? 'Context optimization';
|
|
7008
|
-
|
|
7009
|
-
|
|
7010
|
-
|
|
7011
|
-
|
|
7012
|
-
|
|
7013
|
-
|
|
7014
|
-
|
|
7086
|
+
// Claude Code style: Show "Compacting conversation..." with animation
|
|
7087
|
+
// The renderer handles the animated spinner display
|
|
7088
|
+
if (this.renderer) {
|
|
7089
|
+
this.renderer.showCompactingStatus('Compacting conversation… (esc to interrupt)');
|
|
7090
|
+
}
|
|
7091
|
+
else {
|
|
7092
|
+
display.showSystemMessage(`✻ Compacting conversation… (esc to interrupt)`);
|
|
7093
|
+
}
|
|
7015
7094
|
const result = await contextManager.intelligentCompact(history);
|
|
7016
7095
|
let afterStats = contextManager.getStats(result.compacted);
|
|
7017
7096
|
let appliedHistory = result.compacted;
|
|
@@ -7038,10 +7117,18 @@ Return ONLY JSON array:
|
|
|
7038
7117
|
}
|
|
7039
7118
|
}
|
|
7040
7119
|
if (!changed) {
|
|
7120
|
+
// Hide compacting status before showing info message
|
|
7121
|
+
if (this.renderer) {
|
|
7122
|
+
this.renderer.hideCompactingStatus();
|
|
7123
|
+
}
|
|
7041
7124
|
display.showInfo('Context compaction completed but no changes were applied.');
|
|
7042
7125
|
return;
|
|
7043
7126
|
}
|
|
7044
7127
|
if (!this.hasMeaningfulCompaction(changed, bestTokenSavings, bestPercentSavings, trigger?.forced)) {
|
|
7128
|
+
// Hide compacting status before showing info message
|
|
7129
|
+
if (this.renderer) {
|
|
7130
|
+
this.renderer.hideCompactingStatus();
|
|
7131
|
+
}
|
|
7045
7132
|
display.showInfo('Auto-compaction completed but did not meaningfully reduce context size. Keeping existing history.');
|
|
7046
7133
|
return;
|
|
7047
7134
|
}
|
|
@@ -7057,15 +7144,18 @@ Return ONLY JSON array:
|
|
|
7057
7144
|
this.updateContextUsage(newPercentUsed, CONTEXT_AUTOCOMPACT_PERCENT);
|
|
7058
7145
|
this.lastContextWarningLevel = this.getContextWarningLevel(newPercentUsed);
|
|
7059
7146
|
this.refreshStatusLine(true);
|
|
7060
|
-
|
|
7061
|
-
|
|
7062
|
-
|
|
7063
|
-
|
|
7064
|
-
|
|
7065
|
-
|
|
7066
|
-
|
|
7067
|
-
|
|
7068
|
-
|
|
7147
|
+
// Claude Code style: Show compaction complete with separator and ctrl+o hint
|
|
7148
|
+
// Stop the compacting animation first
|
|
7149
|
+
if (this.renderer) {
|
|
7150
|
+
this.renderer.hideCompactingStatus();
|
|
7151
|
+
}
|
|
7152
|
+
// Show the Claude Code style separator: ══ Conversation compacted · ctrl+o for history ═
|
|
7153
|
+
if (this.renderer) {
|
|
7154
|
+
this.renderer.addCompactBlock('', 'Conversation compacted · ctrl+o for history');
|
|
7155
|
+
}
|
|
7156
|
+
else {
|
|
7157
|
+
display.showSystemMessage('══ Conversation compacted · ctrl+o for history ═');
|
|
7158
|
+
}
|
|
7069
7159
|
this.recordContextCompaction({
|
|
7070
7160
|
timestamp: Date.now(),
|
|
7071
7161
|
source: trigger?.source ?? 'auto',
|
|
@@ -7079,12 +7169,20 @@ Return ONLY JSON array:
|
|
|
7079
7169
|
});
|
|
7080
7170
|
}
|
|
7081
7171
|
catch (error) {
|
|
7172
|
+
// Hide compacting status animation on error
|
|
7173
|
+
if (this.renderer) {
|
|
7174
|
+
this.renderer.hideCompactingStatus();
|
|
7175
|
+
}
|
|
7082
7176
|
display.showError('Context compaction failed.', error);
|
|
7083
7177
|
}
|
|
7084
7178
|
finally {
|
|
7085
7179
|
if (cleanupOverlayActive) {
|
|
7086
7180
|
this.statusTracker.clearOverride(cleanupStatusId);
|
|
7087
7181
|
}
|
|
7182
|
+
// Ensure compacting status is cleared
|
|
7183
|
+
if (this.renderer) {
|
|
7184
|
+
this.renderer.hideCompactingStatus();
|
|
7185
|
+
}
|
|
7088
7186
|
this.cleanupInProgress = false;
|
|
7089
7187
|
this.contextCompactionInFlight = false;
|
|
7090
7188
|
}
|
|
@@ -7386,28 +7484,6 @@ Return ONLY JSON array:
|
|
|
7386
7484
|
const message = error instanceof Error ? error.message : String(error);
|
|
7387
7485
|
display.showError(message);
|
|
7388
7486
|
}
|
|
7389
|
-
handleStreamingFallback(info) {
|
|
7390
|
-
const promptBlock = detectPromptBlockError(info.error ?? info.message);
|
|
7391
|
-
if (promptBlock) {
|
|
7392
|
-
this.handlePromptBlock(promptBlock);
|
|
7393
|
-
this.finishStreamingFormatter('Prompt blocked mid-stream', { mode: 'update' });
|
|
7394
|
-
display.showSystemMessage('Retrying without streaming, but the provider will likely block again until you rephrase.');
|
|
7395
|
-
this.startStreamingHeartbeat('Re-running without streaming');
|
|
7396
|
-
this.requestPromptRefresh(true);
|
|
7397
|
-
return;
|
|
7398
|
-
}
|
|
7399
|
-
const detailText = info.message?.trim() ?? '';
|
|
7400
|
-
const detail = detailText ? ` ${detailText}` : '';
|
|
7401
|
-
const reason = info.reason ? ` (${info.reason.replace(/-/g, ' ')})` : '';
|
|
7402
|
-
const partialNote = info.partialResponse
|
|
7403
|
-
? ' Partial stream captured before failure; continuing from it without restarting.'
|
|
7404
|
-
: '';
|
|
7405
|
-
const baseMessage = 'Streaming interrupted, retrying without streaming';
|
|
7406
|
-
display.showWarning(`${baseMessage}${reason}.${detail}${partialNote}`.trim());
|
|
7407
|
-
this.finishStreamingFormatter('Stream interrupted - retrying without streaming', { mode: 'update' });
|
|
7408
|
-
this.startStreamingHeartbeat('Fallback in progress');
|
|
7409
|
-
this.requestPromptRefresh(true);
|
|
7410
|
-
}
|
|
7411
7487
|
handleProviderError(error, retryAction) {
|
|
7412
7488
|
const promptBlock = detectPromptBlockError(error);
|
|
7413
7489
|
if (promptBlock) {
|