erosolar-cli 2.1.170 → 2.1.172
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 +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 +5 -5
- package/dist/core/agent.d.ts +24 -83
- package/dist/core/agent.d.ts.map +1 -1
- package/dist/core/agent.js +248 -499
- 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 +1 -8
- 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 +3 -6
- package/dist/runtime/agentController.js.map +1 -1
- package/dist/runtime/agentSession.d.ts +2 -0
- 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 +18 -11
- package/dist/shell/interactiveShell.d.ts.map +1 -1
- package/dist/shell/interactiveShell.js +291 -273
- package/dist/shell/interactiveShell.js.map +1 -1
- package/dist/shell/shellApp.d.ts.map +1 -1
- package/dist/shell/shellApp.js +1 -7
- package/dist/shell/shellApp.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/planningTools.d.ts +10 -0
- package/dist/tools/planningTools.d.ts.map +1 -1
- package/dist/tools/planningTools.js +16 -0
- package/dist/tools/planningTools.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 +4 -1
- package/dist/ui/PromptController.d.ts.map +1 -1
- package/dist/ui/PromptController.js +7 -1
- package/dist/ui/PromptController.js.map +1 -1
- package/dist/ui/ShellUIAdapter.d.ts +28 -292
- package/dist/ui/ShellUIAdapter.d.ts.map +1 -1
- package/dist/ui/ShellUIAdapter.js +121 -1513
- package/dist/ui/ShellUIAdapter.js.map +1 -1
- package/dist/ui/UnifiedUIRenderer.d.ts +30 -133
- package/dist/ui/UnifiedUIRenderer.d.ts.map +1 -1
- package/dist/ui/UnifiedUIRenderer.js +370 -939
- package/dist/ui/UnifiedUIRenderer.js.map +1 -1
- package/dist/ui/animatedStatus.d.ts +6 -128
- package/dist/ui/animatedStatus.d.ts.map +1 -1
- package/dist/ui/animatedStatus.js +50 -383
- package/dist/ui/animatedStatus.js.map +1 -1
- package/dist/ui/display.d.ts +26 -182
- package/dist/ui/display.d.ts.map +1 -1
- package/dist/ui/display.js +97 -678
- package/dist/ui/display.js.map +1 -1
- package/dist/ui/layout.d.ts +1 -0
- package/dist/ui/layout.d.ts.map +1 -1
- package/dist/ui/layout.js +12 -0
- package/dist/ui/layout.js.map +1 -1
- package/dist/ui/orchestration/UIUpdateCoordinator.d.ts +7 -61
- package/dist/ui/orchestration/UIUpdateCoordinator.d.ts.map +1 -1
- package/dist/ui/orchestration/UIUpdateCoordinator.js +20 -232
- package/dist/ui/orchestration/UIUpdateCoordinator.js.map +1 -1
- package/dist/ui/planOverlay.d.ts +28 -0
- package/dist/ui/planOverlay.d.ts.map +1 -0
- package/dist/ui/planOverlay.js +156 -0
- package/dist/ui/planOverlay.js.map +1 -0
- package/dist/ui/shortcutsHelp.d.ts.map +1 -1
- package/dist/ui/shortcutsHelp.js +1 -0
- package/dist/ui/shortcutsHelp.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 -30
- package/dist/ui/unified/index.d.ts.map +1 -1
- package/dist/ui/unified/index.js +2 -45
- 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/UnifiedUIController.d.ts +0 -81
- package/dist/ui/UnifiedUIController.d.ts.map +0 -1
- package/dist/ui/UnifiedUIController.js +0 -212
- package/dist/ui/UnifiedUIController.js.map +0 -1
- package/dist/ui/animation/AnimationScheduler.d.ts +0 -192
- package/dist/ui/animation/AnimationScheduler.d.ts.map +0 -1
- package/dist/ui/animation/AnimationScheduler.js +0 -432
- package/dist/ui/animation/AnimationScheduler.js.map +0 -1
- package/dist/ui/inPlaceUpdater.d.ts +0 -181
- package/dist/ui/inPlaceUpdater.d.ts.map +0 -1
- package/dist/ui/inPlaceUpdater.js +0 -515
- package/dist/ui/inPlaceUpdater.js.map +0 -1
- package/dist/ui/interrupts/InterruptManager.d.ts +0 -142
- package/dist/ui/interrupts/InterruptManager.d.ts.map +0 -1
- package/dist/ui/interrupts/InterruptManager.js +0 -439
- package/dist/ui/interrupts/InterruptManager.js.map +0 -1
- package/dist/ui/telemetry/ResponseTracker.d.ts +0 -22
- package/dist/ui/telemetry/ResponseTracker.d.ts.map +0 -1
- package/dist/ui/telemetry/ResponseTracker.js +0 -60
- package/dist/ui/telemetry/ResponseTracker.js.map +0 -1
- package/dist/ui/telemetry/UITelemetry.d.ts +0 -181
- package/dist/ui/telemetry/UITelemetry.d.ts.map +0 -1
- package/dist/ui/telemetry/UITelemetry.js +0 -446
- package/dist/ui/telemetry/UITelemetry.js.map +0 -1
- package/dist/ui/unified/layout.d.ts +0 -12
- package/dist/ui/unified/layout.d.ts.map +0 -1
- package/dist/ui/unified/layout.js +0 -96
- package/dist/ui/unified/layout.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';
|
|
@@ -27,7 +29,7 @@ import { SkillRepository } from '../skills/skillRepository.js';
|
|
|
27
29
|
import { createSkillTools } from '../tools/skillTools.js';
|
|
28
30
|
import { FileChangeTracker } from './fileChangeTracker.js';
|
|
29
31
|
import { formatShortcutsHelp } from '../ui/shortcutsHelp.js';
|
|
30
|
-
import { setPlanApprovalCallback } from '../tools/planningTools.js';
|
|
32
|
+
import { setPlanApprovalCallback, setPlanUpdateCallback } from '../tools/planningTools.js';
|
|
31
33
|
import { MetricsTracker } from '../core/metricsTracker.js';
|
|
32
34
|
import { detectFailure, clearActionHistory, findRecoveryStrategy, } from '../core/failureRecovery.js';
|
|
33
35
|
import { addToolPattern } from '../core/learningPersistence.js';
|
|
@@ -40,6 +42,7 @@ import { startOffsecRun, resumeOffsecRun, recordOffsecOutcome, getOffsecNextActi
|
|
|
40
42
|
import { generateTestFlows, detectBugs, detectUIUpdates, saveTestFlows, saveBugReports, saveUIUpdates, getTestFlowStatus, } from '../core/intelligentTestFlows.js';
|
|
41
43
|
import { PromptController } from '../ui/PromptController.js';
|
|
42
44
|
import { enterStreamingMode, exitStreamingMode, isStreamingMode } from '../ui/globalWriteLock.js';
|
|
45
|
+
import { PlanOverlay } from '../ui/planOverlay.js';
|
|
43
46
|
import { setGlobalAIEnhancer } from '../tools/localExplore.js';
|
|
44
47
|
import { createProvider } from '../providers/providerFactory.js';
|
|
45
48
|
import { getParallelAgentManager } from '../subagents/parallelAgentManager.js';
|
|
@@ -129,11 +132,14 @@ export class InteractiveShell {
|
|
|
129
132
|
activeContextWindowTokens = null;
|
|
130
133
|
latestTokenUsage = { used: null, limit: null };
|
|
131
134
|
planApprovalBridgeRegistered = false;
|
|
135
|
+
planUpdateBridgeRegistered = false;
|
|
136
|
+
planOverlay = new PlanOverlay();
|
|
132
137
|
contextCompactionInFlight = false;
|
|
133
138
|
contextCompactionLog = [];
|
|
134
139
|
lastContextWarningLevel = null;
|
|
135
140
|
sessionPreferences;
|
|
136
141
|
autosaveEnabled;
|
|
142
|
+
autoContinueEnabled;
|
|
137
143
|
verificationEnabled = false;
|
|
138
144
|
criticalApprovalMode = 'auto';
|
|
139
145
|
editGuardMode = 'display-edits';
|
|
@@ -179,6 +185,7 @@ export class InteractiveShell {
|
|
|
179
185
|
streamingOutputSuppressed = false;
|
|
180
186
|
aiRuntimeStart = null;
|
|
181
187
|
aiRuntimeTotalMs = 0;
|
|
188
|
+
streamingFormatter = null;
|
|
182
189
|
streamingThoughtBuffer = '';
|
|
183
190
|
statusLineState = null;
|
|
184
191
|
statusMessageOverride = null;
|
|
@@ -203,6 +210,7 @@ export class InteractiveShell {
|
|
|
203
210
|
this.sessionPreferences = loadSessionPreferences();
|
|
204
211
|
this.thinkingMode = this.sessionPreferences.thinkingMode;
|
|
205
212
|
this.autosaveEnabled = this.sessionPreferences.autosave;
|
|
213
|
+
this.autoContinueEnabled = this.sessionPreferences.autoContinue;
|
|
206
214
|
this.criticalApprovalMode = this.sessionPreferences.criticalApprovalMode;
|
|
207
215
|
const featureFlags = loadFeatureFlags();
|
|
208
216
|
this.verificationEnabled = featureFlags.verification === true;
|
|
@@ -261,6 +269,11 @@ export class InteractiveShell {
|
|
|
261
269
|
description: 'Switch between auto and approval mode for high-impact actions',
|
|
262
270
|
category: 'configuration',
|
|
263
271
|
});
|
|
272
|
+
this.slashCommands.push({
|
|
273
|
+
command: '/plan',
|
|
274
|
+
description: 'Show, hide, or clear the current plan panel (/plan show|hide|clear)',
|
|
275
|
+
category: 'workflow',
|
|
276
|
+
});
|
|
264
277
|
this.slashCommands.push({
|
|
265
278
|
command: '/offsec',
|
|
266
279
|
description: 'AlphaZero offensive security run (start/status/next)',
|
|
@@ -279,10 +292,6 @@ export class InteractiveShell {
|
|
|
279
292
|
this.uiAdapter.setToolStatusCallback((status) => {
|
|
280
293
|
this.updateStatusMessage(status ?? null);
|
|
281
294
|
});
|
|
282
|
-
// Set up activity callback to update activity line with streaming bash output
|
|
283
|
-
this.uiAdapter.setActivityCallback((activity) => {
|
|
284
|
-
this.renderer?.setActivity(activity);
|
|
285
|
-
});
|
|
286
295
|
this.skillRepository = new SkillRepository({
|
|
287
296
|
workingDir: this.workingDir,
|
|
288
297
|
env: process.env,
|
|
@@ -296,18 +305,20 @@ export class InteractiveShell {
|
|
|
296
305
|
onQueue: (text) => this.handleQueuedInput(text),
|
|
297
306
|
onCtrlC: ({ hadBuffer }) => this.handleCtrlCPress(hadBuffer),
|
|
298
307
|
onInterrupt: () => this.handleInterrupt(),
|
|
299
|
-
onExit: () => this.
|
|
308
|
+
onExit: () => this.shutdown(),
|
|
309
|
+
onExpandToolResult: () => display.expandLastToolResult?.(),
|
|
300
310
|
onChange: ({ text, cursor }) => this.handleInputChange(text, cursor, 'renderer'),
|
|
301
311
|
onEditModeChange: (mode) => this.handleEditModeChange(mode),
|
|
302
312
|
onToggleVerify: () => this.toggleVerificationMode(),
|
|
313
|
+
onToggleAutoContinue: () => this.toggleAutoContinueMode(),
|
|
303
314
|
onToggleThinking: () => this.cycleThinkingMode(),
|
|
304
315
|
onClearContext: () => this.handleClearContext(),
|
|
305
316
|
onToggleCriticalApproval: () => this.toggleCriticalApprovalMode('shortcut'),
|
|
306
|
-
onExpandToolResult: () => this.expandLastToolResult(),
|
|
307
317
|
});
|
|
308
318
|
// Share renderer with Display so all output flows through the unified queue
|
|
309
319
|
this.renderer = this.terminalInput.getRenderer();
|
|
310
320
|
display.setRenderer(this.renderer);
|
|
321
|
+
this.uiAdapter.attachRenderer(this.renderer);
|
|
311
322
|
display.setInlinePanelHandler((content) => {
|
|
312
323
|
if (!this.shouldCaptureInlinePanel()) {
|
|
313
324
|
return false;
|
|
@@ -327,6 +338,7 @@ export class InteractiveShell {
|
|
|
327
338
|
// The control bar will be refreshed after the welcome banner
|
|
328
339
|
// Allow planning tools (e.g., ProposePlan) to open the interactive approval UI just like Codex CLI
|
|
329
340
|
this.registerPlanApprovalBridge();
|
|
341
|
+
this.registerPlanUpdateBridge();
|
|
330
342
|
// Set up command autocomplete with all slash commands
|
|
331
343
|
this.setupCommandAutocomplete();
|
|
332
344
|
this.rebuildAgent();
|
|
@@ -447,21 +459,18 @@ export class InteractiveShell {
|
|
|
447
459
|
const top = `╭${'─'.repeat(labelLeft)}${label}${'─'.repeat(labelRight)}╮`;
|
|
448
460
|
const bottom = `╰${'─'.repeat(contentWidth)}╯`;
|
|
449
461
|
const userName = process.env['USER'] || 'there';
|
|
450
|
-
const logo = '⟣╍◎╍⟢';
|
|
462
|
+
const logo = clamp('⟣╍◎╍⟢');
|
|
451
463
|
const versionLabel = version ? clamp(`${brand} · v${version}`) : brand;
|
|
452
464
|
const modelLine = clamp(model);
|
|
453
465
|
const workspaceLine = clamp(workspace);
|
|
454
|
-
|
|
455
|
-
const welcomeLine = clamp(`${logo} Welcome back ${userName}! ${logo}`);
|
|
456
|
-
// Quick reference hints
|
|
457
|
-
const hintsLine = clamp('/model · /secrets · /help · ? shortcuts');
|
|
466
|
+
const welcomeLine = clamp(`Welcome back ${userName}!`);
|
|
458
467
|
const lines = [
|
|
459
468
|
frame(top),
|
|
460
469
|
frame(`│${padCenter(welcomeLine, contentWidth)}│`),
|
|
470
|
+
frame(`│${padCenter(logo, contentWidth)}│`),
|
|
461
471
|
frame(`│${padCenter(modelLine, contentWidth)}│`),
|
|
462
472
|
frame(`│${padCenter(versionLabel, contentWidth)}│`),
|
|
463
473
|
frame(`│${padCenter(workspaceLine, contentWidth)}│`),
|
|
464
|
-
frame(`│${padCenter(hintsLine, contentWidth)}│`),
|
|
465
474
|
frame(bottom),
|
|
466
475
|
].join('\n');
|
|
467
476
|
if (this.renderer) {
|
|
@@ -472,6 +481,8 @@ export class InteractiveShell {
|
|
|
472
481
|
else {
|
|
473
482
|
display.stream(`\n${lines}\n`);
|
|
474
483
|
}
|
|
484
|
+
// Check for updates asynchronously (non-blocking)
|
|
485
|
+
void this.checkAndShowUpdates();
|
|
475
486
|
// Keep UI pinned; no scrollback banners
|
|
476
487
|
this.requestPromptRefresh(true);
|
|
477
488
|
}
|
|
@@ -509,32 +520,22 @@ export class InteractiveShell {
|
|
|
509
520
|
}
|
|
510
521
|
async checkAndShowUpdates() {
|
|
511
522
|
try {
|
|
512
|
-
const { checkForUpdates,
|
|
523
|
+
const { checkForUpdates, formatUpdateNotification, maybeAutoUpdate } = await import('../core/updateChecker.js');
|
|
513
524
|
const currentVersion = this.version || this.getPackageInfo().version || '1.7.458';
|
|
514
525
|
const updateInfo = await checkForUpdates(currentVersion);
|
|
515
526
|
if (!updateInfo || !updateInfo.updateAvailable) {
|
|
516
527
|
return;
|
|
517
528
|
}
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
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
|
|
529
|
+
const updateResult = await maybeAutoUpdate(currentVersion, {
|
|
530
|
+
updateInfo,
|
|
531
|
+
logger: (message) => this.streamEventBlock(message),
|
|
532
|
+
});
|
|
533
|
+
if (updateResult?.updated) {
|
|
528
534
|
return;
|
|
529
535
|
}
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
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
|
|
536
|
+
const note = this.describeUpdateSkipReason(updateResult);
|
|
537
|
+
const notification = formatUpdateNotification(updateInfo, note);
|
|
538
|
+
this.streamEventBlock(notification);
|
|
538
539
|
}
|
|
539
540
|
catch {
|
|
540
541
|
// Silently fail - don't interrupt user experience
|
|
@@ -618,8 +619,6 @@ export class InteractiveShell {
|
|
|
618
619
|
this.pushUiEvent('raw', block);
|
|
619
620
|
}
|
|
620
621
|
async start(initialPrompt) {
|
|
621
|
-
// Check for updates BEFORE terminal input starts (to avoid keypress conflicts)
|
|
622
|
-
await this.checkAndShowUpdates();
|
|
623
622
|
// Initialize the renderer before emitting the banner so we don't render the prompt twice
|
|
624
623
|
this.terminalInput.start();
|
|
625
624
|
this.resetRendererStreamingMode();
|
|
@@ -628,9 +627,8 @@ export class InteractiveShell {
|
|
|
628
627
|
this.refreshControlBar();
|
|
629
628
|
// Now sync renderer and control bar state
|
|
630
629
|
this.syncRendererInput();
|
|
631
|
-
//
|
|
632
|
-
|
|
633
|
-
this.renderer?.render();
|
|
630
|
+
// Ensure the prompt/control bar is rendered after the welcome banner
|
|
631
|
+
this.ensureReadlineReady();
|
|
634
632
|
if (initialPrompt) {
|
|
635
633
|
await this.enqueuePromptForProcessing(initialPrompt, 'programmatic');
|
|
636
634
|
return;
|
|
@@ -638,7 +636,7 @@ export class InteractiveShell {
|
|
|
638
636
|
this.showLaunchCommandPalette();
|
|
639
637
|
// Ensure the terminal input is visible
|
|
640
638
|
this.syncRendererInput();
|
|
641
|
-
this.
|
|
639
|
+
this.ensureReadlineReady();
|
|
642
640
|
}
|
|
643
641
|
showLaunchCommandPalette() {
|
|
644
642
|
// Disabled: Quick commands palette takes up too much space
|
|
@@ -706,10 +704,6 @@ export class InteractiveShell {
|
|
|
706
704
|
*/
|
|
707
705
|
async runPromptJob(job) {
|
|
708
706
|
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);
|
|
713
707
|
await this.processInputBlock(job.text);
|
|
714
708
|
job.resolve();
|
|
715
709
|
}
|
|
@@ -805,6 +799,7 @@ export class InteractiveShell {
|
|
|
805
799
|
'/output-style',
|
|
806
800
|
// Mode toggles
|
|
807
801
|
'/thinking',
|
|
802
|
+
'/autocontinue',
|
|
808
803
|
// Discovery and plugins
|
|
809
804
|
'/local', '/discover',
|
|
810
805
|
'/plugins',
|
|
@@ -894,22 +889,25 @@ export class InteractiveShell {
|
|
|
894
889
|
: '🛡️ High-impact actions now require your approval. (Ctrl+Shift+A to toggle; use /approvals ask to persist)';
|
|
895
890
|
display.showSystemMessage(message);
|
|
896
891
|
}
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
892
|
+
toggleAutoContinueMode() {
|
|
893
|
+
this.setAutoContinueMode(!this.autoContinueEnabled, 'shortcut');
|
|
894
|
+
}
|
|
895
|
+
setAutoContinueMode(enabled, source) {
|
|
896
|
+
const changed = this.autoContinueEnabled !== enabled;
|
|
897
|
+
this.autoContinueEnabled = enabled;
|
|
898
|
+
saveSessionPreferences({ autoContinue: this.autoContinueEnabled });
|
|
899
|
+
if (this.agent) {
|
|
900
|
+
this.agent.setAutoContinue(this.autoContinueEnabled);
|
|
901
|
+
}
|
|
902
|
+
this.refreshControlBar();
|
|
903
|
+
if (!changed && source === 'shortcut') {
|
|
905
904
|
return;
|
|
906
905
|
}
|
|
907
|
-
|
|
908
|
-
const
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
display.stream(`\n${header}\n${content}\n${footer}\n\n`);
|
|
906
|
+
const modeLabel = this.autoContinueEnabled ? 'enabled' : 'disabled';
|
|
907
|
+
const behavior = this.autoContinueEnabled
|
|
908
|
+
? 'The model will be auto-prompted to continue when it expresses intent but does not use tools.'
|
|
909
|
+
: 'The model will not be auto-prompted to continue.';
|
|
910
|
+
display.showInfo(`Auto-continue ${modeLabel}. ${behavior} Toggle with Ctrl+Shift+C.`);
|
|
913
911
|
}
|
|
914
912
|
/**
|
|
915
913
|
* Cycle through thinking modes (Tab shortcut).
|
|
@@ -1063,13 +1061,6 @@ export class InteractiveShell {
|
|
|
1063
1061
|
}
|
|
1064
1062
|
this.ctrlCHandledThisPress = false;
|
|
1065
1063
|
}
|
|
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
|
-
}
|
|
1073
1064
|
/**
|
|
1074
1065
|
* Gracefully tear down the shell and exit
|
|
1075
1066
|
*/
|
|
@@ -1079,7 +1070,7 @@ export class InteractiveShell {
|
|
|
1079
1070
|
}
|
|
1080
1071
|
this.shuttingDown = true;
|
|
1081
1072
|
// Stop any active spinner to prevent process hang
|
|
1082
|
-
display.stopThinking(
|
|
1073
|
+
display.stopThinking();
|
|
1083
1074
|
this.stopStreamingHeartbeat('quit', { quiet: true });
|
|
1084
1075
|
this.endAiRuntime();
|
|
1085
1076
|
this.uiUpdates.dispose();
|
|
@@ -1091,6 +1082,7 @@ export class InteractiveShell {
|
|
|
1091
1082
|
this.pendingCleanup = null;
|
|
1092
1083
|
// Unregister plan approval bridge
|
|
1093
1084
|
setPlanApprovalCallback(null);
|
|
1085
|
+
setPlanUpdateCallback(null);
|
|
1094
1086
|
// Dispose terminal input handler
|
|
1095
1087
|
this.terminalInput.dispose();
|
|
1096
1088
|
// Dispose unified UI adapter
|
|
@@ -1111,6 +1103,38 @@ export class InteractiveShell {
|
|
|
1111
1103
|
});
|
|
1112
1104
|
this.planApprovalBridgeRegistered = true;
|
|
1113
1105
|
}
|
|
1106
|
+
registerPlanUpdateBridge() {
|
|
1107
|
+
if (this.planUpdateBridgeRegistered) {
|
|
1108
|
+
return;
|
|
1109
|
+
}
|
|
1110
|
+
setPlanUpdateCallback((payload) => {
|
|
1111
|
+
if (!payload || !payload.steps.length) {
|
|
1112
|
+
this.planOverlay.clear();
|
|
1113
|
+
this.applyPlanOverlay();
|
|
1114
|
+
return;
|
|
1115
|
+
}
|
|
1116
|
+
this.applyPlanUpdate(payload.steps, payload.explanation);
|
|
1117
|
+
});
|
|
1118
|
+
this.planUpdateBridgeRegistered = true;
|
|
1119
|
+
}
|
|
1120
|
+
applyPlanUpdate(steps, explanation) {
|
|
1121
|
+
this.planOverlay.setPlan(steps, explanation);
|
|
1122
|
+
this.applyPlanOverlay();
|
|
1123
|
+
}
|
|
1124
|
+
applyPlanOverlay() {
|
|
1125
|
+
const lines = this.planOverlay.render();
|
|
1126
|
+
if (!this.renderer) {
|
|
1127
|
+
if (this.planOverlay.hasPlan()) {
|
|
1128
|
+
display.showSystemMessage(this.planOverlay.asText());
|
|
1129
|
+
}
|
|
1130
|
+
return;
|
|
1131
|
+
}
|
|
1132
|
+
if (!lines.length) {
|
|
1133
|
+
this.renderer.clearPersistentPanel();
|
|
1134
|
+
return;
|
|
1135
|
+
}
|
|
1136
|
+
this.renderer.setPersistentPanel(lines);
|
|
1137
|
+
}
|
|
1114
1138
|
/**
|
|
1115
1139
|
* Update status bar message
|
|
1116
1140
|
*/
|
|
@@ -1639,7 +1663,9 @@ export class InteractiveShell {
|
|
|
1639
1663
|
refreshControlBar() {
|
|
1640
1664
|
this.terminalInput.setModeToggles({
|
|
1641
1665
|
verificationEnabled: this.verificationEnabled,
|
|
1666
|
+
autoContinueEnabled: this.autoContinueEnabled,
|
|
1642
1667
|
verificationHotkey: 'ctrl+shift+v',
|
|
1668
|
+
autoContinueHotkey: 'ctrl+shift+c',
|
|
1643
1669
|
thinkingModeLabel: (this.thinkingMode || 'off').toString(),
|
|
1644
1670
|
thinkingHotkey: 'tab',
|
|
1645
1671
|
criticalApprovalMode: this.criticalApprovalMode,
|
|
@@ -1730,14 +1756,15 @@ export class InteractiveShell {
|
|
|
1730
1756
|
*/
|
|
1731
1757
|
refreshStatusLine(forceRender = false) {
|
|
1732
1758
|
const elapsedSeconds = this.getAiRuntimeSeconds();
|
|
1759
|
+
const thinkingMs = display.isSpinnerActive() ? display.getThinkingElapsedMs() : null;
|
|
1733
1760
|
const tokensUsed = this.latestTokenUsage.used;
|
|
1734
1761
|
const tokenLimit = this.latestTokenUsage.limit ?? this.activeContextWindowTokens;
|
|
1735
1762
|
this.terminalInput.setMetaStatus({
|
|
1736
1763
|
elapsedSeconds,
|
|
1737
1764
|
tokensUsed,
|
|
1738
1765
|
tokenLimit,
|
|
1739
|
-
thinkingMs
|
|
1740
|
-
thinkingHasContent:
|
|
1766
|
+
thinkingMs,
|
|
1767
|
+
thinkingHasContent: display.isSpinnerActive(),
|
|
1741
1768
|
});
|
|
1742
1769
|
// Keep model/provider visible in the controls bar
|
|
1743
1770
|
this.terminalInput.setModelContext({
|
|
@@ -1913,16 +1940,12 @@ export class InteractiveShell {
|
|
|
1913
1940
|
enterStreamingMode();
|
|
1914
1941
|
// Set up scroll region for streaming content
|
|
1915
1942
|
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);
|
|
1919
1943
|
this.streamingHeartbeatStart = Date.now();
|
|
1920
1944
|
this.streamingContentSeen = false;
|
|
1921
1945
|
this.streamingStatusText = null;
|
|
1922
1946
|
this.streamingStatusLastUpdate = null;
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
this.streamingOutputSuppressed = false;
|
|
1947
|
+
// Suppress raw streaming lines; show only a single summary/status + final response
|
|
1948
|
+
this.streamingOutputSuppressed = true;
|
|
1926
1949
|
const initialLabel = this.isMeaningfulStreamingSnippet(label)
|
|
1927
1950
|
? this.truncateStreamingLabel(label)
|
|
1928
1951
|
: this.getStreamingFallbackLabel();
|
|
@@ -1966,8 +1989,6 @@ export class InteractiveShell {
|
|
|
1966
1989
|
this.streamingStatusLastUpdate = null;
|
|
1967
1990
|
this.streamingStatusText = null;
|
|
1968
1991
|
this.streamingOutputSuppressed = false;
|
|
1969
|
-
// Clear activity status when streaming ends
|
|
1970
|
-
this.renderer?.setActivity(null);
|
|
1971
1992
|
// Emit a streaming note for stop/quit so the status stays inside the stream
|
|
1972
1993
|
if (reason === 'stop' || reason === 'quit') {
|
|
1973
1994
|
const note = reason === 'quit' ? 'Session closed.' : 'Stream stopped.';
|
|
@@ -1984,15 +2005,11 @@ export class InteractiveShell {
|
|
|
1984
2005
|
// Buffer for accumulating partial <thinking> tags during streaming
|
|
1985
2006
|
thinkingTagBuffer = '';
|
|
1986
2007
|
insideThinkingBlock = false;
|
|
1987
|
-
streamingTokenCount = 0;
|
|
1988
2008
|
handleStreamChunk(chunk, type = 'content') {
|
|
1989
2009
|
if (!chunk) {
|
|
1990
2010
|
return;
|
|
1991
2011
|
}
|
|
1992
2012
|
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);
|
|
1996
2013
|
// Keep pinned status updated for all streaming chunks
|
|
1997
2014
|
this.updateStreamingStatusFromChunk(chunk);
|
|
1998
2015
|
// Handle <thinking> tags as separate events in the queue
|
|
@@ -2001,32 +2018,37 @@ export class InteractiveShell {
|
|
|
2001
2018
|
if (!processed.contentChunk && !processed.thinkingChunk) {
|
|
2002
2019
|
return;
|
|
2003
2020
|
}
|
|
2004
|
-
//
|
|
2005
|
-
|
|
2006
|
-
|
|
2021
|
+
// Emit thinking block as separate 'thought' event
|
|
2022
|
+
if (processed.thinkingChunk) {
|
|
2023
|
+
this.pushUiEvent('thought', `${theme.ui.muted('⏺')} ${theme.ui.muted('<thinking>')}${processed.thinkingChunk}${theme.ui.muted('</thinking>')}`);
|
|
2024
|
+
}
|
|
2007
2025
|
// Process regular content (skip if no content after extracting thinking)
|
|
2008
2026
|
const contentChunk = processed.contentChunk;
|
|
2009
2027
|
if (!contentChunk) {
|
|
2010
2028
|
return;
|
|
2011
2029
|
}
|
|
2012
2030
|
// Suppress raw streaming output in scrollback; keep only status + final message.
|
|
2013
|
-
// Reasoning
|
|
2014
|
-
if (this.streamingOutputSuppressed) {
|
|
2031
|
+
// Reasoning chunks bypass this so the thought process stays visible.
|
|
2032
|
+
if (this.streamingOutputSuppressed && !isReasoning) {
|
|
2015
2033
|
this.captureStreamingThought(contentChunk);
|
|
2016
2034
|
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
|
-
}
|
|
2023
2035
|
return;
|
|
2024
2036
|
}
|
|
2025
|
-
|
|
2026
|
-
|
|
2037
|
+
if (!this.streamingFormatter) {
|
|
2038
|
+
this.streamingFormatter = new StreamingResponseFormatter(output.columns ?? undefined);
|
|
2039
|
+
this.pushUiEvent('streaming', this.streamingFormatter.header());
|
|
2040
|
+
}
|
|
2041
|
+
this.streamingFormatter.setMode(isReasoning ? 'reasoning' : 'content');
|
|
2042
|
+
const formatted = isReasoning
|
|
2043
|
+
? this.streamingFormatter.formatReasoningChunk(contentChunk)
|
|
2044
|
+
: this.streamingFormatter.formatChunk(contentChunk);
|
|
2027
2045
|
this.captureStreamingThought(contentChunk);
|
|
2028
|
-
|
|
2029
|
-
|
|
2046
|
+
if (formatted) {
|
|
2047
|
+
if (formatted.trim().length > 0) {
|
|
2048
|
+
this.streamingContentSeen = true;
|
|
2049
|
+
}
|
|
2050
|
+
this.pushUiEvent('streaming', formatted);
|
|
2051
|
+
}
|
|
2030
2052
|
}
|
|
2031
2053
|
/**
|
|
2032
2054
|
* Process streaming content to extract <thinking> blocks as separate events.
|
|
@@ -2074,12 +2096,22 @@ export class InteractiveShell {
|
|
|
2074
2096
|
}
|
|
2075
2097
|
return { thinkingChunk: thinkingContent, contentChunk: remainingContent };
|
|
2076
2098
|
}
|
|
2077
|
-
finishStreamingFormatter(
|
|
2078
|
-
|
|
2099
|
+
finishStreamingFormatter(note, options) {
|
|
2100
|
+
if (!this.streamingFormatter) {
|
|
2101
|
+
return;
|
|
2102
|
+
}
|
|
2103
|
+
const closing = this.streamingFormatter.finish({
|
|
2104
|
+
note,
|
|
2105
|
+
mode: options?.mode ?? 'complete',
|
|
2106
|
+
});
|
|
2107
|
+
if (closing) {
|
|
2108
|
+
this.pushUiEvent('streaming', closing);
|
|
2109
|
+
}
|
|
2079
2110
|
if (this.streamingThoughtBuffer.trim()) {
|
|
2080
2111
|
this.ui.controller.recordAssistantThought(this.streamingThoughtBuffer.trim());
|
|
2081
2112
|
}
|
|
2082
2113
|
this.streamingThoughtBuffer = '';
|
|
2114
|
+
this.streamingFormatter = null;
|
|
2083
2115
|
if (options?.refreshPrompt ?? true) {
|
|
2084
2116
|
this.requestPromptRefresh(true);
|
|
2085
2117
|
}
|
|
@@ -2371,6 +2403,9 @@ export class InteractiveShell {
|
|
|
2371
2403
|
case '/approvals':
|
|
2372
2404
|
this.handleApprovalsCommand(input);
|
|
2373
2405
|
break;
|
|
2406
|
+
case '/plan':
|
|
2407
|
+
this.handlePlanCommand(input);
|
|
2408
|
+
break;
|
|
2374
2409
|
case '/learn':
|
|
2375
2410
|
this.showLearningStatus(input);
|
|
2376
2411
|
break;
|
|
@@ -2418,6 +2453,9 @@ export class InteractiveShell {
|
|
|
2418
2453
|
case '/thinking':
|
|
2419
2454
|
this.handleThinkingCommand(input);
|
|
2420
2455
|
break;
|
|
2456
|
+
case '/autocontinue':
|
|
2457
|
+
this.handleAutoContinueCommand(input);
|
|
2458
|
+
break;
|
|
2421
2459
|
case '/shortcuts':
|
|
2422
2460
|
case '/keys':
|
|
2423
2461
|
this.handleShortcutsCommand();
|
|
@@ -2521,9 +2559,6 @@ export class InteractiveShell {
|
|
|
2521
2559
|
case '/permissions':
|
|
2522
2560
|
this.handlePermissionsCommand();
|
|
2523
2561
|
break;
|
|
2524
|
-
case '/update':
|
|
2525
|
-
await this.handleUpdateCommand(input);
|
|
2526
|
-
break;
|
|
2527
2562
|
case '/init':
|
|
2528
2563
|
this.handleInitCommand(input);
|
|
2529
2564
|
break;
|
|
@@ -2581,6 +2616,7 @@ export class InteractiveShell {
|
|
|
2581
2616
|
theme.bold(' Mode Toggles'),
|
|
2582
2617
|
` ${theme.info('Shift+Tab')} ${theme.ui.muted('Toggle edit mode (auto/ask)')}`,
|
|
2583
2618
|
` ${theme.info('Option+V')} ${theme.ui.muted('Toggle verification')}`,
|
|
2619
|
+
` ${theme.info('Option+C')} ${theme.ui.muted('Toggle auto-continue')}`,
|
|
2584
2620
|
` ${theme.info('Ctrl+Shift+A')} ${theme.ui.muted('Toggle approvals for high-impact actions')}`,
|
|
2585
2621
|
` ${theme.info('Option+T')} ${theme.ui.muted('Cycle thinking mode')}`,
|
|
2586
2622
|
` ${theme.info('Option+E')} ${theme.ui.muted('Toggle edit permission mode')}`,
|
|
@@ -4294,6 +4330,20 @@ export class InteractiveShell {
|
|
|
4294
4330
|
clearAutosaveSnapshot(this.profile);
|
|
4295
4331
|
display.showInfo('Cleared autosave history.');
|
|
4296
4332
|
}
|
|
4333
|
+
handleAutoContinueCommand(input) {
|
|
4334
|
+
const tokens = input.split(/\s+/).slice(1);
|
|
4335
|
+
const value = tokens[0]?.toLowerCase();
|
|
4336
|
+
if (!value) {
|
|
4337
|
+
// Show current status
|
|
4338
|
+
display.showInfo(`Auto-continue is ${this.autoContinueEnabled ? 'enabled' : 'disabled'}. Use /autocontinue on|off or Ctrl+Shift+C to toggle.`);
|
|
4339
|
+
return;
|
|
4340
|
+
}
|
|
4341
|
+
if (value !== 'on' && value !== 'off') {
|
|
4342
|
+
display.showWarning('Usage: /autocontinue on|off');
|
|
4343
|
+
return;
|
|
4344
|
+
}
|
|
4345
|
+
this.setAutoContinueMode(value === 'on', 'command');
|
|
4346
|
+
}
|
|
4297
4347
|
// ==================== Erosolar-CLI Style Commands ====================
|
|
4298
4348
|
async handleRewindCommand(_input) {
|
|
4299
4349
|
const lines = [];
|
|
@@ -4525,6 +4575,7 @@ export class InteractiveShell {
|
|
|
4525
4575
|
lines.push(`${theme.primary('Provider/Model')}: ${theme.info(`${this.providerLabel(this.sessionState.provider)} · ${this.sessionState.model}`)}`);
|
|
4526
4576
|
lines.push(`${theme.primary('Workspace')}: ${theme.ui.muted(this.abbreviatePath(this.workingDir))}`);
|
|
4527
4577
|
lines.push(`${theme.primary('Autosave')}: ${this.autosaveEnabled ? theme.success('on') : theme.ui.muted('off')}`);
|
|
4578
|
+
lines.push(`${theme.primary('Auto-continue')}: ${this.autoContinueEnabled ? theme.success('on') : theme.ui.muted('off')}`);
|
|
4528
4579
|
lines.push(`${theme.primary('Verification')}: ${this.verificationEnabled ? theme.success('on') : theme.ui.muted('off')}`);
|
|
4529
4580
|
lines.push(`${theme.primary('Approvals')}: ${theme.ui.muted(this.describeEditGuardMode())}`);
|
|
4530
4581
|
lines.push(`${theme.primary('Critical approvals')}: ${this.criticalApprovalMode === 'approval' ? theme.warning('ask') : theme.ui.muted('auto')}`);
|
|
@@ -4660,6 +4711,52 @@ export class InteractiveShell {
|
|
|
4660
4711
|
}
|
|
4661
4712
|
display.showWarning('Usage: /approvals [auto|ask|status]');
|
|
4662
4713
|
}
|
|
4714
|
+
handlePlanCommand(input) {
|
|
4715
|
+
const action = input.split(/\s+/)[1]?.toLowerCase() || 'show';
|
|
4716
|
+
const hasPlan = this.planOverlay.hasPlan();
|
|
4717
|
+
const syncPanel = () => {
|
|
4718
|
+
this.applyPlanOverlay();
|
|
4719
|
+
this.syncRendererInput();
|
|
4720
|
+
};
|
|
4721
|
+
switch (action) {
|
|
4722
|
+
case 'show':
|
|
4723
|
+
case 'on':
|
|
4724
|
+
if (!hasPlan) {
|
|
4725
|
+
display.showInfo('No active plan. The panel will populate when a planning tool updates the plan.');
|
|
4726
|
+
return;
|
|
4727
|
+
}
|
|
4728
|
+
this.planOverlay.show();
|
|
4729
|
+
syncPanel();
|
|
4730
|
+
display.showInfo('Plan panel pinned. Use /plan hide to minimize.');
|
|
4731
|
+
return;
|
|
4732
|
+
case 'hide':
|
|
4733
|
+
case 'off':
|
|
4734
|
+
if (!hasPlan) {
|
|
4735
|
+
display.showInfo('No active plan to hide.');
|
|
4736
|
+
return;
|
|
4737
|
+
}
|
|
4738
|
+
this.planOverlay.hide();
|
|
4739
|
+
syncPanel();
|
|
4740
|
+
display.showInfo('Plan panel hidden. Use /plan show to restore.');
|
|
4741
|
+
return;
|
|
4742
|
+
case 'clear':
|
|
4743
|
+
if (!hasPlan) {
|
|
4744
|
+
display.showInfo('No active plan to clear.');
|
|
4745
|
+
return;
|
|
4746
|
+
}
|
|
4747
|
+
this.planOverlay.clear();
|
|
4748
|
+
syncPanel();
|
|
4749
|
+
display.showInfo('Plan cleared.');
|
|
4750
|
+
return;
|
|
4751
|
+
case 'status':
|
|
4752
|
+
case 'view':
|
|
4753
|
+
case 'text':
|
|
4754
|
+
display.showSystemMessage(this.planOverlay.asText());
|
|
4755
|
+
return;
|
|
4756
|
+
default:
|
|
4757
|
+
display.showWarning('Usage: /plan [show|hide|clear|status]');
|
|
4758
|
+
}
|
|
4759
|
+
}
|
|
4663
4760
|
handlePermissionsCommand() {
|
|
4664
4761
|
const lines = [];
|
|
4665
4762
|
lines.push(theme.bold('Tool Permissions'));
|
|
@@ -4680,67 +4777,6 @@ export class InteractiveShell {
|
|
|
4680
4777
|
lines.push(theme.ui.muted('Use /tools to manage which tool suites are enabled.'));
|
|
4681
4778
|
display.showSystemMessage(lines.join('\n'));
|
|
4682
4779
|
}
|
|
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
|
-
}
|
|
4744
4780
|
handleInitCommand(input) {
|
|
4745
4781
|
const tokens = input.split(/\s+/).slice(1);
|
|
4746
4782
|
const confirm = tokens[0]?.toLowerCase() === 'confirm';
|
|
@@ -5765,7 +5801,6 @@ export class InteractiveShell {
|
|
|
5765
5801
|
}
|
|
5766
5802
|
this.isProcessing = true;
|
|
5767
5803
|
this.uiUpdates.setMode('processing');
|
|
5768
|
-
this.streamingTokenCount = 0; // Reset token counter for new request
|
|
5769
5804
|
this.terminalInput.setStreaming(true);
|
|
5770
5805
|
// Keep the persistent input/control bar active as we transition into streaming.
|
|
5771
5806
|
this.syncRendererInput();
|
|
@@ -5780,7 +5815,6 @@ export class InteractiveShell {
|
|
|
5780
5815
|
this.currentTaskType = classifyTaskType(request);
|
|
5781
5816
|
this.currentToolCalls = [];
|
|
5782
5817
|
this.clearToolUsageMeta();
|
|
5783
|
-
this.renderer?.setActivity('Starting...');
|
|
5784
5818
|
this.uiAdapter.startProcessing('Working on your request');
|
|
5785
5819
|
this.setProcessingStatus();
|
|
5786
5820
|
this.beginAiRuntime();
|
|
@@ -5836,6 +5870,8 @@ export class InteractiveShell {
|
|
|
5836
5870
|
clearActionHistory();
|
|
5837
5871
|
this.lastFailure = null;
|
|
5838
5872
|
}
|
|
5873
|
+
// Run post-hoc AI flow sanity check to catch "looks right but wrong" responses
|
|
5874
|
+
this.analyzeAiFlowForRun(request, responseText, requestStartTime);
|
|
5839
5875
|
}
|
|
5840
5876
|
catch (error) {
|
|
5841
5877
|
const handled = this.handleProviderError(error, () => this.processRequest(request));
|
|
@@ -5855,7 +5891,7 @@ export class InteractiveShell {
|
|
|
5855
5891
|
this.responseRendered = true;
|
|
5856
5892
|
}
|
|
5857
5893
|
this.finishStreamingFormatter(undefined, { refreshPrompt: false, mode: 'complete' });
|
|
5858
|
-
display.stopThinking(
|
|
5894
|
+
display.stopThinking();
|
|
5859
5895
|
this.uiUpdates.setMode('processing');
|
|
5860
5896
|
this.stopStreamingHeartbeat('complete', { quiet: true });
|
|
5861
5897
|
this.endAiRuntime();
|
|
@@ -5906,7 +5942,6 @@ export class InteractiveShell {
|
|
|
5906
5942
|
this.clearToolUsageMeta();
|
|
5907
5943
|
this.isProcessing = true;
|
|
5908
5944
|
this.uiUpdates.setMode('processing');
|
|
5909
|
-
this.streamingTokenCount = 0; // Reset token counter for new request
|
|
5910
5945
|
this.terminalInput.setStreaming(true);
|
|
5911
5946
|
if (this.suppressNextNetworkReset) {
|
|
5912
5947
|
this.suppressNextNetworkReset = false;
|
|
@@ -6072,7 +6107,7 @@ What's the next action?`;
|
|
|
6072
6107
|
await new Promise(resolve => setTimeout(resolve, 500));
|
|
6073
6108
|
}
|
|
6074
6109
|
catch (error) {
|
|
6075
|
-
display.stopThinking(
|
|
6110
|
+
display.stopThinking();
|
|
6076
6111
|
// Handle context overflow specially - the agent should auto-recover
|
|
6077
6112
|
// but if it propagates here, we continue the loop
|
|
6078
6113
|
if (this.isContextOverflowError(error)) {
|
|
@@ -6314,6 +6349,43 @@ What's the next action?`;
|
|
|
6314
6349
|
const parts = candidates.filter((part) => typeof part === 'string' && part.trim().length > 0);
|
|
6315
6350
|
return parts.join('\n').trim();
|
|
6316
6351
|
}
|
|
6352
|
+
/**
|
|
6353
|
+
* Post-run sanity check to catch hallucinated "looks right" answers.
|
|
6354
|
+
* Compares the prompt/response against per-run tool usage and surfaces risks.
|
|
6355
|
+
*/
|
|
6356
|
+
analyzeAiFlowForRun(prompt, responseText, startedAt) {
|
|
6357
|
+
const toolHistory = this.runtimeSession.toolRuntime.getToolHistory?.();
|
|
6358
|
+
if (!toolHistory) {
|
|
6359
|
+
return;
|
|
6360
|
+
}
|
|
6361
|
+
const diffSnapshots = this.runtimeSession.toolRuntime.getDiffSnapshots?.() ?? [];
|
|
6362
|
+
const recentDiffs = diffSnapshots
|
|
6363
|
+
.filter((snapshot) => snapshot.timestamp >= startedAt)
|
|
6364
|
+
.map(({ command, output }) => ({ command, output }));
|
|
6365
|
+
const assessment = assessAiFlow({
|
|
6366
|
+
prompt,
|
|
6367
|
+
response: responseText,
|
|
6368
|
+
startedAt,
|
|
6369
|
+
toolHistory,
|
|
6370
|
+
diffSnapshots: recentDiffs,
|
|
6371
|
+
});
|
|
6372
|
+
this.reportAiFlowAssessment(assessment);
|
|
6373
|
+
}
|
|
6374
|
+
reportAiFlowAssessment(assessment) {
|
|
6375
|
+
if (!assessment || assessment.risks.length === 0) {
|
|
6376
|
+
return;
|
|
6377
|
+
}
|
|
6378
|
+
display.showSystemMessage('⚠️ AI flow check: potential unverified completion detected.');
|
|
6379
|
+
for (const risk of assessment.risks) {
|
|
6380
|
+
const detail = risk.details ? ` — ${risk.details}` : '';
|
|
6381
|
+
display.showWarning(`${risk.summary}${detail}`);
|
|
6382
|
+
}
|
|
6383
|
+
if (assessment.recommendations.length > 0) {
|
|
6384
|
+
const uniqueRecommendations = Array.from(new Set(assessment.recommendations));
|
|
6385
|
+
const advice = uniqueRecommendations.slice(0, 3).map((rec, idx) => `${idx + 1}. ${rec}`).join('\n');
|
|
6386
|
+
display.showSystemMessage(`Follow-ups to de-risk this run:\n${advice}`);
|
|
6387
|
+
}
|
|
6388
|
+
}
|
|
6317
6389
|
runAutoQualityChecks(trigger, assistantResponse, verificationContext) {
|
|
6318
6390
|
if (!this.verificationEnabled) {
|
|
6319
6391
|
return;
|
|
@@ -6694,41 +6766,21 @@ Return ONLY JSON array:
|
|
|
6694
6766
|
maxTokens: this.sessionState.maxTokens,
|
|
6695
6767
|
systemPrompt: this.buildSystemPrompt(),
|
|
6696
6768
|
reasoningEffort: this.sessionState.reasoningEffort,
|
|
6769
|
+
autoContinue: this.autoContinueEnabled,
|
|
6697
6770
|
};
|
|
6698
6771
|
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
|
-
},
|
|
6711
6772
|
onStreamChunk: (chunk, type) => {
|
|
6712
6773
|
this.handleStreamChunk(chunk, type ?? 'content');
|
|
6713
6774
|
},
|
|
6775
|
+
onStreamFallback: (info) => this.handleStreamingFallback(info),
|
|
6714
6776
|
onAssistantMessage: (content, metadata) => {
|
|
6715
6777
|
const enriched = this.buildDisplayMetadata(metadata);
|
|
6716
6778
|
const streamedVisible = metadata.wasStreamed && this.streamingContentSeen && !this.streamingOutputSuppressed;
|
|
6717
6779
|
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
|
-
}
|
|
6726
6780
|
// Update spinner based on message type
|
|
6727
6781
|
if (metadata.isFinal) {
|
|
6728
6782
|
const parsed = this.splitThinkingResponse(content);
|
|
6729
|
-
|
|
6730
|
-
// Don't fall back to original content with raw <thinking> tags
|
|
6731
|
-
const finalContent = parsed ? parsed.response?.trim() : content;
|
|
6783
|
+
const finalContent = parsed?.response?.trim() || content;
|
|
6732
6784
|
const thoughtContent = parsed?.thinking?.trim() || null;
|
|
6733
6785
|
// Show the response if it wasn't already rendered during streaming
|
|
6734
6786
|
if (!streamedVisible) {
|
|
@@ -6831,6 +6883,12 @@ Return ONLY JSON array:
|
|
|
6831
6883
|
this.updateStatusMessage('Retrying with reduced context...');
|
|
6832
6884
|
this.syncRendererInput();
|
|
6833
6885
|
},
|
|
6886
|
+
onAutoContinue: (attempt, maxAttempts, _message) => {
|
|
6887
|
+
// Show auto-continue progress in UI
|
|
6888
|
+
display.showSystemMessage(`🔄 Auto-continue (${attempt}/${maxAttempts}): Model expressed intent, prompting to act...`);
|
|
6889
|
+
this.updateStatusMessage('Auto-continuing...');
|
|
6890
|
+
this.syncRendererInput();
|
|
6891
|
+
},
|
|
6834
6892
|
onCancelled: () => {
|
|
6835
6893
|
// Update UI to show operation was cancelled
|
|
6836
6894
|
display.showWarning('Operation cancelled.');
|
|
@@ -6839,42 +6897,10 @@ Return ONLY JSON array:
|
|
|
6839
6897
|
this.updateStatusMessage(null);
|
|
6840
6898
|
this.terminalInput.setStreaming(false);
|
|
6841
6899
|
},
|
|
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
|
-
},
|
|
6868
6900
|
onVerificationNeeded: (response, context) => {
|
|
6869
6901
|
this.lastAssistantResponse = response;
|
|
6870
6902
|
void this.runAutoQualityChecks('verification', response, context);
|
|
6871
6903
|
},
|
|
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
|
-
},
|
|
6878
6904
|
});
|
|
6879
6905
|
// Register global AI enhancer for explore tool - uses active model by default
|
|
6880
6906
|
this.registerExploreAIEnhancer();
|
|
@@ -6969,30 +6995,20 @@ Return ONLY JSON array:
|
|
|
6969
6995
|
return lines.join('\n').trim();
|
|
6970
6996
|
}
|
|
6971
6997
|
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');
|
|
6980
6998
|
switch (this.thinkingMode) {
|
|
6981
6999
|
case 'extended':
|
|
6982
7000
|
return [
|
|
6983
|
-
|
|
6984
|
-
'',
|
|
6985
|
-
'
|
|
6986
|
-
'
|
|
6987
|
-
'
|
|
7001
|
+
'Extended thinking mode is enabled. Format every reply as:',
|
|
7002
|
+
'<thinking>',
|
|
7003
|
+
'Detailed multi-step reasoning (reference tool runs/files when relevant, keep secrets redacted, no code blocks unless citing filenames).',
|
|
7004
|
+
'</thinking>',
|
|
7005
|
+
'<response>',
|
|
7006
|
+
'Final answer with actionable next steps and any code/commands requested.',
|
|
7007
|
+
'</response>',
|
|
6988
7008
|
].join('\n');
|
|
6989
7009
|
case 'balanced':
|
|
6990
7010
|
default:
|
|
6991
|
-
return
|
|
6992
|
-
baseRequirement,
|
|
6993
|
-
'',
|
|
6994
|
-
'Balanced mode: Keep thinking concise (2-4 sentences) but always present.',
|
|
6995
|
-
].join('\n');
|
|
7011
|
+
return 'Balanced thinking mode: include a short <thinking>...</thinking> block before <response> when the reasoning is non-trivial; skip it for simple answers.';
|
|
6996
7012
|
}
|
|
6997
7013
|
}
|
|
6998
7014
|
buildDisplayMetadata(metadata) {
|
|
@@ -7083,14 +7099,13 @@ Return ONLY JSON array:
|
|
|
7083
7099
|
});
|
|
7084
7100
|
cleanupOverlayActive = true;
|
|
7085
7101
|
const triggerReason = trigger?.reason ?? 'Context optimization';
|
|
7086
|
-
|
|
7087
|
-
|
|
7088
|
-
|
|
7089
|
-
|
|
7090
|
-
|
|
7091
|
-
|
|
7092
|
-
|
|
7093
|
-
}
|
|
7102
|
+
const limitLabel = resolvedWindowTokens
|
|
7103
|
+
? `of ${resolvedWindowTokens.toLocaleString('en-US')} tokens`
|
|
7104
|
+
: 'of estimated context';
|
|
7105
|
+
display.showSystemMessage([
|
|
7106
|
+
`Context usage: ${resolvedTotalTokens.toLocaleString('en-US')} ${limitLabel}`,
|
|
7107
|
+
`(${percentUsed}% full). Auto-compacting${triggerReason ? `: ${triggerReason}` : '...'}`,
|
|
7108
|
+
].join(' '));
|
|
7094
7109
|
const result = await contextManager.intelligentCompact(history);
|
|
7095
7110
|
let afterStats = contextManager.getStats(result.compacted);
|
|
7096
7111
|
let appliedHistory = result.compacted;
|
|
@@ -7117,18 +7132,10 @@ Return ONLY JSON array:
|
|
|
7117
7132
|
}
|
|
7118
7133
|
}
|
|
7119
7134
|
if (!changed) {
|
|
7120
|
-
// Hide compacting status before showing info message
|
|
7121
|
-
if (this.renderer) {
|
|
7122
|
-
this.renderer.hideCompactingStatus();
|
|
7123
|
-
}
|
|
7124
7135
|
display.showInfo('Context compaction completed but no changes were applied.');
|
|
7125
7136
|
return;
|
|
7126
7137
|
}
|
|
7127
7138
|
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
|
-
}
|
|
7132
7139
|
display.showInfo('Auto-compaction completed but did not meaningfully reduce context size. Keeping existing history.');
|
|
7133
7140
|
return;
|
|
7134
7141
|
}
|
|
@@ -7144,18 +7151,15 @@ Return ONLY JSON array:
|
|
|
7144
7151
|
this.updateContextUsage(newPercentUsed, CONTEXT_AUTOCOMPACT_PERCENT);
|
|
7145
7152
|
this.lastContextWarningLevel = this.getContextWarningLevel(newPercentUsed);
|
|
7146
7153
|
this.refreshStatusLine(true);
|
|
7147
|
-
|
|
7148
|
-
|
|
7149
|
-
|
|
7150
|
-
|
|
7151
|
-
|
|
7152
|
-
|
|
7153
|
-
|
|
7154
|
-
|
|
7155
|
-
|
|
7156
|
-
else {
|
|
7157
|
-
display.showSystemMessage('══ Conversation compacted · ctrl+o for history ═');
|
|
7158
|
-
}
|
|
7154
|
+
const primarySignal = result.analysis.signals[0]?.reason ?? triggerReason;
|
|
7155
|
+
const savingsLabel = bestTokenSavings > 0
|
|
7156
|
+
? `(saved ~${bestTokenSavings.toLocaleString('en-US')} tokens, ~${bestPercentSavings.toFixed(1)}%)`
|
|
7157
|
+
: '(no token savings)';
|
|
7158
|
+
display.showSystemMessage([
|
|
7159
|
+
`Context compacted: ${beforeStats.percentage}% → ${afterStats.percentage}%`,
|
|
7160
|
+
savingsLabel,
|
|
7161
|
+
primarySignal ? `Reason: ${primarySignal}.` : '',
|
|
7162
|
+
].filter(Boolean).join(' '));
|
|
7159
7163
|
this.recordContextCompaction({
|
|
7160
7164
|
timestamp: Date.now(),
|
|
7161
7165
|
source: trigger?.source ?? 'auto',
|
|
@@ -7169,20 +7173,12 @@ Return ONLY JSON array:
|
|
|
7169
7173
|
});
|
|
7170
7174
|
}
|
|
7171
7175
|
catch (error) {
|
|
7172
|
-
// Hide compacting status animation on error
|
|
7173
|
-
if (this.renderer) {
|
|
7174
|
-
this.renderer.hideCompactingStatus();
|
|
7175
|
-
}
|
|
7176
7176
|
display.showError('Context compaction failed.', error);
|
|
7177
7177
|
}
|
|
7178
7178
|
finally {
|
|
7179
7179
|
if (cleanupOverlayActive) {
|
|
7180
7180
|
this.statusTracker.clearOverride(cleanupStatusId);
|
|
7181
7181
|
}
|
|
7182
|
-
// Ensure compacting status is cleared
|
|
7183
|
-
if (this.renderer) {
|
|
7184
|
-
this.renderer.hideCompactingStatus();
|
|
7185
|
-
}
|
|
7186
7182
|
this.cleanupInProgress = false;
|
|
7187
7183
|
this.contextCompactionInFlight = false;
|
|
7188
7184
|
}
|
|
@@ -7484,6 +7480,28 @@ Return ONLY JSON array:
|
|
|
7484
7480
|
const message = error instanceof Error ? error.message : String(error);
|
|
7485
7481
|
display.showError(message);
|
|
7486
7482
|
}
|
|
7483
|
+
handleStreamingFallback(info) {
|
|
7484
|
+
const promptBlock = detectPromptBlockError(info.error ?? info.message);
|
|
7485
|
+
if (promptBlock) {
|
|
7486
|
+
this.handlePromptBlock(promptBlock);
|
|
7487
|
+
this.finishStreamingFormatter('Prompt blocked mid-stream', { mode: 'update' });
|
|
7488
|
+
display.showSystemMessage('Retrying without streaming, but the provider will likely block again until you rephrase.');
|
|
7489
|
+
this.startStreamingHeartbeat('Re-running without streaming');
|
|
7490
|
+
this.requestPromptRefresh(true);
|
|
7491
|
+
return;
|
|
7492
|
+
}
|
|
7493
|
+
const detailText = info.message?.trim() ?? '';
|
|
7494
|
+
const detail = detailText ? ` ${detailText}` : '';
|
|
7495
|
+
const reason = info.reason ? ` (${info.reason.replace(/-/g, ' ')})` : '';
|
|
7496
|
+
const partialNote = info.partialResponse
|
|
7497
|
+
? ' Partial stream captured before failure; continuing from it without restarting.'
|
|
7498
|
+
: '';
|
|
7499
|
+
const baseMessage = 'Streaming interrupted, retrying without streaming';
|
|
7500
|
+
display.showWarning(`${baseMessage}${reason}.${detail}${partialNote}`.trim());
|
|
7501
|
+
this.finishStreamingFormatter('Stream interrupted - retrying without streaming', { mode: 'update' });
|
|
7502
|
+
this.startStreamingHeartbeat('Fallback in progress');
|
|
7503
|
+
this.requestPromptRefresh(true);
|
|
7504
|
+
}
|
|
7487
7505
|
handleProviderError(error, retryAction) {
|
|
7488
7506
|
const promptBlock = detectPromptBlockError(error);
|
|
7489
7507
|
if (promptBlock) {
|