erosolar-cli 2.1.172 → 2.1.173
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/agents/erosolar-code.rules.json +2 -2
- package/agents/general.rules.json +21 -3
- package/dist/capabilities/askUserCapability.js +1 -1
- package/dist/capabilities/askUserCapability.js.map +1 -1
- package/dist/capabilities/statusCapability.js +2 -2
- package/dist/capabilities/statusCapability.js.map +1 -1
- package/dist/codex/capabilities/codexCoreCapability.d.ts +6 -0
- package/dist/codex/capabilities/codexCoreCapability.d.ts.map +1 -0
- package/dist/codex/capabilities/codexCoreCapability.js +516 -0
- package/dist/codex/capabilities/codexCoreCapability.js.map +1 -0
- package/dist/codex/fs.d.ts +4 -0
- package/dist/codex/fs.d.ts.map +1 -0
- package/dist/codex/fs.js +25 -0
- package/dist/codex/fs.js.map +1 -0
- package/dist/codex/persistence/planStore.d.ts +4 -0
- package/dist/codex/persistence/planStore.d.ts.map +1 -0
- package/dist/codex/persistence/planStore.js +59 -0
- package/dist/codex/persistence/planStore.js.map +1 -0
- package/dist/codex/pluginAllowlist.d.ts +4 -0
- package/dist/codex/pluginAllowlist.d.ts.map +1 -0
- package/dist/codex/pluginAllowlist.js +14 -0
- package/dist/codex/pluginAllowlist.js.map +1 -0
- package/dist/codex/types.d.ts +21 -0
- package/dist/codex/types.d.ts.map +1 -0
- package/dist/codex/types.js +62 -0
- package/dist/codex/types.js.map +1 -0
- package/dist/contracts/agent-schemas.json +5 -5
- package/dist/core/agent.d.ts +83 -24
- package/dist/core/agent.d.ts.map +1 -1
- package/dist/core/agent.js +499 -248
- package/dist/core/agent.js.map +1 -1
- package/dist/core/preferences.d.ts +1 -0
- package/dist/core/preferences.d.ts.map +1 -1
- package/dist/core/preferences.js +8 -1
- package/dist/core/preferences.js.map +1 -1
- package/dist/core/reliabilityPrompt.d.ts +9 -0
- package/dist/core/reliabilityPrompt.d.ts.map +1 -0
- package/dist/core/reliabilityPrompt.js +31 -0
- package/dist/core/reliabilityPrompt.js.map +1 -0
- package/dist/core/schemaValidator.js +3 -3
- package/dist/core/schemaValidator.js.map +1 -1
- package/dist/core/toolPreconditions.d.ts +0 -11
- package/dist/core/toolPreconditions.d.ts.map +1 -1
- package/dist/core/toolPreconditions.js +33 -164
- package/dist/core/toolPreconditions.js.map +1 -1
- package/dist/core/toolRuntime.d.ts.map +1 -1
- package/dist/core/toolRuntime.js +9 -114
- package/dist/core/toolRuntime.js.map +1 -1
- package/dist/core/updateChecker.d.ts +61 -1
- package/dist/core/updateChecker.d.ts.map +1 -1
- package/dist/core/updateChecker.js +147 -3
- package/dist/core/updateChecker.js.map +1 -1
- package/dist/headless/evalMode.d.ts.map +1 -1
- package/dist/headless/evalMode.js +6 -0
- package/dist/headless/evalMode.js.map +1 -1
- package/dist/headless/headlessApp.d.ts.map +1 -1
- package/dist/headless/headlessApp.js +6 -39
- package/dist/headless/headlessApp.js.map +1 -1
- package/dist/mcp/sseClient.d.ts +4 -1
- package/dist/mcp/sseClient.d.ts.map +1 -1
- package/dist/mcp/sseClient.js +36 -2
- package/dist/mcp/sseClient.js.map +1 -1
- package/dist/mcp/stdioClient.d.ts +4 -1
- package/dist/mcp/stdioClient.d.ts.map +1 -1
- package/dist/mcp/stdioClient.js +41 -1
- package/dist/mcp/stdioClient.js.map +1 -1
- package/dist/mcp/toolBridge.d.ts +3 -0
- package/dist/mcp/toolBridge.d.ts.map +1 -1
- package/dist/mcp/toolBridge.js +2 -2
- package/dist/mcp/toolBridge.js.map +1 -1
- package/dist/mcp/types.d.ts +18 -0
- package/dist/mcp/types.d.ts.map +1 -1
- package/dist/plugins/tools/nodeDefaults.d.ts.map +1 -1
- package/dist/plugins/tools/nodeDefaults.js +0 -2
- package/dist/plugins/tools/nodeDefaults.js.map +1 -1
- package/dist/providers/openaiResponsesProvider.d.ts.map +1 -1
- package/dist/providers/openaiResponsesProvider.js +79 -74
- package/dist/providers/openaiResponsesProvider.js.map +1 -1
- package/dist/runtime/agentController.d.ts.map +1 -1
- package/dist/runtime/agentController.js +6 -3
- package/dist/runtime/agentController.js.map +1 -1
- package/dist/runtime/agentSession.d.ts +0 -2
- package/dist/runtime/agentSession.d.ts.map +1 -1
- package/dist/runtime/agentSession.js +2 -2
- package/dist/runtime/agentSession.js.map +1 -1
- package/dist/shell/interactiveShell.d.ts +25 -18
- package/dist/shell/interactiveShell.d.ts.map +1 -1
- package/dist/shell/interactiveShell.js +345 -291
- package/dist/shell/interactiveShell.js.map +1 -1
- package/dist/shell/shellApp.d.ts.map +1 -1
- package/dist/shell/shellApp.js +15 -8
- package/dist/shell/shellApp.js.map +1 -1
- package/dist/shell/systemPrompt.d.ts.map +1 -1
- package/dist/shell/systemPrompt.js +4 -15
- package/dist/shell/systemPrompt.js.map +1 -1
- package/dist/subagents/taskRunner.js +2 -1
- package/dist/subagents/taskRunner.js.map +1 -1
- package/dist/tools/bashTools.d.ts.map +1 -1
- package/dist/tools/bashTools.js +101 -8
- package/dist/tools/bashTools.js.map +1 -1
- package/dist/tools/diffUtils.d.ts +8 -2
- package/dist/tools/diffUtils.d.ts.map +1 -1
- package/dist/tools/diffUtils.js +72 -13
- package/dist/tools/diffUtils.js.map +1 -1
- package/dist/tools/grepTools.d.ts.map +1 -1
- package/dist/tools/grepTools.js +10 -2
- package/dist/tools/grepTools.js.map +1 -1
- package/dist/tools/planningTools.d.ts +0 -10
- package/dist/tools/planningTools.d.ts.map +1 -1
- package/dist/tools/planningTools.js +0 -16
- package/dist/tools/planningTools.js.map +1 -1
- package/dist/tools/searchTools.d.ts.map +1 -1
- package/dist/tools/searchTools.js +4 -2
- package/dist/tools/searchTools.js.map +1 -1
- package/dist/ui/PromptController.d.ts +7 -4
- package/dist/ui/PromptController.d.ts.map +1 -1
- package/dist/ui/PromptController.js +4 -7
- package/dist/ui/PromptController.js.map +1 -1
- package/dist/ui/ShellUIAdapter.d.ts +286 -28
- package/dist/ui/ShellUIAdapter.d.ts.map +1 -1
- package/dist/ui/ShellUIAdapter.js +1485 -121
- package/dist/ui/ShellUIAdapter.js.map +1 -1
- package/dist/ui/UnifiedUIController.d.ts +80 -0
- package/dist/ui/UnifiedUIController.d.ts.map +1 -0
- package/dist/ui/UnifiedUIController.js +211 -0
- package/dist/ui/UnifiedUIController.js.map +1 -0
- package/dist/ui/UnifiedUIRenderer.d.ts +102 -46
- package/dist/ui/UnifiedUIRenderer.d.ts.map +1 -1
- package/dist/ui/UnifiedUIRenderer.js +680 -610
- package/dist/ui/UnifiedUIRenderer.js.map +1 -1
- package/dist/ui/animatedStatus.d.ts +128 -6
- package/dist/ui/animatedStatus.d.ts.map +1 -1
- package/dist/ui/animatedStatus.js +383 -50
- package/dist/ui/animatedStatus.js.map +1 -1
- package/dist/ui/animation/AnimationScheduler.d.ts +192 -0
- package/dist/ui/animation/AnimationScheduler.d.ts.map +1 -0
- package/dist/ui/animation/AnimationScheduler.js +432 -0
- package/dist/ui/animation/AnimationScheduler.js.map +1 -0
- package/dist/ui/display.d.ts +179 -25
- package/dist/ui/display.d.ts.map +1 -1
- package/dist/ui/display.js +678 -96
- package/dist/ui/display.js.map +1 -1
- package/dist/ui/inPlaceUpdater.d.ts +181 -0
- package/dist/ui/inPlaceUpdater.d.ts.map +1 -0
- package/dist/ui/inPlaceUpdater.js +515 -0
- package/dist/ui/inPlaceUpdater.js.map +1 -0
- package/dist/ui/interrupts/InterruptManager.d.ts +142 -0
- package/dist/ui/interrupts/InterruptManager.d.ts.map +1 -0
- package/dist/ui/interrupts/InterruptManager.js +439 -0
- package/dist/ui/interrupts/InterruptManager.js.map +1 -0
- package/dist/ui/layout.d.ts +0 -1
- package/dist/ui/layout.d.ts.map +1 -1
- package/dist/ui/layout.js +0 -12
- package/dist/ui/layout.js.map +1 -1
- package/dist/ui/orchestration/StatusOrchestrator.d.ts +1 -1
- package/dist/ui/orchestration/StatusOrchestrator.js +1 -1
- package/dist/ui/orchestration/UIUpdateCoordinator.d.ts +61 -7
- package/dist/ui/orchestration/UIUpdateCoordinator.d.ts.map +1 -1
- package/dist/ui/orchestration/UIUpdateCoordinator.js +232 -20
- package/dist/ui/orchestration/UIUpdateCoordinator.js.map +1 -1
- package/dist/ui/shortcutsHelp.d.ts.map +1 -1
- package/dist/ui/shortcutsHelp.js +0 -1
- package/dist/ui/shortcutsHelp.js.map +1 -1
- package/dist/ui/telemetry/ResponseTracker.d.ts +22 -0
- package/dist/ui/telemetry/ResponseTracker.d.ts.map +1 -0
- package/dist/ui/telemetry/ResponseTracker.js +60 -0
- package/dist/ui/telemetry/ResponseTracker.js.map +1 -0
- package/dist/ui/telemetry/UITelemetry.d.ts +181 -0
- package/dist/ui/telemetry/UITelemetry.d.ts.map +1 -0
- package/dist/ui/telemetry/UITelemetry.js +446 -0
- package/dist/ui/telemetry/UITelemetry.js.map +1 -0
- package/dist/ui/unified/index.d.ts +28 -1
- package/dist/ui/unified/index.d.ts.map +1 -1
- package/dist/ui/unified/index.js +41 -2
- package/dist/ui/unified/index.js.map +1 -1
- package/dist/ui/unified/layout.d.ts +12 -0
- package/dist/ui/unified/layout.d.ts.map +1 -0
- package/dist/ui/unified/layout.js +96 -0
- package/dist/ui/unified/layout.js.map +1 -0
- package/package.json +1 -2
- package/dist/StringUtils.d.ts +0 -8
- package/dist/StringUtils.d.ts.map +0 -1
- package/dist/StringUtils.js +0 -11
- package/dist/StringUtils.js.map +0 -1
- package/dist/core/aiFlowSupervisor.d.ts +0 -44
- package/dist/core/aiFlowSupervisor.d.ts.map +0 -1
- package/dist/core/aiFlowSupervisor.js +0 -299
- package/dist/core/aiFlowSupervisor.js.map +0 -1
- package/dist/core/cliTestHarness.d.ts +0 -200
- package/dist/core/cliTestHarness.d.ts.map +0 -1
- package/dist/core/cliTestHarness.js +0 -549
- package/dist/core/cliTestHarness.js.map +0 -1
- package/dist/core/testUtils.d.ts +0 -121
- package/dist/core/testUtils.d.ts.map +0 -1
- package/dist/core/testUtils.js +0 -235
- package/dist/core/testUtils.js.map +0 -1
- package/dist/core/toolValidation.d.ts +0 -116
- package/dist/core/toolValidation.d.ts.map +0 -1
- package/dist/core/toolValidation.js +0 -282
- package/dist/core/toolValidation.js.map +0 -1
- package/dist/ui/planOverlay.d.ts +0 -28
- package/dist/ui/planOverlay.d.ts.map +0 -1
- package/dist/ui/planOverlay.js +0 -156
- package/dist/ui/planOverlay.js.map +0 -1
- package/dist/ui/streamingFormatter.d.ts +0 -30
- package/dist/ui/streamingFormatter.d.ts.map +0 -1
- package/dist/ui/streamingFormatter.js +0 -91
- package/dist/ui/streamingFormatter.js.map +0 -1
- package/dist/utils/errorUtils.d.ts +0 -16
- package/dist/utils/errorUtils.d.ts.map +0 -1
- package/dist/utils/errorUtils.js +0 -66
- package/dist/utils/errorUtils.js.map +0 -1
|
@@ -7,7 +7,6 @@ import { join, resolve } from 'node:path';
|
|
|
7
7
|
import { display } from '../ui/display.js';
|
|
8
8
|
import { theme } from '../ui/theme.js';
|
|
9
9
|
import { getTerminalColumns } from '../ui/layout.js';
|
|
10
|
-
import { StreamingResponseFormatter } from '../ui/streamingFormatter.js';
|
|
11
10
|
import { getContextWindowTokens } from '../core/contextWindow.js';
|
|
12
11
|
import { ensureSecretForProvider, getSecretDefinitionForProvider, getSecretValue, listSecretDefinitions, maskSecret, setSecretValue, } from '../core/secretStore.js';
|
|
13
12
|
import { saveActiveProfilePreference, saveModelPreference, loadToolSettings, saveToolSettings, clearToolSettings, clearActiveProfilePreference, loadSessionPreferences, saveSessionPreferences, loadFeatureFlags, saveFeatureFlags, toggleFeatureFlag, FEATURE_FLAG_INFO, } from '../core/preferences.js';
|
|
@@ -19,7 +18,6 @@ import { detectNetworkError } from '../core/errors/networkErrors.js';
|
|
|
19
18
|
import { buildWorkspaceContext } from '../workspace.js';
|
|
20
19
|
import { buildInteractiveSystemPrompt } from './systemPrompt.js';
|
|
21
20
|
import { getTaskCompletionDetector, resetTaskCompletionDetector, } from './taskCompletionDetector.js';
|
|
22
|
-
import { assessAiFlow } from '../core/aiFlowSupervisor.js';
|
|
23
21
|
import { discoverAllModels, quickCheckProviders, getCachedDiscoveredModels, sortModelsByPriority } from '../core/modelDiscovery.js';
|
|
24
22
|
import { getModels, getSlashCommands, getProviders } from '../core/agentSchemaLoader.js';
|
|
25
23
|
import { loadMcpServers } from '../mcp/config.js';
|
|
@@ -29,7 +27,7 @@ import { SkillRepository } from '../skills/skillRepository.js';
|
|
|
29
27
|
import { createSkillTools } from '../tools/skillTools.js';
|
|
30
28
|
import { FileChangeTracker } from './fileChangeTracker.js';
|
|
31
29
|
import { formatShortcutsHelp } from '../ui/shortcutsHelp.js';
|
|
32
|
-
import { setPlanApprovalCallback
|
|
30
|
+
import { setPlanApprovalCallback } from '../tools/planningTools.js';
|
|
33
31
|
import { MetricsTracker } from '../core/metricsTracker.js';
|
|
34
32
|
import { detectFailure, clearActionHistory, findRecoveryStrategy, } from '../core/failureRecovery.js';
|
|
35
33
|
import { addToolPattern } from '../core/learningPersistence.js';
|
|
@@ -42,7 +40,6 @@ import { startOffsecRun, resumeOffsecRun, recordOffsecOutcome, getOffsecNextActi
|
|
|
42
40
|
import { generateTestFlows, detectBugs, detectUIUpdates, saveTestFlows, saveBugReports, saveUIUpdates, getTestFlowStatus, } from '../core/intelligentTestFlows.js';
|
|
43
41
|
import { PromptController } from '../ui/PromptController.js';
|
|
44
42
|
import { enterStreamingMode, exitStreamingMode, isStreamingMode } from '../ui/globalWriteLock.js';
|
|
45
|
-
import { PlanOverlay } from '../ui/planOverlay.js';
|
|
46
43
|
import { setGlobalAIEnhancer } from '../tools/localExplore.js';
|
|
47
44
|
import { createProvider } from '../providers/providerFactory.js';
|
|
48
45
|
import { getParallelAgentManager } from '../subagents/parallelAgentManager.js';
|
|
@@ -93,6 +90,7 @@ export class InteractiveShell {
|
|
|
93
90
|
profile;
|
|
94
91
|
profileLabel;
|
|
95
92
|
workingDir;
|
|
93
|
+
codexContext;
|
|
96
94
|
runtimeSession;
|
|
97
95
|
baseSystemPrompt;
|
|
98
96
|
workspaceOptions;
|
|
@@ -132,16 +130,15 @@ export class InteractiveShell {
|
|
|
132
130
|
activeContextWindowTokens = null;
|
|
133
131
|
latestTokenUsage = { used: null, limit: null };
|
|
134
132
|
planApprovalBridgeRegistered = false;
|
|
135
|
-
planUpdateBridgeRegistered = false;
|
|
136
|
-
planOverlay = new PlanOverlay();
|
|
137
133
|
contextCompactionInFlight = false;
|
|
138
134
|
contextCompactionLog = [];
|
|
139
135
|
lastContextWarningLevel = null;
|
|
140
136
|
sessionPreferences;
|
|
141
137
|
autosaveEnabled;
|
|
142
|
-
autoContinueEnabled;
|
|
143
138
|
verificationEnabled = false;
|
|
139
|
+
networkEnabled = true; // Default to enabled, toggled via Ctrl+Shift+N
|
|
144
140
|
criticalApprovalMode = 'auto';
|
|
141
|
+
codexApprovalForced = false;
|
|
145
142
|
editGuardMode = 'display-edits';
|
|
146
143
|
pendingPermissionInput = null;
|
|
147
144
|
suppressNextNetworkReset = false;
|
|
@@ -185,7 +182,6 @@ export class InteractiveShell {
|
|
|
185
182
|
streamingOutputSuppressed = false;
|
|
186
183
|
aiRuntimeStart = null;
|
|
187
184
|
aiRuntimeTotalMs = 0;
|
|
188
|
-
streamingFormatter = null;
|
|
189
185
|
streamingThoughtBuffer = '';
|
|
190
186
|
statusLineState = null;
|
|
191
187
|
statusMessageOverride = null;
|
|
@@ -197,6 +193,7 @@ export class InteractiveShell {
|
|
|
197
193
|
version;
|
|
198
194
|
alternateScreenEnabled;
|
|
199
195
|
welcomeShown = false;
|
|
196
|
+
guardrailBannerShown = false;
|
|
200
197
|
renderer;
|
|
201
198
|
// Message queue for streaming mode coordination - prevents race conditions
|
|
202
199
|
pendingMessages = [];
|
|
@@ -204,14 +201,18 @@ export class InteractiveShell {
|
|
|
204
201
|
this.profile = config.profile;
|
|
205
202
|
this.profileLabel = config.profileLabel;
|
|
206
203
|
this.workingDir = config.workingDir;
|
|
204
|
+
this.codexContext = config.codexContext;
|
|
207
205
|
this.runtimeSession = config.session;
|
|
208
206
|
this.baseSystemPrompt = config.baseSystemPrompt;
|
|
209
207
|
this.workspaceOptions = { ...config.workspaceOptions };
|
|
210
208
|
this.sessionPreferences = loadSessionPreferences();
|
|
211
209
|
this.thinkingMode = this.sessionPreferences.thinkingMode;
|
|
212
210
|
this.autosaveEnabled = this.sessionPreferences.autosave;
|
|
213
|
-
this.autoContinueEnabled = this.sessionPreferences.autoContinue;
|
|
214
211
|
this.criticalApprovalMode = this.sessionPreferences.criticalApprovalMode;
|
|
212
|
+
if (this.codexContext && this.shouldForceApprovalMode(this.codexContext.approvalPolicy)) {
|
|
213
|
+
this.criticalApprovalMode = 'approval';
|
|
214
|
+
this.codexApprovalForced = true;
|
|
215
|
+
}
|
|
215
216
|
const featureFlags = loadFeatureFlags();
|
|
216
217
|
this.verificationEnabled = featureFlags.verification === true;
|
|
217
218
|
this.sessionRestoreConfig = config.sessionRestore ?? { mode: 'none' };
|
|
@@ -269,11 +270,6 @@ export class InteractiveShell {
|
|
|
269
270
|
description: 'Switch between auto and approval mode for high-impact actions',
|
|
270
271
|
category: 'configuration',
|
|
271
272
|
});
|
|
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
|
-
});
|
|
277
273
|
this.slashCommands.push({
|
|
278
274
|
command: '/offsec',
|
|
279
275
|
description: 'AlphaZero offensive security run (start/status/next)',
|
|
@@ -292,6 +288,10 @@ export class InteractiveShell {
|
|
|
292
288
|
this.uiAdapter.setToolStatusCallback((status) => {
|
|
293
289
|
this.updateStatusMessage(status ?? null);
|
|
294
290
|
});
|
|
291
|
+
// Set up activity callback to update activity line with streaming bash output
|
|
292
|
+
this.uiAdapter.setActivityCallback((activity) => {
|
|
293
|
+
this.renderer?.setActivity(activity);
|
|
294
|
+
});
|
|
295
295
|
this.skillRepository = new SkillRepository({
|
|
296
296
|
workingDir: this.workingDir,
|
|
297
297
|
env: process.env,
|
|
@@ -305,20 +305,19 @@ export class InteractiveShell {
|
|
|
305
305
|
onQueue: (text) => this.handleQueuedInput(text),
|
|
306
306
|
onCtrlC: ({ hadBuffer }) => this.handleCtrlCPress(hadBuffer),
|
|
307
307
|
onInterrupt: () => this.handleInterrupt(),
|
|
308
|
-
onExit: () => this.
|
|
309
|
-
onExpandToolResult: () => display.expandLastToolResult?.(),
|
|
308
|
+
onExit: () => this.handleExit(),
|
|
310
309
|
onChange: ({ text, cursor }) => this.handleInputChange(text, cursor, 'renderer'),
|
|
311
310
|
onEditModeChange: (mode) => this.handleEditModeChange(mode),
|
|
312
311
|
onToggleVerify: () => this.toggleVerificationMode(),
|
|
313
|
-
onToggleAutoContinue: () => this.toggleAutoContinueMode(),
|
|
314
312
|
onToggleThinking: () => this.cycleThinkingMode(),
|
|
315
313
|
onClearContext: () => this.handleClearContext(),
|
|
316
314
|
onToggleCriticalApproval: () => this.toggleCriticalApprovalMode('shortcut'),
|
|
315
|
+
onExpandToolResult: () => this.expandLastToolResult(),
|
|
316
|
+
onToggleNetwork: () => this.toggleNetworkAccess(),
|
|
317
317
|
});
|
|
318
318
|
// Share renderer with Display so all output flows through the unified queue
|
|
319
319
|
this.renderer = this.terminalInput.getRenderer();
|
|
320
320
|
display.setRenderer(this.renderer);
|
|
321
|
-
this.uiAdapter.attachRenderer(this.renderer);
|
|
322
321
|
display.setInlinePanelHandler((content) => {
|
|
323
322
|
if (!this.shouldCaptureInlinePanel()) {
|
|
324
323
|
return false;
|
|
@@ -338,7 +337,6 @@ export class InteractiveShell {
|
|
|
338
337
|
// The control bar will be refreshed after the welcome banner
|
|
339
338
|
// Allow planning tools (e.g., ProposePlan) to open the interactive approval UI just like Codex CLI
|
|
340
339
|
this.registerPlanApprovalBridge();
|
|
341
|
-
this.registerPlanUpdateBridge();
|
|
342
340
|
// Set up command autocomplete with all slash commands
|
|
343
341
|
this.setupCommandAutocomplete();
|
|
344
342
|
this.rebuildAgent();
|
|
@@ -459,18 +457,21 @@ export class InteractiveShell {
|
|
|
459
457
|
const top = `╭${'─'.repeat(labelLeft)}${label}${'─'.repeat(labelRight)}╮`;
|
|
460
458
|
const bottom = `╰${'─'.repeat(contentWidth)}╯`;
|
|
461
459
|
const userName = process.env['USER'] || 'there';
|
|
462
|
-
const logo =
|
|
460
|
+
const logo = '⟣╍◎╍⟢';
|
|
463
461
|
const versionLabel = version ? clamp(`${brand} · v${version}`) : brand;
|
|
464
462
|
const modelLine = clamp(model);
|
|
465
463
|
const workspaceLine = clamp(workspace);
|
|
466
|
-
|
|
464
|
+
// Compact welcome line with logo on same line
|
|
465
|
+
const welcomeLine = clamp(`${logo} Welcome back ${userName}! ${logo}`);
|
|
466
|
+
// Quick reference hints
|
|
467
|
+
const hintsLine = clamp('/model · /secrets · /help · ? shortcuts');
|
|
467
468
|
const lines = [
|
|
468
469
|
frame(top),
|
|
469
470
|
frame(`│${padCenter(welcomeLine, contentWidth)}│`),
|
|
470
|
-
frame(`│${padCenter(logo, contentWidth)}│`),
|
|
471
471
|
frame(`│${padCenter(modelLine, contentWidth)}│`),
|
|
472
472
|
frame(`│${padCenter(versionLabel, contentWidth)}│`),
|
|
473
473
|
frame(`│${padCenter(workspaceLine, contentWidth)}│`),
|
|
474
|
+
frame(`│${padCenter(hintsLine, contentWidth)}│`),
|
|
474
475
|
frame(bottom),
|
|
475
476
|
].join('\n');
|
|
476
477
|
if (this.renderer) {
|
|
@@ -481,8 +482,6 @@ export class InteractiveShell {
|
|
|
481
482
|
else {
|
|
482
483
|
display.stream(`\n${lines}\n`);
|
|
483
484
|
}
|
|
484
|
-
// Check for updates asynchronously (non-blocking)
|
|
485
|
-
void this.checkAndShowUpdates();
|
|
486
485
|
// Keep UI pinned; no scrollback banners
|
|
487
486
|
this.requestPromptRefresh(true);
|
|
488
487
|
}
|
|
@@ -518,24 +517,80 @@ export class InteractiveShell {
|
|
|
518
517
|
}
|
|
519
518
|
return pathValue;
|
|
520
519
|
}
|
|
520
|
+
shouldForceApprovalMode(policy) {
|
|
521
|
+
return policy === 'on-request' || policy === 'untrusted' || policy === 'never';
|
|
522
|
+
}
|
|
523
|
+
getGuardrailMeta() {
|
|
524
|
+
if (!this.codexContext) {
|
|
525
|
+
return {};
|
|
526
|
+
}
|
|
527
|
+
const approvals = this.codexContext.approvalPolicy === 'on-request' || this.codexContext.approvalPolicy === 'untrusted'
|
|
528
|
+
? 'ask'
|
|
529
|
+
: this.codexContext.approvalPolicy === 'never'
|
|
530
|
+
? 'never'
|
|
531
|
+
: 'auto';
|
|
532
|
+
const sandbox = this.codexContext.sandboxMode === 'workspace-write'
|
|
533
|
+
? 'workspace'
|
|
534
|
+
: this.codexContext.sandboxMode === 'danger-full-access'
|
|
535
|
+
? 'danger'
|
|
536
|
+
: this.codexContext.sandboxMode;
|
|
537
|
+
// Use runtime networkEnabled state (defaults to true, toggled via Ctrl+Shift+N)
|
|
538
|
+
const network = this.networkEnabled ? 'enabled' : 'restricted';
|
|
539
|
+
return { sandbox, network, approvals };
|
|
540
|
+
}
|
|
541
|
+
describePlanPath() {
|
|
542
|
+
const planPath = join(this.workingDir, '.erosolar', 'codex', 'plan.json');
|
|
543
|
+
const relative = planPath.startsWith(this.workingDir) && planPath.length > this.workingDir.length
|
|
544
|
+
? planPath.slice(this.workingDir.length + 1)
|
|
545
|
+
: planPath;
|
|
546
|
+
return this.abbreviatePath(relative);
|
|
547
|
+
}
|
|
548
|
+
maybeShowCodexGuardrails() {
|
|
549
|
+
if (!this.codexContext || this.guardrailBannerShown) {
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
const meta = this.getGuardrailMeta();
|
|
553
|
+
const lines = [];
|
|
554
|
+
lines.push(`Guardrails: sandbox ${meta.sandbox ?? 'workspace'} · network ${meta.network ?? 'restricted'} · approvals ${meta.approvals ?? 'auto'}`);
|
|
555
|
+
lines.push('Flow: plan before edits, targeted reads, one in-progress step, validate once at the end.');
|
|
556
|
+
const planPath = this.describePlanPath();
|
|
557
|
+
if (planPath) {
|
|
558
|
+
lines.push(`Plan file: ${planPath}`);
|
|
559
|
+
}
|
|
560
|
+
if (this.codexApprovalForced) {
|
|
561
|
+
lines.push('Approval mode set to ask per environment policy.');
|
|
562
|
+
}
|
|
563
|
+
this.streamEventBlock(lines.join('\n'));
|
|
564
|
+
this.guardrailBannerShown = true;
|
|
565
|
+
}
|
|
521
566
|
async checkAndShowUpdates() {
|
|
522
567
|
try {
|
|
523
|
-
const { checkForUpdates,
|
|
568
|
+
const { checkForUpdates, getUpdateDecision, formatUpdateBanner, performBackgroundUpdate, readAutoUpdateState, shouldShowUpdateNotification, } = await import('../core/updateChecker.js');
|
|
524
569
|
const currentVersion = this.version || this.getPackageInfo().version || '1.7.458';
|
|
525
570
|
const updateInfo = await checkForUpdates(currentVersion);
|
|
526
571
|
if (!updateInfo || !updateInfo.updateAvailable) {
|
|
527
572
|
return;
|
|
528
573
|
}
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
574
|
+
// Check if we should show notification (respects quiet period)
|
|
575
|
+
const state = readAutoUpdateState();
|
|
576
|
+
if (!shouldShowUpdateNotification(state)) {
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
// Get user's preference-based decision (no interactive prompt)
|
|
580
|
+
const sessionPrefs = loadSessionPreferences();
|
|
581
|
+
const decision = getUpdateDecision(sessionPrefs.autoUpdate);
|
|
582
|
+
if (decision === 'skip') {
|
|
583
|
+
// User chose to always skip - silently ignore updates
|
|
534
584
|
return;
|
|
535
585
|
}
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
586
|
+
// Show non-interactive banner notification
|
|
587
|
+
this.streamEventBlock(formatUpdateBanner(updateInfo, decision));
|
|
588
|
+
if (decision === 'auto') {
|
|
589
|
+
// User chose to always update - perform background update
|
|
590
|
+
await performBackgroundUpdate(updateInfo, (message) => this.streamEventBlock(message));
|
|
591
|
+
}
|
|
592
|
+
// If decision === 'ask', we just show the banner with instructions
|
|
593
|
+
// User can run /update check or /update auto to update
|
|
539
594
|
}
|
|
540
595
|
catch {
|
|
541
596
|
// Silently fail - don't interrupt user experience
|
|
@@ -619,16 +674,20 @@ export class InteractiveShell {
|
|
|
619
674
|
this.pushUiEvent('raw', block);
|
|
620
675
|
}
|
|
621
676
|
async start(initialPrompt) {
|
|
677
|
+
// Check for updates BEFORE terminal input starts (to avoid keypress conflicts)
|
|
678
|
+
await this.checkAndShowUpdates();
|
|
622
679
|
// Initialize the renderer before emitting the banner so we don't render the prompt twice
|
|
623
680
|
this.terminalInput.start();
|
|
624
681
|
this.resetRendererStreamingMode();
|
|
625
682
|
await this.showWelcomeBanner();
|
|
683
|
+
this.maybeShowCodexGuardrails();
|
|
626
684
|
// Now refresh control bar with profile/model/version info
|
|
627
685
|
this.refreshControlBar();
|
|
628
686
|
// Now sync renderer and control bar state
|
|
629
687
|
this.syncRendererInput();
|
|
630
|
-
//
|
|
631
|
-
|
|
688
|
+
// CRITICAL: Force prompt render after banner - flushEvents should have done this,
|
|
689
|
+
// but explicitly ensure the prompt area is visible
|
|
690
|
+
this.renderer?.render();
|
|
632
691
|
if (initialPrompt) {
|
|
633
692
|
await this.enqueuePromptForProcessing(initialPrompt, 'programmatic');
|
|
634
693
|
return;
|
|
@@ -636,7 +695,7 @@ export class InteractiveShell {
|
|
|
636
695
|
this.showLaunchCommandPalette();
|
|
637
696
|
// Ensure the terminal input is visible
|
|
638
697
|
this.syncRendererInput();
|
|
639
|
-
this.
|
|
698
|
+
this.renderer?.render();
|
|
640
699
|
}
|
|
641
700
|
showLaunchCommandPalette() {
|
|
642
701
|
// Disabled: Quick commands palette takes up too much space
|
|
@@ -704,6 +763,10 @@ export class InteractiveShell {
|
|
|
704
763
|
*/
|
|
705
764
|
async runPromptJob(job) {
|
|
706
765
|
try {
|
|
766
|
+
// Show thinking indicator instead of "processing queued prompt"
|
|
767
|
+
display.showThinking('Thinking…');
|
|
768
|
+
// Re-echo the prompt to make it clear what's being processed
|
|
769
|
+
this.renderer?.emitPrompt(job.text);
|
|
707
770
|
await this.processInputBlock(job.text);
|
|
708
771
|
job.resolve();
|
|
709
772
|
}
|
|
@@ -799,7 +862,6 @@ export class InteractiveShell {
|
|
|
799
862
|
'/output-style',
|
|
800
863
|
// Mode toggles
|
|
801
864
|
'/thinking',
|
|
802
|
-
'/autocontinue',
|
|
803
865
|
// Discovery and plugins
|
|
804
866
|
'/local', '/discover',
|
|
805
867
|
'/plugins',
|
|
@@ -889,25 +951,33 @@ export class InteractiveShell {
|
|
|
889
951
|
: '🛡️ High-impact actions now require your approval. (Ctrl+Shift+A to toggle; use /approvals ask to persist)';
|
|
890
952
|
display.showSystemMessage(message);
|
|
891
953
|
}
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
this.autoContinueEnabled = enabled;
|
|
898
|
-
saveSessionPreferences({ autoContinue: this.autoContinueEnabled });
|
|
899
|
-
if (this.agent) {
|
|
900
|
-
this.agent.setAutoContinue(this.autoContinueEnabled);
|
|
901
|
-
}
|
|
954
|
+
/**
|
|
955
|
+
* Toggle network access on/off (Ctrl+Shift+N shortcut).
|
|
956
|
+
*/
|
|
957
|
+
toggleNetworkAccess() {
|
|
958
|
+
this.networkEnabled = !this.networkEnabled;
|
|
902
959
|
this.refreshControlBar();
|
|
903
|
-
|
|
960
|
+
const message = this.networkEnabled
|
|
961
|
+
? '🌐 Network access enabled. (Ctrl+Shift+N to toggle)'
|
|
962
|
+
: '🔒 Network access disabled. (Ctrl+Shift+N to toggle)';
|
|
963
|
+
display.showSystemMessage(message);
|
|
964
|
+
}
|
|
965
|
+
/**
|
|
966
|
+
* Expand the last tool result (Ctrl+O shortcut).
|
|
967
|
+
* Shows the full output from the most recent tool call.
|
|
968
|
+
*/
|
|
969
|
+
expandLastToolResult() {
|
|
970
|
+
const result = this.uiAdapter.getLastToolResult();
|
|
971
|
+
if (!result) {
|
|
972
|
+
display.showInfo('No tool result to expand. Run a tool first, then press Ctrl+O.');
|
|
904
973
|
return;
|
|
905
974
|
}
|
|
906
|
-
|
|
907
|
-
const
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
975
|
+
// Format the expanded output with tool name header
|
|
976
|
+
const header = `${theme.info('━━━')} ${theme.tool(result.toolName)} ${theme.ui.muted('output')} ${theme.info('━━━')}`;
|
|
977
|
+
const content = result.output.trim() || '(empty result)';
|
|
978
|
+
const footer = theme.ui.muted('━'.repeat(Math.min(60, header.length)));
|
|
979
|
+
// Display the expanded result
|
|
980
|
+
display.stream(`\n${header}\n${content}\n${footer}\n\n`);
|
|
911
981
|
}
|
|
912
982
|
/**
|
|
913
983
|
* Cycle through thinking modes (Tab shortcut).
|
|
@@ -1061,6 +1131,13 @@ export class InteractiveShell {
|
|
|
1061
1131
|
}
|
|
1062
1132
|
this.ctrlCHandledThisPress = false;
|
|
1063
1133
|
}
|
|
1134
|
+
/**
|
|
1135
|
+
* Handle exit request (Ctrl+C with empty buffer in idle mode)
|
|
1136
|
+
*/
|
|
1137
|
+
handleExit() {
|
|
1138
|
+
display.showSystemMessage('\nGoodbye!\n');
|
|
1139
|
+
this.shutdown();
|
|
1140
|
+
}
|
|
1064
1141
|
/**
|
|
1065
1142
|
* Gracefully tear down the shell and exit
|
|
1066
1143
|
*/
|
|
@@ -1070,7 +1147,7 @@ export class InteractiveShell {
|
|
|
1070
1147
|
}
|
|
1071
1148
|
this.shuttingDown = true;
|
|
1072
1149
|
// Stop any active spinner to prevent process hang
|
|
1073
|
-
display.stopThinking();
|
|
1150
|
+
display.stopThinking(false);
|
|
1074
1151
|
this.stopStreamingHeartbeat('quit', { quiet: true });
|
|
1075
1152
|
this.endAiRuntime();
|
|
1076
1153
|
this.uiUpdates.dispose();
|
|
@@ -1082,7 +1159,6 @@ export class InteractiveShell {
|
|
|
1082
1159
|
this.pendingCleanup = null;
|
|
1083
1160
|
// Unregister plan approval bridge
|
|
1084
1161
|
setPlanApprovalCallback(null);
|
|
1085
|
-
setPlanUpdateCallback(null);
|
|
1086
1162
|
// Dispose terminal input handler
|
|
1087
1163
|
this.terminalInput.dispose();
|
|
1088
1164
|
// Dispose unified UI adapter
|
|
@@ -1103,38 +1179,6 @@ export class InteractiveShell {
|
|
|
1103
1179
|
});
|
|
1104
1180
|
this.planApprovalBridgeRegistered = true;
|
|
1105
1181
|
}
|
|
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
|
-
}
|
|
1138
1182
|
/**
|
|
1139
1183
|
* Update status bar message
|
|
1140
1184
|
*/
|
|
@@ -1663,16 +1707,18 @@ export class InteractiveShell {
|
|
|
1663
1707
|
refreshControlBar() {
|
|
1664
1708
|
this.terminalInput.setModeToggles({
|
|
1665
1709
|
verificationEnabled: this.verificationEnabled,
|
|
1666
|
-
autoContinueEnabled: this.autoContinueEnabled,
|
|
1667
1710
|
verificationHotkey: 'ctrl+shift+v',
|
|
1668
|
-
autoContinueHotkey: 'ctrl+shift+c',
|
|
1669
1711
|
thinkingModeLabel: (this.thinkingMode || 'off').toString(),
|
|
1670
1712
|
thinkingHotkey: 'tab',
|
|
1671
1713
|
criticalApprovalMode: this.criticalApprovalMode,
|
|
1672
1714
|
criticalApprovalHotkey: 'ctrl+shift+a',
|
|
1715
|
+
networkEnabled: this.networkEnabled,
|
|
1716
|
+
networkHotkey: 'ctrl+shift+n',
|
|
1673
1717
|
});
|
|
1674
1718
|
const workspaceDisplay = this.abbreviatePath(this.workingDir);
|
|
1719
|
+
const guardrails = this.getGuardrailMeta();
|
|
1675
1720
|
this.terminalInput.setChromeMeta({
|
|
1721
|
+
...guardrails,
|
|
1676
1722
|
profile: this.profileLabel,
|
|
1677
1723
|
workspace: workspaceDisplay,
|
|
1678
1724
|
directory: workspaceDisplay,
|
|
@@ -1756,15 +1802,14 @@ export class InteractiveShell {
|
|
|
1756
1802
|
*/
|
|
1757
1803
|
refreshStatusLine(forceRender = false) {
|
|
1758
1804
|
const elapsedSeconds = this.getAiRuntimeSeconds();
|
|
1759
|
-
const thinkingMs = display.isSpinnerActive() ? display.getThinkingElapsedMs() : null;
|
|
1760
1805
|
const tokensUsed = this.latestTokenUsage.used;
|
|
1761
1806
|
const tokenLimit = this.latestTokenUsage.limit ?? this.activeContextWindowTokens;
|
|
1762
1807
|
this.terminalInput.setMetaStatus({
|
|
1763
1808
|
elapsedSeconds,
|
|
1764
1809
|
tokensUsed,
|
|
1765
1810
|
tokenLimit,
|
|
1766
|
-
thinkingMs,
|
|
1767
|
-
thinkingHasContent:
|
|
1811
|
+
thinkingMs: null,
|
|
1812
|
+
thinkingHasContent: false,
|
|
1768
1813
|
});
|
|
1769
1814
|
// Keep model/provider visible in the controls bar
|
|
1770
1815
|
this.terminalInput.setModelContext({
|
|
@@ -1940,12 +1985,16 @@ export class InteractiveShell {
|
|
|
1940
1985
|
enterStreamingMode();
|
|
1941
1986
|
// Set up scroll region for streaming content
|
|
1942
1987
|
this.uiUpdates.setMode('streaming');
|
|
1988
|
+
// Show activity status with animated spinner - use provided label or default
|
|
1989
|
+
const activityLabel = label || 'Thinking';
|
|
1990
|
+
this.renderer?.setActivity(activityLabel);
|
|
1943
1991
|
this.streamingHeartbeatStart = Date.now();
|
|
1944
1992
|
this.streamingContentSeen = false;
|
|
1945
1993
|
this.streamingStatusText = null;
|
|
1946
1994
|
this.streamingStatusLastUpdate = null;
|
|
1947
|
-
|
|
1948
|
-
|
|
1995
|
+
this.streamingTokenCount = 0; // Reset token count for new prompt
|
|
1996
|
+
// Show raw streaming output in real-time (Claude Code style)
|
|
1997
|
+
this.streamingOutputSuppressed = false;
|
|
1949
1998
|
const initialLabel = this.isMeaningfulStreamingSnippet(label)
|
|
1950
1999
|
? this.truncateStreamingLabel(label)
|
|
1951
2000
|
: this.getStreamingFallbackLabel();
|
|
@@ -1989,6 +2038,8 @@ export class InteractiveShell {
|
|
|
1989
2038
|
this.streamingStatusLastUpdate = null;
|
|
1990
2039
|
this.streamingStatusText = null;
|
|
1991
2040
|
this.streamingOutputSuppressed = false;
|
|
2041
|
+
// Clear activity status when streaming ends
|
|
2042
|
+
this.renderer?.setActivity(null);
|
|
1992
2043
|
// Emit a streaming note for stop/quit so the status stays inside the stream
|
|
1993
2044
|
if (reason === 'stop' || reason === 'quit') {
|
|
1994
2045
|
const note = reason === 'quit' ? 'Session closed.' : 'Stream stopped.';
|
|
@@ -2005,11 +2056,15 @@ export class InteractiveShell {
|
|
|
2005
2056
|
// Buffer for accumulating partial <thinking> tags during streaming
|
|
2006
2057
|
thinkingTagBuffer = '';
|
|
2007
2058
|
insideThinkingBlock = false;
|
|
2059
|
+
streamingTokenCount = 0;
|
|
2008
2060
|
handleStreamChunk(chunk, type = 'content') {
|
|
2009
2061
|
if (!chunk) {
|
|
2010
2062
|
return;
|
|
2011
2063
|
}
|
|
2012
2064
|
const isReasoning = type === 'reasoning';
|
|
2065
|
+
// Approximate token count (roughly 4 chars per token)
|
|
2066
|
+
this.streamingTokenCount += Math.ceil(chunk.length / 4);
|
|
2067
|
+
this.renderer?.updateStreamingTokens(this.streamingTokenCount);
|
|
2013
2068
|
// Keep pinned status updated for all streaming chunks
|
|
2014
2069
|
this.updateStreamingStatusFromChunk(chunk);
|
|
2015
2070
|
// Handle <thinking> tags as separate events in the queue
|
|
@@ -2018,37 +2073,32 @@ export class InteractiveShell {
|
|
|
2018
2073
|
if (!processed.contentChunk && !processed.thinkingChunk) {
|
|
2019
2074
|
return;
|
|
2020
2075
|
}
|
|
2021
|
-
//
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
}
|
|
2076
|
+
// Don't emit thinking blocks during streaming - they will be rendered
|
|
2077
|
+
// as a complete block by onAssistantMessage when the response is final.
|
|
2078
|
+
// This prevents duplicate display.
|
|
2025
2079
|
// Process regular content (skip if no content after extracting thinking)
|
|
2026
2080
|
const contentChunk = processed.contentChunk;
|
|
2027
2081
|
if (!contentChunk) {
|
|
2028
2082
|
return;
|
|
2029
2083
|
}
|
|
2030
2084
|
// Suppress raw streaming output in scrollback; keep only status + final message.
|
|
2031
|
-
// Reasoning
|
|
2032
|
-
if (this.streamingOutputSuppressed
|
|
2085
|
+
// Reasoning tokens should also be suppressed and shown only in the indicator.
|
|
2086
|
+
if (this.streamingOutputSuppressed) {
|
|
2033
2087
|
this.captureStreamingThought(contentChunk);
|
|
2034
2088
|
this.streamingContentSeen = true;
|
|
2089
|
+
// Update thinking indicator with a snippet of the content
|
|
2090
|
+
const prefix = isReasoning ? '○ ' : '';
|
|
2091
|
+
const snippet = contentChunk.replace(/\s+/g, ' ').trim().slice(0, 60);
|
|
2092
|
+
if (snippet) {
|
|
2093
|
+
display.updateThinking(prefix + snippet + (contentChunk.length > 60 ? '…' : ''));
|
|
2094
|
+
}
|
|
2035
2095
|
return;
|
|
2036
2096
|
}
|
|
2037
|
-
|
|
2038
|
-
|
|
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);
|
|
2097
|
+
// Buffer all streaming content - rendered as complete block via onAssistantMessage
|
|
2098
|
+
// This prevents fragmented display and ensures thinking tags are properly processed
|
|
2045
2099
|
this.captureStreamingThought(contentChunk);
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
this.streamingContentSeen = true;
|
|
2049
|
-
}
|
|
2050
|
-
this.pushUiEvent('streaming', formatted);
|
|
2051
|
-
}
|
|
2100
|
+
// Activity: show what the model is doing (stable, informative)
|
|
2101
|
+
this.renderer?.setActivity(type === 'reasoning' ? 'Reasoning' : 'Writing');
|
|
2052
2102
|
}
|
|
2053
2103
|
/**
|
|
2054
2104
|
* Process streaming content to extract <thinking> blocks as separate events.
|
|
@@ -2096,22 +2146,12 @@ export class InteractiveShell {
|
|
|
2096
2146
|
}
|
|
2097
2147
|
return { thinkingChunk: thinkingContent, contentChunk: remainingContent };
|
|
2098
2148
|
}
|
|
2099
|
-
finishStreamingFormatter(
|
|
2100
|
-
|
|
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
|
-
}
|
|
2149
|
+
finishStreamingFormatter(_note, options) {
|
|
2150
|
+
// Flush any remaining buffered thoughts
|
|
2110
2151
|
if (this.streamingThoughtBuffer.trim()) {
|
|
2111
2152
|
this.ui.controller.recordAssistantThought(this.streamingThoughtBuffer.trim());
|
|
2112
2153
|
}
|
|
2113
2154
|
this.streamingThoughtBuffer = '';
|
|
2114
|
-
this.streamingFormatter = null;
|
|
2115
2155
|
if (options?.refreshPrompt ?? true) {
|
|
2116
2156
|
this.requestPromptRefresh(true);
|
|
2117
2157
|
}
|
|
@@ -2403,9 +2443,6 @@ export class InteractiveShell {
|
|
|
2403
2443
|
case '/approvals':
|
|
2404
2444
|
this.handleApprovalsCommand(input);
|
|
2405
2445
|
break;
|
|
2406
|
-
case '/plan':
|
|
2407
|
-
this.handlePlanCommand(input);
|
|
2408
|
-
break;
|
|
2409
2446
|
case '/learn':
|
|
2410
2447
|
this.showLearningStatus(input);
|
|
2411
2448
|
break;
|
|
@@ -2453,9 +2490,6 @@ export class InteractiveShell {
|
|
|
2453
2490
|
case '/thinking':
|
|
2454
2491
|
this.handleThinkingCommand(input);
|
|
2455
2492
|
break;
|
|
2456
|
-
case '/autocontinue':
|
|
2457
|
-
this.handleAutoContinueCommand(input);
|
|
2458
|
-
break;
|
|
2459
2493
|
case '/shortcuts':
|
|
2460
2494
|
case '/keys':
|
|
2461
2495
|
this.handleShortcutsCommand();
|
|
@@ -2559,6 +2593,9 @@ export class InteractiveShell {
|
|
|
2559
2593
|
case '/permissions':
|
|
2560
2594
|
this.handlePermissionsCommand();
|
|
2561
2595
|
break;
|
|
2596
|
+
case '/update':
|
|
2597
|
+
await this.handleUpdateCommand(input);
|
|
2598
|
+
break;
|
|
2562
2599
|
case '/init':
|
|
2563
2600
|
this.handleInitCommand(input);
|
|
2564
2601
|
break;
|
|
@@ -2616,7 +2653,6 @@ export class InteractiveShell {
|
|
|
2616
2653
|
theme.bold(' Mode Toggles'),
|
|
2617
2654
|
` ${theme.info('Shift+Tab')} ${theme.ui.muted('Toggle edit mode (auto/ask)')}`,
|
|
2618
2655
|
` ${theme.info('Option+V')} ${theme.ui.muted('Toggle verification')}`,
|
|
2619
|
-
` ${theme.info('Option+C')} ${theme.ui.muted('Toggle auto-continue')}`,
|
|
2620
2656
|
` ${theme.info('Ctrl+Shift+A')} ${theme.ui.muted('Toggle approvals for high-impact actions')}`,
|
|
2621
2657
|
` ${theme.info('Option+T')} ${theme.ui.muted('Cycle thinking mode')}`,
|
|
2622
2658
|
` ${theme.info('Option+E')} ${theme.ui.muted('Toggle edit permission mode')}`,
|
|
@@ -4330,20 +4366,6 @@ export class InteractiveShell {
|
|
|
4330
4366
|
clearAutosaveSnapshot(this.profile);
|
|
4331
4367
|
display.showInfo('Cleared autosave history.');
|
|
4332
4368
|
}
|
|
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
|
-
}
|
|
4347
4369
|
// ==================== Erosolar-CLI Style Commands ====================
|
|
4348
4370
|
async handleRewindCommand(_input) {
|
|
4349
4371
|
const lines = [];
|
|
@@ -4575,7 +4597,6 @@ export class InteractiveShell {
|
|
|
4575
4597
|
lines.push(`${theme.primary('Provider/Model')}: ${theme.info(`${this.providerLabel(this.sessionState.provider)} · ${this.sessionState.model}`)}`);
|
|
4576
4598
|
lines.push(`${theme.primary('Workspace')}: ${theme.ui.muted(this.abbreviatePath(this.workingDir))}`);
|
|
4577
4599
|
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')}`);
|
|
4579
4600
|
lines.push(`${theme.primary('Verification')}: ${this.verificationEnabled ? theme.success('on') : theme.ui.muted('off')}`);
|
|
4580
4601
|
lines.push(`${theme.primary('Approvals')}: ${theme.ui.muted(this.describeEditGuardMode())}`);
|
|
4581
4602
|
lines.push(`${theme.primary('Critical approvals')}: ${this.criticalApprovalMode === 'approval' ? theme.warning('ask') : theme.ui.muted('auto')}`);
|
|
@@ -4711,52 +4732,6 @@ export class InteractiveShell {
|
|
|
4711
4732
|
}
|
|
4712
4733
|
display.showWarning('Usage: /approvals [auto|ask|status]');
|
|
4713
4734
|
}
|
|
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
|
-
}
|
|
4760
4735
|
handlePermissionsCommand() {
|
|
4761
4736
|
const lines = [];
|
|
4762
4737
|
lines.push(theme.bold('Tool Permissions'));
|
|
@@ -4777,6 +4752,67 @@ export class InteractiveShell {
|
|
|
4777
4752
|
lines.push(theme.ui.muted('Use /tools to manage which tool suites are enabled.'));
|
|
4778
4753
|
display.showSystemMessage(lines.join('\n'));
|
|
4779
4754
|
}
|
|
4755
|
+
async handleUpdateCommand(input) {
|
|
4756
|
+
const tokens = input.split(/\s+/).slice(1);
|
|
4757
|
+
const subcommand = tokens[0]?.toLowerCase();
|
|
4758
|
+
const prefs = loadSessionPreferences();
|
|
4759
|
+
const currentPref = prefs.autoUpdate;
|
|
4760
|
+
const prefLabel = currentPref === true ? 'always update' : currentPref === false ? 'always skip' : 'notify only';
|
|
4761
|
+
// Show status or help
|
|
4762
|
+
if (!subcommand || subcommand === 'status') {
|
|
4763
|
+
const lines = [];
|
|
4764
|
+
lines.push(theme.bold('Update Settings'));
|
|
4765
|
+
lines.push('');
|
|
4766
|
+
lines.push(`Current preference: ${theme.info(prefLabel)}`);
|
|
4767
|
+
lines.push('');
|
|
4768
|
+
lines.push(theme.secondary('Commands:'));
|
|
4769
|
+
lines.push(' /update check - Check for updates and install immediately');
|
|
4770
|
+
lines.push(' /update auto - Always auto-update in background');
|
|
4771
|
+
lines.push(' /update skip - Never auto-update (silent)');
|
|
4772
|
+
lines.push(' /update notify - Show notification only (default)');
|
|
4773
|
+
display.showSystemMessage(lines.join('\n'));
|
|
4774
|
+
return;
|
|
4775
|
+
}
|
|
4776
|
+
if (subcommand === 'check') {
|
|
4777
|
+
// Force check and perform update immediately (non-interactive)
|
|
4778
|
+
try {
|
|
4779
|
+
const { checkForUpdates, performUpdate } = await import('../core/updateChecker.js');
|
|
4780
|
+
const currentVersion = this.version || this.getPackageInfo().version || '1.7.458';
|
|
4781
|
+
const updateInfo = await checkForUpdates(currentVersion);
|
|
4782
|
+
if (!updateInfo) {
|
|
4783
|
+
display.showWarning('Unable to check for updates (network issue or timeout).');
|
|
4784
|
+
return;
|
|
4785
|
+
}
|
|
4786
|
+
if (!updateInfo.updateAvailable) {
|
|
4787
|
+
display.showSuccess(`You're on the latest version (v${updateInfo.current}).`);
|
|
4788
|
+
return;
|
|
4789
|
+
}
|
|
4790
|
+
// Show update info and perform update immediately (no interactive prompt)
|
|
4791
|
+
display.showInfo(`Update available: v${updateInfo.current} → v${updateInfo.latest}`);
|
|
4792
|
+
await performUpdate(updateInfo, (msg) => this.streamEventBlock(msg));
|
|
4793
|
+
}
|
|
4794
|
+
catch (error) {
|
|
4795
|
+
display.showError(`Update check failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
4796
|
+
}
|
|
4797
|
+
return;
|
|
4798
|
+
}
|
|
4799
|
+
if (subcommand === 'auto' || subcommand === 'always') {
|
|
4800
|
+
saveSessionPreferences({ autoUpdate: true });
|
|
4801
|
+
display.showSuccess('Auto-update enabled. Updates will install automatically.');
|
|
4802
|
+
return;
|
|
4803
|
+
}
|
|
4804
|
+
if (subcommand === 'skip' || subcommand === 'never' || subcommand === 'off') {
|
|
4805
|
+
saveSessionPreferences({ autoUpdate: false });
|
|
4806
|
+
display.showInfo('Auto-update disabled. Updates will be skipped silently.');
|
|
4807
|
+
return;
|
|
4808
|
+
}
|
|
4809
|
+
if (subcommand === 'ask' || subcommand === 'prompt' || subcommand === 'reset' || subcommand === 'notify') {
|
|
4810
|
+
saveSessionPreferences({ autoUpdate: null });
|
|
4811
|
+
display.showInfo('Update preference reset. You will see a notification when updates are available.');
|
|
4812
|
+
return;
|
|
4813
|
+
}
|
|
4814
|
+
display.showWarning('Usage: /update [check|auto|skip|notify|status]');
|
|
4815
|
+
}
|
|
4780
4816
|
handleInitCommand(input) {
|
|
4781
4817
|
const tokens = input.split(/\s+/).slice(1);
|
|
4782
4818
|
const confirm = tokens[0]?.toLowerCase() === 'confirm';
|
|
@@ -5801,6 +5837,7 @@ export class InteractiveShell {
|
|
|
5801
5837
|
}
|
|
5802
5838
|
this.isProcessing = true;
|
|
5803
5839
|
this.uiUpdates.setMode('processing');
|
|
5840
|
+
this.streamingTokenCount = 0; // Reset token counter for new request
|
|
5804
5841
|
this.terminalInput.setStreaming(true);
|
|
5805
5842
|
// Keep the persistent input/control bar active as we transition into streaming.
|
|
5806
5843
|
this.syncRendererInput();
|
|
@@ -5815,6 +5852,7 @@ export class InteractiveShell {
|
|
|
5815
5852
|
this.currentTaskType = classifyTaskType(request);
|
|
5816
5853
|
this.currentToolCalls = [];
|
|
5817
5854
|
this.clearToolUsageMeta();
|
|
5855
|
+
this.renderer?.setActivity('Starting...');
|
|
5818
5856
|
this.uiAdapter.startProcessing('Working on your request');
|
|
5819
5857
|
this.setProcessingStatus();
|
|
5820
5858
|
this.beginAiRuntime();
|
|
@@ -5870,8 +5908,6 @@ export class InteractiveShell {
|
|
|
5870
5908
|
clearActionHistory();
|
|
5871
5909
|
this.lastFailure = null;
|
|
5872
5910
|
}
|
|
5873
|
-
// Run post-hoc AI flow sanity check to catch "looks right but wrong" responses
|
|
5874
|
-
this.analyzeAiFlowForRun(request, responseText, requestStartTime);
|
|
5875
5911
|
}
|
|
5876
5912
|
catch (error) {
|
|
5877
5913
|
const handled = this.handleProviderError(error, () => this.processRequest(request));
|
|
@@ -5891,7 +5927,7 @@ export class InteractiveShell {
|
|
|
5891
5927
|
this.responseRendered = true;
|
|
5892
5928
|
}
|
|
5893
5929
|
this.finishStreamingFormatter(undefined, { refreshPrompt: false, mode: 'complete' });
|
|
5894
|
-
display.stopThinking();
|
|
5930
|
+
display.stopThinking(false);
|
|
5895
5931
|
this.uiUpdates.setMode('processing');
|
|
5896
5932
|
this.stopStreamingHeartbeat('complete', { quiet: true });
|
|
5897
5933
|
this.endAiRuntime();
|
|
@@ -5942,6 +5978,7 @@ export class InteractiveShell {
|
|
|
5942
5978
|
this.clearToolUsageMeta();
|
|
5943
5979
|
this.isProcessing = true;
|
|
5944
5980
|
this.uiUpdates.setMode('processing');
|
|
5981
|
+
this.streamingTokenCount = 0; // Reset token counter for new request
|
|
5945
5982
|
this.terminalInput.setStreaming(true);
|
|
5946
5983
|
if (this.suppressNextNetworkReset) {
|
|
5947
5984
|
this.suppressNextNetworkReset = false;
|
|
@@ -6107,7 +6144,7 @@ What's the next action?`;
|
|
|
6107
6144
|
await new Promise(resolve => setTimeout(resolve, 500));
|
|
6108
6145
|
}
|
|
6109
6146
|
catch (error) {
|
|
6110
|
-
display.stopThinking();
|
|
6147
|
+
display.stopThinking(false);
|
|
6111
6148
|
// Handle context overflow specially - the agent should auto-recover
|
|
6112
6149
|
// but if it propagates here, we continue the loop
|
|
6113
6150
|
if (this.isContextOverflowError(error)) {
|
|
@@ -6349,43 +6386,6 @@ What's the next action?`;
|
|
|
6349
6386
|
const parts = candidates.filter((part) => typeof part === 'string' && part.trim().length > 0);
|
|
6350
6387
|
return parts.join('\n').trim();
|
|
6351
6388
|
}
|
|
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
|
-
}
|
|
6389
6389
|
runAutoQualityChecks(trigger, assistantResponse, verificationContext) {
|
|
6390
6390
|
if (!this.verificationEnabled) {
|
|
6391
6391
|
return;
|
|
@@ -6766,21 +6766,41 @@ Return ONLY JSON array:
|
|
|
6766
6766
|
maxTokens: this.sessionState.maxTokens,
|
|
6767
6767
|
systemPrompt: this.buildSystemPrompt(),
|
|
6768
6768
|
reasoningEffort: this.sessionState.reasoningEffort,
|
|
6769
|
-
autoContinue: this.autoContinueEnabled,
|
|
6770
6769
|
};
|
|
6771
6770
|
this.agent = this.runtimeSession.createAgent(selection, {
|
|
6771
|
+
onRequestReceived: (requestPreview) => {
|
|
6772
|
+
const normalized = requestPreview?.trim();
|
|
6773
|
+
const activity = normalized ? `Working: ${normalized}` : 'Working';
|
|
6774
|
+
this.renderer?.setActivity(activity);
|
|
6775
|
+
},
|
|
6776
|
+
onBeforeFirstToolCall: (toolNames) => {
|
|
6777
|
+
const primaryTool = toolNames[0];
|
|
6778
|
+
if (primaryTool) {
|
|
6779
|
+
this.renderer?.setActivity(`Running ${primaryTool}`);
|
|
6780
|
+
}
|
|
6781
|
+
return undefined;
|
|
6782
|
+
},
|
|
6772
6783
|
onStreamChunk: (chunk, type) => {
|
|
6773
6784
|
this.handleStreamChunk(chunk, type ?? 'content');
|
|
6774
6785
|
},
|
|
6775
|
-
onStreamFallback: (info) => this.handleStreamingFallback(info),
|
|
6776
6786
|
onAssistantMessage: (content, metadata) => {
|
|
6777
6787
|
const enriched = this.buildDisplayMetadata(metadata);
|
|
6778
6788
|
const streamedVisible = metadata.wasStreamed && this.streamingContentSeen && !this.streamingOutputSuppressed;
|
|
6779
6789
|
let renderedFinal = false;
|
|
6790
|
+
// Update streaming token count from usage info (more accurate than chunk counting)
|
|
6791
|
+
if (metadata.usage) {
|
|
6792
|
+
const totalTokens = this.totalTokens(metadata.usage);
|
|
6793
|
+
if (totalTokens !== null && totalTokens > this.streamingTokenCount) {
|
|
6794
|
+
this.streamingTokenCount = totalTokens;
|
|
6795
|
+
this.renderer?.updateStreamingTokens(this.streamingTokenCount);
|
|
6796
|
+
}
|
|
6797
|
+
}
|
|
6780
6798
|
// Update spinner based on message type
|
|
6781
6799
|
if (metadata.isFinal) {
|
|
6782
6800
|
const parsed = this.splitThinkingResponse(content);
|
|
6783
|
-
|
|
6801
|
+
// If we successfully parsed thinking, use the parsed response (may be empty)
|
|
6802
|
+
// Don't fall back to original content with raw <thinking> tags
|
|
6803
|
+
const finalContent = parsed ? parsed.response?.trim() : content;
|
|
6784
6804
|
const thoughtContent = parsed?.thinking?.trim() || null;
|
|
6785
6805
|
// Show the response if it wasn't already rendered during streaming
|
|
6786
6806
|
if (!streamedVisible) {
|
|
@@ -6862,7 +6882,7 @@ Return ONLY JSON array:
|
|
|
6862
6882
|
display.showSystemMessage(`⚡ Context Recovery (${attempt}/${maxAttempts}): ${message}`);
|
|
6863
6883
|
},
|
|
6864
6884
|
onContextPruned: (removedCount, stats) => {
|
|
6865
|
-
// Clear squish
|
|
6885
|
+
// Clear context squish status if active
|
|
6866
6886
|
this.statusTracker.clearOverride('context-squish');
|
|
6867
6887
|
// Show notification that context was pruned
|
|
6868
6888
|
const method = stats['method'];
|
|
@@ -6883,12 +6903,6 @@ Return ONLY JSON array:
|
|
|
6883
6903
|
this.updateStatusMessage('Retrying with reduced context...');
|
|
6884
6904
|
this.syncRendererInput();
|
|
6885
6905
|
},
|
|
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
|
-
},
|
|
6892
6906
|
onCancelled: () => {
|
|
6893
6907
|
// Update UI to show operation was cancelled
|
|
6894
6908
|
display.showWarning('Operation cancelled.');
|
|
@@ -6897,10 +6911,42 @@ Return ONLY JSON array:
|
|
|
6897
6911
|
this.updateStatusMessage(null);
|
|
6898
6912
|
this.terminalInput.setStreaming(false);
|
|
6899
6913
|
},
|
|
6914
|
+
onToolExecution: (toolName, isStart, args) => {
|
|
6915
|
+
// Update activity status to show what tool is being executed
|
|
6916
|
+
if (isStart) {
|
|
6917
|
+
// Show more specific activity for long-running tools
|
|
6918
|
+
let activity = `Running ${toolName}`;
|
|
6919
|
+
if (toolName === 'execute_bash' && args?.['command']) {
|
|
6920
|
+
const cmd = String(args['command']).slice(0, 40);
|
|
6921
|
+
activity = `$ ${cmd}${String(args['command']).length > 40 ? '...' : ''}`;
|
|
6922
|
+
}
|
|
6923
|
+
else if (toolName === 'read_file' && args?.['file_path']) {
|
|
6924
|
+
const path = String(args['file_path']).split('/').pop() || args['file_path'];
|
|
6925
|
+
activity = `Reading ${path}`;
|
|
6926
|
+
}
|
|
6927
|
+
this.renderer?.setActivity(activity);
|
|
6928
|
+
// Estimate tokens for tool call (~50 tokens per call)
|
|
6929
|
+
this.streamingTokenCount += 50;
|
|
6930
|
+
this.renderer?.updateStreamingTokens(this.streamingTokenCount);
|
|
6931
|
+
}
|
|
6932
|
+
else {
|
|
6933
|
+
// Tool finished - estimate result tokens (~100 per result)
|
|
6934
|
+
this.streamingTokenCount += 100;
|
|
6935
|
+
this.renderer?.updateStreamingTokens(this.streamingTokenCount);
|
|
6936
|
+
// Reset to thinking state while model generates next response
|
|
6937
|
+
this.renderer?.setActivity('Thinking');
|
|
6938
|
+
}
|
|
6939
|
+
},
|
|
6900
6940
|
onVerificationNeeded: (response, context) => {
|
|
6901
6941
|
this.lastAssistantResponse = response;
|
|
6902
6942
|
void this.runAutoQualityChecks('verification', response, context);
|
|
6903
6943
|
},
|
|
6944
|
+
// Retry notification for transient errors
|
|
6945
|
+
onRetrying: (attempt, maxAttempts, error) => {
|
|
6946
|
+
const shortError = error.message.slice(0, 100);
|
|
6947
|
+
display.showSystemMessage(`⚡ Retry ${attempt}/${maxAttempts}: ${shortError}${error.message.length > 100 ? '...' : ''}`);
|
|
6948
|
+
this.renderer?.setActivity(`Retrying (${attempt}/${maxAttempts})...`);
|
|
6949
|
+
},
|
|
6904
6950
|
});
|
|
6905
6951
|
// Register global AI enhancer for explore tool - uses active model by default
|
|
6906
6952
|
this.registerExploreAIEnhancer();
|
|
@@ -6995,20 +7041,30 @@ Return ONLY JSON array:
|
|
|
6995
7041
|
return lines.join('\n').trim();
|
|
6996
7042
|
}
|
|
6997
7043
|
buildThinkingDirective() {
|
|
7044
|
+
// Base requirement: ALWAYS think before acting (applies to all modes)
|
|
7045
|
+
const baseRequirement = [
|
|
7046
|
+
'CRITICAL: Before calling ANY tool, ALWAYS output a <thinking>...</thinking> block explaining:',
|
|
7047
|
+
'1. What you understand the user is asking',
|
|
7048
|
+
'2. Your approach/plan to solve it',
|
|
7049
|
+
'3. Which tools you will use and why',
|
|
7050
|
+
'This thinking block MUST appear before your first tool call in every response.',
|
|
7051
|
+
].join('\n');
|
|
6998
7052
|
switch (this.thinkingMode) {
|
|
6999
7053
|
case 'extended':
|
|
7000
7054
|
return [
|
|
7001
|
-
|
|
7002
|
-
'
|
|
7003
|
-
'
|
|
7004
|
-
'
|
|
7005
|
-
'<response
|
|
7006
|
-
'Final answer with actionable next steps and any code/commands requested.',
|
|
7007
|
-
'</response>',
|
|
7055
|
+
baseRequirement,
|
|
7056
|
+
'',
|
|
7057
|
+
'Extended thinking mode: Use detailed multi-step reasoning in your <thinking> block.',
|
|
7058
|
+
'Reference tool runs/files when relevant. Keep secrets redacted.',
|
|
7059
|
+
'After thinking, wrap your final answer in <response>...</response>.',
|
|
7008
7060
|
].join('\n');
|
|
7009
7061
|
case 'balanced':
|
|
7010
7062
|
default:
|
|
7011
|
-
return
|
|
7063
|
+
return [
|
|
7064
|
+
baseRequirement,
|
|
7065
|
+
'',
|
|
7066
|
+
'Balanced mode: Keep thinking concise (2-4 sentences) but always present.',
|
|
7067
|
+
].join('\n');
|
|
7012
7068
|
}
|
|
7013
7069
|
}
|
|
7014
7070
|
buildDisplayMetadata(metadata) {
|
|
@@ -7099,13 +7155,14 @@ Return ONLY JSON array:
|
|
|
7099
7155
|
});
|
|
7100
7156
|
cleanupOverlayActive = true;
|
|
7101
7157
|
const triggerReason = trigger?.reason ?? 'Context optimization';
|
|
7102
|
-
|
|
7103
|
-
|
|
7104
|
-
|
|
7105
|
-
|
|
7106
|
-
|
|
7107
|
-
|
|
7108
|
-
|
|
7158
|
+
// Claude Code style: Show "Compacting conversation..." with animation
|
|
7159
|
+
// The renderer handles the animated spinner display
|
|
7160
|
+
if (this.renderer) {
|
|
7161
|
+
this.renderer.showCompactingStatus('Compacting conversation… (esc to interrupt)');
|
|
7162
|
+
}
|
|
7163
|
+
else {
|
|
7164
|
+
display.showSystemMessage(`✻ Compacting conversation… (esc to interrupt)`);
|
|
7165
|
+
}
|
|
7109
7166
|
const result = await contextManager.intelligentCompact(history);
|
|
7110
7167
|
let afterStats = contextManager.getStats(result.compacted);
|
|
7111
7168
|
let appliedHistory = result.compacted;
|
|
@@ -7132,10 +7189,18 @@ Return ONLY JSON array:
|
|
|
7132
7189
|
}
|
|
7133
7190
|
}
|
|
7134
7191
|
if (!changed) {
|
|
7192
|
+
// Hide compacting status before showing info message
|
|
7193
|
+
if (this.renderer) {
|
|
7194
|
+
this.renderer.hideCompactingStatus();
|
|
7195
|
+
}
|
|
7135
7196
|
display.showInfo('Context compaction completed but no changes were applied.');
|
|
7136
7197
|
return;
|
|
7137
7198
|
}
|
|
7138
7199
|
if (!this.hasMeaningfulCompaction(changed, bestTokenSavings, bestPercentSavings, trigger?.forced)) {
|
|
7200
|
+
// Hide compacting status before showing info message
|
|
7201
|
+
if (this.renderer) {
|
|
7202
|
+
this.renderer.hideCompactingStatus();
|
|
7203
|
+
}
|
|
7139
7204
|
display.showInfo('Auto-compaction completed but did not meaningfully reduce context size. Keeping existing history.');
|
|
7140
7205
|
return;
|
|
7141
7206
|
}
|
|
@@ -7151,15 +7216,18 @@ Return ONLY JSON array:
|
|
|
7151
7216
|
this.updateContextUsage(newPercentUsed, CONTEXT_AUTOCOMPACT_PERCENT);
|
|
7152
7217
|
this.lastContextWarningLevel = this.getContextWarningLevel(newPercentUsed);
|
|
7153
7218
|
this.refreshStatusLine(true);
|
|
7154
|
-
|
|
7155
|
-
|
|
7156
|
-
|
|
7157
|
-
|
|
7158
|
-
|
|
7159
|
-
|
|
7160
|
-
|
|
7161
|
-
|
|
7162
|
-
|
|
7219
|
+
// Claude Code style: Show compaction complete with separator and ctrl+o hint
|
|
7220
|
+
// Stop the compacting animation first
|
|
7221
|
+
if (this.renderer) {
|
|
7222
|
+
this.renderer.hideCompactingStatus();
|
|
7223
|
+
}
|
|
7224
|
+
// Show the Claude Code style separator: ══ Conversation compacted · ctrl+o for history ═
|
|
7225
|
+
if (this.renderer) {
|
|
7226
|
+
this.renderer.addCompactBlock('', 'Conversation compacted · ctrl+o for history');
|
|
7227
|
+
}
|
|
7228
|
+
else {
|
|
7229
|
+
display.showSystemMessage('══ Conversation compacted · ctrl+o for history ═');
|
|
7230
|
+
}
|
|
7163
7231
|
this.recordContextCompaction({
|
|
7164
7232
|
timestamp: Date.now(),
|
|
7165
7233
|
source: trigger?.source ?? 'auto',
|
|
@@ -7173,12 +7241,20 @@ Return ONLY JSON array:
|
|
|
7173
7241
|
});
|
|
7174
7242
|
}
|
|
7175
7243
|
catch (error) {
|
|
7244
|
+
// Hide compacting status animation on error
|
|
7245
|
+
if (this.renderer) {
|
|
7246
|
+
this.renderer.hideCompactingStatus();
|
|
7247
|
+
}
|
|
7176
7248
|
display.showError('Context compaction failed.', error);
|
|
7177
7249
|
}
|
|
7178
7250
|
finally {
|
|
7179
7251
|
if (cleanupOverlayActive) {
|
|
7180
7252
|
this.statusTracker.clearOverride(cleanupStatusId);
|
|
7181
7253
|
}
|
|
7254
|
+
// Ensure compacting status is cleared
|
|
7255
|
+
if (this.renderer) {
|
|
7256
|
+
this.renderer.hideCompactingStatus();
|
|
7257
|
+
}
|
|
7182
7258
|
this.cleanupInProgress = false;
|
|
7183
7259
|
this.contextCompactionInFlight = false;
|
|
7184
7260
|
}
|
|
@@ -7480,28 +7556,6 @@ Return ONLY JSON array:
|
|
|
7480
7556
|
const message = error instanceof Error ? error.message : String(error);
|
|
7481
7557
|
display.showError(message);
|
|
7482
7558
|
}
|
|
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
|
-
}
|
|
7505
7559
|
handleProviderError(error, retryAction) {
|
|
7506
7560
|
const promptBlock = detectPromptBlockError(error);
|
|
7507
7561
|
if (promptBlock) {
|