erosolar-cli 2.1.167 → 2.1.168
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/agents/erosolar-code.rules.json +2 -2
- package/agents/general.rules.json +3 -21
- package/dist/StringUtils.d.ts +8 -0
- package/dist/StringUtils.d.ts.map +1 -0
- package/dist/StringUtils.js +11 -0
- package/dist/StringUtils.js.map +1 -0
- package/dist/capabilities/statusCapability.js +2 -2
- package/dist/capabilities/statusCapability.js.map +1 -1
- package/dist/contracts/agent-schemas.json +0 -5
- package/dist/core/agent.d.ts +11 -72
- package/dist/core/agent.d.ts.map +1 -1
- package/dist/core/agent.js +182 -869
- package/dist/core/agent.js.map +1 -1
- package/dist/core/aiFlowSupervisor.d.ts +44 -0
- package/dist/core/aiFlowSupervisor.d.ts.map +1 -0
- package/dist/core/aiFlowSupervisor.js +299 -0
- package/dist/core/aiFlowSupervisor.js.map +1 -0
- package/dist/core/cliTestHarness.d.ts +200 -0
- package/dist/core/cliTestHarness.d.ts.map +1 -0
- package/dist/core/cliTestHarness.js +549 -0
- package/dist/core/cliTestHarness.js.map +1 -0
- package/dist/core/preferences.d.ts +0 -1
- package/dist/core/preferences.d.ts.map +1 -1
- package/dist/core/preferences.js +2 -9
- package/dist/core/preferences.js.map +1 -1
- package/dist/core/schemaValidator.js +3 -3
- package/dist/core/schemaValidator.js.map +1 -1
- package/dist/core/testUtils.d.ts +121 -0
- package/dist/core/testUtils.d.ts.map +1 -0
- package/dist/core/testUtils.js +235 -0
- package/dist/core/testUtils.js.map +1 -0
- package/dist/core/toolPreconditions.d.ts +11 -0
- package/dist/core/toolPreconditions.d.ts.map +1 -1
- package/dist/core/toolPreconditions.js +164 -33
- package/dist/core/toolPreconditions.js.map +1 -1
- package/dist/core/toolRuntime.d.ts.map +1 -1
- package/dist/core/toolRuntime.js +114 -9
- package/dist/core/toolRuntime.js.map +1 -1
- package/dist/core/toolValidation.d.ts +116 -0
- package/dist/core/toolValidation.d.ts.map +1 -0
- package/dist/core/toolValidation.js +282 -0
- package/dist/core/toolValidation.js.map +1 -0
- package/dist/core/updateChecker.d.ts +1 -61
- package/dist/core/updateChecker.d.ts.map +1 -1
- package/dist/core/updateChecker.js +3 -147
- package/dist/core/updateChecker.js.map +1 -1
- package/dist/headless/headlessApp.d.ts.map +1 -1
- package/dist/headless/headlessApp.js +39 -0
- package/dist/headless/headlessApp.js.map +1 -1
- package/dist/plugins/tools/nodeDefaults.d.ts.map +1 -1
- package/dist/plugins/tools/nodeDefaults.js +2 -0
- package/dist/plugins/tools/nodeDefaults.js.map +1 -1
- package/dist/providers/openaiResponsesProvider.d.ts.map +1 -1
- package/dist/providers/openaiResponsesProvider.js +74 -79
- package/dist/providers/openaiResponsesProvider.js.map +1 -1
- package/dist/runtime/agentController.d.ts.map +1 -1
- package/dist/runtime/agentController.js +0 -6
- package/dist/runtime/agentController.js.map +1 -1
- package/dist/runtime/agentSession.d.ts.map +1 -1
- package/dist/runtime/agentSession.js +2 -3
- package/dist/runtime/agentSession.js.map +1 -1
- package/dist/shell/interactiveShell.d.ts +8 -16
- package/dist/shell/interactiveShell.d.ts.map +1 -1
- package/dist/shell/interactiveShell.js +159 -388
- package/dist/shell/interactiveShell.js.map +1 -1
- package/dist/shell/systemPrompt.d.ts.map +1 -1
- package/dist/shell/systemPrompt.js +15 -4
- package/dist/shell/systemPrompt.js.map +1 -1
- package/dist/subagents/taskRunner.js +1 -2
- package/dist/subagents/taskRunner.js.map +1 -1
- package/dist/tools/bashTools.d.ts.map +1 -1
- package/dist/tools/bashTools.js +8 -101
- package/dist/tools/bashTools.js.map +1 -1
- package/dist/tools/diffUtils.d.ts +2 -8
- package/dist/tools/diffUtils.d.ts.map +1 -1
- package/dist/tools/diffUtils.js +13 -72
- package/dist/tools/diffUtils.js.map +1 -1
- package/dist/tools/grepTools.d.ts.map +1 -1
- package/dist/tools/grepTools.js +2 -10
- package/dist/tools/grepTools.js.map +1 -1
- package/dist/tools/searchTools.d.ts.map +1 -1
- package/dist/tools/searchTools.js +2 -4
- package/dist/tools/searchTools.js.map +1 -1
- package/dist/ui/PromptController.d.ts +0 -2
- package/dist/ui/PromptController.d.ts.map +1 -1
- package/dist/ui/PromptController.js +0 -2
- package/dist/ui/PromptController.js.map +1 -1
- package/dist/ui/ShellUIAdapter.d.ts +18 -71
- package/dist/ui/ShellUIAdapter.d.ts.map +1 -1
- package/dist/ui/ShellUIAdapter.js +139 -237
- package/dist/ui/ShellUIAdapter.js.map +1 -1
- package/dist/ui/UnifiedUIController.d.ts +1 -0
- package/dist/ui/UnifiedUIController.d.ts.map +1 -1
- package/dist/ui/UnifiedUIController.js +1 -0
- package/dist/ui/UnifiedUIController.js.map +1 -1
- package/dist/ui/UnifiedUIRenderer.d.ts +5 -122
- package/dist/ui/UnifiedUIRenderer.d.ts.map +1 -1
- package/dist/ui/UnifiedUIRenderer.js +125 -830
- package/dist/ui/UnifiedUIRenderer.js.map +1 -1
- package/dist/ui/compactRenderer.d.ts +139 -0
- package/dist/ui/compactRenderer.d.ts.map +1 -0
- package/dist/ui/compactRenderer.js +398 -0
- package/dist/ui/compactRenderer.js.map +1 -0
- package/dist/ui/display.d.ts +48 -13
- package/dist/ui/display.d.ts.map +1 -1
- package/dist/ui/display.js +105 -22
- package/dist/ui/display.js.map +1 -1
- package/dist/ui/streamingFormatter.d.ts +30 -0
- package/dist/ui/streamingFormatter.d.ts.map +1 -0
- package/dist/ui/streamingFormatter.js +91 -0
- package/dist/ui/streamingFormatter.js.map +1 -0
- 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 +2 -0
- package/dist/ui/unified/index.js.map +1 -1
- package/dist/utils/errorUtils.d.ts +16 -0
- package/dist/utils/errorUtils.d.ts.map +1 -0
- package/dist/utils/errorUtils.js +66 -0
- package/dist/utils/errorUtils.js.map +1 -0
- package/package.json +2 -1
- package/dist/core/reliabilityPrompt.d.ts +0 -9
- package/dist/core/reliabilityPrompt.d.ts.map +0 -1
- package/dist/core/reliabilityPrompt.js +0 -31
- package/dist/core/reliabilityPrompt.js.map +0 -1
- package/dist/ui/animatedStatus.d.ts +0 -129
- package/dist/ui/animatedStatus.d.ts.map +0 -1
- package/dist/ui/animatedStatus.js +0 -384
- package/dist/ui/animatedStatus.js.map +0 -1
|
@@ -7,6 +7,7 @@ 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';
|
|
10
11
|
import { getContextWindowTokens } from '../core/contextWindow.js';
|
|
11
12
|
import { ensureSecretForProvider, getSecretDefinitionForProvider, getSecretValue, listSecretDefinitions, maskSecret, setSecretValue, } from '../core/secretStore.js';
|
|
12
13
|
import { saveActiveProfilePreference, saveModelPreference, loadToolSettings, saveToolSettings, clearToolSettings, clearActiveProfilePreference, loadSessionPreferences, saveSessionPreferences, loadFeatureFlags, saveFeatureFlags, toggleFeatureFlag, FEATURE_FLAG_INFO, } from '../core/preferences.js';
|
|
@@ -18,6 +19,7 @@ import { detectNetworkError } from '../core/errors/networkErrors.js';
|
|
|
18
19
|
import { buildWorkspaceContext } from '../workspace.js';
|
|
19
20
|
import { buildInteractiveSystemPrompt } from './systemPrompt.js';
|
|
20
21
|
import { getTaskCompletionDetector, resetTaskCompletionDetector, } from './taskCompletionDetector.js';
|
|
22
|
+
import { assessAiFlow } from '../core/aiFlowSupervisor.js';
|
|
21
23
|
import { discoverAllModels, quickCheckProviders, getCachedDiscoveredModels, sortModelsByPriority } from '../core/modelDiscovery.js';
|
|
22
24
|
import { getModels, getSlashCommands, getProviders } from '../core/agentSchemaLoader.js';
|
|
23
25
|
import { loadMcpServers } from '../mcp/config.js';
|
|
@@ -135,7 +137,6 @@ export class InteractiveShell {
|
|
|
135
137
|
sessionPreferences;
|
|
136
138
|
autosaveEnabled;
|
|
137
139
|
autoContinueEnabled;
|
|
138
|
-
autoContinueEnforced = true;
|
|
139
140
|
verificationEnabled = false;
|
|
140
141
|
criticalApprovalMode = 'auto';
|
|
141
142
|
editGuardMode = 'display-edits';
|
|
@@ -162,10 +163,6 @@ export class InteractiveShell {
|
|
|
162
163
|
lastUserQuery = '';
|
|
163
164
|
lastAssistantResponse = null;
|
|
164
165
|
responseRendered = false;
|
|
165
|
-
autoContinueInsightState = {
|
|
166
|
-
attempt: 0,
|
|
167
|
-
lastMessage: null,
|
|
168
|
-
};
|
|
169
166
|
lastFailure = null;
|
|
170
167
|
lastAutoTestRun = null;
|
|
171
168
|
// Auto-build tracking
|
|
@@ -185,6 +182,7 @@ export class InteractiveShell {
|
|
|
185
182
|
streamingOutputSuppressed = false;
|
|
186
183
|
aiRuntimeStart = null;
|
|
187
184
|
aiRuntimeTotalMs = 0;
|
|
185
|
+
streamingFormatter = null;
|
|
188
186
|
streamingThoughtBuffer = '';
|
|
189
187
|
statusLineState = null;
|
|
190
188
|
statusMessageOverride = null;
|
|
@@ -210,11 +208,6 @@ export class InteractiveShell {
|
|
|
210
208
|
this.thinkingMode = this.sessionPreferences.thinkingMode;
|
|
211
209
|
this.autosaveEnabled = this.sessionPreferences.autosave;
|
|
212
210
|
this.autoContinueEnabled = this.sessionPreferences.autoContinue;
|
|
213
|
-
if (this.autoContinueEnforced && !this.autoContinueEnabled) {
|
|
214
|
-
// Project policy: keep auto-continue locked on to avoid stalls
|
|
215
|
-
this.autoContinueEnabled = true;
|
|
216
|
-
saveSessionPreferences({ autoContinue: true });
|
|
217
|
-
}
|
|
218
211
|
this.criticalApprovalMode = this.sessionPreferences.criticalApprovalMode;
|
|
219
212
|
const featureFlags = loadFeatureFlags();
|
|
220
213
|
this.verificationEnabled = featureFlags.verification === true;
|
|
@@ -291,10 +284,6 @@ export class InteractiveShell {
|
|
|
291
284
|
this.uiAdapter.setToolStatusCallback((status) => {
|
|
292
285
|
this.updateStatusMessage(status ?? null);
|
|
293
286
|
});
|
|
294
|
-
// Set up activity callback to update activity line with streaming bash output
|
|
295
|
-
this.uiAdapter.setActivityCallback((activity) => {
|
|
296
|
-
this.renderer?.setActivity(activity);
|
|
297
|
-
});
|
|
298
287
|
this.skillRepository = new SkillRepository({
|
|
299
288
|
workingDir: this.workingDir,
|
|
300
289
|
env: process.env,
|
|
@@ -308,7 +297,6 @@ export class InteractiveShell {
|
|
|
308
297
|
onQueue: (text) => this.handleQueuedInput(text),
|
|
309
298
|
onCtrlC: ({ hadBuffer }) => this.handleCtrlCPress(hadBuffer),
|
|
310
299
|
onInterrupt: () => this.handleInterrupt(),
|
|
311
|
-
onExit: () => this.handleExit(),
|
|
312
300
|
onChange: ({ text, cursor }) => this.handleInputChange(text, cursor, 'renderer'),
|
|
313
301
|
onEditModeChange: (mode) => this.handleEditModeChange(mode),
|
|
314
302
|
onToggleVerify: () => this.toggleVerificationMode(),
|
|
@@ -316,7 +304,6 @@ export class InteractiveShell {
|
|
|
316
304
|
onToggleThinking: () => this.cycleThinkingMode(),
|
|
317
305
|
onClearContext: () => this.handleClearContext(),
|
|
318
306
|
onToggleCriticalApproval: () => this.toggleCriticalApprovalMode('shortcut'),
|
|
319
|
-
onExpandToolResult: () => this.expandLastToolResult(),
|
|
320
307
|
});
|
|
321
308
|
// Share renderer with Display so all output flows through the unified queue
|
|
322
309
|
this.renderer = this.terminalInput.getRenderer();
|
|
@@ -460,21 +447,18 @@ export class InteractiveShell {
|
|
|
460
447
|
const top = `╭${'─'.repeat(labelLeft)}${label}${'─'.repeat(labelRight)}╮`;
|
|
461
448
|
const bottom = `╰${'─'.repeat(contentWidth)}╯`;
|
|
462
449
|
const userName = process.env['USER'] || 'there';
|
|
463
|
-
const logo = '⟣╍◎╍⟢';
|
|
450
|
+
const logo = clamp('⟣╍◎╍⟢');
|
|
464
451
|
const versionLabel = version ? clamp(`${brand} · v${version}`) : brand;
|
|
465
452
|
const modelLine = clamp(model);
|
|
466
453
|
const workspaceLine = clamp(workspace);
|
|
467
|
-
|
|
468
|
-
const welcomeLine = clamp(`${logo} Welcome back ${userName}! ${logo}`);
|
|
469
|
-
// Quick reference hints
|
|
470
|
-
const hintsLine = clamp('/model · /secrets · /help · ? shortcuts');
|
|
454
|
+
const welcomeLine = clamp(`Welcome back ${userName}!`);
|
|
471
455
|
const lines = [
|
|
472
456
|
frame(top),
|
|
473
457
|
frame(`│${padCenter(welcomeLine, contentWidth)}│`),
|
|
458
|
+
frame(`│${padCenter(logo, contentWidth)}│`),
|
|
474
459
|
frame(`│${padCenter(modelLine, contentWidth)}│`),
|
|
475
460
|
frame(`│${padCenter(versionLabel, contentWidth)}│`),
|
|
476
461
|
frame(`│${padCenter(workspaceLine, contentWidth)}│`),
|
|
477
|
-
frame(`│${padCenter(hintsLine, contentWidth)}│`),
|
|
478
462
|
frame(bottom),
|
|
479
463
|
].join('\n');
|
|
480
464
|
if (this.renderer) {
|
|
@@ -485,6 +469,8 @@ export class InteractiveShell {
|
|
|
485
469
|
else {
|
|
486
470
|
display.stream(`\n${lines}\n`);
|
|
487
471
|
}
|
|
472
|
+
// Check for updates asynchronously (non-blocking)
|
|
473
|
+
void this.checkAndShowUpdates();
|
|
488
474
|
// Keep UI pinned; no scrollback banners
|
|
489
475
|
this.requestPromptRefresh(true);
|
|
490
476
|
}
|
|
@@ -522,32 +508,22 @@ export class InteractiveShell {
|
|
|
522
508
|
}
|
|
523
509
|
async checkAndShowUpdates() {
|
|
524
510
|
try {
|
|
525
|
-
const { checkForUpdates,
|
|
511
|
+
const { checkForUpdates, formatUpdateNotification, maybeAutoUpdate } = await import('../core/updateChecker.js');
|
|
526
512
|
const currentVersion = this.version || this.getPackageInfo().version || '1.7.458';
|
|
527
513
|
const updateInfo = await checkForUpdates(currentVersion);
|
|
528
514
|
if (!updateInfo || !updateInfo.updateAvailable) {
|
|
529
515
|
return;
|
|
530
516
|
}
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
// Get user's preference-based decision (no interactive prompt)
|
|
537
|
-
const sessionPrefs = loadSessionPreferences();
|
|
538
|
-
const decision = getUpdateDecision(sessionPrefs.autoUpdate);
|
|
539
|
-
if (decision === 'skip') {
|
|
540
|
-
// User chose to always skip - silently ignore updates
|
|
517
|
+
const updateResult = await maybeAutoUpdate(currentVersion, {
|
|
518
|
+
updateInfo,
|
|
519
|
+
logger: (message) => this.streamEventBlock(message),
|
|
520
|
+
});
|
|
521
|
+
if (updateResult?.updated) {
|
|
541
522
|
return;
|
|
542
523
|
}
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
// User chose to always update - perform background update
|
|
547
|
-
await performBackgroundUpdate(updateInfo, (message) => this.streamEventBlock(message));
|
|
548
|
-
}
|
|
549
|
-
// If decision === 'ask', we just show the banner with instructions
|
|
550
|
-
// User can run /update check or /update auto to update
|
|
524
|
+
const note = this.describeUpdateSkipReason(updateResult);
|
|
525
|
+
const notification = formatUpdateNotification(updateInfo, note);
|
|
526
|
+
this.streamEventBlock(notification);
|
|
551
527
|
}
|
|
552
528
|
catch {
|
|
553
529
|
// Silently fail - don't interrupt user experience
|
|
@@ -631,8 +607,6 @@ export class InteractiveShell {
|
|
|
631
607
|
this.pushUiEvent('raw', block);
|
|
632
608
|
}
|
|
633
609
|
async start(initialPrompt) {
|
|
634
|
-
// Check for updates BEFORE terminal input starts (to avoid keypress conflicts)
|
|
635
|
-
await this.checkAndShowUpdates();
|
|
636
610
|
// Initialize the renderer before emitting the banner so we don't render the prompt twice
|
|
637
611
|
this.terminalInput.start();
|
|
638
612
|
this.resetRendererStreamingMode();
|
|
@@ -641,9 +615,8 @@ export class InteractiveShell {
|
|
|
641
615
|
this.refreshControlBar();
|
|
642
616
|
// Now sync renderer and control bar state
|
|
643
617
|
this.syncRendererInput();
|
|
644
|
-
//
|
|
645
|
-
|
|
646
|
-
this.renderer?.render();
|
|
618
|
+
// Ensure the prompt/control bar is rendered after the welcome banner
|
|
619
|
+
this.ensureReadlineReady();
|
|
647
620
|
if (initialPrompt) {
|
|
648
621
|
await this.enqueuePromptForProcessing(initialPrompt, 'programmatic');
|
|
649
622
|
return;
|
|
@@ -651,7 +624,7 @@ export class InteractiveShell {
|
|
|
651
624
|
this.showLaunchCommandPalette();
|
|
652
625
|
// Ensure the terminal input is visible
|
|
653
626
|
this.syncRendererInput();
|
|
654
|
-
this.
|
|
627
|
+
this.ensureReadlineReady();
|
|
655
628
|
}
|
|
656
629
|
showLaunchCommandPalette() {
|
|
657
630
|
// Disabled: Quick commands palette takes up too much space
|
|
@@ -719,10 +692,6 @@ export class InteractiveShell {
|
|
|
719
692
|
*/
|
|
720
693
|
async runPromptJob(job) {
|
|
721
694
|
try {
|
|
722
|
-
// Show thinking indicator instead of "processing queued prompt"
|
|
723
|
-
display.showThinking('Thinking…');
|
|
724
|
-
// Re-echo the prompt to make it clear what's being processed
|
|
725
|
-
this.renderer?.emitPrompt(job.text);
|
|
726
695
|
await this.processInputBlock(job.text);
|
|
727
696
|
job.resolve();
|
|
728
697
|
}
|
|
@@ -908,49 +877,10 @@ export class InteractiveShell {
|
|
|
908
877
|
: '🛡️ High-impact actions now require your approval. (Ctrl+Shift+A to toggle; use /approvals ask to persist)';
|
|
909
878
|
display.showSystemMessage(message);
|
|
910
879
|
}
|
|
911
|
-
enforceAutoContinuePolicy() {
|
|
912
|
-
if (!this.autoContinueEnforced) {
|
|
913
|
-
return;
|
|
914
|
-
}
|
|
915
|
-
if (!this.autoContinueEnabled) {
|
|
916
|
-
this.autoContinueEnabled = true;
|
|
917
|
-
saveSessionPreferences({ autoContinue: true });
|
|
918
|
-
if (this.agent) {
|
|
919
|
-
this.agent.setAutoContinue(true);
|
|
920
|
-
}
|
|
921
|
-
this.refreshControlBar();
|
|
922
|
-
}
|
|
923
|
-
}
|
|
924
|
-
resetAutoContinueInsights() {
|
|
925
|
-
this.autoContinueInsightState = { attempt: 0, lastMessage: null };
|
|
926
|
-
}
|
|
927
|
-
showAutoContinueInsight(attempt, maxAttempts, message) {
|
|
928
|
-
const normalized = (message || '').trim();
|
|
929
|
-
const isDuplicate = this.autoContinueInsightState.attempt === attempt &&
|
|
930
|
-
this.autoContinueInsightState.lastMessage === normalized;
|
|
931
|
-
if (isDuplicate) {
|
|
932
|
-
return;
|
|
933
|
-
}
|
|
934
|
-
this.autoContinueInsightState = { attempt, lastMessage: normalized };
|
|
935
|
-
const detail = normalized || 'Continuing automatically after intent without action.';
|
|
936
|
-
display.showSystemMessage(`Auto-continue ${attempt}/${maxAttempts}: ${detail}`);
|
|
937
|
-
}
|
|
938
880
|
toggleAutoContinueMode() {
|
|
939
|
-
if (this.autoContinueEnforced) {
|
|
940
|
-
this.enforceAutoContinuePolicy();
|
|
941
|
-
display.showInfo('Auto-continue is enforced ON for erosolar-cli reliability and cannot be disabled.');
|
|
942
|
-
return;
|
|
943
|
-
}
|
|
944
881
|
this.setAutoContinueMode(!this.autoContinueEnabled, 'shortcut');
|
|
945
882
|
}
|
|
946
883
|
setAutoContinueMode(enabled, source) {
|
|
947
|
-
if (this.autoContinueEnforced && !enabled) {
|
|
948
|
-
this.enforceAutoContinuePolicy();
|
|
949
|
-
if (source === 'command') {
|
|
950
|
-
display.showInfo('Auto-continue is enforced ON for erosolar-cli reliability and cannot be disabled.');
|
|
951
|
-
}
|
|
952
|
-
return;
|
|
953
|
-
}
|
|
954
884
|
const changed = this.autoContinueEnabled !== enabled;
|
|
955
885
|
this.autoContinueEnabled = enabled;
|
|
956
886
|
saveSessionPreferences({ autoContinue: this.autoContinueEnabled });
|
|
@@ -967,23 +897,6 @@ export class InteractiveShell {
|
|
|
967
897
|
: 'The model will not be auto-prompted to continue.';
|
|
968
898
|
display.showInfo(`Auto-continue ${modeLabel}. ${behavior} Toggle with Ctrl+Shift+C.`);
|
|
969
899
|
}
|
|
970
|
-
/**
|
|
971
|
-
* Expand the last tool result (Ctrl+O shortcut).
|
|
972
|
-
* Shows the full output from the most recent tool call.
|
|
973
|
-
*/
|
|
974
|
-
expandLastToolResult() {
|
|
975
|
-
const result = this.uiAdapter.getLastToolResult();
|
|
976
|
-
if (!result) {
|
|
977
|
-
display.showInfo('No tool result to expand. Run a tool first, then press Ctrl+O.');
|
|
978
|
-
return;
|
|
979
|
-
}
|
|
980
|
-
// Format the expanded output with tool name header
|
|
981
|
-
const header = `${theme.info('━━━')} ${theme.tool(result.toolName)} ${theme.ui.muted('output')} ${theme.info('━━━')}`;
|
|
982
|
-
const content = result.output.trim() || '(empty result)';
|
|
983
|
-
const footer = theme.ui.muted('━'.repeat(Math.min(60, header.length)));
|
|
984
|
-
// Display the expanded result
|
|
985
|
-
display.stream(`\n${header}\n${content}\n${footer}\n\n`);
|
|
986
|
-
}
|
|
987
900
|
/**
|
|
988
901
|
* Cycle through thinking modes (Tab shortcut).
|
|
989
902
|
*/
|
|
@@ -1136,13 +1049,6 @@ export class InteractiveShell {
|
|
|
1136
1049
|
}
|
|
1137
1050
|
this.ctrlCHandledThisPress = false;
|
|
1138
1051
|
}
|
|
1139
|
-
/**
|
|
1140
|
-
* Handle exit request (Ctrl+C with empty buffer in idle mode)
|
|
1141
|
-
*/
|
|
1142
|
-
handleExit() {
|
|
1143
|
-
display.showSystemMessage('\nGoodbye!\n');
|
|
1144
|
-
this.shutdown();
|
|
1145
|
-
}
|
|
1146
1052
|
/**
|
|
1147
1053
|
* Gracefully tear down the shell and exit
|
|
1148
1054
|
*/
|
|
@@ -1805,14 +1711,15 @@ export class InteractiveShell {
|
|
|
1805
1711
|
*/
|
|
1806
1712
|
refreshStatusLine(forceRender = false) {
|
|
1807
1713
|
const elapsedSeconds = this.getAiRuntimeSeconds();
|
|
1714
|
+
const thinkingMs = display.isSpinnerActive() ? display.getThinkingElapsedMs() : null;
|
|
1808
1715
|
const tokensUsed = this.latestTokenUsage.used;
|
|
1809
1716
|
const tokenLimit = this.latestTokenUsage.limit ?? this.activeContextWindowTokens;
|
|
1810
1717
|
this.terminalInput.setMetaStatus({
|
|
1811
1718
|
elapsedSeconds,
|
|
1812
1719
|
tokensUsed,
|
|
1813
1720
|
tokenLimit,
|
|
1814
|
-
thinkingMs
|
|
1815
|
-
thinkingHasContent:
|
|
1721
|
+
thinkingMs,
|
|
1722
|
+
thinkingHasContent: display.isSpinnerActive(),
|
|
1816
1723
|
});
|
|
1817
1724
|
// Keep model/provider visible in the controls bar
|
|
1818
1725
|
this.terminalInput.setModelContext({
|
|
@@ -1988,16 +1895,12 @@ export class InteractiveShell {
|
|
|
1988
1895
|
enterStreamingMode();
|
|
1989
1896
|
// Set up scroll region for streaming content
|
|
1990
1897
|
this.uiUpdates.setMode('streaming');
|
|
1991
|
-
// Show activity status with animated spinner - use provided label or default
|
|
1992
|
-
const activityLabel = label || 'Thinking';
|
|
1993
|
-
this.renderer?.setActivity(activityLabel);
|
|
1994
1898
|
this.streamingHeartbeatStart = Date.now();
|
|
1995
1899
|
this.streamingContentSeen = false;
|
|
1996
1900
|
this.streamingStatusText = null;
|
|
1997
1901
|
this.streamingStatusLastUpdate = null;
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
this.streamingOutputSuppressed = false;
|
|
1902
|
+
// Suppress raw streaming lines; show only a single summary/status + final response
|
|
1903
|
+
this.streamingOutputSuppressed = true;
|
|
2001
1904
|
const initialLabel = this.isMeaningfulStreamingSnippet(label)
|
|
2002
1905
|
? this.truncateStreamingLabel(label)
|
|
2003
1906
|
: this.getStreamingFallbackLabel();
|
|
@@ -2041,8 +1944,6 @@ export class InteractiveShell {
|
|
|
2041
1944
|
this.streamingStatusLastUpdate = null;
|
|
2042
1945
|
this.streamingStatusText = null;
|
|
2043
1946
|
this.streamingOutputSuppressed = false;
|
|
2044
|
-
// Clear activity status when streaming ends
|
|
2045
|
-
this.renderer?.setActivity(null);
|
|
2046
1947
|
// Emit a streaming note for stop/quit so the status stays inside the stream
|
|
2047
1948
|
if (reason === 'stop' || reason === 'quit') {
|
|
2048
1949
|
const note = reason === 'quit' ? 'Session closed.' : 'Stream stopped.';
|
|
@@ -2059,15 +1960,11 @@ export class InteractiveShell {
|
|
|
2059
1960
|
// Buffer for accumulating partial <thinking> tags during streaming
|
|
2060
1961
|
thinkingTagBuffer = '';
|
|
2061
1962
|
insideThinkingBlock = false;
|
|
2062
|
-
streamingTokenCount = 0;
|
|
2063
1963
|
handleStreamChunk(chunk, type = 'content') {
|
|
2064
1964
|
if (!chunk) {
|
|
2065
1965
|
return;
|
|
2066
1966
|
}
|
|
2067
1967
|
const isReasoning = type === 'reasoning';
|
|
2068
|
-
// Approximate token count (roughly 4 chars per token)
|
|
2069
|
-
this.streamingTokenCount += Math.ceil(chunk.length / 4);
|
|
2070
|
-
this.renderer?.updateStreamingTokens(this.streamingTokenCount);
|
|
2071
1968
|
// Keep pinned status updated for all streaming chunks
|
|
2072
1969
|
this.updateStreamingStatusFromChunk(chunk);
|
|
2073
1970
|
// Handle <thinking> tags as separate events in the queue
|
|
@@ -2076,32 +1973,37 @@ export class InteractiveShell {
|
|
|
2076
1973
|
if (!processed.contentChunk && !processed.thinkingChunk) {
|
|
2077
1974
|
return;
|
|
2078
1975
|
}
|
|
2079
|
-
//
|
|
2080
|
-
|
|
2081
|
-
|
|
1976
|
+
// Emit thinking block as separate 'thought' event
|
|
1977
|
+
if (processed.thinkingChunk) {
|
|
1978
|
+
this.pushUiEvent('thought', `${theme.ui.muted('⏺')} ${theme.ui.muted('<thinking>')}${processed.thinkingChunk}${theme.ui.muted('</thinking>')}`);
|
|
1979
|
+
}
|
|
2082
1980
|
// Process regular content (skip if no content after extracting thinking)
|
|
2083
1981
|
const contentChunk = processed.contentChunk;
|
|
2084
1982
|
if (!contentChunk) {
|
|
2085
1983
|
return;
|
|
2086
1984
|
}
|
|
2087
1985
|
// Suppress raw streaming output in scrollback; keep only status + final message.
|
|
2088
|
-
// Reasoning
|
|
2089
|
-
if (this.streamingOutputSuppressed) {
|
|
1986
|
+
// Reasoning chunks bypass this so the thought process stays visible.
|
|
1987
|
+
if (this.streamingOutputSuppressed && !isReasoning) {
|
|
2090
1988
|
this.captureStreamingThought(contentChunk);
|
|
2091
1989
|
this.streamingContentSeen = true;
|
|
2092
|
-
// Update thinking indicator with a snippet of the content
|
|
2093
|
-
const prefix = isReasoning ? '○ ' : '';
|
|
2094
|
-
const snippet = contentChunk.replace(/\s+/g, ' ').trim().slice(0, 60);
|
|
2095
|
-
if (snippet) {
|
|
2096
|
-
display.updateThinking(prefix + snippet + (contentChunk.length > 60 ? '…' : ''));
|
|
2097
|
-
}
|
|
2098
1990
|
return;
|
|
2099
1991
|
}
|
|
2100
|
-
|
|
2101
|
-
|
|
1992
|
+
if (!this.streamingFormatter) {
|
|
1993
|
+
this.streamingFormatter = new StreamingResponseFormatter(output.columns ?? undefined);
|
|
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);
|
|
2102
2000
|
this.captureStreamingThought(contentChunk);
|
|
2103
|
-
|
|
2104
|
-
|
|
2001
|
+
if (formatted) {
|
|
2002
|
+
if (formatted.trim().length > 0) {
|
|
2003
|
+
this.streamingContentSeen = true;
|
|
2004
|
+
}
|
|
2005
|
+
this.pushUiEvent('streaming', formatted);
|
|
2006
|
+
}
|
|
2105
2007
|
}
|
|
2106
2008
|
/**
|
|
2107
2009
|
* Process streaming content to extract <thinking> blocks as separate events.
|
|
@@ -2149,12 +2051,22 @@ export class InteractiveShell {
|
|
|
2149
2051
|
}
|
|
2150
2052
|
return { thinkingChunk: thinkingContent, contentChunk: remainingContent };
|
|
2151
2053
|
}
|
|
2152
|
-
finishStreamingFormatter(
|
|
2153
|
-
|
|
2054
|
+
finishStreamingFormatter(note, options) {
|
|
2055
|
+
if (!this.streamingFormatter) {
|
|
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
|
+
}
|
|
2154
2065
|
if (this.streamingThoughtBuffer.trim()) {
|
|
2155
2066
|
this.ui.controller.recordAssistantThought(this.streamingThoughtBuffer.trim());
|
|
2156
2067
|
}
|
|
2157
2068
|
this.streamingThoughtBuffer = '';
|
|
2069
|
+
this.streamingFormatter = null;
|
|
2158
2070
|
if (options?.refreshPrompt ?? true) {
|
|
2159
2071
|
this.requestPromptRefresh(true);
|
|
2160
2072
|
}
|
|
@@ -2599,9 +2511,6 @@ export class InteractiveShell {
|
|
|
2599
2511
|
case '/permissions':
|
|
2600
2512
|
this.handlePermissionsCommand();
|
|
2601
2513
|
break;
|
|
2602
|
-
case '/update':
|
|
2603
|
-
await this.handleUpdateCommand(input);
|
|
2604
|
-
break;
|
|
2605
2514
|
case '/init':
|
|
2606
2515
|
this.handleInitCommand(input);
|
|
2607
2516
|
break;
|
|
@@ -4378,21 +4287,13 @@ export class InteractiveShell {
|
|
|
4378
4287
|
const value = tokens[0]?.toLowerCase();
|
|
4379
4288
|
if (!value) {
|
|
4380
4289
|
// Show current status
|
|
4381
|
-
|
|
4382
|
-
? ' (locked ON for erosolar-cli reliability)'
|
|
4383
|
-
: ' Use /autocontinue on|off or Ctrl+Shift+C to toggle.';
|
|
4384
|
-
display.showInfo(`Auto-continue is ${this.autoContinueEnabled ? 'enabled' : 'disabled'}.${lockedNote}`);
|
|
4290
|
+
display.showInfo(`Auto-continue is ${this.autoContinueEnabled ? 'enabled' : 'disabled'}. Use /autocontinue on|off or Ctrl+Shift+C to toggle.`);
|
|
4385
4291
|
return;
|
|
4386
4292
|
}
|
|
4387
4293
|
if (value !== 'on' && value !== 'off') {
|
|
4388
4294
|
display.showWarning('Usage: /autocontinue on|off');
|
|
4389
4295
|
return;
|
|
4390
4296
|
}
|
|
4391
|
-
if (value === 'off' && this.autoContinueEnforced) {
|
|
4392
|
-
this.enforceAutoContinuePolicy();
|
|
4393
|
-
display.showInfo('Auto-continue is enforced ON for erosolar-cli reliability and cannot be disabled.');
|
|
4394
|
-
return;
|
|
4395
|
-
}
|
|
4396
4297
|
this.setAutoContinueMode(value === 'on', 'command');
|
|
4397
4298
|
}
|
|
4398
4299
|
// ==================== Erosolar-CLI Style Commands ====================
|
|
@@ -4626,9 +4527,7 @@ export class InteractiveShell {
|
|
|
4626
4527
|
lines.push(`${theme.primary('Provider/Model')}: ${theme.info(`${this.providerLabel(this.sessionState.provider)} · ${this.sessionState.model}`)}`);
|
|
4627
4528
|
lines.push(`${theme.primary('Workspace')}: ${theme.ui.muted(this.abbreviatePath(this.workingDir))}`);
|
|
4628
4529
|
lines.push(`${theme.primary('Autosave')}: ${this.autosaveEnabled ? theme.success('on') : theme.ui.muted('off')}`);
|
|
4629
|
-
|
|
4630
|
-
const autoContinueLabel = this.autoContinueEnforced ? `${autoContinueStatus} (locked)` : autoContinueStatus;
|
|
4631
|
-
lines.push(`${theme.primary('Auto-continue')}: ${this.autoContinueEnabled ? theme.success(autoContinueLabel) : theme.ui.muted(autoContinueLabel)}`);
|
|
4530
|
+
lines.push(`${theme.primary('Auto-continue')}: ${this.autoContinueEnabled ? theme.success('on') : theme.ui.muted('off')}`);
|
|
4632
4531
|
lines.push(`${theme.primary('Verification')}: ${this.verificationEnabled ? theme.success('on') : theme.ui.muted('off')}`);
|
|
4633
4532
|
lines.push(`${theme.primary('Approvals')}: ${theme.ui.muted(this.describeEditGuardMode())}`);
|
|
4634
4533
|
lines.push(`${theme.primary('Critical approvals')}: ${this.criticalApprovalMode === 'approval' ? theme.warning('ask') : theme.ui.muted('auto')}`);
|
|
@@ -4784,67 +4683,6 @@ export class InteractiveShell {
|
|
|
4784
4683
|
lines.push(theme.ui.muted('Use /tools to manage which tool suites are enabled.'));
|
|
4785
4684
|
display.showSystemMessage(lines.join('\n'));
|
|
4786
4685
|
}
|
|
4787
|
-
async handleUpdateCommand(input) {
|
|
4788
|
-
const tokens = input.split(/\s+/).slice(1);
|
|
4789
|
-
const subcommand = tokens[0]?.toLowerCase();
|
|
4790
|
-
const prefs = loadSessionPreferences();
|
|
4791
|
-
const currentPref = prefs.autoUpdate;
|
|
4792
|
-
const prefLabel = currentPref === true ? 'always update' : currentPref === false ? 'always skip' : 'notify only';
|
|
4793
|
-
// Show status or help
|
|
4794
|
-
if (!subcommand || subcommand === 'status') {
|
|
4795
|
-
const lines = [];
|
|
4796
|
-
lines.push(theme.bold('Update Settings'));
|
|
4797
|
-
lines.push('');
|
|
4798
|
-
lines.push(`Current preference: ${theme.info(prefLabel)}`);
|
|
4799
|
-
lines.push('');
|
|
4800
|
-
lines.push(theme.secondary('Commands:'));
|
|
4801
|
-
lines.push(' /update check - Check for updates and install immediately');
|
|
4802
|
-
lines.push(' /update auto - Always auto-update in background');
|
|
4803
|
-
lines.push(' /update skip - Never auto-update (silent)');
|
|
4804
|
-
lines.push(' /update notify - Show notification only (default)');
|
|
4805
|
-
display.showSystemMessage(lines.join('\n'));
|
|
4806
|
-
return;
|
|
4807
|
-
}
|
|
4808
|
-
if (subcommand === 'check') {
|
|
4809
|
-
// Force check and perform update immediately (non-interactive)
|
|
4810
|
-
try {
|
|
4811
|
-
const { checkForUpdates, performUpdate } = await import('../core/updateChecker.js');
|
|
4812
|
-
const currentVersion = this.version || this.getPackageInfo().version || '1.7.458';
|
|
4813
|
-
const updateInfo = await checkForUpdates(currentVersion);
|
|
4814
|
-
if (!updateInfo) {
|
|
4815
|
-
display.showWarning('Unable to check for updates (network issue or timeout).');
|
|
4816
|
-
return;
|
|
4817
|
-
}
|
|
4818
|
-
if (!updateInfo.updateAvailable) {
|
|
4819
|
-
display.showSuccess(`You're on the latest version (v${updateInfo.current}).`);
|
|
4820
|
-
return;
|
|
4821
|
-
}
|
|
4822
|
-
// Show update info and perform update immediately (no interactive prompt)
|
|
4823
|
-
display.showInfo(`Update available: v${updateInfo.current} → v${updateInfo.latest}`);
|
|
4824
|
-
await performUpdate(updateInfo, (msg) => this.streamEventBlock(msg));
|
|
4825
|
-
}
|
|
4826
|
-
catch (error) {
|
|
4827
|
-
display.showError(`Update check failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
4828
|
-
}
|
|
4829
|
-
return;
|
|
4830
|
-
}
|
|
4831
|
-
if (subcommand === 'auto' || subcommand === 'always') {
|
|
4832
|
-
saveSessionPreferences({ autoUpdate: true });
|
|
4833
|
-
display.showSuccess('Auto-update enabled. Updates will install automatically.');
|
|
4834
|
-
return;
|
|
4835
|
-
}
|
|
4836
|
-
if (subcommand === 'skip' || subcommand === 'never' || subcommand === 'off') {
|
|
4837
|
-
saveSessionPreferences({ autoUpdate: false });
|
|
4838
|
-
display.showInfo('Auto-update disabled. Updates will be skipped silently.');
|
|
4839
|
-
return;
|
|
4840
|
-
}
|
|
4841
|
-
if (subcommand === 'ask' || subcommand === 'prompt' || subcommand === 'reset' || subcommand === 'notify') {
|
|
4842
|
-
saveSessionPreferences({ autoUpdate: null });
|
|
4843
|
-
display.showInfo('Update preference reset. You will see a notification when updates are available.');
|
|
4844
|
-
return;
|
|
4845
|
-
}
|
|
4846
|
-
display.showWarning('Usage: /update [check|auto|skip|notify|status]');
|
|
4847
|
-
}
|
|
4848
4686
|
handleInitCommand(input) {
|
|
4849
4687
|
const tokens = input.split(/\s+/).slice(1);
|
|
4850
4688
|
const confirm = tokens[0]?.toLowerCase() === 'confirm';
|
|
@@ -5840,9 +5678,6 @@ export class InteractiveShell {
|
|
|
5840
5678
|
this.clearInlinePanel();
|
|
5841
5679
|
this.syncRendererInput();
|
|
5842
5680
|
}
|
|
5843
|
-
// NOTE: emitPromptAcknowledgement has been removed - acknowledgement is now handled
|
|
5844
|
-
// by the onRequestReceived callback in agent.send(), which fires IMMEDIATELY when
|
|
5845
|
-
// the request is received, guaranteeing instant user feedback before any processing.
|
|
5846
5681
|
async processRequest(request) {
|
|
5847
5682
|
if (this.isProcessing) {
|
|
5848
5683
|
this.enqueueFollowUpAction({ type: 'request', text: request });
|
|
@@ -5858,8 +5693,6 @@ export class InteractiveShell {
|
|
|
5858
5693
|
if (!agent) {
|
|
5859
5694
|
return;
|
|
5860
5695
|
}
|
|
5861
|
-
this.enforceAutoContinuePolicy();
|
|
5862
|
-
this.resetAutoContinueInsights();
|
|
5863
5696
|
this.runtimeSession.toolRuntime.clearDiffSnapshots?.();
|
|
5864
5697
|
if (this.suppressNextNetworkReset) {
|
|
5865
5698
|
this.suppressNextNetworkReset = false;
|
|
@@ -5874,7 +5707,6 @@ export class InteractiveShell {
|
|
|
5874
5707
|
}
|
|
5875
5708
|
this.isProcessing = true;
|
|
5876
5709
|
this.uiUpdates.setMode('processing');
|
|
5877
|
-
this.streamingTokenCount = 0; // Reset token counter for new request
|
|
5878
5710
|
this.terminalInput.setStreaming(true);
|
|
5879
5711
|
// Keep the persistent input/control bar active as we transition into streaming.
|
|
5880
5712
|
this.syncRendererInput();
|
|
@@ -5889,9 +5721,6 @@ export class InteractiveShell {
|
|
|
5889
5721
|
this.currentTaskType = classifyTaskType(request);
|
|
5890
5722
|
this.currentToolCalls = [];
|
|
5891
5723
|
this.clearToolUsageMeta();
|
|
5892
|
-
// NOTE: Acknowledgement is now handled by onRequestReceived callback in agent.send()
|
|
5893
|
-
// This fires IMMEDIATELY when the request is received, before any provider call
|
|
5894
|
-
this.renderer?.setActivity('Starting...');
|
|
5895
5724
|
this.uiAdapter.startProcessing('Working on your request');
|
|
5896
5725
|
this.setProcessingStatus();
|
|
5897
5726
|
this.beginAiRuntime();
|
|
@@ -5947,6 +5776,8 @@ export class InteractiveShell {
|
|
|
5947
5776
|
clearActionHistory();
|
|
5948
5777
|
this.lastFailure = null;
|
|
5949
5778
|
}
|
|
5779
|
+
// Run post-hoc AI flow sanity check to catch "looks right but wrong" responses
|
|
5780
|
+
this.analyzeAiFlowForRun(request, responseText, requestStartTime);
|
|
5950
5781
|
}
|
|
5951
5782
|
catch (error) {
|
|
5952
5783
|
const handled = this.handleProviderError(error, () => this.processRequest(request));
|
|
@@ -6013,13 +5844,10 @@ export class InteractiveShell {
|
|
|
6013
5844
|
if (!agent) {
|
|
6014
5845
|
return;
|
|
6015
5846
|
}
|
|
6016
|
-
this.enforceAutoContinuePolicy();
|
|
6017
|
-
this.resetAutoContinueInsights();
|
|
6018
5847
|
this.lastUserQuery = initialRequest;
|
|
6019
5848
|
this.clearToolUsageMeta();
|
|
6020
5849
|
this.isProcessing = true;
|
|
6021
5850
|
this.uiUpdates.setMode('processing');
|
|
6022
|
-
this.streamingTokenCount = 0; // Reset token counter for new request
|
|
6023
5851
|
this.terminalInput.setStreaming(true);
|
|
6024
5852
|
if (this.suppressNextNetworkReset) {
|
|
6025
5853
|
this.suppressNextNetworkReset = false;
|
|
@@ -6037,7 +5865,6 @@ export class InteractiveShell {
|
|
|
6037
5865
|
completionDetector.reset();
|
|
6038
5866
|
// Display user prompt in scrollback (Claude Code style)
|
|
6039
5867
|
this.logUserPrompt(initialRequest);
|
|
6040
|
-
// NOTE: Acknowledgement is now handled by onRequestReceived callback in agent.send()
|
|
6041
5868
|
display.showSystemMessage(`Continuous mode active. Ctrl+C to stop.`);
|
|
6042
5869
|
this.uiAdapter.startProcessing('Continuous execution mode');
|
|
6043
5870
|
this.setProcessingStatus();
|
|
@@ -6074,8 +5901,6 @@ When truly finished with ALL tasks, explicitly state "TASK_FULLY_COMPLETE".`;
|
|
|
6074
5901
|
// Send the request and capture the response (streaming disabled)
|
|
6075
5902
|
display.showThinking('Responding...');
|
|
6076
5903
|
this.refreshStatusLine(true);
|
|
6077
|
-
this.enforceAutoContinuePolicy();
|
|
6078
|
-
this.resetAutoContinueInsights();
|
|
6079
5904
|
const response = await agent.send(currentPrompt, true);
|
|
6080
5905
|
this.finishStreamingFormatter(undefined, { refreshPrompt: false, mode: 'complete' });
|
|
6081
5906
|
await this.awaitPendingCleanup();
|
|
@@ -6430,6 +6255,43 @@ What's the next action?`;
|
|
|
6430
6255
|
const parts = candidates.filter((part) => typeof part === 'string' && part.trim().length > 0);
|
|
6431
6256
|
return parts.join('\n').trim();
|
|
6432
6257
|
}
|
|
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
|
+
}
|
|
6433
6295
|
runAutoQualityChecks(trigger, assistantResponse, verificationContext) {
|
|
6434
6296
|
if (!this.verificationEnabled) {
|
|
6435
6297
|
return;
|
|
@@ -6782,8 +6644,6 @@ Return ONLY JSON array:
|
|
|
6782
6644
|
// Send the error to the agent for fixing
|
|
6783
6645
|
display.showThinking('Analyzing build errors');
|
|
6784
6646
|
this.refreshStatusLine(true);
|
|
6785
|
-
this.enforceAutoContinuePolicy();
|
|
6786
|
-
this.resetAutoContinueInsights();
|
|
6787
6647
|
const response = await this.agent.send(prompt, true);
|
|
6788
6648
|
display.stopThinking();
|
|
6789
6649
|
this.refreshStatusLine(true);
|
|
@@ -6803,7 +6663,6 @@ Return ONLY JSON array:
|
|
|
6803
6663
|
rebuildAgent() {
|
|
6804
6664
|
try {
|
|
6805
6665
|
ensureSecretForProvider(this.sessionState.provider);
|
|
6806
|
-
this.enforceAutoContinuePolicy();
|
|
6807
6666
|
this.runtimeSession.updateToolContext(this.sessionState);
|
|
6808
6667
|
this.ensureActiveSummarizer(this.runtimeSession.contextManager);
|
|
6809
6668
|
const selection = {
|
|
@@ -6819,39 +6678,27 @@ Return ONLY JSON array:
|
|
|
6819
6678
|
onStreamChunk: (chunk, type) => {
|
|
6820
6679
|
this.handleStreamChunk(chunk, type ?? 'content');
|
|
6821
6680
|
},
|
|
6681
|
+
onStreamFallback: (info) => this.handleStreamingFallback(info),
|
|
6822
6682
|
onAssistantMessage: (content, metadata) => {
|
|
6823
6683
|
const enriched = this.buildDisplayMetadata(metadata);
|
|
6824
6684
|
const streamedVisible = metadata.wasStreamed && this.streamingContentSeen && !this.streamingOutputSuppressed;
|
|
6825
|
-
const displaySuppressed = metadata.suppressDisplay === true;
|
|
6826
6685
|
let renderedFinal = false;
|
|
6827
|
-
// Update streaming token count from usage info (more accurate than chunk counting)
|
|
6828
|
-
if (metadata.usage) {
|
|
6829
|
-
const totalTokens = this.totalTokens(metadata.usage);
|
|
6830
|
-
if (totalTokens !== null && totalTokens > this.streamingTokenCount) {
|
|
6831
|
-
this.streamingTokenCount = totalTokens;
|
|
6832
|
-
this.renderer?.updateStreamingTokens(this.streamingTokenCount);
|
|
6833
|
-
}
|
|
6834
|
-
}
|
|
6835
6686
|
// Update spinner based on message type
|
|
6836
6687
|
if (metadata.isFinal) {
|
|
6837
6688
|
const parsed = this.splitThinkingResponse(content);
|
|
6838
|
-
|
|
6839
|
-
// Don't fall back to original content with raw <thinking> tags
|
|
6840
|
-
const finalContent = parsed ? parsed.response?.trim() : content;
|
|
6689
|
+
const finalContent = parsed?.response?.trim() || content;
|
|
6841
6690
|
const thoughtContent = parsed?.thinking?.trim() || null;
|
|
6842
6691
|
// Show the response if it wasn't already rendered during streaming
|
|
6843
6692
|
if (!streamedVisible) {
|
|
6844
6693
|
if (thoughtContent) {
|
|
6845
|
-
|
|
6846
|
-
|
|
6847
|
-
|
|
6848
|
-
display.updateThinking(`💭 ${summary}`);
|
|
6849
|
-
}
|
|
6850
|
-
display.showAssistantMessage(thoughtContent, { ...enriched, isFinal: false });
|
|
6851
|
-
renderedFinal = true;
|
|
6694
|
+
const summary = this.extractThoughtSummary(thoughtContent);
|
|
6695
|
+
if (summary) {
|
|
6696
|
+
display.updateThinking(`💭 ${summary}`);
|
|
6852
6697
|
}
|
|
6698
|
+
display.showAssistantMessage(thoughtContent, { ...enriched, isFinal: false });
|
|
6699
|
+
renderedFinal = true;
|
|
6853
6700
|
}
|
|
6854
|
-
if (finalContent
|
|
6701
|
+
if (finalContent) {
|
|
6855
6702
|
display.showAssistantMessage(finalContent, enriched);
|
|
6856
6703
|
renderedFinal = true;
|
|
6857
6704
|
}
|
|
@@ -6869,7 +6716,7 @@ Return ONLY JSON array:
|
|
|
6869
6716
|
this.updateContextUsage(percentage);
|
|
6870
6717
|
}
|
|
6871
6718
|
}
|
|
6872
|
-
if (finalContent
|
|
6719
|
+
if (finalContent) {
|
|
6873
6720
|
this.lastAssistantResponse = finalContent;
|
|
6874
6721
|
}
|
|
6875
6722
|
// Track thought + response as a single response event for telemetry
|
|
@@ -6891,7 +6738,7 @@ Return ONLY JSON array:
|
|
|
6891
6738
|
// Stop spinner and show the narrative text directly
|
|
6892
6739
|
display.stopThinking();
|
|
6893
6740
|
// Skip display if content was already streamed to avoid double-display
|
|
6894
|
-
if (!streamedVisible
|
|
6741
|
+
if (!streamedVisible) {
|
|
6895
6742
|
const narrative = content.trim();
|
|
6896
6743
|
if (narrative) {
|
|
6897
6744
|
display.showNarrative(narrative);
|
|
@@ -6942,10 +6789,11 @@ Return ONLY JSON array:
|
|
|
6942
6789
|
this.updateStatusMessage('Retrying with reduced context...');
|
|
6943
6790
|
this.syncRendererInput();
|
|
6944
6791
|
},
|
|
6945
|
-
onAutoContinue: (attempt, maxAttempts,
|
|
6946
|
-
//
|
|
6947
|
-
|
|
6948
|
-
this.
|
|
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();
|
|
6949
6797
|
},
|
|
6950
6798
|
onCancelled: () => {
|
|
6951
6799
|
// Update UI to show operation was cancelled
|
|
@@ -6955,79 +6803,10 @@ Return ONLY JSON array:
|
|
|
6955
6803
|
this.updateStatusMessage(null);
|
|
6956
6804
|
this.terminalInput.setStreaming(false);
|
|
6957
6805
|
},
|
|
6958
|
-
onToolExecution: (toolName, isStart, args) => {
|
|
6959
|
-
// Update activity status to show what tool is being executed
|
|
6960
|
-
if (isStart) {
|
|
6961
|
-
// Show more specific activity for long-running tools
|
|
6962
|
-
let activity = `Running ${toolName}`;
|
|
6963
|
-
if (toolName === 'execute_bash' && args?.['command']) {
|
|
6964
|
-
const cmd = String(args['command']).slice(0, 40);
|
|
6965
|
-
activity = `$ ${cmd}${String(args['command']).length > 40 ? '...' : ''}`;
|
|
6966
|
-
}
|
|
6967
|
-
else if (toolName === 'read_file' && args?.['file_path']) {
|
|
6968
|
-
const path = String(args['file_path']).split('/').pop() || args['file_path'];
|
|
6969
|
-
activity = `Reading ${path}`;
|
|
6970
|
-
}
|
|
6971
|
-
this.renderer?.setActivity(activity);
|
|
6972
|
-
// Estimate tokens for tool call (~50 tokens per call)
|
|
6973
|
-
this.streamingTokenCount += 50;
|
|
6974
|
-
this.renderer?.updateStreamingTokens(this.streamingTokenCount);
|
|
6975
|
-
}
|
|
6976
|
-
else {
|
|
6977
|
-
// Tool finished - estimate result tokens (~100 per result)
|
|
6978
|
-
this.streamingTokenCount += 100;
|
|
6979
|
-
this.renderer?.updateStreamingTokens(this.streamingTokenCount);
|
|
6980
|
-
// Reset to thinking state while model generates next response
|
|
6981
|
-
this.renderer?.setActivity('Thinking');
|
|
6982
|
-
}
|
|
6983
|
-
},
|
|
6984
|
-
onBeforeFirstToolCall: (toolNames, hasModelNarration) => {
|
|
6985
|
-
// ALWAYS inject acknowledgement - even if model provided narration
|
|
6986
|
-
// This ensures user always sees immediate feedback that their request was received
|
|
6987
|
-
// The acknowledgement appears BEFORE any tool output
|
|
6988
|
-
const primaryTool = toolNames[0] || 'tools';
|
|
6989
|
-
const toolDescriptions = {
|
|
6990
|
-
'list_files': 'Looking at the project files...',
|
|
6991
|
-
'Glob': 'Searching for files...',
|
|
6992
|
-
'read_file': 'Reading the file...',
|
|
6993
|
-
'Read': 'Reading the file...',
|
|
6994
|
-
'execute_bash': 'Running command...',
|
|
6995
|
-
'Bash': 'Running command...',
|
|
6996
|
-
'grep_search': 'Searching the code...',
|
|
6997
|
-
'Grep': 'Searching the code...',
|
|
6998
|
-
'write_file': 'Writing the file...',
|
|
6999
|
-
'Write': 'Writing the file...',
|
|
7000
|
-
'edit_file': 'Editing the file...',
|
|
7001
|
-
'Edit': 'Editing the file...',
|
|
7002
|
-
'WebFetch': 'Fetching web content...',
|
|
7003
|
-
'WebSearch': 'Searching the web...',
|
|
7004
|
-
'Task': 'Starting task...',
|
|
7005
|
-
};
|
|
7006
|
-
// Return acknowledgement regardless of hasModelNarration
|
|
7007
|
-
// If model provided narration, it will appear after this acknowledgement
|
|
7008
|
-
return toolDescriptions[primaryTool] || 'Running tools to advance your request...';
|
|
7009
|
-
},
|
|
7010
6806
|
onVerificationNeeded: (response, context) => {
|
|
7011
6807
|
this.lastAssistantResponse = response;
|
|
7012
6808
|
void this.runAutoQualityChecks('verification', response, context);
|
|
7013
6809
|
},
|
|
7014
|
-
// NEW: Immediate acknowledgement when request is received (BEFORE provider call)
|
|
7015
|
-
onRequestReceived: (requestPreview) => {
|
|
7016
|
-
// This fires IMMEDIATELY when user submits request, guaranteeing instant feedback
|
|
7017
|
-
// The message appears before any AI processing begins
|
|
7018
|
-
const normalized = requestPreview?.trim();
|
|
7019
|
-
const message = normalized
|
|
7020
|
-
? `⚡ Acknowledged — focusing on "${normalized}" and acting now.`
|
|
7021
|
-
: '⚡ Acknowledged — acting now.';
|
|
7022
|
-
display.showAssistantMessage(message, { isFinal: false });
|
|
7023
|
-
this.renderer?.setActivity('Acknowledged');
|
|
7024
|
-
},
|
|
7025
|
-
// NEW: Retry notification for transient errors
|
|
7026
|
-
onRetrying: (attempt, maxAttempts, error) => {
|
|
7027
|
-
const shortError = error.message.slice(0, 100);
|
|
7028
|
-
display.showSystemMessage(`⚡ Retry ${attempt}/${maxAttempts}: ${shortError}${error.message.length > 100 ? '...' : ''}`);
|
|
7029
|
-
this.renderer?.setActivity(`Retrying (${attempt}/${maxAttempts})...`);
|
|
7030
|
-
},
|
|
7031
6810
|
});
|
|
7032
6811
|
// Register global AI enhancer for explore tool - uses active model by default
|
|
7033
6812
|
this.registerExploreAIEnhancer();
|
|
@@ -7122,30 +6901,20 @@ Return ONLY JSON array:
|
|
|
7122
6901
|
return lines.join('\n').trim();
|
|
7123
6902
|
}
|
|
7124
6903
|
buildThinkingDirective() {
|
|
7125
|
-
// Base requirement: ALWAYS think before acting (applies to all modes)
|
|
7126
|
-
const baseRequirement = [
|
|
7127
|
-
'CRITICAL: Before calling ANY tool, ALWAYS output a <thinking>...</thinking> block explaining:',
|
|
7128
|
-
'1. What you understand the user is asking',
|
|
7129
|
-
'2. Your approach/plan to solve it',
|
|
7130
|
-
'3. Which tools you will use and why',
|
|
7131
|
-
'This thinking block MUST appear before your first tool call in every response.',
|
|
7132
|
-
].join('\n');
|
|
7133
6904
|
switch (this.thinkingMode) {
|
|
7134
6905
|
case 'extended':
|
|
7135
6906
|
return [
|
|
7136
|
-
|
|
7137
|
-
'',
|
|
7138
|
-
'
|
|
7139
|
-
'
|
|
7140
|
-
'
|
|
6907
|
+
'Extended thinking mode is enabled. Format every reply as:',
|
|
6908
|
+
'<thinking>',
|
|
6909
|
+
'Detailed multi-step reasoning (reference tool runs/files when relevant, keep secrets redacted, no code blocks unless citing filenames).',
|
|
6910
|
+
'</thinking>',
|
|
6911
|
+
'<response>',
|
|
6912
|
+
'Final answer with actionable next steps and any code/commands requested.',
|
|
6913
|
+
'</response>',
|
|
7141
6914
|
].join('\n');
|
|
7142
6915
|
case 'balanced':
|
|
7143
6916
|
default:
|
|
7144
|
-
return
|
|
7145
|
-
baseRequirement,
|
|
7146
|
-
'',
|
|
7147
|
-
'Balanced mode: Keep thinking concise (2-4 sentences) but always present.',
|
|
7148
|
-
].join('\n');
|
|
6917
|
+
return 'Balanced thinking mode: include a short <thinking>...</thinking> block before <response> when the reasoning is non-trivial; skip it for simple answers.';
|
|
7149
6918
|
}
|
|
7150
6919
|
}
|
|
7151
6920
|
buildDisplayMetadata(metadata) {
|
|
@@ -7236,14 +7005,13 @@ Return ONLY JSON array:
|
|
|
7236
7005
|
});
|
|
7237
7006
|
cleanupOverlayActive = true;
|
|
7238
7007
|
const triggerReason = trigger?.reason ?? 'Context optimization';
|
|
7239
|
-
|
|
7240
|
-
|
|
7241
|
-
|
|
7242
|
-
|
|
7243
|
-
|
|
7244
|
-
|
|
7245
|
-
|
|
7246
|
-
}
|
|
7008
|
+
const limitLabel = resolvedWindowTokens
|
|
7009
|
+
? `of ${resolvedWindowTokens.toLocaleString('en-US')} tokens`
|
|
7010
|
+
: 'of estimated context';
|
|
7011
|
+
display.showSystemMessage([
|
|
7012
|
+
`Context usage: ${resolvedTotalTokens.toLocaleString('en-US')} ${limitLabel}`,
|
|
7013
|
+
`(${percentUsed}% full). Auto-compacting${triggerReason ? `: ${triggerReason}` : '...'}`,
|
|
7014
|
+
].join(' '));
|
|
7247
7015
|
const result = await contextManager.intelligentCompact(history);
|
|
7248
7016
|
let afterStats = contextManager.getStats(result.compacted);
|
|
7249
7017
|
let appliedHistory = result.compacted;
|
|
@@ -7270,18 +7038,10 @@ Return ONLY JSON array:
|
|
|
7270
7038
|
}
|
|
7271
7039
|
}
|
|
7272
7040
|
if (!changed) {
|
|
7273
|
-
// Hide compacting status before showing info message
|
|
7274
|
-
if (this.renderer) {
|
|
7275
|
-
this.renderer.hideCompactingStatus();
|
|
7276
|
-
}
|
|
7277
7041
|
display.showInfo('Context compaction completed but no changes were applied.');
|
|
7278
7042
|
return;
|
|
7279
7043
|
}
|
|
7280
7044
|
if (!this.hasMeaningfulCompaction(changed, bestTokenSavings, bestPercentSavings, trigger?.forced)) {
|
|
7281
|
-
// Hide compacting status before showing info message
|
|
7282
|
-
if (this.renderer) {
|
|
7283
|
-
this.renderer.hideCompactingStatus();
|
|
7284
|
-
}
|
|
7285
7045
|
display.showInfo('Auto-compaction completed but did not meaningfully reduce context size. Keeping existing history.');
|
|
7286
7046
|
return;
|
|
7287
7047
|
}
|
|
@@ -7297,18 +7057,15 @@ Return ONLY JSON array:
|
|
|
7297
7057
|
this.updateContextUsage(newPercentUsed, CONTEXT_AUTOCOMPACT_PERCENT);
|
|
7298
7058
|
this.lastContextWarningLevel = this.getContextWarningLevel(newPercentUsed);
|
|
7299
7059
|
this.refreshStatusLine(true);
|
|
7300
|
-
|
|
7301
|
-
|
|
7302
|
-
|
|
7303
|
-
|
|
7304
|
-
|
|
7305
|
-
|
|
7306
|
-
|
|
7307
|
-
|
|
7308
|
-
|
|
7309
|
-
else {
|
|
7310
|
-
display.showSystemMessage('══ Conversation compacted · ctrl+o for history ═');
|
|
7311
|
-
}
|
|
7060
|
+
const primarySignal = result.analysis.signals[0]?.reason ?? triggerReason;
|
|
7061
|
+
const savingsLabel = bestTokenSavings > 0
|
|
7062
|
+
? `(saved ~${bestTokenSavings.toLocaleString('en-US')} tokens, ~${bestPercentSavings.toFixed(1)}%)`
|
|
7063
|
+
: '(no token savings)';
|
|
7064
|
+
display.showSystemMessage([
|
|
7065
|
+
`Context compacted: ${beforeStats.percentage}% → ${afterStats.percentage}%`,
|
|
7066
|
+
savingsLabel,
|
|
7067
|
+
primarySignal ? `Reason: ${primarySignal}.` : '',
|
|
7068
|
+
].filter(Boolean).join(' '));
|
|
7312
7069
|
this.recordContextCompaction({
|
|
7313
7070
|
timestamp: Date.now(),
|
|
7314
7071
|
source: trigger?.source ?? 'auto',
|
|
@@ -7322,20 +7079,12 @@ Return ONLY JSON array:
|
|
|
7322
7079
|
});
|
|
7323
7080
|
}
|
|
7324
7081
|
catch (error) {
|
|
7325
|
-
// Hide compacting status animation on error
|
|
7326
|
-
if (this.renderer) {
|
|
7327
|
-
this.renderer.hideCompactingStatus();
|
|
7328
|
-
}
|
|
7329
7082
|
display.showError('Context compaction failed.', error);
|
|
7330
7083
|
}
|
|
7331
7084
|
finally {
|
|
7332
7085
|
if (cleanupOverlayActive) {
|
|
7333
7086
|
this.statusTracker.clearOverride(cleanupStatusId);
|
|
7334
7087
|
}
|
|
7335
|
-
// Ensure compacting status is cleared
|
|
7336
|
-
if (this.renderer) {
|
|
7337
|
-
this.renderer.hideCompactingStatus();
|
|
7338
|
-
}
|
|
7339
7088
|
this.cleanupInProgress = false;
|
|
7340
7089
|
this.contextCompactionInFlight = false;
|
|
7341
7090
|
}
|
|
@@ -7637,6 +7386,28 @@ Return ONLY JSON array:
|
|
|
7637
7386
|
const message = error instanceof Error ? error.message : String(error);
|
|
7638
7387
|
display.showError(message);
|
|
7639
7388
|
}
|
|
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
|
+
}
|
|
7640
7411
|
handleProviderError(error, retryAction) {
|
|
7641
7412
|
const promptBlock = detectPromptBlockError(error);
|
|
7642
7413
|
if (promptBlock) {
|