erosolar-cli 2.1.171 → 2.1.172

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (209) hide show
  1. package/README.md +1 -1
  2. package/agents/erosolar-code.rules.json +2 -2
  3. package/agents/general.rules.json +3 -21
  4. package/dist/StringUtils.d.ts +8 -0
  5. package/dist/StringUtils.d.ts.map +1 -0
  6. package/dist/StringUtils.js +11 -0
  7. package/dist/StringUtils.js.map +1 -0
  8. package/dist/capabilities/statusCapability.js +2 -2
  9. package/dist/capabilities/statusCapability.js.map +1 -1
  10. package/dist/contracts/agent-schemas.json +5 -5
  11. package/dist/core/agent.d.ts +24 -83
  12. package/dist/core/agent.d.ts.map +1 -1
  13. package/dist/core/agent.js +248 -499
  14. package/dist/core/agent.js.map +1 -1
  15. package/dist/core/aiFlowSupervisor.d.ts +44 -0
  16. package/dist/core/aiFlowSupervisor.d.ts.map +1 -0
  17. package/dist/core/aiFlowSupervisor.js +299 -0
  18. package/dist/core/aiFlowSupervisor.js.map +1 -0
  19. package/dist/core/cliTestHarness.d.ts +200 -0
  20. package/dist/core/cliTestHarness.d.ts.map +1 -0
  21. package/dist/core/cliTestHarness.js +549 -0
  22. package/dist/core/cliTestHarness.js.map +1 -0
  23. package/dist/core/preferences.d.ts +0 -1
  24. package/dist/core/preferences.d.ts.map +1 -1
  25. package/dist/core/preferences.js +1 -8
  26. package/dist/core/preferences.js.map +1 -1
  27. package/dist/core/schemaValidator.js +3 -3
  28. package/dist/core/schemaValidator.js.map +1 -1
  29. package/dist/core/testUtils.d.ts +121 -0
  30. package/dist/core/testUtils.d.ts.map +1 -0
  31. package/dist/core/testUtils.js +235 -0
  32. package/dist/core/testUtils.js.map +1 -0
  33. package/dist/core/toolPreconditions.d.ts +11 -0
  34. package/dist/core/toolPreconditions.d.ts.map +1 -1
  35. package/dist/core/toolPreconditions.js +164 -33
  36. package/dist/core/toolPreconditions.js.map +1 -1
  37. package/dist/core/toolRuntime.d.ts.map +1 -1
  38. package/dist/core/toolRuntime.js +114 -9
  39. package/dist/core/toolRuntime.js.map +1 -1
  40. package/dist/core/toolValidation.d.ts +116 -0
  41. package/dist/core/toolValidation.d.ts.map +1 -0
  42. package/dist/core/toolValidation.js +282 -0
  43. package/dist/core/toolValidation.js.map +1 -0
  44. package/dist/core/updateChecker.d.ts +1 -61
  45. package/dist/core/updateChecker.d.ts.map +1 -1
  46. package/dist/core/updateChecker.js +3 -147
  47. package/dist/core/updateChecker.js.map +1 -1
  48. package/dist/headless/evalMode.d.ts.map +1 -1
  49. package/dist/headless/evalMode.js +0 -6
  50. package/dist/headless/evalMode.js.map +1 -1
  51. package/dist/headless/headlessApp.d.ts.map +1 -1
  52. package/dist/headless/headlessApp.js +39 -6
  53. package/dist/headless/headlessApp.js.map +1 -1
  54. package/dist/mcp/sseClient.d.ts +1 -4
  55. package/dist/mcp/sseClient.d.ts.map +1 -1
  56. package/dist/mcp/sseClient.js +2 -36
  57. package/dist/mcp/sseClient.js.map +1 -1
  58. package/dist/mcp/stdioClient.d.ts +1 -4
  59. package/dist/mcp/stdioClient.d.ts.map +1 -1
  60. package/dist/mcp/stdioClient.js +1 -41
  61. package/dist/mcp/stdioClient.js.map +1 -1
  62. package/dist/mcp/toolBridge.d.ts +0 -3
  63. package/dist/mcp/toolBridge.d.ts.map +1 -1
  64. package/dist/mcp/toolBridge.js +2 -2
  65. package/dist/mcp/toolBridge.js.map +1 -1
  66. package/dist/mcp/types.d.ts +0 -18
  67. package/dist/mcp/types.d.ts.map +1 -1
  68. package/dist/plugins/tools/nodeDefaults.d.ts.map +1 -1
  69. package/dist/plugins/tools/nodeDefaults.js +2 -0
  70. package/dist/plugins/tools/nodeDefaults.js.map +1 -1
  71. package/dist/providers/openaiResponsesProvider.d.ts.map +1 -1
  72. package/dist/providers/openaiResponsesProvider.js +74 -79
  73. package/dist/providers/openaiResponsesProvider.js.map +1 -1
  74. package/dist/runtime/agentController.d.ts.map +1 -1
  75. package/dist/runtime/agentController.js +3 -6
  76. package/dist/runtime/agentController.js.map +1 -1
  77. package/dist/runtime/agentSession.d.ts +2 -0
  78. package/dist/runtime/agentSession.d.ts.map +1 -1
  79. package/dist/runtime/agentSession.js +2 -2
  80. package/dist/runtime/agentSession.js.map +1 -1
  81. package/dist/shell/interactiveShell.d.ts +18 -20
  82. package/dist/shell/interactiveShell.d.ts.map +1 -1
  83. package/dist/shell/interactiveShell.js +291 -329
  84. package/dist/shell/interactiveShell.js.map +1 -1
  85. package/dist/shell/shellApp.d.ts.map +1 -1
  86. package/dist/shell/shellApp.js +8 -16
  87. package/dist/shell/shellApp.js.map +1 -1
  88. package/dist/shell/systemPrompt.d.ts.map +1 -1
  89. package/dist/shell/systemPrompt.js +15 -4
  90. package/dist/shell/systemPrompt.js.map +1 -1
  91. package/dist/subagents/taskRunner.js +1 -2
  92. package/dist/subagents/taskRunner.js.map +1 -1
  93. package/dist/tools/bashTools.d.ts.map +1 -1
  94. package/dist/tools/bashTools.js +8 -101
  95. package/dist/tools/bashTools.js.map +1 -1
  96. package/dist/tools/diffUtils.d.ts +2 -8
  97. package/dist/tools/diffUtils.d.ts.map +1 -1
  98. package/dist/tools/diffUtils.js +13 -72
  99. package/dist/tools/diffUtils.js.map +1 -1
  100. package/dist/tools/grepTools.d.ts.map +1 -1
  101. package/dist/tools/grepTools.js +2 -10
  102. package/dist/tools/grepTools.js.map +1 -1
  103. package/dist/tools/planningTools.d.ts +10 -0
  104. package/dist/tools/planningTools.d.ts.map +1 -1
  105. package/dist/tools/planningTools.js +16 -0
  106. package/dist/tools/planningTools.js.map +1 -1
  107. package/dist/tools/searchTools.d.ts.map +1 -1
  108. package/dist/tools/searchTools.js +2 -4
  109. package/dist/tools/searchTools.js.map +1 -1
  110. package/dist/ui/PromptController.d.ts +4 -4
  111. package/dist/ui/PromptController.d.ts.map +1 -1
  112. package/dist/ui/PromptController.js +7 -1
  113. package/dist/ui/PromptController.js.map +1 -1
  114. package/dist/ui/ShellUIAdapter.d.ts +28 -292
  115. package/dist/ui/ShellUIAdapter.d.ts.map +1 -1
  116. package/dist/ui/ShellUIAdapter.js +121 -1513
  117. package/dist/ui/ShellUIAdapter.js.map +1 -1
  118. package/dist/ui/UnifiedUIRenderer.d.ts +30 -136
  119. package/dist/ui/UnifiedUIRenderer.d.ts.map +1 -1
  120. package/dist/ui/UnifiedUIRenderer.js +370 -955
  121. package/dist/ui/UnifiedUIRenderer.js.map +1 -1
  122. package/dist/ui/animatedStatus.d.ts +6 -128
  123. package/dist/ui/animatedStatus.d.ts.map +1 -1
  124. package/dist/ui/animatedStatus.js +50 -383
  125. package/dist/ui/animatedStatus.js.map +1 -1
  126. package/dist/ui/display.d.ts +26 -182
  127. package/dist/ui/display.d.ts.map +1 -1
  128. package/dist/ui/display.js +97 -678
  129. package/dist/ui/display.js.map +1 -1
  130. package/dist/ui/layout.d.ts +1 -0
  131. package/dist/ui/layout.d.ts.map +1 -1
  132. package/dist/ui/layout.js +12 -0
  133. package/dist/ui/layout.js.map +1 -1
  134. package/dist/ui/orchestration/UIUpdateCoordinator.d.ts +7 -61
  135. package/dist/ui/orchestration/UIUpdateCoordinator.d.ts.map +1 -1
  136. package/dist/ui/orchestration/UIUpdateCoordinator.js +20 -232
  137. package/dist/ui/orchestration/UIUpdateCoordinator.js.map +1 -1
  138. package/dist/ui/planOverlay.d.ts +28 -0
  139. package/dist/ui/planOverlay.d.ts.map +1 -0
  140. package/dist/ui/planOverlay.js +156 -0
  141. package/dist/ui/planOverlay.js.map +1 -0
  142. package/dist/ui/shortcutsHelp.d.ts.map +1 -1
  143. package/dist/ui/shortcutsHelp.js +1 -0
  144. package/dist/ui/shortcutsHelp.js.map +1 -1
  145. package/dist/ui/streamingFormatter.d.ts +30 -0
  146. package/dist/ui/streamingFormatter.d.ts.map +1 -0
  147. package/dist/ui/streamingFormatter.js +91 -0
  148. package/dist/ui/streamingFormatter.js.map +1 -0
  149. package/dist/ui/unified/index.d.ts +1 -30
  150. package/dist/ui/unified/index.d.ts.map +1 -1
  151. package/dist/ui/unified/index.js +2 -45
  152. package/dist/ui/unified/index.js.map +1 -1
  153. package/dist/utils/errorUtils.d.ts +16 -0
  154. package/dist/utils/errorUtils.d.ts.map +1 -0
  155. package/dist/utils/errorUtils.js +66 -0
  156. package/dist/utils/errorUtils.js.map +1 -0
  157. package/package.json +2 -1
  158. package/dist/codex/capabilities/codexCoreCapability.d.ts +0 -6
  159. package/dist/codex/capabilities/codexCoreCapability.d.ts.map +0 -1
  160. package/dist/codex/capabilities/codexCoreCapability.js +0 -516
  161. package/dist/codex/capabilities/codexCoreCapability.js.map +0 -1
  162. package/dist/codex/fs.d.ts +0 -4
  163. package/dist/codex/fs.d.ts.map +0 -1
  164. package/dist/codex/fs.js +0 -25
  165. package/dist/codex/fs.js.map +0 -1
  166. package/dist/codex/persistence/planStore.d.ts +0 -4
  167. package/dist/codex/persistence/planStore.d.ts.map +0 -1
  168. package/dist/codex/persistence/planStore.js +0 -59
  169. package/dist/codex/persistence/planStore.js.map +0 -1
  170. package/dist/codex/pluginAllowlist.d.ts +0 -4
  171. package/dist/codex/pluginAllowlist.d.ts.map +0 -1
  172. package/dist/codex/pluginAllowlist.js +0 -14
  173. package/dist/codex/pluginAllowlist.js.map +0 -1
  174. package/dist/codex/types.d.ts +0 -21
  175. package/dist/codex/types.d.ts.map +0 -1
  176. package/dist/codex/types.js +0 -62
  177. package/dist/codex/types.js.map +0 -1
  178. package/dist/core/reliabilityPrompt.d.ts +0 -9
  179. package/dist/core/reliabilityPrompt.d.ts.map +0 -1
  180. package/dist/core/reliabilityPrompt.js +0 -31
  181. package/dist/core/reliabilityPrompt.js.map +0 -1
  182. package/dist/ui/UnifiedUIController.d.ts +0 -81
  183. package/dist/ui/UnifiedUIController.d.ts.map +0 -1
  184. package/dist/ui/UnifiedUIController.js +0 -212
  185. package/dist/ui/UnifiedUIController.js.map +0 -1
  186. package/dist/ui/animation/AnimationScheduler.d.ts +0 -192
  187. package/dist/ui/animation/AnimationScheduler.d.ts.map +0 -1
  188. package/dist/ui/animation/AnimationScheduler.js +0 -432
  189. package/dist/ui/animation/AnimationScheduler.js.map +0 -1
  190. package/dist/ui/inPlaceUpdater.d.ts +0 -181
  191. package/dist/ui/inPlaceUpdater.d.ts.map +0 -1
  192. package/dist/ui/inPlaceUpdater.js +0 -515
  193. package/dist/ui/inPlaceUpdater.js.map +0 -1
  194. package/dist/ui/interrupts/InterruptManager.d.ts +0 -142
  195. package/dist/ui/interrupts/InterruptManager.d.ts.map +0 -1
  196. package/dist/ui/interrupts/InterruptManager.js +0 -439
  197. package/dist/ui/interrupts/InterruptManager.js.map +0 -1
  198. package/dist/ui/telemetry/ResponseTracker.d.ts +0 -22
  199. package/dist/ui/telemetry/ResponseTracker.d.ts.map +0 -1
  200. package/dist/ui/telemetry/ResponseTracker.js +0 -60
  201. package/dist/ui/telemetry/ResponseTracker.js.map +0 -1
  202. package/dist/ui/telemetry/UITelemetry.d.ts +0 -181
  203. package/dist/ui/telemetry/UITelemetry.d.ts.map +0 -1
  204. package/dist/ui/telemetry/UITelemetry.js +0 -446
  205. package/dist/ui/telemetry/UITelemetry.js.map +0 -1
  206. package/dist/ui/unified/layout.d.ts +0 -12
  207. package/dist/ui/unified/layout.d.ts.map +0 -1
  208. package/dist/ui/unified/layout.js +0 -96
  209. package/dist/ui/unified/layout.js.map +0 -1
