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.
Files changed (213) hide show
  1. package/README.md +1 -1
  2. package/agents/erosolar-code.rules.json +2 -2
  3. package/agents/general.rules.json +21 -3
  4. package/dist/capabilities/askUserCapability.js +1 -1
  5. package/dist/capabilities/askUserCapability.js.map +1 -1
  6. package/dist/capabilities/statusCapability.js +2 -2
  7. package/dist/capabilities/statusCapability.js.map +1 -1
  8. package/dist/codex/capabilities/codexCoreCapability.d.ts +6 -0
  9. package/dist/codex/capabilities/codexCoreCapability.d.ts.map +1 -0
  10. package/dist/codex/capabilities/codexCoreCapability.js +516 -0
  11. package/dist/codex/capabilities/codexCoreCapability.js.map +1 -0
  12. package/dist/codex/fs.d.ts +4 -0
  13. package/dist/codex/fs.d.ts.map +1 -0
  14. package/dist/codex/fs.js +25 -0
  15. package/dist/codex/fs.js.map +1 -0
  16. package/dist/codex/persistence/planStore.d.ts +4 -0
  17. package/dist/codex/persistence/planStore.d.ts.map +1 -0
  18. package/dist/codex/persistence/planStore.js +59 -0
  19. package/dist/codex/persistence/planStore.js.map +1 -0
  20. package/dist/codex/pluginAllowlist.d.ts +4 -0
  21. package/dist/codex/pluginAllowlist.d.ts.map +1 -0
  22. package/dist/codex/pluginAllowlist.js +14 -0
  23. package/dist/codex/pluginAllowlist.js.map +1 -0
  24. package/dist/codex/types.d.ts +21 -0
  25. package/dist/codex/types.d.ts.map +1 -0
  26. package/dist/codex/types.js +62 -0
  27. package/dist/codex/types.js.map +1 -0
  28. package/dist/contracts/agent-schemas.json +5 -5
  29. package/dist/core/agent.d.ts +83 -24
  30. package/dist/core/agent.d.ts.map +1 -1
  31. package/dist/core/agent.js +499 -248
  32. package/dist/core/agent.js.map +1 -1
  33. package/dist/core/preferences.d.ts +1 -0
  34. package/dist/core/preferences.d.ts.map +1 -1
  35. package/dist/core/preferences.js +8 -1
  36. package/dist/core/preferences.js.map +1 -1
  37. package/dist/core/reliabilityPrompt.d.ts +9 -0
  38. package/dist/core/reliabilityPrompt.d.ts.map +1 -0
  39. package/dist/core/reliabilityPrompt.js +31 -0
  40. package/dist/core/reliabilityPrompt.js.map +1 -0
  41. package/dist/core/schemaValidator.js +3 -3
  42. package/dist/core/schemaValidator.js.map +1 -1
  43. package/dist/core/toolPreconditions.d.ts +0 -11
  44. package/dist/core/toolPreconditions.d.ts.map +1 -1
  45. package/dist/core/toolPreconditions.js +33 -164
  46. package/dist/core/toolPreconditions.js.map +1 -1
  47. package/dist/core/toolRuntime.d.ts.map +1 -1
  48. package/dist/core/toolRuntime.js +9 -114
  49. package/dist/core/toolRuntime.js.map +1 -1
  50. package/dist/core/updateChecker.d.ts +61 -1
  51. package/dist/core/updateChecker.d.ts.map +1 -1
  52. package/dist/core/updateChecker.js +147 -3
  53. package/dist/core/updateChecker.js.map +1 -1
  54. package/dist/headless/evalMode.d.ts.map +1 -1
  55. package/dist/headless/evalMode.js +6 -0
  56. package/dist/headless/evalMode.js.map +1 -1
  57. package/dist/headless/headlessApp.d.ts.map +1 -1
  58. package/dist/headless/headlessApp.js +6 -39
  59. package/dist/headless/headlessApp.js.map +1 -1
  60. package/dist/mcp/sseClient.d.ts +4 -1
  61. package/dist/mcp/sseClient.d.ts.map +1 -1
  62. package/dist/mcp/sseClient.js +36 -2
  63. package/dist/mcp/sseClient.js.map +1 -1
  64. package/dist/mcp/stdioClient.d.ts +4 -1
  65. package/dist/mcp/stdioClient.d.ts.map +1 -1
  66. package/dist/mcp/stdioClient.js +41 -1
  67. package/dist/mcp/stdioClient.js.map +1 -1
  68. package/dist/mcp/toolBridge.d.ts +3 -0
  69. package/dist/mcp/toolBridge.d.ts.map +1 -1
  70. package/dist/mcp/toolBridge.js +2 -2
  71. package/dist/mcp/toolBridge.js.map +1 -1
  72. package/dist/mcp/types.d.ts +18 -0
  73. package/dist/mcp/types.d.ts.map +1 -1
  74. package/dist/plugins/tools/nodeDefaults.d.ts.map +1 -1
  75. package/dist/plugins/tools/nodeDefaults.js +0 -2
  76. package/dist/plugins/tools/nodeDefaults.js.map +1 -1
  77. package/dist/providers/openaiResponsesProvider.d.ts.map +1 -1
  78. package/dist/providers/openaiResponsesProvider.js +79 -74
  79. package/dist/providers/openaiResponsesProvider.js.map +1 -1
  80. package/dist/runtime/agentController.d.ts.map +1 -1
  81. package/dist/runtime/agentController.js +6 -3
  82. package/dist/runtime/agentController.js.map +1 -1
  83. package/dist/runtime/agentSession.d.ts +0 -2
  84. package/dist/runtime/agentSession.d.ts.map +1 -1
  85. package/dist/runtime/agentSession.js +2 -2
  86. package/dist/runtime/agentSession.js.map +1 -1
  87. package/dist/shell/interactiveShell.d.ts +25 -18
  88. package/dist/shell/interactiveShell.d.ts.map +1 -1
  89. package/dist/shell/interactiveShell.js +345 -291
  90. package/dist/shell/interactiveShell.js.map +1 -1
  91. package/dist/shell/shellApp.d.ts.map +1 -1
  92. package/dist/shell/shellApp.js +15 -8
  93. package/dist/shell/shellApp.js.map +1 -1
  94. package/dist/shell/systemPrompt.d.ts.map +1 -1
  95. package/dist/shell/systemPrompt.js +4 -15
  96. package/dist/shell/systemPrompt.js.map +1 -1
  97. package/dist/subagents/taskRunner.js +2 -1
  98. package/dist/subagents/taskRunner.js.map +1 -1
  99. package/dist/tools/bashTools.d.ts.map +1 -1
  100. package/dist/tools/bashTools.js +101 -8
  101. package/dist/tools/bashTools.js.map +1 -1
  102. package/dist/tools/diffUtils.d.ts +8 -2
  103. package/dist/tools/diffUtils.d.ts.map +1 -1
  104. package/dist/tools/diffUtils.js +72 -13
  105. package/dist/tools/diffUtils.js.map +1 -1
  106. package/dist/tools/grepTools.d.ts.map +1 -1
  107. package/dist/tools/grepTools.js +10 -2
  108. package/dist/tools/grepTools.js.map +1 -1
  109. package/dist/tools/planningTools.d.ts +0 -10
  110. package/dist/tools/planningTools.d.ts.map +1 -1
  111. package/dist/tools/planningTools.js +0 -16
  112. package/dist/tools/planningTools.js.map +1 -1
  113. package/dist/tools/searchTools.d.ts.map +1 -1
  114. package/dist/tools/searchTools.js +4 -2
  115. package/dist/tools/searchTools.js.map +1 -1
  116. package/dist/ui/PromptController.d.ts +7 -4
  117. package/dist/ui/PromptController.d.ts.map +1 -1
  118. package/dist/ui/PromptController.js +4 -7
  119. package/dist/ui/PromptController.js.map +1 -1
  120. package/dist/ui/ShellUIAdapter.d.ts +286 -28
  121. package/dist/ui/ShellUIAdapter.d.ts.map +1 -1
  122. package/dist/ui/ShellUIAdapter.js +1485 -121
  123. package/dist/ui/ShellUIAdapter.js.map +1 -1
  124. package/dist/ui/UnifiedUIController.d.ts +80 -0
  125. package/dist/ui/UnifiedUIController.d.ts.map +1 -0
  126. package/dist/ui/UnifiedUIController.js +211 -0
  127. package/dist/ui/UnifiedUIController.js.map +1 -0
  128. package/dist/ui/UnifiedUIRenderer.d.ts +102 -46
  129. package/dist/ui/UnifiedUIRenderer.d.ts.map +1 -1
  130. package/dist/ui/UnifiedUIRenderer.js +680 -610
  131. package/dist/ui/UnifiedUIRenderer.js.map +1 -1
  132. package/dist/ui/animatedStatus.d.ts +128 -6
  133. package/dist/ui/animatedStatus.d.ts.map +1 -1
  134. package/dist/ui/animatedStatus.js +383 -50
  135. package/dist/ui/animatedStatus.js.map +1 -1
  136. package/dist/ui/animation/AnimationScheduler.d.ts +192 -0
  137. package/dist/ui/animation/AnimationScheduler.d.ts.map +1 -0
  138. package/dist/ui/animation/AnimationScheduler.js +432 -0
  139. package/dist/ui/animation/AnimationScheduler.js.map +1 -0
  140. package/dist/ui/display.d.ts +179 -25
  141. package/dist/ui/display.d.ts.map +1 -1
  142. package/dist/ui/display.js +678 -96
  143. package/dist/ui/display.js.map +1 -1
  144. package/dist/ui/inPlaceUpdater.d.ts +181 -0
  145. package/dist/ui/inPlaceUpdater.d.ts.map +1 -0
  146. package/dist/ui/inPlaceUpdater.js +515 -0
  147. package/dist/ui/inPlaceUpdater.js.map +1 -0
  148. package/dist/ui/interrupts/InterruptManager.d.ts +142 -0
  149. package/dist/ui/interrupts/InterruptManager.d.ts.map +1 -0
  150. package/dist/ui/interrupts/InterruptManager.js +439 -0
  151. package/dist/ui/interrupts/InterruptManager.js.map +1 -0
  152. package/dist/ui/layout.d.ts +0 -1
  153. package/dist/ui/layout.d.ts.map +1 -1
  154. package/dist/ui/layout.js +0 -12
  155. package/dist/ui/layout.js.map +1 -1
  156. package/dist/ui/orchestration/StatusOrchestrator.d.ts +1 -1
  157. package/dist/ui/orchestration/StatusOrchestrator.js +1 -1
  158. package/dist/ui/orchestration/UIUpdateCoordinator.d.ts +61 -7
  159. package/dist/ui/orchestration/UIUpdateCoordinator.d.ts.map +1 -1
  160. package/dist/ui/orchestration/UIUpdateCoordinator.js +232 -20
  161. package/dist/ui/orchestration/UIUpdateCoordinator.js.map +1 -1
  162. package/dist/ui/shortcutsHelp.d.ts.map +1 -1
  163. package/dist/ui/shortcutsHelp.js +0 -1
  164. package/dist/ui/shortcutsHelp.js.map +1 -1
  165. package/dist/ui/telemetry/ResponseTracker.d.ts +22 -0
  166. package/dist/ui/telemetry/ResponseTracker.d.ts.map +1 -0
  167. package/dist/ui/telemetry/ResponseTracker.js +60 -0
  168. package/dist/ui/telemetry/ResponseTracker.js.map +1 -0
  169. package/dist/ui/telemetry/UITelemetry.d.ts +181 -0
  170. package/dist/ui/telemetry/UITelemetry.d.ts.map +1 -0
  171. package/dist/ui/telemetry/UITelemetry.js +446 -0
  172. package/dist/ui/telemetry/UITelemetry.js.map +1 -0
  173. package/dist/ui/unified/index.d.ts +28 -1
  174. package/dist/ui/unified/index.d.ts.map +1 -1
  175. package/dist/ui/unified/index.js +41 -2
  176. package/dist/ui/unified/index.js.map +1 -1
  177. package/dist/ui/unified/layout.d.ts +12 -0
  178. package/dist/ui/unified/layout.d.ts.map +1 -0
  179. package/dist/ui/unified/layout.js +96 -0
  180. package/dist/ui/unified/layout.js.map +1 -0
  181. package/package.json +1 -2
  182. package/dist/StringUtils.d.ts +0 -8
  183. package/dist/StringUtils.d.ts.map +0 -1
  184. package/dist/StringUtils.js +0 -11
  185. package/dist/StringUtils.js.map +0 -1
  186. package/dist/core/aiFlowSupervisor.d.ts +0 -44
  187. package/dist/core/aiFlowSupervisor.d.ts.map +0 -1
  188. package/dist/core/aiFlowSupervisor.js +0 -299
  189. package/dist/core/aiFlowSupervisor.js.map +0 -1
  190. package/dist/core/cliTestHarness.d.ts +0 -200
  191. package/dist/core/cliTestHarness.d.ts.map +0 -1
  192. package/dist/core/cliTestHarness.js +0 -549
  193. package/dist/core/cliTestHarness.js.map +0 -1
  194. package/dist/core/testUtils.d.ts +0 -121
  195. package/dist/core/testUtils.d.ts.map +0 -1
  196. package/dist/core/testUtils.js +0 -235
  197. package/dist/core/testUtils.js.map +0 -1
  198. package/dist/core/toolValidation.d.ts +0 -116
  199. package/dist/core/toolValidation.d.ts.map +0 -1
  200. package/dist/core/toolValidation.js +0 -282
  201. package/dist/core/toolValidation.js.map +0 -1
  202. package/dist/ui/planOverlay.d.ts +0 -28
  203. package/dist/ui/planOverlay.d.ts.map +0 -1
  204. package/dist/ui/planOverlay.js +0 -156
  205. package/dist/ui/planOverlay.js.map +0 -1
  206. package/dist/ui/streamingFormatter.d.ts +0 -30
  207. package/dist/ui/streamingFormatter.d.ts.map +0 -1
  208. package/dist/ui/streamingFormatter.js +0 -91
  209. package/dist/ui/streamingFormatter.js.map +0 -1
  210. package/dist/utils/errorUtils.d.ts +0 -16
  211. package/dist/utils/errorUtils.d.ts.map +0 -1
  212. package/dist/utils/errorUtils.js +0 -66
  213. 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, setPlanUpdateCallback } from '../tools/planningTools.js';
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.shutdown(),
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 = clamp('⟣╍◎╍⟢');
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
- const welcomeLine = clamp(`Welcome back ${userName}!`);
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, formatUpdateNotification, maybeAutoUpdate } = await import('../core/updateChecker.js');
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
- const updateResult = await maybeAutoUpdate(currentVersion, {
530
- updateInfo,
531
- logger: (message) => this.streamEventBlock(message),
532
- });
533
- if (updateResult?.updated) {
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
- const note = this.describeUpdateSkipReason(updateResult);
537
- const notification = formatUpdateNotification(updateInfo, note);
538
- this.streamEventBlock(notification);
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
- // Ensure the prompt/control bar is rendered after the welcome banner
631
- this.ensureReadlineReady();
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.ensureReadlineReady();
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
- 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
- }
954
+ /**
955
+ * Toggle network access on/off (Ctrl+Shift+N shortcut).
956
+ */
957
+ toggleNetworkAccess() {
958
+ this.networkEnabled = !this.networkEnabled;
902
959
  this.refreshControlBar();
903
- if (!changed && source === 'shortcut') {
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
- 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.`);
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: display.isSpinnerActive(),
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
- // Suppress raw streaming lines; show only a single summary/status + final response
1948
- this.streamingOutputSuppressed = true;
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
- // 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
- }
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 chunks bypass this so the thought process stays visible.
2032
- if (this.streamingOutputSuppressed && !isReasoning) {
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
- 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);
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
- if (formatted) {
2047
- if (formatted.trim().length > 0) {
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(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
- }
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
- const finalContent = parsed?.response?.trim() || content;
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 overlay if active
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
- '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>',
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 'Balanced thinking mode: include a short <thinking>...</thinking> block before <response> when the reasoning is non-trivial; skip it for simple answers.';
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
- 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(' '));
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
- 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(' '));
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) {