@@ -7,6 +7,7 @@ import { join, resolve } from 'node:path';
7
7
  import { display } from '../ui/display.js';
8
8
  import { theme } from '../ui/theme.js';
9
9
  import { getTerminalColumns } from '../ui/layout.js';
10
+ import { StreamingResponseFormatter } from '../ui/streamingFormatter.js';
10
11
  import { getContextWindowTokens } from '../core/contextWindow.js';
11
12
  import { ensureSecretForProvider, getSecretDefinitionForProvider, getSecretValue, listSecretDefinitions, maskSecret, setSecretValue, } from '../core/secretStore.js';
12
13
  import { saveActiveProfilePreference, saveModelPreference, loadToolSettings, saveToolSettings, clearToolSettings, clearActiveProfilePreference, loadSessionPreferences, saveSessionPreferences, loadFeatureFlags, saveFeatureFlags, toggleFeatureFlag, FEATURE_FLAG_INFO, } from '../core/preferences.js';
@@ -18,6 +19,7 @@ import { detectNetworkError } from '../core/errors/networkErrors.js';
18
19
  import { buildWorkspaceContext } from '../workspace.js';
19
20
  import { buildInteractiveSystemPrompt } from './systemPrompt.js';
20
21
  import { getTaskCompletionDetector, resetTaskCompletionDetector, } from './taskCompletionDetector.js';
22
+ import { assessAiFlow } from '../core/aiFlowSupervisor.js';
21
23
  import { discoverAllModels, quickCheckProviders, getCachedDiscoveredModels, sortModelsByPriority } from '../core/modelDiscovery.js';
22
24
  import { getModels, getSlashCommands, getProviders } from '../core/agentSchemaLoader.js';
23
25
  import { loadMcpServers } from '../mcp/config.js';
@@ -27,7 +29,7 @@ import { SkillRepository } from '../skills/skillRepository.js';
27
29
  import { createSkillTools } from '../tools/skillTools.js';
28
30
  import { FileChangeTracker } from './fileChangeTracker.js';
29
31
  import { formatShortcutsHelp } from '../ui/shortcutsHelp.js';
30
- import { setPlanApprovalCallback } from '../tools/planningTools.js';
32
+ import { setPlanApprovalCallback, setPlanUpdateCallback } from '../tools/planningTools.js';
31
33
  import { MetricsTracker } from '../core/metricsTracker.js';
32
34
  import { detectFailure, clearActionHistory, findRecoveryStrategy, } from '../core/failureRecovery.js';
33
35
  import { addToolPattern } from '../core/learningPersistence.js';
@@ -40,6 +42,7 @@ import { startOffsecRun, resumeOffsecRun, recordOffsecOutcome, getOffsecNextActi
40
42
  import { generateTestFlows, detectBugs, detectUIUpdates, saveTestFlows, saveBugReports, saveUIUpdates, getTestFlowStatus, } from '../core/intelligentTestFlows.js';
41
43
  import { PromptController } from '../ui/PromptController.js';
42
44
  import { enterStreamingMode, exitStreamingMode, isStreamingMode } from '../ui/globalWriteLock.js';
45
+ import { PlanOverlay } from '../ui/planOverlay.js';
43
46
  import { setGlobalAIEnhancer } from '../tools/localExplore.js';
44
47
  import { createProvider } from '../providers/providerFactory.js';
45
48
  import { getParallelAgentManager } from '../subagents/parallelAgentManager.js';
@@ -90,7 +93,6 @@ export class InteractiveShell {
90
93
  profile;
91
94
  profileLabel;
92
95
  workingDir;
93
- codexContext;
94
96
  runtimeSession;
95
97
  baseSystemPrompt;
96
98
  workspaceOptions;
@@ -130,14 +132,16 @@ export class InteractiveShell {
130
132
  activeContextWindowTokens = null;
131
133
  latestTokenUsage = { used: null, limit: null };
132
134
  planApprovalBridgeRegistered = false;
135
+ planUpdateBridgeRegistered = false;
136
+ planOverlay = new PlanOverlay();
133
137
  contextCompactionInFlight = false;
134
138
  contextCompactionLog = [];
135
139
  lastContextWarningLevel = null;
136
140
  sessionPreferences;
137
141
  autosaveEnabled;
142
+ autoContinueEnabled;
138
143
  verificationEnabled = false;
139
144
  criticalApprovalMode = 'auto';
140
- codexApprovalForced = false;
141
145
  editGuardMode = 'display-edits';
142
146
  pendingPermissionInput = null;
143
147
  suppressNextNetworkReset = false;
@@ -181,6 +185,7 @@ export class InteractiveShell {
181
185
  streamingOutputSuppressed = false;
182
186
  aiRuntimeStart = null;
183
187
  aiRuntimeTotalMs = 0;
188
+ streamingFormatter = null;
184
189
  streamingThoughtBuffer = '';
185
190
  statusLineState = null;
186
191
  statusMessageOverride = null;
@@ -192,7 +197,6 @@ export class InteractiveShell {
192
197
  version;
193
198
  alternateScreenEnabled;
194
199
  welcomeShown = false;
195
- guardrailBannerShown = false;
196
200
  renderer;
197
201
  // Message queue for streaming mode coordination - prevents race conditions
198
202
  pendingMessages = [];
@@ -200,18 +204,14 @@ export class InteractiveShell {
200
204
  this.profile = config.profile;
201
205
  this.profileLabel = config.profileLabel;
202
206
  this.workingDir = config.workingDir;
203
- this.codexContext = config.codexContext;
204
207
  this.runtimeSession = config.session;
205
208
  this.baseSystemPrompt = config.baseSystemPrompt;
206
209
  this.workspaceOptions = { ...config.workspaceOptions };
207
210
  this.sessionPreferences = loadSessionPreferences();
208
211
  this.thinkingMode = this.sessionPreferences.thinkingMode;
209
212
  this.autosaveEnabled = this.sessionPreferences.autosave;
213
+ this.autoContinueEnabled = this.sessionPreferences.autoContinue;
210
214
  this.criticalApprovalMode = this.sessionPreferences.criticalApprovalMode;
211
- if (this.codexContext && this.shouldForceApprovalMode(this.codexContext.approvalPolicy)) {
212
- this.criticalApprovalMode = 'approval';
213
- this.codexApprovalForced = true;
214
- }
215
215
  const featureFlags = loadFeatureFlags();
216
216
  this.verificationEnabled = featureFlags.verification === true;
217
217
  this.sessionRestoreConfig = config.sessionRestore ?? { mode: 'none' };
@@ -269,6 +269,11 @@ export class InteractiveShell {
269
269
  description: 'Switch between auto and approval mode for high-impact actions',
270
270
  category: 'configuration',
271
271
  });
272
+ this.slashCommands.push({
273
+ command: '/plan',
274
+ description: 'Show, hide, or clear the current plan panel (/plan show|hide|clear)',
275
+ category: 'workflow',
276
+ });
272
277
  this.slashCommands.push({
273
278
  command: '/offsec',
274
279
  description: 'AlphaZero offensive security run (start/status/next)',
@@ -287,10 +292,6 @@ export class InteractiveShell {
287
292
  this.uiAdapter.setToolStatusCallback((status) => {
288
293
  this.updateStatusMessage(status ?? null);
289
294
  });
290
- // Set up activity callback to update activity line with streaming bash output
291
- this.uiAdapter.setActivityCallback((activity) => {
292
- this.renderer?.setActivity(activity);
293
- });
294
295
  this.skillRepository = new SkillRepository({
295
296
  workingDir: this.workingDir,
296
297
  env: process.env,
@@ -304,18 +305,20 @@ export class InteractiveShell {
304
305
  onQueue: (text) => this.handleQueuedInput(text),
305
306
  onCtrlC: ({ hadBuffer }) => this.handleCtrlCPress(hadBuffer),
306
307
  onInterrupt: () => this.handleInterrupt(),
307
- onExit: () => this.handleExit(),
308
+ onExit: () => this.shutdown(),
309
+ onExpandToolResult: () => display.expandLastToolResult?.(),
308
310
  onChange: ({ text, cursor }) => this.handleInputChange(text, cursor, 'renderer'),
309
311
  onEditModeChange: (mode) => this.handleEditModeChange(mode),
310
312
  onToggleVerify: () => this.toggleVerificationMode(),
313
+ onToggleAutoContinue: () => this.toggleAutoContinueMode(),
311
314
  onToggleThinking: () => this.cycleThinkingMode(),
312
315
  onClearContext: () => this.handleClearContext(),
313
316
  onToggleCriticalApproval: () => this.toggleCriticalApprovalMode('shortcut'),
314
- onExpandToolResult: () => this.expandLastToolResult(),
315
317
  });
316
318
  // Share renderer with Display so all output flows through the unified queue
317
319
  this.renderer = this.terminalInput.getRenderer();
318
320
  display.setRenderer(this.renderer);
321
+ this.uiAdapter.attachRenderer(this.renderer);
319
322
  display.setInlinePanelHandler((content) => {
320
323
  if (!this.shouldCaptureInlinePanel()) {
321
324
  return false;
@@ -335,6 +338,7 @@ export class InteractiveShell {
335
338
  // The control bar will be refreshed after the welcome banner
336
339
  // Allow planning tools (e.g., ProposePlan) to open the interactive approval UI just like Codex CLI
337
340
  this.registerPlanApprovalBridge();
341
+ this.registerPlanUpdateBridge();
338
342
  // Set up command autocomplete with all slash commands
339
343
  this.setupCommandAutocomplete();
340
344
  this.rebuildAgent();
@@ -455,21 +459,18 @@ export class InteractiveShell {
455
459
  const top = `╭${'─'.repeat(labelLeft)}${label}${'─'.repeat(labelRight)}╮`;
456
460
  const bottom = `╰${'─'.repeat(contentWidth)}╯`;
457
461
  const userName = process.env['USER'] || 'there';
458
- const logo = '⟣╍◎╍⟢';
462
+ const logo = clamp('⟣╍◎╍⟢');
459
463
  const versionLabel = version ? clamp(`${brand} · v${version}`) : brand;
460
464
  const modelLine = clamp(model);
461
465
  const workspaceLine = clamp(workspace);
462
- // Compact welcome line with logo on same line
463
- const welcomeLine = clamp(`${logo} Welcome back ${userName}! ${logo}`);
464
- // Quick reference hints
465
- const hintsLine = clamp('/model · /secrets · /help · ? shortcuts');
466
+ const welcomeLine = clamp(`Welcome back ${userName}!`);
466
467
  const lines = [
467
468
  frame(top),
468
469
  frame(`│${padCenter(welcomeLine, contentWidth)}│`),
470
+ frame(`│${padCenter(logo, contentWidth)}│`),
469
471
  frame(`│${padCenter(modelLine, contentWidth)}│`),
470
472
  frame(`│${padCenter(versionLabel, contentWidth)}│`),
471
473
  frame(`│${padCenter(workspaceLine, contentWidth)}│`),
472
- frame(`│${padCenter(hintsLine, contentWidth)}│`),
473
474
  frame(bottom),
474
475
  ].join('\n');
475
476
  if (this.renderer) {
@@ -480,6 +481,8 @@ export class InteractiveShell {
480
481
  else {
481
482
  display.stream(`\n${lines}\n`);
482
483
  }
484
+ // Check for updates asynchronously (non-blocking)
485
+ void this.checkAndShowUpdates();
483
486
  // Keep UI pinned; no scrollback banners
484
487
  this.requestPromptRefresh(true);
485
488
  }
@@ -515,79 +518,24 @@ export class InteractiveShell {
515
518
  }
516
519
  return pathValue;
517
520
  }
518
- shouldForceApprovalMode(policy) {
519
- return policy === 'on-request' || policy === 'untrusted' || policy === 'never';
520
- }
521
- getGuardrailMeta() {
522
- if (!this.codexContext) {
523
- return {};
524
- }
525
- const approvals = this.codexContext.approvalPolicy === 'on-request' || this.codexContext.approvalPolicy === 'untrusted'
526
- ? 'ask'
527
- : this.codexContext.approvalPolicy === 'never'
528
- ? 'never'
529
- : 'auto';
530
- const sandbox = this.codexContext.sandboxMode === 'workspace-write'
531
- ? 'workspace'
532
- : this.codexContext.sandboxMode === 'danger-full-access'
533
- ? 'danger'
534
- : this.codexContext.sandboxMode;
535
- const network = this.codexContext.networkAccess === 'enabled' ? 'enabled' : 'restricted';
536
- return { sandbox, network, approvals };
537
- }
538
- describePlanPath() {
539
- const planPath = join(this.workingDir, '.erosolar', 'codex', 'plan.json');
540
- const relative = planPath.startsWith(this.workingDir) && planPath.length > this.workingDir.length
541
- ? planPath.slice(this.workingDir.length + 1)
542
- : planPath;
543
- return this.abbreviatePath(relative);
544
- }
545
- maybeShowCodexGuardrails() {
546
- if (!this.codexContext || this.guardrailBannerShown) {
547
- return;
548
- }
549
- const meta = this.getGuardrailMeta();
550
- const lines = [];
551
- lines.push(`Guardrails: sandbox ${meta.sandbox ?? 'workspace'} · network ${meta.network ?? 'restricted'} · approvals ${meta.approvals ?? 'auto'}`);
552
- lines.push('Flow: plan before edits, targeted reads, one in-progress step, validate once at the end.');
553
- const planPath = this.describePlanPath();
554
- if (planPath) {
555
- lines.push(`Plan file: ${planPath}`);
556
- }
557
- if (this.codexApprovalForced) {
558
- lines.push('Approval mode set to ask per environment policy.');
559
- }
560
- this.streamEventBlock(lines.join('\n'));
561
- this.guardrailBannerShown = true;
562
- }
563
521
  async checkAndShowUpdates() {
564
522
  try {
565
- const { checkForUpdates, getUpdateDecision, formatUpdateBanner, performBackgroundUpdate, readAutoUpdateState, shouldShowUpdateNotification, } = await import('../core/updateChecker.js');
523
+ const { checkForUpdates, formatUpdateNotification, maybeAutoUpdate } = await import('../core/updateChecker.js');
566
524
  const currentVersion = this.version || this.getPackageInfo().version || '1.7.458';
567
525
  const updateInfo = await checkForUpdates(currentVersion);
568
526
  if (!updateInfo || !updateInfo.updateAvailable) {
569
527
  return;
570
528
  }
571
- // Check if we should show notification (respects quiet period)
572
- const state = readAutoUpdateState();
573
- if (!shouldShowUpdateNotification(state)) {
574
- return;
575
- }
576
- // Get user's preference-based decision (no interactive prompt)
577
- const sessionPrefs = loadSessionPreferences();
578
- const decision = getUpdateDecision(sessionPrefs.autoUpdate);
579
- if (decision === 'skip') {
580
- // User chose to always skip - silently ignore updates
529
+ const updateResult = await maybeAutoUpdate(currentVersion, {
530
+ updateInfo,
531
+ logger: (message) => this.streamEventBlock(message),
532
+ });
533
+ if (updateResult?.updated) {
581
534
  return;
582
535
  }
583
- // Show non-interactive banner notification
584
- this.streamEventBlock(formatUpdateBanner(updateInfo, decision));
585
- if (decision === 'auto') {
586
- // User chose to always update - perform background update
587
- await performBackgroundUpdate(updateInfo, (message) => this.streamEventBlock(message));
588
- }
589
- // If decision === 'ask', we just show the banner with instructions
590
- // User can run /update check or /update auto to update
536
+ const note = this.describeUpdateSkipReason(updateResult);
537
+ const notification = formatUpdateNotification(updateInfo, note);
538
+ this.streamEventBlock(notification);
591
539
  }
592
540
  catch {
593
541
  // Silently fail - don't interrupt user experience
@@ -671,20 +619,16 @@ export class InteractiveShell {
671
619
  this.pushUiEvent('raw', block);
672
620
  }
673
621
  async start(initialPrompt) {
674
- // Check for updates BEFORE terminal input starts (to avoid keypress conflicts)
675
- await this.checkAndShowUpdates();
676
622
  // Initialize the renderer before emitting the banner so we don't render the prompt twice
677
623
  this.terminalInput.start();
678
624
  this.resetRendererStreamingMode();
679
625
  await this.showWelcomeBanner();
680
- this.maybeShowCodexGuardrails();
681
626
  // Now refresh control bar with profile/model/version info
682
627
  this.refreshControlBar();
683
628
  // Now sync renderer and control bar state
684
629
  this.syncRendererInput();
685
- // CRITICAL: Force prompt render after banner - flushEvents should have done this,
686
- // but explicitly ensure the prompt area is visible
687
- this.renderer?.render();
630
+ // Ensure the prompt/control bar is rendered after the welcome banner
631
+ this.ensureReadlineReady();
688
632
  if (initialPrompt) {
689
633
  await this.enqueuePromptForProcessing(initialPrompt, 'programmatic');
690
634
  return;
@@ -692,7 +636,7 @@ export class InteractiveShell {
692
636
  this.showLaunchCommandPalette();
693
637
  // Ensure the terminal input is visible
694
638
  this.syncRendererInput();
695
- this.renderer?.render();
639
+ this.ensureReadlineReady();
696
640
  }
697
641
  showLaunchCommandPalette() {
698
642
  // Disabled: Quick commands palette takes up too much space
@@ -760,10 +704,6 @@ export class InteractiveShell {
760
704
  */
761
705
  async runPromptJob(job) {
762
706
  try {
763
- // Show thinking indicator instead of "processing queued prompt"
764
- display.showThinking('Thinking…');
765
- // Re-echo the prompt to make it clear what's being processed
766
- this.renderer?.emitPrompt(job.text);
767
707
  await this.processInputBlock(job.text);
768
708
  job.resolve();
769
709
  }
@@ -859,6 +799,7 @@ export class InteractiveShell {
859
799
  '/output-style',
860
800
  // Mode toggles
861
801
  '/thinking',
802
+ '/autocontinue',
862
803
  // Discovery and plugins
863
804
  '/local', '/discover',
864
805
  '/plugins',
@@ -948,22 +889,25 @@ export class InteractiveShell {
948
889
  : '🛡️ High-impact actions now require your approval. (Ctrl+Shift+A to toggle; use /approvals ask to persist)';
949
890
  display.showSystemMessage(message);
950
891
  }
951
- /**
952
- * Expand the last tool result (Ctrl+O shortcut).
953
- * Shows the full output from the most recent tool call.
954
- */
955
- expandLastToolResult() {
956
- const result = this.uiAdapter.getLastToolResult();
957
- if (!result) {
958
- display.showInfo('No tool result to expand. Run a tool first, then press Ctrl+O.');
892
+ toggleAutoContinueMode() {
893
+ this.setAutoContinueMode(!this.autoContinueEnabled, 'shortcut');
894
+ }
895
+ setAutoContinueMode(enabled, source) {
896
+ const changed = this.autoContinueEnabled !== enabled;
897
+ this.autoContinueEnabled = enabled;
898
+ saveSessionPreferences({ autoContinue: this.autoContinueEnabled });
899
+ if (this.agent) {
900
+ this.agent.setAutoContinue(this.autoContinueEnabled);
901
+ }
902
+ this.refreshControlBar();
903
+ if (!changed && source === 'shortcut') {
959
904
  return;
960
905
  }
961
- // Format the expanded output with tool name header
962
- const header = `${theme.info('━━━')} ${theme.tool(result.toolName)} ${theme.ui.muted('output')} ${theme.info('━━━')}`;
963
- const content = result.output.trim() || '(empty result)';
964
- const footer = theme.ui.muted('━'.repeat(Math.min(60, header.length)));
965
- // Display the expanded result
966
- display.stream(`\n${header}\n${content}\n${footer}\n\n`);
906
+ const modeLabel = this.autoContinueEnabled ? 'enabled' : 'disabled';
907
+ const behavior = this.autoContinueEnabled
908
+ ? 'The model will be auto-prompted to continue when it expresses intent but does not use tools.'
909
+ : 'The model will not be auto-prompted to continue.';
910
+ display.showInfo(`Auto-continue ${modeLabel}. ${behavior} Toggle with Ctrl+Shift+C.`);
967
911
  }
968
912
  /**
969
913
  * Cycle through thinking modes (Tab shortcut).
@@ -1117,13 +1061,6 @@ export class InteractiveShell {
1117
1061
  }
1118
1062
  this.ctrlCHandledThisPress = false;
1119
1063
  }
1120
- /**
1121
- * Handle exit request (Ctrl+C with empty buffer in idle mode)
1122
- */
1123
- handleExit() {
1124
- display.showSystemMessage('\nGoodbye!\n');
1125
- this.shutdown();
1126
- }
1127
1064
  /**
1128
1065
  * Gracefully tear down the shell and exit
1129
1066
  */
@@ -1133,7 +1070,7 @@ export class InteractiveShell {
1133
1070
  }
1134
1071
  this.shuttingDown = true;
1135
1072
  // Stop any active spinner to prevent process hang
1136
- display.stopThinking(false);
1073
+ display.stopThinking();
1137
1074
  this.stopStreamingHeartbeat('quit', { quiet: true });
1138
1075
  this.endAiRuntime();
1139
1076
  this.uiUpdates.dispose();
@@ -1145,6 +1082,7 @@ export class InteractiveShell {
1145
1082
  this.pendingCleanup = null;
1146
1083
  // Unregister plan approval bridge
1147
1084
  setPlanApprovalCallback(null);
1085
+ setPlanUpdateCallback(null);
1148
1086
  // Dispose terminal input handler
1149
1087
  this.terminalInput.dispose();
1150
1088
  // Dispose unified UI adapter
@@ -1165,6 +1103,38 @@ export class InteractiveShell {
1165
1103
  });
1166
1104
  this.planApprovalBridgeRegistered = true;
1167
1105
  }
1106
+ registerPlanUpdateBridge() {
1107
+ if (this.planUpdateBridgeRegistered) {
1108
+ return;
1109
+ }
1110
+ setPlanUpdateCallback((payload) => {
1111
+ if (!payload || !payload.steps.length) {
1112
+ this.planOverlay.clear();
1113
+ this.applyPlanOverlay();
1114
+ return;
1115
+ }
1116
+ this.applyPlanUpdate(payload.steps, payload.explanation);
1117
+ });
1118
+ this.planUpdateBridgeRegistered = true;
1119
+ }
1120
+ applyPlanUpdate(steps, explanation) {
1121
+ this.planOverlay.setPlan(steps, explanation);
1122
+ this.applyPlanOverlay();
1123
+ }
1124
+ applyPlanOverlay() {
1125
+ const lines = this.planOverlay.render();
1126
+ if (!this.renderer) {
1127
+ if (this.planOverlay.hasPlan()) {
1128
+ display.showSystemMessage(this.planOverlay.asText());
1129
+ }
1130
+ return;
1131
+ }
1132
+ if (!lines.length) {
1133
+ this.renderer.clearPersistentPanel();
1134
+ return;
1135
+ }
1136
+ this.renderer.setPersistentPanel(lines);
1137
+ }
1168
1138
  /**
1169
1139
  * Update status bar message
1170
1140
  */
@@ -1693,16 +1663,16 @@ export class InteractiveShell {
1693
1663
  refreshControlBar() {
1694
1664
  this.terminalInput.setModeToggles({
1695
1665
  verificationEnabled: this.verificationEnabled,
1666
+ autoContinueEnabled: this.autoContinueEnabled,
1696
1667
  verificationHotkey: 'ctrl+shift+v',
1668
+ autoContinueHotkey: 'ctrl+shift+c',
1697
1669
  thinkingModeLabel: (this.thinkingMode || 'off').toString(),
1698
1670
  thinkingHotkey: 'tab',
1699
1671
  criticalApprovalMode: this.criticalApprovalMode,
1700
1672
  criticalApprovalHotkey: 'ctrl+shift+a',
1701
1673
  });
1702
1674
  const workspaceDisplay = this.abbreviatePath(this.workingDir);
1703
- const guardrails = this.getGuardrailMeta();
1704
1675
  this.terminalInput.setChromeMeta({
1705
- ...guardrails,
1706
1676
  profile: this.profileLabel,
1707
1677
  workspace: workspaceDisplay,
1708
1678
  directory: workspaceDisplay,
@@ -1786,14 +1756,15 @@ export class InteractiveShell {
1786
1756
  */
1787
1757
  refreshStatusLine(forceRender = false) {
1788
1758
  const elapsedSeconds = this.getAiRuntimeSeconds();
1759
+ const thinkingMs = display.isSpinnerActive() ? display.getThinkingElapsedMs() : null;
1789
1760
  const tokensUsed = this.latestTokenUsage.used;
1790
1761
  const tokenLimit = this.latestTokenUsage.limit ?? this.activeContextWindowTokens;
1791
1762
  this.terminalInput.setMetaStatus({
1792
1763
  elapsedSeconds,
1793
1764
  tokensUsed,
1794
1765
  tokenLimit,
1795
- thinkingMs: null,
1796
- thinkingHasContent: false,
1766
+ thinkingMs,
1767
+ thinkingHasContent: display.isSpinnerActive(),
1797
1768
  });
1798
1769
  // Keep model/provider visible in the controls bar
1799
1770
  this.terminalInput.setModelContext({
@@ -1969,16 +1940,12 @@ export class InteractiveShell {
1969
1940
  enterStreamingMode();
1970
1941
  // Set up scroll region for streaming content
1971
1942
  this.uiUpdates.setMode('streaming');
1972
- // Show activity status with animated spinner - use provided label or default
1973
- const activityLabel = label || 'Thinking';
1974
- this.renderer?.setActivity(activityLabel);
1975
1943
  this.streamingHeartbeatStart = Date.now();
1976
1944
  this.streamingContentSeen = false;
1977
1945
  this.streamingStatusText = null;
1978
1946
  this.streamingStatusLastUpdate = null;
1979
- this.streamingTokenCount = 0; // Reset token count for new prompt
1980
- // Show raw streaming output in real-time (Claude Code style)
1981
- this.streamingOutputSuppressed = false;
1947
+ // Suppress raw streaming lines; show only a single summary/status + final response
1948
+ this.streamingOutputSuppressed = true;
1982
1949
  const initialLabel = this.isMeaningfulStreamingSnippet(label)
1983
1950
  ? this.truncateStreamingLabel(label)
1984
1951
  : this.getStreamingFallbackLabel();
@@ -2022,8 +1989,6 @@ export class InteractiveShell {
2022
1989
  this.streamingStatusLastUpdate = null;
2023
1990
  this.streamingStatusText = null;
2024
1991
  this.streamingOutputSuppressed = false;
2025
- // Clear activity status when streaming ends
2026
- this.renderer?.setActivity(null);
2027
1992
  // Emit a streaming note for stop/quit so the status stays inside the stream
2028
1993
  if (reason === 'stop' || reason === 'quit') {
2029
1994
  const note = reason === 'quit' ? 'Session closed.' : 'Stream stopped.';
@@ -2040,15 +2005,11 @@ export class InteractiveShell {
2040
2005
  // Buffer for accumulating partial <thinking> tags during streaming
2041
2006
  thinkingTagBuffer = '';
2042
2007
  insideThinkingBlock = false;
2043
- streamingTokenCount = 0;
2044
2008
  handleStreamChunk(chunk, type = 'content') {
2045
2009
  if (!chunk) {
2046
2010
  return;
2047
2011
  }
2048
2012
  const isReasoning = type === 'reasoning';
2049
- // Approximate token count (roughly 4 chars per token)
2050
- this.streamingTokenCount += Math.ceil(chunk.length / 4);
2051
- this.renderer?.updateStreamingTokens(this.streamingTokenCount);
2052
2013
  // Keep pinned status updated for all streaming chunks
2053
2014
  this.updateStreamingStatusFromChunk(chunk);
2054
2015
  // Handle <thinking> tags as separate events in the queue
@@ -2057,32 +2018,37 @@ export class InteractiveShell {
2057
2018
  if (!processed.contentChunk && !processed.thinkingChunk) {
2058
2019
  return;
2059
2020
  }
2060
- // Don't emit thinking blocks during streaming - they will be rendered
2061
- // as a complete block by onAssistantMessage when the response is final.
2062
- // This prevents duplicate display.
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
+ }
2063
2025
  // Process regular content (skip if no content after extracting thinking)
2064
2026
  const contentChunk = processed.contentChunk;
2065
2027
  if (!contentChunk) {
2066
2028
  return;
2067
2029
  }
2068
2030
  // Suppress raw streaming output in scrollback; keep only status + final message.
2069
- // Reasoning tokens should also be suppressed and shown only in the indicator.
2070
- if (this.streamingOutputSuppressed) {
2031
+ // Reasoning chunks bypass this so the thought process stays visible.
2032
+ if (this.streamingOutputSuppressed && !isReasoning) {
2071
2033
  this.captureStreamingThought(contentChunk);
2072
2034
  this.streamingContentSeen = true;
2073
- // Update thinking indicator with a snippet of the content
2074
- const prefix = isReasoning ? '○ ' : '';
2075
- const snippet = contentChunk.replace(/\s+/g, ' ').trim().slice(0, 60);
2076
- if (snippet) {
2077
- display.updateThinking(prefix + snippet + (contentChunk.length > 60 ? '…' : ''));
2078
- }
2079
2035
  return;
2080
2036
  }
2081
- // Buffer all streaming content - rendered as complete block via onAssistantMessage
2082
- // This prevents fragmented display and ensures thinking tags are properly processed
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);
2083
2045
  this.captureStreamingThought(contentChunk);
2084
- // Activity: show what the model is doing (stable, informative)
2085
- this.renderer?.setActivity(type === 'reasoning' ? 'Reasoning' : 'Writing');
2046
+ if (formatted) {
2047
+ if (formatted.trim().length > 0) {
2048
+ this.streamingContentSeen = true;
2049
+ }
2050
+ this.pushUiEvent('streaming', formatted);
2051
+ }
2086
2052
  }
2087
2053
  /**
2088
2054
  * Process streaming content to extract <thinking> blocks as separate events.
@@ -2130,12 +2096,22 @@ export class InteractiveShell {
2130
2096
  }
2131
2097
  return { thinkingChunk: thinkingContent, contentChunk: remainingContent };
2132
2098
  }
2133
- finishStreamingFormatter(_note, options) {
2134
- // Flush any remaining buffered thoughts
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
+ }
2135
2110
  if (this.streamingThoughtBuffer.trim()) {
2136
2111
  this.ui.controller.recordAssistantThought(this.streamingThoughtBuffer.trim());
2137
2112
  }
2138
2113
  this.streamingThoughtBuffer = '';
2114
+ this.streamingFormatter = null;
2139
2115
  if (options?.refreshPrompt ?? true) {
2140
2116
  this.requestPromptRefresh(true);
2141
2117
  }
@@ -2427,6 +2403,9 @@ export class InteractiveShell {
2427
2403
  case '/approvals':
2428
2404
  this.handleApprovalsCommand(input);
2429
2405
  break;
2406
+ case '/plan':
2407
+ this.handlePlanCommand(input);
2408
+ break;
2430
2409
  case '/learn':
2431
2410
  this.showLearningStatus(input);
2432
2411
  break;
@@ -2474,6 +2453,9 @@ export class InteractiveShell {
2474
2453
  case '/thinking':
2475
2454
  this.handleThinkingCommand(input);
2476
2455
  break;
2456
+ case '/autocontinue':
2457
+ this.handleAutoContinueCommand(input);
2458
+ break;
2477
2459
  case '/shortcuts':
2478
2460
  case '/keys':
2479
2461
  this.handleShortcutsCommand();
@@ -2577,9 +2559,6 @@ export class InteractiveShell {
2577
2559
  case '/permissions':
2578
2560
  this.handlePermissionsCommand();
2579
2561
  break;
2580
- case '/update':
2581
- await this.handleUpdateCommand(input);
2582
- break;
2583
2562
  case '/init':
2584
2563
  this.handleInitCommand(input);
2585
2564
  break;
@@ -2637,6 +2616,7 @@ export class InteractiveShell {
2637
2616
  theme.bold(' Mode Toggles'),
2638
2617
  ` ${theme.info('Shift+Tab')} ${theme.ui.muted('Toggle edit mode (auto/ask)')}`,
2639
2618
  ` ${theme.info('Option+V')} ${theme.ui.muted('Toggle verification')}`,
2619
+ ` ${theme.info('Option+C')} ${theme.ui.muted('Toggle auto-continue')}`,
2640
2620
  ` ${theme.info('Ctrl+Shift+A')} ${theme.ui.muted('Toggle approvals for high-impact actions')}`,
2641
2621
  ` ${theme.info('Option+T')} ${theme.ui.muted('Cycle thinking mode')}`,
2642
2622
  ` ${theme.info('Option+E')} ${theme.ui.muted('Toggle edit permission mode')}`,
@@ -4350,6 +4330,20 @@ export class InteractiveShell {
4350
4330
  clearAutosaveSnapshot(this.profile);
4351
4331
  display.showInfo('Cleared autosave history.');
4352
4332
  }
4333
+ handleAutoContinueCommand(input) {
4334
+ const tokens = input.split(/\s+/).slice(1);
4335
+ const value = tokens[0]?.toLowerCase();
4336
+ if (!value) {
4337
+ // Show current status
4338
+ display.showInfo(`Auto-continue is ${this.autoContinueEnabled ? 'enabled' : 'disabled'}. Use /autocontinue on|off or Ctrl+Shift+C to toggle.`);
4339
+ return;
4340
+ }
4341
+ if (value !== 'on' && value !== 'off') {
4342
+ display.showWarning('Usage: /autocontinue on|off');
4343
+ return;
4344
+ }
4345
+ this.setAutoContinueMode(value === 'on', 'command');
4346
+ }
4353
4347
  // ==================== Erosolar-CLI Style Commands ====================
4354
4348
  async handleRewindCommand(_input) {
4355
4349
  const lines = [];
@@ -4581,6 +4575,7 @@ export class InteractiveShell {
4581
4575
  lines.push(`${theme.primary('Provider/Model')}: ${theme.info(`${this.providerLabel(this.sessionState.provider)} · ${this.sessionState.model}`)}`);
4582
4576
  lines.push(`${theme.primary('Workspace')}: ${theme.ui.muted(this.abbreviatePath(this.workingDir))}`);
4583
4577
  lines.push(`${theme.primary('Autosave')}: ${this.autosaveEnabled ? theme.success('on') : theme.ui.muted('off')}`);
4578
+ lines.push(`${theme.primary('Auto-continue')}: ${this.autoContinueEnabled ? theme.success('on') : theme.ui.muted('off')}`);
4584
4579
  lines.push(`${theme.primary('Verification')}: ${this.verificationEnabled ? theme.success('on') : theme.ui.muted('off')}`);
4585
4580
  lines.push(`${theme.primary('Approvals')}: ${theme.ui.muted(this.describeEditGuardMode())}`);
4586
4581
  lines.push(`${theme.primary('Critical approvals')}: ${this.criticalApprovalMode === 'approval' ? theme.warning('ask') : theme.ui.muted('auto')}`);
@@ -4716,6 +4711,52 @@ export class InteractiveShell {
4716
4711
  }
4717
4712
  display.showWarning('Usage: /approvals [auto|ask|status]');
4718
4713
  }
4714
+ handlePlanCommand(input) {
4715
+ const action = input.split(/\s+/)[1]?.toLowerCase() || 'show';
4716
+ const hasPlan = this.planOverlay.hasPlan();
4717
+ const syncPanel = () => {
4718
+ this.applyPlanOverlay();
4719
+ this.syncRendererInput();
4720
+ };
4721
+ switch (action) {
4722
+ case 'show':
4723
+ case 'on':
4724
+ if (!hasPlan) {
4725
+ display.showInfo('No active plan. The panel will populate when a planning tool updates the plan.');
4726
+ return;
4727
+ }
4728
+ this.planOverlay.show();
4729
+ syncPanel();
4730
+ display.showInfo('Plan panel pinned. Use /plan hide to minimize.');
4731
+ return;
4732
+ case 'hide':
4733
+ case 'off':
4734
+ if (!hasPlan) {
4735
+ display.showInfo('No active plan to hide.');
4736
+ return;
4737
+ }
4738
+ this.planOverlay.hide();
4739
+ syncPanel();
4740
+ display.showInfo('Plan panel hidden. Use /plan show to restore.');
4741
+ return;
4742
+ case 'clear':
4743
+ if (!hasPlan) {
4744
+ display.showInfo('No active plan to clear.');
4745
+ return;
4746
+ }
4747
+ this.planOverlay.clear();
4748
+ syncPanel();
4749
+ display.showInfo('Plan cleared.');
4750
+ return;
4751
+ case 'status':
4752
+ case 'view':
4753
+ case 'text':
4754
+ display.showSystemMessage(this.planOverlay.asText());
4755
+ return;
4756
+ default:
4757
+ display.showWarning('Usage: /plan [show|hide|clear|status]');
4758
+ }
4759
+ }
4719
4760
  handlePermissionsCommand() {
4720
4761
  const lines = [];
4721
4762
  lines.push(theme.bold('Tool Permissions'));
@@ -4736,67 +4777,6 @@ export class InteractiveShell {
4736
4777
  lines.push(theme.ui.muted('Use /tools to manage which tool suites are enabled.'));
4737
4778
  display.showSystemMessage(lines.join('\n'));
4738
4779
  }
4739
- async handleUpdateCommand(input) {
4740
- const tokens = input.split(/\s+/).slice(1);
4741
- const subcommand = tokens[0]?.toLowerCase();
4742
- const prefs = loadSessionPreferences();
4743
- const currentPref = prefs.autoUpdate;
4744
- const prefLabel = currentPref === true ? 'always update' : currentPref === false ? 'always skip' : 'notify only';
4745
- // Show status or help
4746
- if (!subcommand || subcommand === 'status') {
4747
- const lines = [];
4748
- lines.push(theme.bold('Update Settings'));
4749
- lines.push('');
4750
- lines.push(`Current preference: ${theme.info(prefLabel)}`);
4751
- lines.push('');
4752
- lines.push(theme.secondary('Commands:'));
4753
- lines.push(' /update check - Check for updates and install immediately');
4754
- lines.push(' /update auto - Always auto-update in background');
4755
- lines.push(' /update skip - Never auto-update (silent)');
4756
- lines.push(' /update notify - Show notification only (default)');
4757
- display.showSystemMessage(lines.join('\n'));
4758
- return;
4759
- }
4760
- if (subcommand === 'check') {
4761
- // Force check and perform update immediately (non-interactive)
4762
- try {
4763
- const { checkForUpdates, performUpdate } = await import('../core/updateChecker.js');
4764
- const currentVersion = this.version || this.getPackageInfo().version || '1.7.458';
4765
- const updateInfo = await checkForUpdates(currentVersion);
4766
- if (!updateInfo) {
4767
- display.showWarning('Unable to check for updates (network issue or timeout).');
4768
- return;
4769
- }
4770
- if (!updateInfo.updateAvailable) {
4771
- display.showSuccess(`You're on the latest version (v${updateInfo.current}).`);
4772
- return;
4773
- }
4774
- // Show update info and perform update immediately (no interactive prompt)
4775
- display.showInfo(`Update available: v${updateInfo.current} → v${updateInfo.latest}`);
4776
- await performUpdate(updateInfo, (msg) => this.streamEventBlock(msg));
4777
- }
4778
- catch (error) {
4779
- display.showError(`Update check failed: ${error instanceof Error ? error.message : String(error)}`);
4780
- }
4781
- return;
4782
- }
4783
- if (subcommand === 'auto' || subcommand === 'always') {
4784
- saveSessionPreferences({ autoUpdate: true });
4785
- display.showSuccess('Auto-update enabled. Updates will install automatically.');
4786
- return;
4787
- }
4788
- if (subcommand === 'skip' || subcommand === 'never' || subcommand === 'off') {
4789
- saveSessionPreferences({ autoUpdate: false });
4790
- display.showInfo('Auto-update disabled. Updates will be skipped silently.');
4791
- return;
4792
- }
4793
- if (subcommand === 'ask' || subcommand === 'prompt' || subcommand === 'reset' || subcommand === 'notify') {
4794
- saveSessionPreferences({ autoUpdate: null });
4795
- display.showInfo('Update preference reset. You will see a notification when updates are available.');
4796
- return;
4797
- }
4798
- display.showWarning('Usage: /update [check|auto|skip|notify|status]');
4799
- }
4800
4780
  handleInitCommand(input) {
4801
4781
  const tokens = input.split(/\s+/).slice(1);
4802
4782
  const confirm = tokens[0]?.toLowerCase() === 'confirm';
@@ -5821,7 +5801,6 @@ export class InteractiveShell {
5821
5801
  }
5822
5802
  this.isProcessing = true;
5823
5803
  this.uiUpdates.setMode('processing');
5824
- this.streamingTokenCount = 0; // Reset token counter for new request
5825
5804
  this.terminalInput.setStreaming(true);
5826
5805
  // Keep the persistent input/control bar active as we transition into streaming.
5827
5806
  this.syncRendererInput();
@@ -5836,7 +5815,6 @@ export class InteractiveShell {
5836
5815
  this.currentTaskType = classifyTaskType(request);
5837
5816
  this.currentToolCalls = [];
5838
5817
  this.clearToolUsageMeta();
5839
- this.renderer?.setActivity('Starting...');
5840
5818
  this.uiAdapter.startProcessing('Working on your request');
5841
5819
  this.setProcessingStatus();
5842
5820
  this.beginAiRuntime();
@@ -5892,6 +5870,8 @@ export class InteractiveShell {
5892
5870
  clearActionHistory();
5893
5871
  this.lastFailure = null;
5894
5872
  }
5873
+ // Run post-hoc AI flow sanity check to catch "looks right but wrong" responses
5874
+ this.analyzeAiFlowForRun(request, responseText, requestStartTime);
5895
5875
  }
5896
5876
  catch (error) {
5897
5877
  const handled = this.handleProviderError(error, () => this.processRequest(request));
@@ -5911,7 +5891,7 @@ export class InteractiveShell {
5911
5891
  this.responseRendered = true;
5912
5892
  }
5913
5893
  this.finishStreamingFormatter(undefined, { refreshPrompt: false, mode: 'complete' });
5914
- display.stopThinking(false);
5894
+ display.stopThinking();
5915
5895
  this.uiUpdates.setMode('processing');
5916
5896
  this.stopStreamingHeartbeat('complete', { quiet: true });
5917
5897
  this.endAiRuntime();
@@ -5962,7 +5942,6 @@ export class InteractiveShell {
5962
5942
  this.clearToolUsageMeta();
5963
5943
  this.isProcessing = true;
5964
5944
  this.uiUpdates.setMode('processing');
5965
- this.streamingTokenCount = 0; // Reset token counter for new request
5966
5945
  this.terminalInput.setStreaming(true);
5967
5946
  if (this.suppressNextNetworkReset) {
5968
5947
  this.suppressNextNetworkReset = false;
@@ -6128,7 +6107,7 @@ What's the next action?`;
6128
6107
  await new Promise(resolve => setTimeout(resolve, 500));
6129
6108
  }
6130
6109
  catch (error) {
6131
- display.stopThinking(false);
6110
+ display.stopThinking();
6132
6111
  // Handle context overflow specially - the agent should auto-recover
6133
6112
  // but if it propagates here, we continue the loop
6134
6113
  if (this.isContextOverflowError(error)) {
@@ -6370,6 +6349,43 @@ What's the next action?`;
6370
6349
  const parts = candidates.filter((part) => typeof part === 'string' && part.trim().length > 0);
6371
6350
  return parts.join('\n').trim();
6372
6351
  }
6352
+ /**
6353
+ * Post-run sanity check to catch hallucinated "looks right" answers.
6354
+ * Compares the prompt/response against per-run tool usage and surfaces risks.
6355
+ */
6356
+ analyzeAiFlowForRun(prompt, responseText, startedAt) {
6357
+ const toolHistory = this.runtimeSession.toolRuntime.getToolHistory?.();
6358
+ if (!toolHistory) {
6359
+ return;
6360
+ }
6361
+ const diffSnapshots = this.runtimeSession.toolRuntime.getDiffSnapshots?.() ?? [];
6362
+ const recentDiffs = diffSnapshots
6363
+ .filter((snapshot) => snapshot.timestamp >= startedAt)
6364
+ .map(({ command, output }) => ({ command, output }));
6365
+ const assessment = assessAiFlow({
6366
+ prompt,
6367
+ response: responseText,
6368
+ startedAt,
6369
+ toolHistory,
6370
+ diffSnapshots: recentDiffs,
6371
+ });
6372
+ this.reportAiFlowAssessment(assessment);
6373
+ }
6374
+ reportAiFlowAssessment(assessment) {
6375
+ if (!assessment || assessment.risks.length === 0) {
6376
+ return;
6377
+ }
6378
+ display.showSystemMessage('⚠️ AI flow check: potential unverified completion detected.');
6379
+ for (const risk of assessment.risks) {
6380
+ const detail = risk.details ? ` — ${risk.details}` : '';
6381
+ display.showWarning(`${risk.summary}${detail}`);
6382
+ }
6383
+ if (assessment.recommendations.length > 0) {
6384
+ const uniqueRecommendations = Array.from(new Set(assessment.recommendations));
6385
+ const advice = uniqueRecommendations.slice(0, 3).map((rec, idx) => `${idx + 1}. ${rec}`).join('\n');
6386
+ display.showSystemMessage(`Follow-ups to de-risk this run:\n${advice}`);
6387
+ }
6388
+ }
6373
6389
  runAutoQualityChecks(trigger, assistantResponse, verificationContext) {
6374
6390
  if (!this.verificationEnabled) {
6375
6391
  return;
@@ -6750,41 +6766,21 @@ Return ONLY JSON array:
6750
6766
  maxTokens: this.sessionState.maxTokens,
6751
6767
  systemPrompt: this.buildSystemPrompt(),
6752
6768
  reasoningEffort: this.sessionState.reasoningEffort,
6769
+ autoContinue: this.autoContinueEnabled,
6753
6770
  };
6754
6771
  this.agent = this.runtimeSession.createAgent(selection, {
6755
- onRequestReceived: (requestPreview) => {
6756
- const normalized = requestPreview?.trim();
6757
- const activity = normalized ? `Working: ${normalized}` : 'Working';
6758
- this.renderer?.setActivity(activity);
6759
- },
6760
- onBeforeFirstToolCall: (toolNames) => {
6761
- const primaryTool = toolNames[0];
6762
- if (primaryTool) {
6763
- this.renderer?.setActivity(`Running ${primaryTool}`);
6764
- }
6765
- return undefined;
6766
- },
6767
6772
  onStreamChunk: (chunk, type) => {
6768
6773
  this.handleStreamChunk(chunk, type ?? 'content');
6769
6774
  },
6775
+ onStreamFallback: (info) => this.handleStreamingFallback(info),
6770
6776
  onAssistantMessage: (content, metadata) => {
6771
6777
  const enriched = this.buildDisplayMetadata(metadata);
6772
6778
  const streamedVisible = metadata.wasStreamed && this.streamingContentSeen && !this.streamingOutputSuppressed;
6773
6779
  let renderedFinal = false;
6774
- // Update streaming token count from usage info (more accurate than chunk counting)
6775
- if (metadata.usage) {
6776
- const totalTokens = this.totalTokens(metadata.usage);
6777
- if (totalTokens !== null && totalTokens > this.streamingTokenCount) {
6778
- this.streamingTokenCount = totalTokens;
6779
- this.renderer?.updateStreamingTokens(this.streamingTokenCount);
6780
- }
6781
- }
6782
6780
  // Update spinner based on message type
6783
6781
  if (metadata.isFinal) {
6784
6782
  const parsed = this.splitThinkingResponse(content);
6785
- // If we successfully parsed thinking, use the parsed response (may be empty)
6786
- // Don't fall back to original content with raw <thinking> tags
6787
- const finalContent = parsed ? parsed.response?.trim() : content;
6783
+ const finalContent = parsed?.response?.trim() || content;
6788
6784
  const thoughtContent = parsed?.thinking?.trim() || null;
6789
6785
  // Show the response if it wasn't already rendered during streaming
6790
6786
  if (!streamedVisible) {
@@ -6887,6 +6883,12 @@ Return ONLY JSON array:
6887
6883
  this.updateStatusMessage('Retrying with reduced context...');
6888
6884
  this.syncRendererInput();
6889
6885
  },
6886
+ onAutoContinue: (attempt, maxAttempts, _message) => {
6887
+ // Show auto-continue progress in UI
6888
+ display.showSystemMessage(`🔄 Auto-continue (${attempt}/${maxAttempts}): Model expressed intent, prompting to act...`);
6889
+ this.updateStatusMessage('Auto-continuing...');
6890
+ this.syncRendererInput();
6891
+ },
6890
6892
  onCancelled: () => {
6891
6893
  // Update UI to show operation was cancelled
6892
6894
  display.showWarning('Operation cancelled.');
@@ -6895,42 +6897,10 @@ Return ONLY JSON array:
6895
6897
  this.updateStatusMessage(null);
6896
6898
  this.terminalInput.setStreaming(false);
6897
6899
  },
6898
- onToolExecution: (toolName, isStart, args) => {
6899
- // Update activity status to show what tool is being executed
6900
- if (isStart) {
6901
- // Show more specific activity for long-running tools
6902
- let activity = `Running ${toolName}`;
6903
- if (toolName === 'execute_bash' && args?.['command']) {
6904
- const cmd = String(args['command']).slice(0, 40);
6905
- activity = `$ ${cmd}${String(args['command']).length > 40 ? '...' : ''}`;
6906
- }
6907
- else if (toolName === 'read_file' && args?.['file_path']) {
6908
- const path = String(args['file_path']).split('/').pop() || args['file_path'];
6909
- activity = `Reading ${path}`;
6910
- }
6911
- this.renderer?.setActivity(activity);
6912
- // Estimate tokens for tool call (~50 tokens per call)
6913
- this.streamingTokenCount += 50;
6914
- this.renderer?.updateStreamingTokens(this.streamingTokenCount);
6915
- }
6916
- else {
6917
- // Tool finished - estimate result tokens (~100 per result)
6918
- this.streamingTokenCount += 100;
6919
- this.renderer?.updateStreamingTokens(this.streamingTokenCount);
6920
- // Reset to thinking state while model generates next response
6921
- this.renderer?.setActivity('Thinking');
6922
- }
6923
- },
6924
6900
  onVerificationNeeded: (response, context) => {
6925
6901
  this.lastAssistantResponse = response;
6926
6902
  void this.runAutoQualityChecks('verification', response, context);
6927
6903
  },
6928
- // Retry notification for transient errors
6929
- onRetrying: (attempt, maxAttempts, error) => {
6930
- const shortError = error.message.slice(0, 100);
6931
- display.showSystemMessage(`⚡ Retry ${attempt}/${maxAttempts}: ${shortError}${error.message.length > 100 ? '...' : ''}`);
6932
- this.renderer?.setActivity(`Retrying (${attempt}/${maxAttempts})...`);
6933
- },
6934
6904
  });
6935
6905
  // Register global AI enhancer for explore tool - uses active model by default
6936
6906
  this.registerExploreAIEnhancer();
@@ -7025,30 +6995,20 @@ Return ONLY JSON array:
7025
6995
  return lines.join('\n').trim();
7026
6996
  }
7027
6997
  buildThinkingDirective() {
7028
- // Base requirement: ALWAYS think before acting (applies to all modes)
7029
- const baseRequirement = [
7030
- 'CRITICAL: Before calling ANY tool, ALWAYS output a <thinking>...</thinking> block explaining:',
7031
- '1. What you understand the user is asking',
7032
- '2. Your approach/plan to solve it',
7033
- '3. Which tools you will use and why',
7034
- 'This thinking block MUST appear before your first tool call in every response.',
7035
- ].join('\n');
7036
6998
  switch (this.thinkingMode) {
7037
6999
  case 'extended':
7038
7000
  return [
7039
- baseRequirement,
7040
- '',
7041
- 'Extended thinking mode: Use detailed multi-step reasoning in your <thinking> block.',
7042
- 'Reference tool runs/files when relevant. Keep secrets redacted.',
7043
- 'After thinking, wrap your final answer in <response>...</response>.',
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>',
7044
7008
  ].join('\n');
7045
7009
  case 'balanced':
7046
7010
  default:
7047
- return [
7048
- baseRequirement,
7049
- '',
7050
- 'Balanced mode: Keep thinking concise (2-4 sentences) but always present.',
7051
- ].join('\n');
7011
+ return 'Balanced thinking mode: include a short <thinking>...</thinking> block before <response> when the reasoning is non-trivial; skip it for simple answers.';
7052
7012
  }
7053
7013
  }
7054
7014
  buildDisplayMetadata(metadata) {
@@ -7139,14 +7099,13 @@ Return ONLY JSON array:
7139
7099
  });
7140
7100
  cleanupOverlayActive = true;
7141
7101
  const triggerReason = trigger?.reason ?? 'Context optimization';
7142
- // Claude Code style: Show "Compacting conversation..." with animation
7143
- // The renderer handles the animated spinner display
7144
- if (this.renderer) {
7145
- this.renderer.showCompactingStatus('Compacting conversation… (esc to interrupt)');
7146
- }
7147
- else {
7148
- display.showSystemMessage(`✻ Compacting conversation… (esc to interrupt)`);
7149
- }
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(' '));
7150
7109
  const result = await contextManager.intelligentCompact(history);
7151
7110
  let afterStats = contextManager.getStats(result.compacted);
7152
7111
  let appliedHistory = result.compacted;
@@ -7173,18 +7132,10 @@ Return ONLY JSON array:
7173
7132
  }
7174
7133
  }
7175
7134
  if (!changed) {
7176
- // Hide compacting status before showing info message
7177
- if (this.renderer) {
7178
- this.renderer.hideCompactingStatus();
7179
- }
7180
7135
  display.showInfo('Context compaction completed but no changes were applied.');
7181
7136
  return;
7182
7137
  }
7183
7138
  if (!this.hasMeaningfulCompaction(changed, bestTokenSavings, bestPercentSavings, trigger?.forced)) {
7184
- // Hide compacting status before showing info message
7185
- if (this.renderer) {
7186
- this.renderer.hideCompactingStatus();
7187
- }
7188
7139
  display.showInfo('Auto-compaction completed but did not meaningfully reduce context size. Keeping existing history.');
7189
7140
  return;
7190
7141
  }
@@ -7200,18 +7151,15 @@ Return ONLY JSON array:
7200
7151
  this.updateContextUsage(newPercentUsed, CONTEXT_AUTOCOMPACT_PERCENT);
7201
7152
  this.lastContextWarningLevel = this.getContextWarningLevel(newPercentUsed);
7202
7153
  this.refreshStatusLine(true);
7203
- // Claude Code style: Show compaction complete with separator and ctrl+o hint
7204
- // Stop the compacting animation first
7205
- if (this.renderer) {
7206
- this.renderer.hideCompactingStatus();
7207
- }
7208
- // Show the Claude Code style separator: ══ Conversation compacted · ctrl+o for history ═
7209
- if (this.renderer) {
7210
- this.renderer.addCompactBlock('', 'Conversation compacted · ctrl+o for history');
7211
- }
7212
- else {
7213
- display.showSystemMessage('══ Conversation compacted · ctrl+o for history ═');
7214
- }
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(' '));
7215
7163
  this.recordContextCompaction({
7216
7164
  timestamp: Date.now(),
7217
7165
  source: trigger?.source ?? 'auto',
@@ -7225,20 +7173,12 @@ Return ONLY JSON array:
7225
7173
  });
7226
7174
  }
7227
7175
  catch (error) {
7228
- // Hide compacting status animation on error
7229
- if (this.renderer) {
7230
- this.renderer.hideCompactingStatus();
7231
- }
7232
7176
  display.showError('Context compaction failed.', error);
7233
7177
  }
7234
7178
  finally {
7235
7179
  if (cleanupOverlayActive) {
7236
7180
  this.statusTracker.clearOverride(cleanupStatusId);
7237
7181
  }
7238
- // Ensure compacting status is cleared
7239
- if (this.renderer) {
7240
- this.renderer.hideCompactingStatus();
7241
- }
7242
7182
  this.cleanupInProgress = false;
7243
7183
  this.contextCompactionInFlight = false;
7244
7184
  }
@@ -7540,6 +7480,28 @@ Return ONLY JSON array:
7540
7480
  const message = error instanceof Error ? error.message : String(error);
7541
7481
  display.showError(message);
7542
7482
  }
7483
+ handleStreamingFallback(info) {
7484
+ const promptBlock = detectPromptBlockError(info.error ?? info.message);
7485
+ if (promptBlock) {
7486
+ this.handlePromptBlock(promptBlock);
7487
+ this.finishStreamingFormatter('Prompt blocked mid-stream', { mode: 'update' });
7488
+ display.showSystemMessage('Retrying without streaming, but the provider will likely block again until you rephrase.');
7489
+ this.startStreamingHeartbeat('Re-running without streaming');
7490
+ this.requestPromptRefresh(true);
7491
+ return;
7492
+ }
7493
+ const detailText = info.message?.trim() ?? '';
7494
+ const detail = detailText ? ` ${detailText}` : '';
7495
+ const reason = info.reason ? ` (${info.reason.replace(/-/g, ' ')})` : '';
7496
+ const partialNote = info.partialResponse
7497
+ ? ' Partial stream captured before failure; continuing from it without restarting.'
7498
+ : '';
7499
+ const baseMessage = 'Streaming interrupted, retrying without streaming';
7500
+ display.showWarning(`${baseMessage}${reason}.${detail}${partialNote}`.trim());
7501
+ this.finishStreamingFormatter('Stream interrupted - retrying without streaming', { mode: 'update' });
7502
+ this.startStreamingHeartbeat('Fallback in progress');
7503
+ this.requestPromptRefresh(true);
7504
+ }
7543
7505
  handleProviderError(error, retryAction) {
7544
7506
  const promptBlock = detectPromptBlockError(error);
7545
7507
  if (promptBlock) {