erosolar-cli 1.7.409 → 1.7.411

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 (192) hide show
  1. package/README.md +6 -6
  2. package/dist/StringUtils.d.ts +1 -4
  3. package/dist/StringUtils.d.ts.map +1 -1
  4. package/dist/StringUtils.js +2 -8
  5. package/dist/StringUtils.js.map +1 -1
  6. package/dist/browser/BrowserSessionManager.d.ts +1 -3
  7. package/dist/browser/BrowserSessionManager.d.ts.map +1 -1
  8. package/dist/browser/BrowserSessionManager.js +4 -24
  9. package/dist/browser/BrowserSessionManager.js.map +1 -1
  10. package/dist/capabilities/toolRegistry.d.ts +2 -0
  11. package/dist/capabilities/toolRegistry.d.ts.map +1 -1
  12. package/dist/capabilities/toolRegistry.js +40 -5
  13. package/dist/capabilities/toolRegistry.js.map +1 -1
  14. package/dist/contracts/agent-profiles.schema.json +5 -5
  15. package/dist/contracts/agent-schemas.json +6 -16
  16. package/dist/contracts/schemas/agent.schema.json +1 -5
  17. package/dist/contracts/schemas/tool-selection.schema.json +1 -7
  18. package/dist/contracts/tools.schema.json +80 -207
  19. package/dist/contracts/unified-schema.json +4 -5
  20. package/dist/contracts/v1/agent.d.ts +0 -3
  21. package/dist/contracts/v1/agent.d.ts.map +1 -1
  22. package/dist/contracts/v1/provider.d.ts +1 -2
  23. package/dist/contracts/v1/provider.d.ts.map +1 -1
  24. package/dist/contracts/v1/toolAccess.d.ts +1 -1
  25. package/dist/contracts/v1/toolAccess.d.ts.map +1 -1
  26. package/dist/core/agent.d.ts +1 -7
  27. package/dist/core/agent.d.ts.map +1 -1
  28. package/dist/core/agent.js +2 -131
  29. package/dist/core/agent.js.map +1 -1
  30. package/dist/core/alphaZeroEngine.d.ts +0 -8
  31. package/dist/core/alphaZeroEngine.d.ts.map +1 -1
  32. package/dist/core/alphaZeroEngine.js +35 -149
  33. package/dist/core/alphaZeroEngine.js.map +1 -1
  34. package/dist/core/alphaZeroOrchestrator.d.ts +0 -17
  35. package/dist/core/alphaZeroOrchestrator.d.ts.map +1 -1
  36. package/dist/core/alphaZeroOrchestrator.js +8 -95
  37. package/dist/core/alphaZeroOrchestrator.js.map +1 -1
  38. package/dist/core/cliTestHarness.d.ts +0 -5
  39. package/dist/core/cliTestHarness.d.ts.map +1 -1
  40. package/dist/core/cliTestHarness.js +3 -14
  41. package/dist/core/cliTestHarness.js.map +1 -1
  42. package/dist/core/contextManager.d.ts +0 -30
  43. package/dist/core/contextManager.d.ts.map +1 -1
  44. package/dist/core/contextManager.js +5 -87
  45. package/dist/core/contextManager.js.map +1 -1
  46. package/dist/core/contextWindow.d.ts +4 -4
  47. package/dist/core/contextWindow.js +9 -9
  48. package/dist/core/contextWindow.js.map +1 -1
  49. package/dist/core/modelDiscovery.js +3 -3
  50. package/dist/core/modelDiscovery.js.map +1 -1
  51. package/dist/core/preferences.d.ts +2 -3
  52. package/dist/core/preferences.d.ts.map +1 -1
  53. package/dist/core/preferences.js +11 -18
  54. package/dist/core/preferences.js.map +1 -1
  55. package/dist/core/secretStore.d.ts.map +1 -1
  56. package/dist/core/secretStore.js +31 -0
  57. package/dist/core/secretStore.js.map +1 -1
  58. package/dist/core/toolPreconditions.d.ts.map +1 -1
  59. package/dist/core/toolPreconditions.js +0 -60
  60. package/dist/core/toolPreconditions.js.map +1 -1
  61. package/dist/core/toolRuntime.d.ts.map +1 -1
  62. package/dist/core/toolRuntime.js +0 -17
  63. package/dist/core/toolRuntime.js.map +1 -1
  64. package/dist/core/types.d.ts +1 -1
  65. package/dist/core/types.d.ts.map +1 -1
  66. package/dist/plugins/providers/google/index.js +3 -2
  67. package/dist/plugins/providers/google/index.js.map +1 -1
  68. package/dist/providers/openaiChatCompletionsProvider.d.ts.map +1 -1
  69. package/dist/providers/openaiChatCompletionsProvider.js +6 -60
  70. package/dist/providers/openaiChatCompletionsProvider.js.map +1 -1
  71. package/dist/runtime/agentController.d.ts.map +1 -1
  72. package/dist/runtime/agentController.js +6 -27
  73. package/dist/runtime/agentController.js.map +1 -1
  74. package/dist/shell/interactiveShell.d.ts +30 -79
  75. package/dist/shell/interactiveShell.d.ts.map +1 -1
  76. package/dist/shell/interactiveShell.js +726 -1519
  77. package/dist/shell/interactiveShell.js.map +1 -1
  78. package/dist/shell/shellApp.d.ts.map +1 -1
  79. package/dist/shell/shellApp.js +41 -15
  80. package/dist/shell/shellApp.js.map +1 -1
  81. package/dist/shell/systemPrompt.d.ts.map +1 -1
  82. package/dist/shell/systemPrompt.js +0 -1
  83. package/dist/shell/systemPrompt.js.map +1 -1
  84. package/dist/shell/terminalInput.d.ts +21 -85
  85. package/dist/shell/terminalInput.d.ts.map +1 -1
  86. package/dist/shell/terminalInput.js +60 -517
  87. package/dist/shell/terminalInput.js.map +1 -1
  88. package/dist/shell/terminalInputAdapter.d.ts +16 -37
  89. package/dist/shell/terminalInputAdapter.d.ts.map +1 -1
  90. package/dist/shell/terminalInputAdapter.js +22 -44
  91. package/dist/shell/terminalInputAdapter.js.map +1 -1
  92. package/dist/shell/updateManager.d.ts +1 -8
  93. package/dist/shell/updateManager.d.ts.map +1 -1
  94. package/dist/shell/updateManager.js +14 -9
  95. package/dist/shell/updateManager.js.map +1 -1
  96. package/dist/subagents/parallelAgentManager.d.ts.map +1 -1
  97. package/dist/subagents/parallelAgentManager.js +2 -1
  98. package/dist/subagents/parallelAgentManager.js.map +1 -1
  99. package/dist/tools/buildTools.d.ts.map +1 -1
  100. package/dist/tools/buildTools.js +76 -19
  101. package/dist/tools/buildTools.js.map +1 -1
  102. package/dist/tools/editTools.js +1 -1
  103. package/dist/tools/editTools.js.map +1 -1
  104. package/dist/tools/enhancedCodeIntelligenceTools.js +2 -1
  105. package/dist/tools/enhancedCodeIntelligenceTools.js.map +1 -1
  106. package/dist/tools/fileTools.js +0 -3
  107. package/dist/tools/fileTools.js.map +1 -1
  108. package/dist/tools/frontendTestingTools.js +1 -1
  109. package/dist/tools/frontendTestingTools.js.map +1 -1
  110. package/dist/tools/learnTools.d.ts +0 -2
  111. package/dist/tools/learnTools.d.ts.map +1 -1
  112. package/dist/tools/learnTools.js +81 -29
  113. package/dist/tools/learnTools.js.map +1 -1
  114. package/dist/tools/localExplore.d.ts.map +1 -1
  115. package/dist/tools/localExplore.js +1 -0
  116. package/dist/tools/localExplore.js.map +1 -1
  117. package/dist/tools/notebookEditTools.js.map +1 -1
  118. package/dist/tools/repoChecksTools.js +3 -4
  119. package/dist/tools/repoChecksTools.js.map +1 -1
  120. package/dist/tools/searchTools.js +0 -4
  121. package/dist/tools/searchTools.js.map +1 -1
  122. package/dist/tools/softwareEngineeringTools.d.ts.map +1 -1
  123. package/dist/tools/softwareEngineeringTools.js +0 -1
  124. package/dist/tools/softwareEngineeringTools.js.map +1 -1
  125. package/dist/tools/webTools.d.ts.map +1 -1
  126. package/dist/tools/webTools.js.map +1 -1
  127. package/dist/ui/ShellUIAdapter.d.ts +16 -44
  128. package/dist/ui/ShellUIAdapter.d.ts.map +1 -1
  129. package/dist/ui/ShellUIAdapter.js +84 -337
  130. package/dist/ui/ShellUIAdapter.js.map +1 -1
  131. package/dist/ui/display.d.ts +37 -18
  132. package/dist/ui/display.d.ts.map +1 -1
  133. package/dist/ui/display.js +310 -95
  134. package/dist/ui/display.js.map +1 -1
  135. package/dist/ui/orchestration/UIUpdateCoordinator.d.ts.map +1 -1
  136. package/dist/ui/orchestration/UIUpdateCoordinator.js +3 -5
  137. package/dist/ui/orchestration/UIUpdateCoordinator.js.map +1 -1
  138. package/dist/ui/shortcutsHelp.d.ts +11 -1
  139. package/dist/ui/shortcutsHelp.d.ts.map +1 -1
  140. package/dist/ui/shortcutsHelp.js +54 -8
  141. package/dist/ui/shortcutsHelp.js.map +1 -1
  142. package/dist/ui/streamingFormatter.d.ts +16 -0
  143. package/dist/ui/streamingFormatter.d.ts.map +1 -0
  144. package/dist/ui/streamingFormatter.js +62 -0
  145. package/dist/ui/streamingFormatter.js.map +1 -0
  146. package/dist/ui/theme.d.ts +100 -100
  147. package/dist/ui/theme.d.ts.map +1 -1
  148. package/dist/ui/theme.js.map +1 -1
  149. package/dist/ui/toolDisplay.d.ts +3 -3
  150. package/dist/ui/toolDisplay.d.ts.map +1 -1
  151. package/dist/ui/toolDisplay.js +8 -7
  152. package/dist/ui/toolDisplay.js.map +1 -1
  153. package/dist/ui/unified/index.d.ts +2 -2
  154. package/dist/ui/unified/index.d.ts.map +1 -1
  155. package/dist/ui/unified/index.js.map +1 -1
  156. package/dist/ui/unified/layout.d.ts +23 -0
  157. package/dist/ui/unified/layout.d.ts.map +1 -1
  158. package/dist/ui/unified/layout.js +113 -11
  159. package/dist/ui/unified/layout.js.map +1 -1
  160. package/package.json +24 -37
  161. package/dist/core/alphaZeroConfig.d.ts +0 -11
  162. package/dist/core/alphaZeroConfig.d.ts.map +0 -1
  163. package/dist/core/alphaZeroConfig.js +0 -59
  164. package/dist/core/alphaZeroConfig.js.map +0 -1
  165. package/dist/core/alphaZeroEnhanced.d.ts +0 -125
  166. package/dist/core/alphaZeroEnhanced.d.ts.map +0 -1
  167. package/dist/core/alphaZeroEnhanced.js +0 -386
  168. package/dist/core/alphaZeroEnhanced.js.map +0 -1
  169. package/dist/core/autonomousVerification.d.ts +0 -103
  170. package/dist/core/autonomousVerification.d.ts.map +0 -1
  171. package/dist/core/autonomousVerification.js +0 -583
  172. package/dist/core/autonomousVerification.js.map +0 -1
  173. package/dist/core/offsecAlphaZeroEnhanced.d.ts +0 -98
  174. package/dist/core/offsecAlphaZeroEnhanced.d.ts.map +0 -1
  175. package/dist/core/offsecAlphaZeroEnhanced.js +0 -441
  176. package/dist/core/offsecAlphaZeroEnhanced.js.map +0 -1
  177. package/dist/core/parallelAgentOrchestrator.d.ts +0 -171
  178. package/dist/core/parallelAgentOrchestrator.d.ts.map +0 -1
  179. package/dist/core/parallelAgentOrchestrator.js +0 -459
  180. package/dist/core/parallelAgentOrchestrator.js.map +0 -1
  181. package/dist/index.d.ts +0 -5
  182. package/dist/index.d.ts.map +0 -1
  183. package/dist/index.js +0 -3
  184. package/dist/index.js.map +0 -1
  185. package/dist/tools/detectCommands.d.ts +0 -8
  186. package/dist/tools/detectCommands.d.ts.map +0 -1
  187. package/dist/tools/detectCommands.js +0 -183
  188. package/dist/tools/detectCommands.js.map +0 -1
  189. package/dist/ui/assistantBlockRenderer.d.ts +0 -30
  190. package/dist/ui/assistantBlockRenderer.d.ts.map +0 -1
  191. package/dist/ui/assistantBlockRenderer.js +0 -121
  192. package/dist/ui/assistantBlockRenderer.js.map +0 -1
@@ -1,18 +1,19 @@
1
1
  import { stdin as input, stdout as output, exit } from 'node:process';
2
2
  import { exec } from 'node:child_process';
3
3
  import { promisify } from 'node:util';
4
- import { AssistantBlockRenderer } from '../ui/assistantBlockRenderer.js';
4
+ import { existsSync, readFileSync } from 'node:fs';
5
+ import { join } from 'node:path';
5
6
  import { display } from '../ui/display.js';
6
- import { compactRenderer } from '../ui/compactRenderer.js';
7
- import { theme, icons } from '../ui/theme.js';
7
+ import { isPlainOutputMode } from '../ui/outputMode.js';
8
+ import { theme } from '../ui/theme.js';
9
+ import { StreamingResponseFormatter } from '../ui/streamingFormatter.js';
8
10
  import { getContextWindowTokens } from '../core/contextWindow.js';
9
- import { ensureSecretForProvider, getSecretDefinitionForProvider, getSecretValue, listSecretDefinitions, MissingSecretError, maskSecret, setSecretValue, } from '../core/secretStore.js';
11
+ import { ensureSecretForProvider, getSecretDefinitionForProvider, getSecretValue, listSecretDefinitions, maskSecret, setSecretValue, } from '../core/secretStore.js';
10
12
  import { saveActiveProfilePreference, saveModelPreference, loadToolSettings, saveToolSettings, clearToolSettings, clearActiveProfilePreference, loadSessionPreferences, saveSessionPreferences, loadFeatureFlags, saveFeatureFlags, toggleFeatureFlag, FEATURE_FLAG_INFO, } from '../core/preferences.js';
11
13
  import { getLearningSummary, getRecentLearning, commitLearning, exportAllLearning, getLearningDir, } from '../core/learningPersistence.js';
12
14
  import { buildEnabledToolSet, evaluateToolPermissions, getToolToggleOptions, } from '../capabilities/toolRegistry.js';
13
15
  import { detectApiKeyError } from '../core/errors/apiKeyErrors.js';
14
16
  import { buildWorkspaceContext } from '../workspace.js';
15
- import { detectBuildCommand, detectTestCommand } from '../tools/detectCommands.js';
16
17
  import { buildInteractiveSystemPrompt } from './systemPrompt.js';
17
18
  import { getTaskCompletionDetector, resetTaskCompletionDetector, } from './taskCompletionDetector.js';
18
19
  import { discoverAllModels, quickCheckProviders, getCachedDiscoveredModels, sortModelsByPriority } from '../core/modelDiscovery.js';
@@ -37,13 +38,11 @@ import { startOffsecRun, resumeOffsecRun, recordOffsecOutcome, getOffsecNextActi
37
38
  import { generateTestFlows, detectBugs, detectUIUpdates, saveTestFlows, saveBugReports, saveUIUpdates, getTestFlowStatus, } from '../core/intelligentTestFlows.js';
38
39
  import { TerminalInputAdapter } from './terminalInputAdapter.js';
39
40
  import { renderSessionFrame } from '../ui/unified/layout.js';
40
- import { isUpdateInProgress, maybeOfferCliUpdate } from './updateManager.js';
41
41
  import { writeLock } from '../ui/writeLock.js';
42
42
  import { enterStreamingMode, exitStreamingMode } from '../ui/globalWriteLock.js';
43
43
  import { setGlobalAIEnhancer } from '../tools/localExplore.js';
44
44
  import { createProvider } from '../providers/providerFactory.js';
45
45
  import { getParallelAgentManager } from '../subagents/parallelAgentManager.js';
46
- import { formatThinkingContent } from '../ui/textHighlighter.js';
47
46
  const execAsync = promisify(exec);
48
47
  const DROPDOWN_COLORS = [
49
48
  theme.primary,
@@ -118,14 +117,11 @@ export class InteractiveShell {
118
117
  ui;
119
118
  uiAdapter;
120
119
  uiUpdates;
121
- assistantBlocksEnabled;
122
- assistantBlockRenderer;
123
120
  _fileChangeTracker = new FileChangeTracker(); // Reserved for future file tracking features
124
121
  alphaZeroMetrics; // Alpha Zero 2 performance tracking
125
122
  statusSubscription = null;
126
123
  followUpQueue = [];
127
124
  isDrainingQueue = false;
128
- apiKeyGateActive = false;
129
125
  activeContextWindowTokens = null;
130
126
  latestTokenUsage = { used: null, limit: null };
131
127
  planApprovalBridgeRegistered = false;
@@ -134,9 +130,7 @@ export class InteractiveShell {
134
130
  sessionPreferences;
135
131
  autosaveEnabled;
136
132
  autoContinueEnabled;
137
- verificationEnabled = true;
138
- alphaZeroModeEnabled;
139
- alphaZeroVerificationSnapshot = null;
133
+ verificationEnabled = false;
140
134
  editGuardMode = 'display-edits';
141
135
  pendingPermissionInput = null;
142
136
  pendingHistoryLoad = null;
@@ -148,51 +142,36 @@ export class InteractiveShell {
148
142
  customCommandMap;
149
143
  sessionRestoreConfig;
150
144
  _enabledPlugins;
151
- autoVerificationInFlight = false;
145
+ // Cached provider status for unified status bar display after streaming
146
+ cachedProviderStatus = [];
152
147
  // Auto-test tracking
153
148
  autoTestInFlight = false;
154
149
  // AlphaZero learning tracking
155
150
  currentTaskType = 'general';
156
151
  currentToolCalls = [];
157
152
  lastUserQuery = '';
153
+ lastAssistantResponse = null;
158
154
  lastFailure = null;
159
155
  lastAutoTestRun = null;
160
- runIdCounter = 0;
161
- lastRunLog = null;
162
- lastReflectedRunId = null;
163
- skipNextAutoReflection = false;
164
- alphaZeroAutoImproveActive = false;
165
- alphaZeroAutoImproveIterations = 0;
166
- alphaZeroAutoImproveMaxIterations = 6;
167
156
  // Auto-build tracking
168
157
  autoBuildInFlight = false;
158
+ autoBuildPromise = null;
169
159
  lastAutoBuildRun = null;
160
+ lastBuildSucceededAt = null;
170
161
  // Offsec AlphaZero tracking
171
162
  offsecRunId = null;
172
163
  // Streaming UX tracking
173
164
  streamingHeartbeatStart = null;
174
165
  streamingHeartbeatFrame = 0;
175
166
  streamingStatusLabel = null;
176
- streamingStatusBase = null;
177
- streamingStatusDetail = null;
178
167
  lastStreamingElapsedSeconds = null; // Preserve final elapsed time
168
+ streamingFormatter = null;
179
169
  statusLineState = null;
180
170
  statusMessageOverride = null;
181
- latestThoughtSummary = null;
182
- hasShownThoughtProcess = false;
183
171
  promptRefreshTimer = null;
184
172
  launchPaletteShown = false;
185
173
  version;
186
174
  alternateScreenEnabled;
187
- inputInitialized = false;
188
- assistantStreamActive = false;
189
- assistantStreamPhase = null;
190
- assistantStreamHeaderShown = false;
191
- assistantStreamBuffer = '';
192
- assistantStreamMetadata;
193
- assistantStreamHadContent = false;
194
- lastRequestStartedAt = null;
195
- lastToolSummaryRenderedAt = null;
196
175
  constructor(config) {
197
176
  this.profile = config.profile;
198
177
  this.profileLabel = config.profileLabel;
@@ -204,10 +183,10 @@ export class InteractiveShell {
204
183
  this.thinkingMode = this.sessionPreferences.thinkingMode;
205
184
  this.autosaveEnabled = this.sessionPreferences.autosave;
206
185
  this.autoContinueEnabled = this.sessionPreferences.autoContinue;
207
- this.alphaZeroModeEnabled = this.sessionPreferences.alphaZeroMode;
186
+ const featureFlags = loadFeatureFlags();
187
+ this.verificationEnabled = featureFlags.verification === true;
208
188
  this.sessionRestoreConfig = config.sessionRestore ?? { mode: 'none' };
209
189
  this._enabledPlugins = config.enabledPlugins ?? [];
210
- this.assistantBlocksEnabled = config.assistantBlocksEnabled ?? true;
211
190
  this.version = config.version ?? '0.0.0';
212
191
  // Alternate screen disabled - use terminal-native mode for proper scrollback and text selection
213
192
  this.alternateScreenEnabled = false;
@@ -256,21 +235,11 @@ export class InteractiveShell {
256
235
  description: 'Show available and loaded plugins',
257
236
  category: 'configuration',
258
237
  });
259
- this.slashCommands.push({
260
- command: '/contextlog',
261
- description: 'Show recent context compaction summaries',
262
- category: 'context',
263
- });
264
238
  this.slashCommands.push({
265
239
  command: '/offsec',
266
240
  description: 'AlphaZero offensive security run (start/status/next)',
267
241
  category: 'automation',
268
242
  });
269
- this.slashCommands.push({
270
- command: '/alphazero',
271
- description: 'Toggle AlphaZero RL mode with full-cycle verification',
272
- category: 'mode',
273
- });
274
243
  this.statusTracker = config.statusTracker;
275
244
  this.ui = config.ui;
276
245
  this.uiAdapter = config.ui.adapter;
@@ -282,11 +251,7 @@ export class InteractiveShell {
282
251
  });
283
252
  // Set up tool status callback to update status during tool execution
284
253
  this.uiAdapter.setToolStatusCallback((status) => {
285
- const statusText = status?.text ?? null;
286
- if (statusText) {
287
- this.terminalInput.recordRecentAction(`[tool] ${statusText}`);
288
- }
289
- this.updateStatusMessage(statusText, { logRecent: false });
254
+ this.updateStatusMessage(status ?? null);
290
255
  });
291
256
  this.skillRepository = new SkillRepository({
292
257
  workingDir: this.workingDir,
@@ -306,19 +271,14 @@ export class InteractiveShell {
306
271
  onToggleVerify: () => this.toggleVerificationMode(),
307
272
  onToggleAutoContinue: () => this.toggleAutoContinueMode(),
308
273
  onToggleThinking: () => this.cycleThinkingMode(),
309
- onToggleAlphaZero: () => this.toggleAlphaZeroMode('shortcut'),
310
274
  onClearContext: () => this.handleClearContext(),
311
275
  });
312
- this.assistantBlockRenderer = this.assistantBlocksEnabled
313
- ? new AssistantBlockRenderer({
314
- write: (text) => this.writeAssistantBlock(text),
315
- updates: this.uiUpdates,
316
- })
317
- : null;
318
276
  // Initialize Alpha Zero 2 metrics tracking
319
277
  this.alphaZeroMetrics = new MetricsTracker(`${this.profile}-${Date.now()}`);
320
278
  this.setupStatusTracking();
321
279
  this.refreshContextGauge();
280
+ // Start terminal input (sets up handlers)
281
+ this.terminalInput.start();
322
282
  // Allow planning tools (e.g., ProposePlan) to open the interactive approval UI just like Codex CLI
323
283
  this.registerPlanApprovalBridge();
324
284
  // Capture display output into the scrollback/chat log so system messages
@@ -336,6 +296,9 @@ export class InteractiveShell {
336
296
  // Stream banner first - this sets up scroll region dynamically
337
297
  const banner = this.buildBanner();
338
298
  this.terminalInput.streamContent(banner + '\n\n');
299
+ // Render chat box after banner is streamed
300
+ this.refreshControlBar();
301
+ this.terminalInput.forceRender();
339
302
  this.rebuildAgent();
340
303
  this.setupHandlers();
341
304
  this.refreshBannerSessionInfo();
@@ -350,13 +313,7 @@ export class InteractiveShell {
350
313
  this.parallelAgentDisplayLines = manager.formatDisplay();
351
314
  // Trigger UI refresh if streaming
352
315
  if (this.streamingHeartbeatStart) {
353
- this.uiUpdates.enqueue({
354
- lane: 'stream',
355
- mode: ['streaming', 'processing'],
356
- coalesceKey: 'parallel-agents',
357
- description: 'parallel agent status',
358
- run: () => this.displayParallelAgents(),
359
- });
316
+ this.displayParallelAgents();
360
317
  }
361
318
  };
362
319
  manager.on('agent:started', updateDisplay);
@@ -427,56 +384,13 @@ export class InteractiveShell {
427
384
  this.sessionResumeNotice = null;
428
385
  }
429
386
  async start(initialPrompt) {
430
- await this.runStartupUpdatePrompt();
431
- this.ensureInputInitialized();
432
387
  if (initialPrompt) {
433
388
  await this.processInputBlock(initialPrompt);
434
389
  return;
435
390
  }
436
391
  this.showLaunchCommandPalette();
437
392
  // Ensure the terminal input is visible
438
- this.renderPromptArea();
439
- }
440
- async runStartupUpdatePrompt() {
441
- if (this.version) {
442
- try {
443
- await maybeOfferCliUpdate(this.version);
444
- }
445
- catch {
446
- // Ignore update check failures at startup
447
- }
448
- }
449
- this.showStartupSecretGuidance();
450
- }
451
- ensureInputInitialized() {
452
- if (this.inputInitialized) {
453
- return;
454
- }
455
- this.terminalInput.start();
456
- this.refreshControlBar();
457
- this.renderPromptArea(true);
458
- this.inputInitialized = true;
459
- }
460
- showStartupSecretGuidance() {
461
- const definitions = listSecretDefinitions();
462
- const providerSecret = definitions.find((definition) => definition.providers.includes(this.sessionState.provider));
463
- const missingProviderKey = providerSecret ? !getSecretValue(providerSecret.id) : false;
464
- const hasSearchKey = Boolean(getSecretValue('TAVILY_API_KEY')) ||
465
- Boolean(getSecretValue('BRAVE_SEARCH_API_KEY')) ||
466
- Boolean(getSecretValue('SERPAPI_API_KEY'));
467
- if (!missingProviderKey && hasSearchKey) {
468
- return;
469
- }
470
- const lines = [];
471
- lines.push(theme.gradient.primary('Quick setup needed:'));
472
- if (missingProviderKey && providerSecret) {
473
- lines.push(`${theme.primary('•')} Set ${providerSecret.label} to use ${this.providerLabel(this.sessionState.provider)}.`);
474
- }
475
- if (!hasSearchKey) {
476
- lines.push(`${theme.primary('•')} Add a web search API key (Tavily recommended) for web tools.`);
477
- }
478
- lines.push(theme.ui.muted('Run /secrets to configure keys now. Stored values override env vars.'));
479
- display.showSystemMessage(lines.join('\n'));
393
+ this.terminalInput.render();
480
394
  }
481
395
  showLaunchCommandPalette() {
482
396
  // Disabled: Quick commands palette takes up too much space
@@ -487,10 +401,6 @@ export class InteractiveShell {
487
401
  * TerminalInputAdapter submit handler
488
402
  */
489
403
  processInput(text) {
490
- // Guard: Don't process input if CLI update is in progress (we're about to exit)
491
- if (isUpdateInProgress()) {
492
- return;
493
- }
494
404
  const approved = this.resolveEditPermission(text);
495
405
  if (!approved) {
496
406
  this.handleInputChange('');
@@ -506,10 +416,6 @@ export class InteractiveShell {
506
416
  * TerminalInputAdapter queue handler (streaming mode)
507
417
  */
508
418
  handleQueuedInput(text) {
509
- // Guard: Don't queue input if CLI update is in progress (we're about to exit)
510
- if (isUpdateInProgress()) {
511
- return;
512
- }
513
419
  if (this.isExitCommand(text)) {
514
420
  // Allow immediate exits even while streaming
515
421
  this.terminalInput.dequeue();
@@ -534,8 +440,7 @@ export class InteractiveShell {
534
440
  // Keep adapter queue trimmed so hints stay accurate
535
441
  this.terminalInput.dequeue();
536
442
  this.followUpQueue.push({ type: 'request', text });
537
- // Record the queued action in the pinned recent strip instead of the scroll log
538
- this.terminalInput.recordRecentAction(`queued: ${text}`);
443
+ display.showInfo(`Queued: "${text}"`);
539
444
  this.refreshQueueIndicators();
540
445
  this.scheduleQueueProcessing();
541
446
  this.handleInputChange('');
@@ -574,7 +479,6 @@ export class InteractiveShell {
574
479
  // Mode toggles
575
480
  '/thinking',
576
481
  '/autocontinue',
577
- '/alphazero',
578
482
  // Discovery and plugins
579
483
  '/local', '/discover',
580
484
  '/plugins',
@@ -596,30 +500,15 @@ export class InteractiveShell {
596
500
  * Execute a command immediately during streaming.
597
501
  */
598
502
  async executeImmediateCommand(text) {
599
- const label = text.trim() || 'command';
600
- // Keep the action visible in the pinned input area instead of the scroll log
601
- this.terminalInput.recordRecentAction(label);
602
- // Surface a lightweight inline status while the command runs
603
- const previousOverride = this.statusMessageOverride;
604
- this.statusMessageOverride = `Running ${label}`;
605
- this.refreshStatusLine(true);
606
- try {
607
- await this.processSlashCommand(text);
608
- }
609
- finally {
610
- this.statusMessageOverride = previousOverride;
611
- this.refreshStatusLine(true);
612
- }
503
+ // Pause streaming display briefly to show command output
504
+ display.showInfo(`Running command during stream: ${text}`);
505
+ await this.processSlashCommand(text);
613
506
  }
614
507
  /**
615
508
  * TerminalInputAdapter change handler
616
509
  */
617
510
  handleInputChange(text) {
618
- const previous = this.currentInput;
619
511
  this.currentInput = text;
620
- if (previous !== text) {
621
- this.terminalInput.clearInlineCommandPanel();
622
- }
623
512
  if (text.length > 0) {
624
513
  this.resetCtrlCSequence();
625
514
  }
@@ -641,7 +530,7 @@ export class InteractiveShell {
641
530
  display.showSystemMessage('✏️ Display edits mode enabled.');
642
531
  }
643
532
  }
644
- this.renderPromptArea();
533
+ this.terminalInput.render();
645
534
  }
646
535
  toggleVerificationMode() {
647
536
  this.setVerificationMode(!this.verificationEnabled, 'shortcut');
@@ -654,8 +543,8 @@ export class InteractiveShell {
654
543
  return;
655
544
  }
656
545
  const message = enabled
657
- ? '✅ Verification on. Auto-tests will run after edits. (Ctrl+Shift+V to toggle)'
658
- : '⏭️ Verification off. Skipping auto-tests until re-enabled. (Ctrl+Shift+V to toggle)';
546
+ ? '✅ Verification on for this session. Auto-tests will run after edits. (Ctrl+Shift+V to toggle; use /features verification on and restart to make this the default)'
547
+ : '⏭️ Verification off. Skipping auto-tests until re-enabled. (Ctrl+Shift+V to toggle; defaults to off unless enabled via /features)';
659
548
  display.showSystemMessage(message);
660
549
  }
661
550
  toggleAutoContinueMode() {
@@ -678,50 +567,15 @@ export class InteractiveShell {
678
567
  : 'The model will not be auto-prompted to continue.') +
679
568
  ' Toggle with Ctrl+Shift+C.');
680
569
  }
681
- toggleAlphaZeroMode(source = 'shortcut') {
682
- this.setAlphaZeroMode(!this.alphaZeroModeEnabled, source);
683
- }
684
- setAlphaZeroMode(enabled, source) {
685
- const changed = this.alphaZeroModeEnabled !== enabled;
686
- this.alphaZeroModeEnabled = enabled;
687
- saveSessionPreferences({ alphaZeroMode: this.alphaZeroModeEnabled });
688
- // Force verification on while AlphaZero mode is active to guarantee deep checks
689
- if (enabled) {
690
- if (this.alphaZeroVerificationSnapshot === null) {
691
- this.alphaZeroVerificationSnapshot = this.verificationEnabled;
692
- }
693
- if (!this.verificationEnabled) {
694
- this.setVerificationMode(true, 'command');
695
- }
696
- }
697
- else if (this.alphaZeroVerificationSnapshot !== null) {
698
- this.setVerificationMode(this.alphaZeroVerificationSnapshot, 'command');
699
- this.alphaZeroVerificationSnapshot = null;
700
- }
701
- this.refreshControlBar();
702
- if (!changed && source === 'shortcut') {
703
- return;
704
- }
705
- if (enabled) {
706
- display.showSystemMessage('♞ AlphaZero RL mode enabled. Difficult prompts will use the duel/self-critique playbook with full lifecycle verification.');
707
- }
708
- else {
709
- display.showInfo('AlphaZero RL mode disabled. Returning to standard flow.');
710
- }
711
- }
712
570
  /**
713
571
  * Cycle through thinking modes (Ctrl+Shift+T keyboard shortcut).
714
572
  */
715
573
  cycleThinkingMode() {
716
- const modes = ['concise', 'balanced', 'extended'];
717
- const currentIndex = modes.indexOf(this.thinkingMode);
718
- const nextIndex = (currentIndex + 1) % modes.length;
719
- const nextMode = modes[nextIndex];
574
+ const nextMode = this.thinkingMode === 'balanced' ? 'extended' : 'balanced';
720
575
  this.thinkingMode = nextMode;
721
576
  saveSessionPreferences({ thinkingMode: this.thinkingMode });
722
577
  this.refreshControlBar();
723
578
  const descriptions = {
724
- concise: 'Minimal reasoning, faster responses',
725
579
  balanced: 'Default reasoning depth',
726
580
  extended: 'Deep reasoning, thorough analysis',
727
581
  };
@@ -744,27 +598,17 @@ export class InteractiveShell {
744
598
  */
745
599
  async performContextCompaction() {
746
600
  try {
747
- const sourceHistory = this.agent ? this.agent.getHistory() : this.cachedHistory;
748
- const oldLength = sourceHistory.length;
601
+ // For now, just clear the history and show a message
602
+ // A full implementation would summarize the conversation
603
+ const oldLength = this.cachedHistory.length;
749
604
  if (oldLength === 0) {
750
605
  display.showInfo('Context is already empty.');
751
606
  return;
752
607
  }
753
- // Preserve the first system message (base prompt) and the last few user/assistant turns.
754
- const systemMessage = sourceHistory.find((message) => message.role === 'system') ?? null;
755
- const conversationalMessages = sourceHistory.filter((message) => message.role !== 'system');
756
- const keepCount = Math.min(4, conversationalMessages.length);
757
- const tail = keepCount > 0 ? conversationalMessages.slice(-keepCount) : [];
758
- const compacted = systemMessage ? [systemMessage, ...tail] : tail;
759
- this.cachedHistory = compacted;
760
- if (this.agent) {
761
- this.agent.loadHistory(compacted);
762
- }
763
- // Reset tracked context usage so tools and status bars stay in sync post-compaction.
764
- this.latestTokenUsage = { used: null, limit: this.activeContextWindowTokens ?? null };
765
- this.updateContextUsage(0, CONTEXT_AUTOCOMPACT_PERCENT);
766
- this.lastContextWarningLevel = null;
767
- display.showSuccess(`Context compacted: ${oldLength} messages reduced to ${compacted.length}. ` +
608
+ // Keep the last few messages for continuity
609
+ const keepCount = Math.min(4, oldLength);
610
+ this.cachedHistory = this.cachedHistory.slice(-keepCount);
611
+ display.showSuccess(`Context compacted: ${oldLength} messages reduced to ${keepCount}. ` +
768
612
  `Context usage reset. Continue your conversation.`);
769
613
  this.refreshControlBar();
770
614
  }
@@ -796,7 +640,7 @@ export class InteractiveShell {
796
640
  if (['n', 'no', 'cancel', '/cancel'].includes(lower)) {
797
641
  this.pendingPermissionInput = null;
798
642
  display.showInfo('Request cancelled.');
799
- this.renderPromptArea();
643
+ this.terminalInput.render();
800
644
  return null;
801
645
  }
802
646
  // Treat any other input as a replacement request that also needs confirmation
@@ -814,7 +658,7 @@ export class InteractiveShell {
814
658
  ]
815
659
  .filter(Boolean)
816
660
  .join('\n'));
817
- this.renderPromptArea();
661
+ this.terminalInput.render();
818
662
  }
819
663
  /**
820
664
  * Handle Ctrl+C presses in three stages:
@@ -828,8 +672,8 @@ export class InteractiveShell {
828
672
  if (this.ctrlCPressCount === 1) {
829
673
  this.clearChatInput();
830
674
  const prefix = hadBuffer ? 'Input cleared.' : 'Nothing to clear.';
831
- display.showSystemMessage(`${prefix} Press Ctrl+C again to pause the AI; a third time quits.`);
832
- this.renderPromptArea();
675
+ display.showSystemMessage(`${prefix} Press Ctrl+C again to pause the AI.`);
676
+ this.terminalInput.render();
833
677
  return;
834
678
  }
835
679
  if (this.ctrlCPressCount === 2) {
@@ -841,10 +685,10 @@ export class InteractiveShell {
841
685
  pauseAiExecution() {
842
686
  if (this.isProcessing && this.agent) {
843
687
  this.agent.requestCancellation();
844
- display.showWarning('Pausing AI execution... (Press Ctrl+C again to quit)');
688
+ display.showWarning('Pausing AI execution...');
845
689
  return;
846
690
  }
847
- display.showInfo('No active AI execution to pause. Press Ctrl+C again to quit.');
691
+ display.showInfo('No active AI execution to pause.');
848
692
  }
849
693
  resetCtrlCSequence() {
850
694
  this.ctrlCPressCount = 0;
@@ -877,7 +721,7 @@ export class InteractiveShell {
877
721
  : null;
878
722
  // Stop any active spinner to prevent process hang
879
723
  display.stopThinking(false);
880
- this.stopStreamingHeartbeat({ skipRender: true });
724
+ this.stopStreamingHeartbeat();
881
725
  this.uiUpdates.dispose();
882
726
  this.clearPromptRefreshTimer();
883
727
  this.teardownStatusTracking();
@@ -886,9 +730,7 @@ export class InteractiveShell {
886
730
  // Clear any pending cleanup to prevent hanging
887
731
  this.pendingCleanup = null;
888
732
  // Reset terminal state before disposing adapters
889
- if (this.terminalInput.isScrollRegionActive()) {
890
- this.terminalInput.exitStreamingScrollRegion({ skipRender: true });
891
- }
733
+ this.terminalInput.exitStreamingScrollRegion();
892
734
  if (this.alternateScreenEnabled) {
893
735
  this.terminalInput.exitAlternateScreen();
894
736
  }
@@ -901,9 +743,7 @@ export class InteractiveShell {
901
743
  if (scrollbackSnapshot && scrollbackSnapshot.length > 0) {
902
744
  this.restoreScrollbackSnapshot(scrollbackSnapshot);
903
745
  }
904
- console.log(theme.ui.muted(''.repeat(44)));
905
- console.log(theme.ui.muted(' Goodbye! · support@ero.solar'));
906
- console.log(theme.ui.muted('━'.repeat(44)));
746
+ display.stream(`\n${theme.ui.muted('Session closed.')}\n`);
907
747
  exit(0);
908
748
  }
909
749
  restoreScrollbackSnapshot(lines) {
@@ -913,8 +753,8 @@ export class InteractiveShell {
913
753
  const transcript = lines.join('\n');
914
754
  const separator = theme.ui.muted('─'.repeat(44));
915
755
  const header = theme.ui.muted('Restored scrollback from this session:');
916
- // Write directly to stdout after exiting alternate screen to preserve the transcript
917
- process.stdout.write(`\n${separator}\n${header}\n${transcript}\n${separator}\n`);
756
+ // Stream the restored transcript so it stays within the in-stream UI flow
757
+ display.stream(`\n${separator}\n${header}\n${transcript}\n${separator}\n`);
918
758
  }
919
759
  /**
920
760
  * Wire the planning tool suite to the interactive plan approval UI so ProposePlan behaves like Codex CLI.
@@ -931,11 +771,8 @@ export class InteractiveShell {
931
771
  /**
932
772
  * Update status bar message
933
773
  */
934
- updateStatusMessage(message, options = {}) {
774
+ updateStatusMessage(message) {
935
775
  this.statusMessageOverride = message;
936
- if (message && options.logRecent !== false) {
937
- this.terminalInput.recordRecentAction(`[status] ${message}`);
938
- }
939
776
  // During streaming we still want the spinner prefix; when idle force a fast refresh.
940
777
  this.refreshStatusLine(!this.isProcessing);
941
778
  }
@@ -947,26 +784,26 @@ export class InteractiveShell {
947
784
  const trimmed = input.trim();
948
785
  if (!trimmed) {
949
786
  display.showWarning('Enter a number, "save", "defaults", or "cancel".');
950
- this.renderPromptArea();
787
+ this.terminalInput.render();
951
788
  return;
952
789
  }
953
790
  const normalized = trimmed.toLowerCase();
954
791
  if (normalized === 'cancel') {
955
792
  this.pendingInteraction = null;
956
793
  display.showInfo('Tool selection cancelled.');
957
- this.renderPromptArea();
794
+ this.terminalInput.render();
958
795
  return;
959
796
  }
960
797
  if (normalized === 'defaults') {
961
798
  pending.selection = buildEnabledToolSet(null);
962
799
  this.renderToolMenu(pending);
963
- this.renderPromptArea();
800
+ this.terminalInput.render();
964
801
  return;
965
802
  }
966
803
  if (normalized === 'save') {
967
804
  await this.persistToolSelection(pending);
968
805
  this.pendingInteraction = null;
969
- this.renderPromptArea();
806
+ this.terminalInput.render();
970
807
  return;
971
808
  }
972
809
  const choice = Number.parseInt(trimmed, 10);
@@ -976,19 +813,24 @@ export class InteractiveShell {
976
813
  display.showWarning('That option is not available.');
977
814
  }
978
815
  else {
979
- if (pending.selection.has(option.id)) {
980
- pending.selection.delete(option.id);
816
+ if (option.locked) {
817
+ display.showInfo('The default tool package is always enabled and cannot be toggled.');
981
818
  }
982
819
  else {
983
- pending.selection.add(option.id);
820
+ if (pending.selection.has(option.id)) {
821
+ pending.selection.delete(option.id);
822
+ }
823
+ else {
824
+ pending.selection.add(option.id);
825
+ }
984
826
  }
985
827
  this.renderToolMenu(pending);
986
828
  }
987
- this.renderPromptArea();
829
+ this.terminalInput.render();
988
830
  return;
989
831
  }
990
832
  display.showWarning('Enter a number, "save", "defaults", or "cancel".');
991
- this.renderPromptArea();
833
+ this.terminalInput.render();
992
834
  }
993
835
  async persistToolSelection(interaction) {
994
836
  if (setsEqual(interaction.selection, interaction.initialSelection)) {
@@ -998,10 +840,11 @@ export class InteractiveShell {
998
840
  const defaults = buildEnabledToolSet(null);
999
841
  if (setsEqual(interaction.selection, defaults)) {
1000
842
  clearToolSettings();
1001
- display.showInfo('Tool settings cleared. Defaults will be used on the next launch. Next steps: enable any optional suites from the Tools menu or preferences (restart to apply) if you want them available by default.');
843
+ display.showInfo('Tool settings cleared. Defaults will be used on the next launch.');
1002
844
  return;
1003
845
  }
1004
846
  const ordered = interaction.options
847
+ .filter((option) => !option.locked)
1005
848
  .map((option) => option.id)
1006
849
  .filter((id) => interaction.selection.has(id));
1007
850
  saveToolSettings({ enabledTools: ordered });
@@ -1015,36 +858,36 @@ export class InteractiveShell {
1015
858
  if (!this.agentMenu) {
1016
859
  this.pendingInteraction = null;
1017
860
  display.showWarning('Agent selection is unavailable in this CLI.');
1018
- this.renderPromptArea();
861
+ this.terminalInput.render();
1019
862
  return;
1020
863
  }
1021
864
  const trimmed = input.trim();
1022
865
  if (!trimmed) {
1023
866
  display.showWarning('Enter a number or type "cancel".');
1024
- this.renderPromptArea();
867
+ this.terminalInput.render();
1025
868
  return;
1026
869
  }
1027
870
  if (trimmed.toLowerCase() === 'cancel') {
1028
871
  this.pendingInteraction = null;
1029
872
  display.showInfo('Agent selection cancelled.');
1030
- this.renderPromptArea();
873
+ this.terminalInput.render();
1031
874
  return;
1032
875
  }
1033
876
  const choice = Number.parseInt(trimmed, 10);
1034
877
  if (!Number.isFinite(choice)) {
1035
878
  display.showWarning('Please enter a valid number.');
1036
- this.renderPromptArea();
879
+ this.terminalInput.render();
1037
880
  return;
1038
881
  }
1039
882
  const option = pending.options[choice - 1];
1040
883
  if (!option) {
1041
884
  display.showWarning('That option is not available.');
1042
- this.renderPromptArea();
885
+ this.terminalInput.render();
1043
886
  return;
1044
887
  }
1045
888
  await this.persistAgentSelection(option.name);
1046
889
  this.pendingInteraction = null;
1047
- this.renderPromptArea();
890
+ this.terminalInput.render();
1048
891
  }
1049
892
  async persistAgentSelection(profileName) {
1050
893
  if (!this.agentMenu) {
@@ -1117,7 +960,7 @@ export class InteractiveShell {
1117
960
  lines.push(` ${theme.primary('[text]')} Submit your own solution instead`);
1118
961
  lines.push('');
1119
962
  display.showSystemMessage(lines.join('\n'));
1120
- this.renderPromptArea();
963
+ this.terminalInput.render();
1121
964
  }
1122
965
  async handlePlanApprovalInput(input) {
1123
966
  const pending = this.pendingInteraction;
@@ -1126,7 +969,7 @@ export class InteractiveShell {
1126
969
  const trimmed = input.trim();
1127
970
  if (!trimmed) {
1128
971
  display.showWarning('Enter a command or your own solution.');
1129
- this.renderPromptArea();
972
+ this.terminalInput.render();
1130
973
  return;
1131
974
  }
1132
975
  const lower = trimmed.toLowerCase();
@@ -1134,7 +977,7 @@ export class InteractiveShell {
1134
977
  if (lower === 'cancel' || lower === 'c') {
1135
978
  this.pendingInteraction = null;
1136
979
  display.showInfo('Plan cancelled. You can continue with a different approach.');
1137
- this.renderPromptArea();
980
+ this.terminalInput.render();
1138
981
  return;
1139
982
  }
1140
983
  // Select all
@@ -1154,7 +997,7 @@ export class InteractiveShell {
1154
997
  const selectedSteps = pending.steps.filter(s => pending.selectedSteps.has(s.id));
1155
998
  if (selectedSteps.length === 0) {
1156
999
  display.showWarning('No steps selected. Select steps or enter your own solution.');
1157
- this.renderPromptArea();
1000
+ this.terminalInput.render();
1158
1001
  return;
1159
1002
  }
1160
1003
  this.pendingInteraction = null;
@@ -1187,18 +1030,15 @@ export class InteractiveShell {
1187
1030
  return;
1188
1031
  }
1189
1032
  display.showWarning('Invalid input. Enter a step number, command (go/cancel/all/none), or your own solution.');
1190
- this.renderPromptArea();
1033
+ this.terminalInput.render();
1191
1034
  }
1192
1035
  setupHandlers() {
1193
1036
  // Handle terminal resize
1194
1037
  output.on('resize', () => {
1195
- if (!this.inputInitialized) {
1196
- return;
1197
- }
1198
- this.terminalInput.resetContentPosition();
1199
1038
  this.terminalInput.handleResize();
1200
- this.terminalInput.forceRender();
1201
1039
  });
1040
+ // Show initial input UI
1041
+ this.terminalInput.render();
1202
1042
  }
1203
1043
  /**
1204
1044
  * Set up command autocomplete with all available slash commands.
@@ -1229,14 +1069,6 @@ export class InteractiveShell {
1229
1069
  catch {
1230
1070
  // Custom commands are optional
1231
1071
  }
1232
- // Add manual commands that are not yet in the schema
1233
- if (!commands.some((cmd) => cmd.command === '/alphazero')) {
1234
- commands.push({
1235
- command: '/alphazero',
1236
- description: 'Toggle AlphaZero RL mode',
1237
- category: 'mode',
1238
- });
1239
- }
1240
1072
  // Sort commands alphabetically
1241
1073
  commands.sort((a, b) => a.command.localeCompare(b.command));
1242
1074
  this.terminalInput.setAvailableCommands(commands);
@@ -1317,318 +1149,23 @@ export class InteractiveShell {
1317
1149
  autoContinueHotkey: 'ctrl+shift+c',
1318
1150
  thinkingModeLabel: this.thinkingMode,
1319
1151
  thinkingHotkey: 'ctrl+shift+t',
1320
- alphaZeroEnabled: this.alphaZeroModeEnabled,
1321
- alphaZeroHotkey: 'ctrl+shift+a',
1322
- alphaZeroLabel: 'AlphaZero RL',
1323
1152
  });
1324
- this.refreshFeatureStatusDisplay();
1325
1153
  this.refreshStatusLine();
1326
- this.renderPromptArea();
1327
- }
1328
- refreshFeatureStatusDisplay() {
1329
- this.terminalInput.setFeatureStatus(this.buildFeatureStatusSnapshot());
1154
+ this.terminalInput.render();
1330
1155
  }
1331
1156
  writeLocked(content) {
1332
1157
  if (!content) {
1333
1158
  return;
1334
1159
  }
1335
- // Route through display stream so scroll regions and streaming locks stay in sync
1336
- display.stream(content);
1337
- }
1338
- resetAssistantStreamTracking() {
1339
- this.assistantStreamActive = false;
1340
- this.assistantStreamPhase = null;
1341
- this.assistantStreamHeaderShown = false;
1342
- this.assistantStreamBuffer = '';
1343
- this.assistantStreamMetadata = undefined;
1344
- this.assistantStreamHadContent = false;
1345
- }
1346
- startAssistantStream(metadata) {
1347
- if (this.assistantStreamActive) {
1348
- this.assistantStreamMetadata = metadata ?? this.assistantStreamMetadata;
1349
- return;
1350
- }
1351
- this.assistantStreamActive = true;
1352
- // Default to thought phase so the first output after a prompt is the assistant's thinking.
1353
- this.assistantStreamPhase = 'thought';
1354
- this.assistantStreamHeaderShown = false;
1355
- this.assistantStreamBuffer = '';
1356
- this.assistantStreamMetadata = metadata;
1357
- this.assistantStreamHadContent = false;
1358
- }
1359
- streamAssistantChunk(chunk, channel) {
1360
- if (!chunk) {
1160
+ // If lock is already held, write directly - we're in a protected context
1161
+ // This prevents queuing issues where content gets delayed
1162
+ if (writeLock.isLocked()) {
1163
+ process.stdout.write(content);
1361
1164
  return;
1362
1165
  }
1363
- if (!this.assistantStreamActive) {
1364
- this.startAssistantStream({
1365
- contextWindowTokens: this.activeContextWindowTokens,
1366
- });
1367
- }
1368
- // Use channel to directly set phase for streaming chunks
1369
- // Agent sends channel='thought' for reasoning content, channel='response' for regular content
1370
- if (channel === 'thought' || channel === 'response') {
1371
- this.setAssistantStreamPhase(channel === 'thought' ? 'thought' : 'response');
1372
- // Write content directly without tag parsing for channel-routed chunks
1373
- this.writeAssistantStream(chunk);
1374
- }
1375
- else {
1376
- // Fall back to tag parsing for chunks without explicit channel
1377
- this.processAssistantStreamChunk(chunk);
1378
- }
1379
- }
1380
- finalizeAssistantStream() {
1381
- if (!this.assistantStreamActive) {
1382
- return;
1383
- }
1384
- if (this.assistantStreamBuffer) {
1385
- this.writeAssistantStream(this.assistantStreamBuffer);
1386
- this.assistantStreamBuffer = '';
1387
- }
1388
- this.assistantStreamActive = false;
1389
- this.assistantStreamPhase = null;
1390
- this.assistantStreamHeaderShown = false;
1391
- this.assistantStreamMetadata = undefined;
1392
- }
1393
- processAssistantStreamChunk(chunk) {
1394
- this.assistantStreamBuffer += chunk;
1395
- const tagRegex = /<(\/?)(thinking|response)>/i;
1396
- while (this.assistantStreamBuffer.length > 0) {
1397
- const match = tagRegex.exec(this.assistantStreamBuffer);
1398
- if (!match || match.index === undefined) {
1399
- // No more full tags - flush what we can but keep any partial tag prefix buffered
1400
- const { flushable, remainder } = this.splitAssistantStreamRemainder(this.assistantStreamBuffer);
1401
- if (flushable) {
1402
- this.writeAssistantStream(flushable);
1403
- }
1404
- this.assistantStreamBuffer = remainder;
1405
- return;
1406
- }
1407
- const tagIndex = match.index;
1408
- const beforeTag = this.assistantStreamBuffer.slice(0, tagIndex);
1409
- if (beforeTag) {
1410
- this.writeAssistantStream(beforeTag);
1411
- }
1412
- // Advance buffer past the tag
1413
- this.assistantStreamBuffer = this.assistantStreamBuffer.slice(tagIndex + match[0].length);
1414
- const isClosing = match[1] === '/';
1415
- const rawTagType = (match[2] ?? '').toLowerCase();
1416
- // Map 'thinking' to 'thought' for AssistantBlockType compatibility
1417
- const tagType = (rawTagType === 'thinking' ? 'thought' : rawTagType);
1418
- if (isClosing) {
1419
- if (this.assistantStreamPhase === tagType) {
1420
- this.assistantStreamPhase = null;
1421
- this.assistantStreamHeaderShown = false;
1422
- }
1423
- }
1424
- else {
1425
- this.setAssistantStreamPhase(tagType);
1426
- }
1427
- }
1428
- }
1429
- writeAssistantStream(content) {
1430
- if (!content) {
1431
- return;
1432
- }
1433
- if (content.trim().length > 0) {
1434
- this.assistantStreamHadContent = true;
1435
- }
1436
- // If no explicit phase has been set yet, keep the stream labeled as "thought"
1437
- // until a <response> tag arrives so operators see thinking first.
1438
- this.setAssistantStreamPhase(this.assistantStreamPhase ?? 'thought');
1439
- if (this.assistantStreamPhase) {
1440
- this.renderAssistantStreamHeader(this.assistantStreamPhase);
1441
- }
1442
- let output = content;
1443
- if (this.assistantStreamPhase === 'thought') {
1444
- output = this.formatThoughtDisplay(content, { streaming: true });
1445
- }
1446
- this.enqueueAssistantStream(output);
1447
- }
1448
- setAssistantStreamPhase(phase) {
1449
- if (this.assistantStreamPhase === phase) {
1450
- return;
1451
- }
1452
- this.assistantStreamPhase = phase;
1453
- this.assistantStreamHeaderShown = false;
1454
- if (phase) {
1455
- this.renderAssistantStreamHeader(phase);
1456
- }
1457
- }
1458
- renderAssistantStreamHeader(type) {
1459
- if (this.assistantStreamHeaderShown) {
1460
- return;
1461
- }
1462
- const header = this.formatAssistantHeader(type, this.assistantStreamMetadata);
1463
- if (header.trim()) {
1464
- this.enqueueAssistantStream(`\n${header}\n`);
1465
- }
1466
- this.assistantStreamHeaderShown = true;
1467
- }
1468
- splitAssistantStreamRemainder(buffer) {
1469
- if (!buffer) {
1470
- return { flushable: '', remainder: '' };
1471
- }
1472
- const tags = ['<thinking>', '</thinking>', '<response>', '</response>'];
1473
- let longest = '';
1474
- for (const tag of tags) {
1475
- for (let i = 1; i < tag.length; i += 1) {
1476
- const prefix = tag.slice(0, i);
1477
- if (buffer.endsWith(prefix) && prefix.length > longest.length) {
1478
- longest = prefix;
1479
- }
1480
- }
1481
- }
1482
- if (!longest) {
1483
- return { flushable: buffer, remainder: '' };
1484
- }
1485
- return {
1486
- flushable: buffer.slice(0, -longest.length),
1487
- remainder: longest,
1488
- };
1489
- }
1490
- enqueueAssistantStream(content) {
1491
- if (!content) {
1492
- return;
1493
- }
1494
- const run = () => this.writeAssistantBlock(content);
1495
- this.uiUpdates.enqueue({
1496
- lane: 'stream',
1497
- mode: ['streaming', 'processing', 'idle'],
1498
- priority: 'high',
1499
- description: 'assistant stream flush',
1500
- run,
1501
- });
1502
- }
1503
- writeAssistantBlock(content) {
1504
- if (!content) {
1505
- return;
1506
- }
1507
- // Ensure assistant block writes are serialized with terminal input renders
1508
- writeLock.safeWrite(() => {
1509
- this.terminalInput.streamContent(content);
1510
- });
1511
- }
1512
- renderAssistantBlock(type, content, metadata) {
1513
- if (!this.assistantBlocksEnabled || !this.assistantBlockRenderer) {
1514
- return;
1515
- }
1516
- this.assistantBlockRenderer.renderBlock(type, content, metadata);
1517
- // Ensure the prompt stays pinned below freshly written blocks
1518
- this.renderPromptArea();
1519
- }
1520
- renderAssistantContent(type, content, metadata) {
1521
- const normalized = content.replace(/\r\n/g, '\n').trim();
1522
- if (!normalized) {
1523
- return;
1524
- }
1525
- const formatted = type === 'thought' ? this.formatThoughtDisplay(normalized) : normalized;
1526
- if (this.assistantBlocksEnabled && this.assistantBlockRenderer) {
1527
- this.renderAssistantBlock(type, formatted, metadata);
1528
- return;
1529
- }
1530
- this.renderAssistantFallback(type, formatted, metadata);
1531
- }
1532
- renderAssistantFallback(type, content, metadata) {
1533
- const header = this.formatAssistantHeader(type, metadata);
1534
- const compact = content.trimEnd().replace(/\n{3,}/g, '\n\n');
1535
- const block = `\n${header}\n${compact}\n\n`;
1536
- this.enqueueAssistantStream(block);
1537
- }
1538
- formatThoughtDisplay(content, options = {}) {
1539
- const normalized = content.replace(/\r\n/g, '\n');
1540
- if (options.streaming) {
1541
- return formatThinkingContent(normalized);
1542
- }
1543
- const lines = normalized.split('\n');
1544
- return lines
1545
- .map((line, index) => {
1546
- const prefix = index === 0 ? theme.info(icons.action) : theme.ui.muted(icons.subaction);
1547
- const trimmed = line.trim();
1548
- const body = trimmed ? formatThinkingContent(trimmed) : '';
1549
- return body ? `${prefix} ${body}` : `${prefix}`;
1550
- })
1551
- .join('\n');
1552
- }
1553
- formatAssistantHeader(type, metadata) {
1554
- if (this.assistantBlocksEnabled && this.assistantBlockRenderer) {
1555
- return this.assistantBlockRenderer.formatHeader(type, metadata);
1556
- }
1557
- const badges = [];
1558
- badges.push(this.buildAssistantTypeBadge(type));
1559
- const timestamp = new Date().toLocaleTimeString('en-US', {
1560
- hour12: false,
1561
- hour: '2-digit',
1562
- minute: '2-digit',
1563
- second: '2-digit',
1564
- });
1565
- badges.push({ text: timestamp, style: 'muted', icon: '🕑' });
1566
- const total = metadata?.usage ? this.totalTokens(metadata.usage) : null;
1567
- const windowTokens = metadata?.contextWindowTokens;
1568
- if (typeof total === 'number') {
1569
- badges.push({ text: `${total} tok`, style: 'muted', icon: '⎍' });
1570
- }
1571
- if (typeof total === 'number' && typeof windowTokens === 'number' && windowTokens > 0) {
1572
- const percentage = Math.round((total / windowTokens) * 100);
1573
- const style = percentage > 85 ? 'error' : percentage > 70 ? 'warning' : 'info';
1574
- badges.push({ text: `${percentage}% ctx`, style, icon: '⊛' });
1575
- }
1576
- const elapsedBadge = this.buildElapsedBadge(metadata);
1577
- if (elapsedBadge) {
1578
- badges.push(elapsedBadge);
1579
- }
1580
- return compactRenderer.formatBadges(badges, theme.ui.muted(' │ '));
1581
- }
1582
- buildAssistantTypeBadge(type) {
1583
- switch (type) {
1584
- case 'thought':
1585
- return { text: 'Thought', style: 'info', icon: '💭' };
1586
- case 'tools':
1587
- return { text: 'Tools', style: 'primary', icon: '🛠' };
1588
- case 'response':
1589
- default:
1590
- return { text: 'Response', style: 'success', icon: '💬' };
1591
- }
1592
- }
1593
- buildElapsedBadge(metadata) {
1594
- const elapsed = metadata?.elapsedMs;
1595
- if (typeof elapsed !== 'number' || elapsed <= 0) {
1596
- return null;
1597
- }
1598
- const formatted = elapsed < 1000 ? `${Math.round(elapsed)}ms` : `${Math.round(elapsed / 1000)}s`;
1599
- return { text: formatted, style: 'muted', icon: '⏱' };
1600
- }
1601
- isStreamingUiActive() {
1602
- return this.streamingHeartbeatStart !== null;
1603
- }
1604
- /**
1605
- * Render the prompt/control bar. During streaming, rely on the streaming frame
1606
- * renderer and enqueue through the UIUpdateCoordinator to avoid fighting the
1607
- * scroll region or duplicating the prompt.
1608
- */
1609
- renderPromptArea(force = false) {
1610
- if (this.isStreamingUiActive()) {
1611
- this.uiUpdates.enqueue({
1612
- lane: 'prompt',
1613
- mode: ['streaming', 'processing'],
1614
- coalesceKey: 'prompt:streaming-frame',
1615
- description: 'render streaming prompt frame',
1616
- run: () => {
1617
- if (force) {
1618
- this.terminalInput.renderStreamingFrame(true);
1619
- return;
1620
- }
1621
- this.terminalInput.renderStreamingFrame();
1622
- },
1623
- });
1624
- return;
1625
- }
1626
- if (force) {
1627
- this.terminalInput.forceRender();
1628
- }
1629
- else {
1630
- this.terminalInput.render();
1631
- }
1166
+ writeLock.withLock(() => {
1167
+ process.stdout.write(content);
1168
+ }, 'interactiveShell.stdout');
1632
1169
  }
1633
1170
  /**
1634
1171
  * Refresh the status line in the persistent input area.
@@ -1646,23 +1183,19 @@ export class InteractiveShell {
1646
1183
  // Surface meta header (elapsed + context usage) above the divider
1647
1184
  // Use streaming elapsed time if available, otherwise fall back to status line state
1648
1185
  let elapsedSeconds = null;
1649
- const shouldShowElapsed = this.streamingHeartbeatStart !== null || this.isProcessing;
1650
- if (this.streamingHeartbeatStart && shouldShowElapsed) {
1186
+ if (this.streamingHeartbeatStart) {
1651
1187
  // Actively streaming - compute live elapsed
1652
1188
  elapsedSeconds = Math.max(0, Math.floor((Date.now() - this.streamingHeartbeatStart) / 1000));
1653
1189
  }
1654
- else if (shouldShowElapsed && this.lastStreamingElapsedSeconds !== null) {
1190
+ else if (this.lastStreamingElapsedSeconds !== null) {
1655
1191
  // Just finished streaming - use preserved final time
1656
1192
  elapsedSeconds = this.lastStreamingElapsedSeconds;
1657
1193
  }
1658
- else if (shouldShowElapsed && this.statusLineState) {
1194
+ else if (this.statusLineState) {
1659
1195
  // Fallback to status line state elapsed
1660
1196
  elapsedSeconds = Math.max(0, Math.floor((Date.now() - this.statusLineState.startedAt) / 1000));
1661
1197
  }
1662
- const hasThoughtSummary = !!this.latestThoughtSummary;
1663
- const thinkingMs = hasThoughtSummary && display.isSpinnerActive()
1664
- ? display.getThinkingElapsedMs()
1665
- : null;
1198
+ const thinkingMs = display.isSpinnerActive() ? display.getThinkingElapsedMs() : null;
1666
1199
  const tokensUsed = this.latestTokenUsage.used;
1667
1200
  const tokenLimit = this.latestTokenUsage.limit ?? this.activeContextWindowTokens;
1668
1201
  this.terminalInput.setMetaStatus({
@@ -1670,7 +1203,7 @@ export class InteractiveShell {
1670
1203
  tokensUsed,
1671
1204
  tokenLimit,
1672
1205
  thinkingMs,
1673
- thinkingHasContent: hasThoughtSummary,
1206
+ thinkingHasContent: display.isSpinnerActive(),
1674
1207
  });
1675
1208
  // Keep model/provider visible in the controls bar
1676
1209
  this.terminalInput.setModelContext({
@@ -1678,7 +1211,7 @@ export class InteractiveShell {
1678
1211
  provider: this.providerLabel(this.sessionState.provider),
1679
1212
  });
1680
1213
  if (forceRender) {
1681
- this.renderPromptArea(true);
1214
+ this.terminalInput.render();
1682
1215
  }
1683
1216
  }
1684
1217
  /**
@@ -1715,7 +1248,7 @@ export class InteractiveShell {
1715
1248
  * Ensure the terminal input is ready for interactive input.
1716
1249
  */
1717
1250
  ensureReadlineReady() {
1718
- this.renderPromptArea();
1251
+ this.terminalInput.render();
1719
1252
  }
1720
1253
  /**
1721
1254
  * Log user prompt to the scroll region so it's part of the conversation flow.
@@ -1737,7 +1270,7 @@ export class InteractiveShell {
1737
1270
  }
1738
1271
  requestPromptRefresh(force = false) {
1739
1272
  if (force) {
1740
- this.renderPromptArea(true);
1273
+ this.terminalInput.forceRender();
1741
1274
  return;
1742
1275
  }
1743
1276
  if (this.promptRefreshTimer) {
@@ -1745,7 +1278,7 @@ export class InteractiveShell {
1745
1278
  }
1746
1279
  this.promptRefreshTimer = setTimeout(() => {
1747
1280
  this.promptRefreshTimer = null;
1748
- this.renderPromptArea();
1281
+ this.terminalInput.render();
1749
1282
  }, 48);
1750
1283
  }
1751
1284
  clearPromptRefreshTimer() {
@@ -1754,53 +1287,35 @@ export class InteractiveShell {
1754
1287
  this.promptRefreshTimer = null;
1755
1288
  }
1756
1289
  }
1757
- async withStreamingUi(label, run) {
1758
- if (this.isStreamingUiActive()) {
1759
- return run();
1760
- }
1761
- this.resetAssistantStreamTracking();
1762
- this.terminalInput.setStreaming(true);
1763
- this.startStreamingHeartbeat(label);
1764
- try {
1765
- return await run();
1766
- }
1767
- finally {
1768
- this.stopStreamingHeartbeat();
1769
- this.terminalInput.setStreaming(false);
1770
- const nextMode = this.isProcessing ? 'processing' : 'idle';
1771
- this.uiUpdates.setMode(nextMode);
1772
- if (nextMode === 'processing') {
1773
- queueMicrotask(() => this.uiUpdates.setMode('idle'));
1774
- }
1775
- }
1776
- }
1777
1290
  startStreamingHeartbeat(label = 'Streaming') {
1778
- this.stopStreamingHeartbeat({ skipRender: true });
1291
+ this.stopStreamingHeartbeat();
1779
1292
  // Enter global streaming mode - blocks all non-streaming UI output
1780
1293
  enterStreamingMode();
1781
- this.streamingStatusBase = label;
1782
- this.streamingStatusDetail = null;
1783
- this.latestThoughtSummary = null;
1784
- this.lastStreamingElapsedSeconds = null;
1785
1294
  // Set up scroll region for streaming content
1786
1295
  this.terminalInput.enterStreamingScrollRegion();
1787
1296
  this.uiUpdates.setMode('streaming');
1788
1297
  this.streamingHeartbeatStart = Date.now();
1789
1298
  this.streamingHeartbeatFrame = 0;
1790
- this.rebuildStreamingStatusLabel();
1299
+ const initialFrame = STREAMING_SPINNER_FRAMES[this.streamingHeartbeatFrame];
1300
+ this.streamingStatusLabel = this.buildStreamingStatus(`${initialFrame} ${label}`, 0);
1301
+ display.updateStreamingStatus(this.streamingStatusLabel);
1791
1302
  this.refreshStatusLine(true);
1792
1303
  // Periodically refresh the pinned input/status region while streaming so
1793
1304
  // elapsed time remains visible without interrupting the scroll region.
1794
1305
  this.uiUpdates.startHeartbeat('streaming', {
1795
1306
  intervalMs: 1000,
1796
1307
  lane: 'heartbeat',
1797
- priority: 'high',
1798
1308
  mode: ['streaming', 'processing'],
1799
1309
  coalesceKey: 'streaming:heartbeat',
1800
1310
  run: () => {
1311
+ const elapsedSeconds = this.streamingHeartbeatStart
1312
+ ? Math.max(0, Math.floor((Date.now() - this.streamingHeartbeatStart) / 1000))
1313
+ : 0;
1801
1314
  this.streamingHeartbeatFrame =
1802
1315
  (this.streamingHeartbeatFrame + 1) % STREAMING_SPINNER_FRAMES.length;
1803
- this.rebuildStreamingStatusLabel();
1316
+ const frame = STREAMING_SPINNER_FRAMES[this.streamingHeartbeatFrame];
1317
+ this.streamingStatusLabel = this.buildStreamingStatus(`${frame} ${label}`, elapsedSeconds);
1318
+ display.updateStreamingStatus(this.streamingStatusLabel);
1804
1319
  // Update parallel agent display during streaming
1805
1320
  const manager = getParallelAgentManager();
1806
1321
  if (manager.isRunning()) {
@@ -1811,13 +1326,7 @@ export class InteractiveShell {
1811
1326
  },
1812
1327
  });
1813
1328
  }
1814
- stopStreamingHeartbeat(options = {}) {
1815
- const skipRender = !!options.skipRender;
1816
- const streamingActive = this.isStreamingUiActive();
1817
- const scrollRegionActive = this.terminalInput.isScrollRegionActive();
1818
- if (!streamingActive && !scrollRegionActive) {
1819
- return;
1820
- }
1329
+ stopStreamingHeartbeat() {
1821
1330
  // Exit global streaming mode - allows UI to render again
1822
1331
  exitStreamingMode();
1823
1332
  // Preserve final elapsed time before clearing heartbeat start
@@ -1825,44 +1334,51 @@ export class InteractiveShell {
1825
1334
  this.lastStreamingElapsedSeconds = Math.max(0, Math.floor((Date.now() - this.streamingHeartbeatStart) / 1000));
1826
1335
  }
1827
1336
  // Exit scroll region mode
1828
- this.terminalInput.exitStreamingScrollRegion({ skipRender });
1337
+ this.terminalInput.exitStreamingScrollRegion();
1829
1338
  this.uiUpdates.stopHeartbeat('streaming');
1830
1339
  this.streamingHeartbeatStart = null;
1831
1340
  this.streamingHeartbeatFrame = 0;
1832
1341
  this.streamingStatusLabel = null;
1833
- this.streamingStatusBase = null;
1834
- this.streamingStatusDetail = null;
1835
- this.latestThoughtSummary = null;
1836
1342
  // Clear streaming label specifically (keeps override and main status if set)
1837
1343
  this.terminalInput.setStreamingLabel(null);
1838
1344
  // Clear streaming status from display
1839
1345
  display.updateStreamingStatus(null);
1840
1346
  // Force refresh to update the input area now that streaming has ended
1841
- if (!skipRender) {
1842
- this.refreshStatusLine(true);
1843
- }
1347
+ this.refreshStatusLine(true);
1844
1348
  }
1845
- buildStreamingStatus(label, _elapsedSeconds) {
1846
- // Model + elapsed time already live in the pinned meta header; keep the streaming
1847
- // status focused on the activity and most recent thought summary.
1848
- const prefix = theme.info('⏺');
1849
- const parts = [label.trim()];
1850
- if (this.streamingStatusDetail) {
1851
- const detail = this.streamingStatusDetail.length > 52
1852
- ? `${this.streamingStatusDetail.slice(0, 51)}…`
1853
- : this.streamingStatusDetail;
1854
- parts.push(theme.ui.muted(detail));
1349
+ handleStreamChunk(chunk) {
1350
+ if (!chunk) {
1351
+ return;
1352
+ }
1353
+ // Preserve raw output in plain/CI modes or non-TTY environments
1354
+ if (isPlainOutputMode() || !output.isTTY) {
1355
+ this.terminalInput.streamContent(chunk);
1356
+ return;
1357
+ }
1358
+ if (!this.streamingFormatter) {
1359
+ this.streamingFormatter = new StreamingResponseFormatter(output.columns ?? undefined);
1360
+ this.terminalInput.streamContent(this.streamingFormatter.header());
1361
+ }
1362
+ const formatted = this.streamingFormatter.formatChunk(chunk);
1363
+ if (formatted) {
1364
+ this.terminalInput.streamContent(formatted);
1855
1365
  }
1856
- return `${prefix} ${parts.join(' · ')}`.trim();
1857
1366
  }
1858
- rebuildStreamingStatusLabel() {
1859
- if (this.streamingHeartbeatStart === null) {
1367
+ finishStreamingFormatter(note) {
1368
+ if (!this.streamingFormatter) {
1860
1369
  return;
1861
1370
  }
1862
- const frame = STREAMING_SPINNER_FRAMES[this.streamingHeartbeatFrame];
1863
- const base = this.streamingStatusBase ?? 'Streaming';
1864
- this.streamingStatusLabel = this.buildStreamingStatus(`${frame} ${base}`);
1865
- display.updateStreamingStatus(this.streamingStatusLabel);
1371
+ const closing = this.streamingFormatter.finish(note);
1372
+ if (closing) {
1373
+ this.terminalInput.streamContent(closing);
1374
+ }
1375
+ this.streamingFormatter = null;
1376
+ }
1377
+ buildStreamingStatus(label, _elapsedSeconds) {
1378
+ // Model + elapsed time already live in the pinned meta header; keep the streaming
1379
+ // status focused on the activity to avoid duplicate info.
1380
+ const prefix = theme.info('⏺');
1381
+ return `${prefix} ${label}`.trim();
1866
1382
  }
1867
1383
  formatElapsedShort(seconds) {
1868
1384
  if (seconds < 60) {
@@ -1879,7 +1395,7 @@ export class InteractiveShell {
1879
1395
  else {
1880
1396
  this.setIdleStatus();
1881
1397
  }
1882
- this.renderPromptArea();
1398
+ this.terminalInput.render();
1883
1399
  }
1884
1400
  enqueueFollowUpAction(action) {
1885
1401
  this.followUpQueue.push(action);
@@ -1898,21 +1414,14 @@ export class InteractiveShell {
1898
1414
  this.refreshQueueIndicators();
1899
1415
  this.scheduleQueueProcessing();
1900
1416
  // Re-show the prompt so user can continue typing more follow-ups
1901
- this.renderPromptArea();
1417
+ this.terminalInput.render();
1902
1418
  }
1903
1419
  scheduleQueueProcessing() {
1904
1420
  if (!this.followUpQueue.length) {
1905
1421
  this.refreshQueueIndicators();
1906
1422
  return;
1907
1423
  }
1908
- if (this.apiKeyGateActive) {
1909
- this.refreshQueueIndicators();
1910
- return;
1911
- }
1912
1424
  queueMicrotask(() => {
1913
- if (this.apiKeyGateActive) {
1914
- return;
1915
- }
1916
1425
  void this.processQueuedActions();
1917
1426
  });
1918
1427
  }
@@ -1920,12 +1429,12 @@ export class InteractiveShell {
1920
1429
  * Process queued follow-up actions.
1921
1430
  */
1922
1431
  async processQueuedActions() {
1923
- if (this.apiKeyGateActive || this.isDrainingQueue || this.isProcessing || !this.followUpQueue.length) {
1432
+ if (this.isDrainingQueue || this.isProcessing || !this.followUpQueue.length) {
1924
1433
  return;
1925
1434
  }
1926
1435
  this.isDrainingQueue = true;
1927
1436
  try {
1928
- while (!this.isProcessing && !this.apiKeyGateActive && this.followUpQueue.length) {
1437
+ while (!this.isProcessing && this.followUpQueue.length) {
1929
1438
  const next = this.followUpQueue.shift();
1930
1439
  const remaining = this.followUpQueue.length;
1931
1440
  const label = next.type === 'continuous' ? 'continuous command' : 'follow-up';
@@ -1960,12 +1469,12 @@ export class InteractiveShell {
1960
1469
  }
1961
1470
  if (lower === 'clear') {
1962
1471
  display.clear();
1963
- this.renderPromptArea();
1472
+ this.terminalInput.render();
1964
1473
  return;
1965
1474
  }
1966
1475
  if (lower === 'help') {
1967
1476
  this.showHelp();
1968
- this.renderPromptArea();
1477
+ this.terminalInput.render();
1969
1478
  return;
1970
1479
  }
1971
1480
  if (trimmed.startsWith('/')) {
@@ -1975,12 +1484,12 @@ export class InteractiveShell {
1975
1484
  // Check for continuous/infinite loop commands
1976
1485
  if (this.isContinuousCommand(trimmed)) {
1977
1486
  await this.processContinuousRequest(trimmed);
1978
- this.renderPromptArea();
1487
+ this.terminalInput.render();
1979
1488
  return;
1980
1489
  }
1981
1490
  // Direct execution for all inputs, including multi-line pastes
1982
1491
  await this.processRequest(trimmed);
1983
- this.renderPromptArea();
1492
+ this.terminalInput.render();
1984
1493
  }
1985
1494
  /**
1986
1495
  * Check if the command is a continuous/infinite loop command
@@ -2018,153 +1527,6 @@ export class InteractiveShell {
2018
1527
  ];
2019
1528
  return patterns.some(pattern => pattern.test(lower));
2020
1529
  }
2021
- isDifficultProblem(input) {
2022
- const normalized = input.toLowerCase();
2023
- const wordCount = normalized.split(/\s+/).filter(Boolean).length;
2024
- if (normalized.length > 600 || wordCount > 80) {
2025
- return true;
2026
- }
2027
- const signals = [
2028
- 'root cause',
2029
- 'postmortem',
2030
- 'crash',
2031
- 'incident',
2032
- 'outage',
2033
- 'optimiz',
2034
- 'performance',
2035
- 'throughput',
2036
- 'latency',
2037
- 'scalab',
2038
- 'architecture',
2039
- 'rewrite',
2040
- 'migration',
2041
- 'refactor',
2042
- 'reverse engineer',
2043
- 'security',
2044
- 'exploit',
2045
- 'injection',
2046
- 'vulnerability',
2047
- 'compliance',
2048
- 'multi-step',
2049
- 'complex',
2050
- 'difficult',
2051
- 'hard problem',
2052
- 'debug',
2053
- 'trace',
2054
- 'profil',
2055
- 'bottleneck',
2056
- ];
2057
- return signals.some((signal) => normalized.includes(signal));
2058
- }
2059
- buildAlphaZeroPrompt(request, flaggedDifficult) {
2060
- const playbook = [
2061
- 'AlphaZero RL MODE is ACTIVE. Operate as a self-play reinforcement loop.',
2062
- flaggedDifficult
2063
- ? 'Treat this as a difficult, high-risk task and over-verify the result.'
2064
- : 'Apply the reinforcement loop even if the task looks small.',
2065
- 'Follow this closed-loop playbook:',
2066
- '- Draft two competing solution strategies and merge the strongest ideas before executing.',
2067
- '- Execute with tools while logging decisions and evidence.',
2068
- '- Self-critique and repair until the quality is excellent (aim ≥90/100).',
2069
- '- Run full-lifecycle verification like a human reviewer: build/tests, manual sanity checks, edge cases, performance/safety/security probes, docs/UX/readiness notes.',
2070
- '- Keep a verification ledger: each check with PASS/FAIL, evidence, and remaining risks. If anything fails, fix and re-verify before claiming completion.',
2071
- 'Finish with a concise sign-off that lists what was achieved and the proof of completion.',
2072
- ];
2073
- return `${playbook.join('\n')}\n\nPrimary user request:\n${request.trim()}`;
2074
- }
2075
- buildRunLogExcerpt(startIndex) {
2076
- const buffer = this.terminalInput.getScrollbackBuffer();
2077
- const sinceStart = buffer.slice(Math.max(0, startIndex));
2078
- const excerpt = sinceStart.slice(-200); // Cap prompt size
2079
- return excerpt.join('\n').trim();
2080
- }
2081
- buildRunLogEntry(request, response, scrollbackStartIndex, meta) {
2082
- return {
2083
- id: ++this.runIdCounter,
2084
- request,
2085
- response,
2086
- timestamp: new Date().toISOString(),
2087
- alphaZero: meta.alphaZeroEngaged,
2088
- difficult: meta.alphaZeroDifficult,
2089
- failureType: meta.failureType,
2090
- outputExcerpt: this.buildRunLogExcerpt(scrollbackStartIndex),
2091
- };
2092
- }
2093
- buildAlphaZeroReflectionPrompt(runLog, options) {
2094
- const lines = [];
2095
- lines.push('AlphaZero Post-Run Self-Reflection (erosolar-cli)');
2096
- lines.push(`Timestamp: ${runLog.timestamp}`);
2097
- lines.push(`AlphaZero: ${runLog.alphaZero ? 'on' : 'off'}${runLog.difficult ? ' | difficult' : ''}${runLog.failureType ? ` | signal: ${runLog.failureType}` : ''}`);
2098
- lines.push('');
2099
- lines.push('User request:');
2100
- lines.push(runLog.request);
2101
- lines.push('');
2102
- lines.push('Previous run log excerpt:');
2103
- lines.push(runLog.outputExcerpt || '[empty]');
2104
- lines.push('');
2105
- lines.push('Instructions:');
2106
- lines.push('- Reflect on the log to spot erosolar-cli bugs, UX issues, or reliability gaps.');
2107
- lines.push('- Propose and apply targeted fixes in this repository only (no user workspace edits).');
2108
- lines.push('- Prefer small, test-backed changes; run any relevant checks you invoke.');
2109
- lines.push('- Keep notes concise and finish with applied changes plus follow-ups.');
2110
- if (options.autoChain) {
2111
- lines.push('- Continue iterating automatically while meaningful improvements remain.');
2112
- lines.push('- When no further improvements are possible, reply with NO_MORE_IMPROVEMENTS on its own line.');
2113
- }
2114
- return lines.join('\n');
2115
- }
2116
- maybeQueueAlphaZeroSelfReflection(runLog, alphaZeroEngaged, allowAutoChain) {
2117
- if (!alphaZeroEngaged) {
2118
- return;
2119
- }
2120
- if (!isErosolarRepo(this.workingDir)) {
2121
- return;
2122
- }
2123
- if (!runLog.outputExcerpt) {
2124
- return;
2125
- }
2126
- if (this.lastReflectedRunId === runLog.id) {
2127
- return;
2128
- }
2129
- if (allowAutoChain) {
2130
- if (!this.autoContinueEnabled) {
2131
- return;
2132
- }
2133
- if (this.alphaZeroAutoImproveIterations >= this.alphaZeroAutoImproveMaxIterations) {
2134
- display.showInfo('AlphaZero auto-improvement limit reached; stopping.');
2135
- this.alphaZeroAutoImproveActive = false;
2136
- this.alphaZeroAutoImproveIterations = 0;
2137
- return;
2138
- }
2139
- if (!this.alphaZeroAutoImproveActive) {
2140
- this.alphaZeroAutoImproveIterations = 0;
2141
- }
2142
- this.alphaZeroAutoImproveActive = true;
2143
- this.alphaZeroAutoImproveIterations++;
2144
- }
2145
- const prompt = this.buildAlphaZeroReflectionPrompt(runLog, { autoChain: allowAutoChain });
2146
- this.lastReflectedRunId = runLog.id;
2147
- this.skipNextAutoReflection = !allowAutoChain; // Prevent reflection-on-reflection unless auto-chaining
2148
- this.enqueueFollowUpAction({ type: 'request', text: prompt });
2149
- display.showInfo(allowAutoChain
2150
- ? 'Auto AlphaZero self-improvement queued (auto-continue enabled).'
2151
- : 'Queued AlphaZero self-reflection to improve erosolar-cli from the latest run log.');
2152
- }
2153
- shouldStopAlphaZeroAutoImprove(responseText, allowAutoChain) {
2154
- if (!this.alphaZeroAutoImproveActive) {
2155
- return false;
2156
- }
2157
- if (!allowAutoChain) {
2158
- return true;
2159
- }
2160
- if (!responseText) {
2161
- return false;
2162
- }
2163
- const normalized = responseText.toLowerCase();
2164
- return (normalized.includes('no_more_improvements') ||
2165
- normalized.includes('no more improvements') ||
2166
- normalized.includes('stop_auto_improve'));
2167
- }
2168
1530
  async handlePendingInteraction(input) {
2169
1531
  if (!this.pendingInteraction) {
2170
1532
  return false;
@@ -2172,7 +1534,7 @@ export class InteractiveShell {
2172
1534
  switch (this.pendingInteraction.type) {
2173
1535
  case 'model-loading':
2174
1536
  display.showInfo('Still fetching model options. Please wait a moment.');
2175
- this.renderPromptArea();
1537
+ this.terminalInput.render();
2176
1538
  return true;
2177
1539
  case 'model-provider':
2178
1540
  await this.handleModelProviderSelection(input);
@@ -2203,213 +1565,166 @@ export class InteractiveShell {
2203
1565
  const [command] = input.split(/\s+/);
2204
1566
  if (!command) {
2205
1567
  display.showWarning('Enter a slash command.');
2206
- this.renderPromptArea();
1568
+ this.terminalInput.render();
2207
1569
  return;
2208
1570
  }
2209
- // Keep the slash action visible in the pinned recent strip
2210
- this.terminalInput.recordRecentAction(command);
2211
- const runCommand = async () => {
2212
- switch (command) {
2213
- case '/help':
2214
- case '/?':
2215
- this.showHelp();
2216
- break;
2217
- case '/features':
2218
- this.showFeaturesMenu(input);
2219
- break;
2220
- case '/learn':
2221
- this.showLearningStatus(input);
2222
- break;
2223
- case '/improve':
2224
- void this.handleImprovementCommand(input);
2225
- break;
2226
- case '/model':
2227
- this.showModelMenu();
2228
- break;
2229
- case '/exit':
2230
- case '/quit':
2231
- case '/q':
2232
- this.shutdown();
2233
- break;
2234
- case '/secrets':
2235
- this.showSecretsMenu();
2236
- break;
2237
- case '/tools':
2238
- this.showToolsMenu();
2239
- break;
2240
- case '/mcp':
2241
- await this.showMcpStatus();
2242
- break;
2243
- case '/doctor':
2244
- this.runDoctor();
2245
- break;
2246
- case '/checks':
2247
- await this.runRepoChecksCommand();
2248
- break;
2249
- case '/context':
2250
- await this.refreshWorkspaceContextCommand(input);
2251
- break;
2252
- case '/contextlog':
2253
- this.showContextSummaryLog(input);
2254
- break;
2255
- case '/agents':
2256
- this.showAgentsMenu();
2257
- break;
2258
- case '/sessions':
2259
- await this.handleSessionCommand(input);
2260
- break;
2261
- case '/skills':
2262
- await this.handleSkillsCommand(input);
2263
- break;
2264
- case '/thinking':
2265
- this.handleThinkingCommand(input);
2266
- break;
2267
- case '/autocontinue':
2268
- this.handleAutoContinueCommand(input);
2269
- break;
2270
- case '/alphazero':
2271
- this.handleAlphaZeroCommand(input);
2272
- break;
2273
- case '/shortcuts':
2274
- case '/keys':
2275
- this.handleShortcutsCommand();
2276
- break;
2277
- case '/changes':
2278
- case '/summary':
2279
- this.showFileChangeSummary();
2280
- break;
2281
- case '/metrics':
2282
- case '/stats':
2283
- case '/perf':
2284
- this.showAlphaZeroMetrics();
2285
- break;
2286
- case '/suggestions':
2287
- case '/improve':
2288
- this.showImprovementSuggestions();
2289
- break;
2290
- case '/plugins':
2291
- this.showPluginStatus();
2292
- break;
2293
- case '/evolve':
2294
- void this.handleEvolveCommand(input);
2295
- break;
2296
- case '/modular':
2297
- case '/a0':
2298
- void this.handleModularCommand(input);
2299
- break;
2300
- case '/offsec':
2301
- void this.handleOffsecCommand(input);
2302
- break;
2303
- case '/test':
2304
- case '/tests':
2305
- void this.handleTestCommand(input);
2306
- break;
2307
- case '/provider':
2308
- await this.handleProviderCommand(input);
2309
- break;
2310
- case '/providers':
2311
- this.showConfiguredProviders();
2312
- break;
2313
- case '/local':
2314
- await this.handleLocalCommand(input);
2315
- break;
2316
- case '/discover':
2317
- await this.discoverModelsCommand();
2318
- break;
2319
- // Claude Code style commands
2320
- case '/rewind':
2321
- await this.handleRewindCommand(input);
2322
- break;
2323
- case '/memory':
2324
- this.handleMemoryCommand(input);
2325
- break;
2326
- case '/vim':
2327
- this.handleVimCommand();
2328
- break;
2329
- case '/output-style':
2330
- this.handleOutputStyleCommand(input);
2331
- break;
2332
- case '/cost':
2333
- this.handleCostCommand();
2334
- break;
2335
- case '/usage':
2336
- this.handleUsageCommand();
2337
- break;
2338
- case '/update':
2339
- await this.handleUpdateCommand();
2340
- break;
2341
- case '/clear':
2342
- this.handleClearCommand();
2343
- break;
2344
- case '/resume':
2345
- await this.handleResumeCommand(input);
2346
- break;
2347
- case '/export':
2348
- this.handleExportCommand(input);
2349
- break;
2350
- case '/review':
2351
- await this.handleReviewCommand();
2352
- break;
2353
- case '/security-review':
2354
- await this.handleSecurityReviewCommand();
2355
- break;
2356
- case '/bug':
2357
- this.handleBugCommand();
2358
- break;
2359
- case '/terminal-setup':
2360
- this.handleTerminalSetupCommand();
2361
- break;
2362
- case '/permissions':
2363
- this.handlePermissionsCommand();
2364
- break;
2365
- case '/init':
2366
- this.handleInitCommand();
2367
- break;
2368
- case '/compact':
2369
- await this.handleCompactCommand();
2370
- break;
2371
- default:
2372
- if (!(await this.tryCustomSlashCommand(command, input))) {
2373
- display.showWarning(`Unknown command "${command}".`);
2374
- }
2375
- break;
2376
- }
2377
- };
2378
- const streamingUiActive = this.isStreamingUiActive();
2379
- const captureOptions = streamingUiActive
2380
- ? { includeStreaming: false, suppressTypes: ['normal'] }
2381
- : undefined;
2382
- let capturedOutput = '';
2383
- try {
2384
- const { output: outputBuffer } = await display.captureOutput(runCommand, captureOptions);
2385
- capturedOutput = outputBuffer;
2386
- }
2387
- catch (error) {
2388
- capturedOutput = error?.capturedOutput ?? capturedOutput;
2389
- if (streamingUiActive) {
2390
- const message = error instanceof Error ? error.message : String(error);
2391
- const { output: errorOutput } = await display.captureOutput(() => display.showError(message, error), { includeStreaming: false, suppressTypes: ['normal'] });
2392
- capturedOutput = capturedOutput || errorOutput || message;
2393
- }
2394
- else {
2395
- display.showError(error instanceof Error ? error.message : String(error), error);
2396
- }
2397
- }
2398
- const panelContent = this.buildInlineCommandPanel(command, capturedOutput);
2399
- this.terminalInput.setInlineCommandPanel(panelContent);
2400
- this.renderPromptArea();
2401
- }
2402
- buildInlineCommandPanel(command, output) {
2403
- const normalized = output ? output.replace(/\r\n/g, '\n').replace(/\r/g, '\n').trimEnd() : '';
2404
- if (!normalized) {
2405
- return null;
2406
- }
2407
- const header = theme.ui.muted(command);
2408
- const body = normalized.split('\n');
2409
- if (body.length === 1) {
2410
- return [`${header} ${body[0] ?? ''}`.trimEnd()];
1571
+ switch (command) {
1572
+ case '/help':
1573
+ case '/?':
1574
+ this.showHelp();
1575
+ break;
1576
+ case '/features':
1577
+ this.showFeaturesMenu(input);
1578
+ break;
1579
+ case '/learn':
1580
+ this.showLearningStatus(input);
1581
+ break;
1582
+ case '/improve':
1583
+ void this.handleImprovementCommand(input);
1584
+ break;
1585
+ case '/model':
1586
+ this.showModelMenu();
1587
+ break;
1588
+ case '/exit':
1589
+ case '/quit':
1590
+ case '/q':
1591
+ this.shutdown();
1592
+ break;
1593
+ case '/secrets':
1594
+ this.showSecretsMenu();
1595
+ break;
1596
+ case '/tools':
1597
+ this.showToolsMenu();
1598
+ break;
1599
+ case '/mcp':
1600
+ await this.showMcpStatus();
1601
+ break;
1602
+ case '/doctor':
1603
+ this.runDoctor();
1604
+ break;
1605
+ case '/checks':
1606
+ await this.runRepoChecksCommand();
1607
+ break;
1608
+ case '/context':
1609
+ await this.refreshWorkspaceContextCommand(input);
1610
+ break;
1611
+ case '/agents':
1612
+ this.showAgentsMenu();
1613
+ break;
1614
+ case '/sessions':
1615
+ await this.handleSessionCommand(input);
1616
+ break;
1617
+ case '/skills':
1618
+ await this.handleSkillsCommand(input);
1619
+ break;
1620
+ case '/thinking':
1621
+ this.handleThinkingCommand(input);
1622
+ break;
1623
+ case '/autocontinue':
1624
+ this.handleAutoContinueCommand(input);
1625
+ break;
1626
+ case '/shortcuts':
1627
+ case '/keys':
1628
+ this.handleShortcutsCommand();
1629
+ break;
1630
+ case '/changes':
1631
+ case '/summary':
1632
+ this.showFileChangeSummary();
1633
+ break;
1634
+ case '/metrics':
1635
+ case '/stats':
1636
+ case '/perf':
1637
+ this.showAlphaZeroMetrics();
1638
+ break;
1639
+ case '/suggestions':
1640
+ case '/improve':
1641
+ this.showImprovementSuggestions();
1642
+ break;
1643
+ case '/plugins':
1644
+ this.showPluginStatus();
1645
+ break;
1646
+ case '/evolve':
1647
+ void this.handleEvolveCommand(input);
1648
+ break;
1649
+ case '/modular':
1650
+ case '/a0':
1651
+ void this.handleModularCommand(input);
1652
+ break;
1653
+ case '/offsec':
1654
+ void this.handleOffsecCommand(input);
1655
+ break;
1656
+ case '/test':
1657
+ case '/tests':
1658
+ void this.handleTestCommand(input);
1659
+ break;
1660
+ case '/provider':
1661
+ await this.handleProviderCommand(input);
1662
+ break;
1663
+ case '/providers':
1664
+ this.showConfiguredProviders();
1665
+ break;
1666
+ case '/local':
1667
+ await this.handleLocalCommand(input);
1668
+ break;
1669
+ case '/discover':
1670
+ await this.discoverModelsCommand();
1671
+ break;
1672
+ // Claude Code style commands
1673
+ case '/rewind':
1674
+ await this.handleRewindCommand(input);
1675
+ break;
1676
+ case '/memory':
1677
+ this.handleMemoryCommand(input);
1678
+ break;
1679
+ case '/vim':
1680
+ this.handleVimCommand();
1681
+ break;
1682
+ case '/output-style':
1683
+ this.handleOutputStyleCommand(input);
1684
+ break;
1685
+ case '/cost':
1686
+ this.handleCostCommand();
1687
+ break;
1688
+ case '/usage':
1689
+ this.handleUsageCommand();
1690
+ break;
1691
+ case '/clear':
1692
+ this.handleClearCommand();
1693
+ break;
1694
+ case '/resume':
1695
+ await this.handleResumeCommand(input);
1696
+ break;
1697
+ case '/export':
1698
+ this.handleExportCommand(input);
1699
+ break;
1700
+ case '/review':
1701
+ await this.handleReviewCommand();
1702
+ break;
1703
+ case '/security-review':
1704
+ await this.handleSecurityReviewCommand();
1705
+ break;
1706
+ case '/bug':
1707
+ this.handleBugCommand();
1708
+ break;
1709
+ case '/terminal-setup':
1710
+ this.handleTerminalSetupCommand();
1711
+ break;
1712
+ case '/permissions':
1713
+ this.handlePermissionsCommand();
1714
+ break;
1715
+ case '/init':
1716
+ this.handleInitCommand();
1717
+ break;
1718
+ case '/compact':
1719
+ await this.handleCompactCommand();
1720
+ break;
1721
+ default:
1722
+ if (!(await this.tryCustomSlashCommand(command, input))) {
1723
+ display.showWarning(`Unknown command "${command}".`);
1724
+ }
1725
+ break;
2411
1726
  }
2412
- return [header, ...body];
1727
+ this.terminalInput.render();
2413
1728
  }
2414
1729
  async tryCustomSlashCommand(command, fullInput) {
2415
1730
  const custom = this.customCommandMap.get(command);
@@ -2446,13 +1761,11 @@ export class InteractiveShell {
2446
1761
  ` ${theme.info('Option+V')} ${theme.ui.muted('Toggle verification')}`,
2447
1762
  ` ${theme.info('Option+C')} ${theme.ui.muted('Toggle auto-continue')}`,
2448
1763
  ` ${theme.info('Option+T')} ${theme.ui.muted('Cycle thinking mode')}`,
2449
- ` ${theme.info('Option+A')} ${theme.ui.muted('Toggle AlphaZero RL mode')}`,
2450
1764
  ` ${theme.info('Option+E')} ${theme.ui.muted('Toggle edit permission mode')}`,
2451
1765
  ` ${theme.info('Option+X')} ${theme.ui.muted('Clear/compact context')}`,
2452
1766
  '',
2453
1767
  theme.bold(' Navigation'),
2454
1768
  ` ${theme.info('PageUp/PageDown')} ${theme.ui.muted('Scroll through output')}`,
2455
- ` ${theme.info('Escape')} ${theme.ui.muted('Stop streaming')}`,
2456
1769
  ` ${theme.info('Ctrl+C')} ${theme.ui.muted('Clear input or interrupt')}`,
2457
1770
  ` ${theme.info('Up/Down')} ${theme.ui.muted('Navigate command history')}`,
2458
1771
  '',
@@ -2550,66 +1863,6 @@ export class InteractiveShell {
2550
1863
  display.showWarning('Workspace snapshot refreshed, but the agent failed to rebuild. Run /doctor for details.');
2551
1864
  }
2552
1865
  }
2553
- showContextSummaryLog(input) {
2554
- const agent = this.agent;
2555
- if (!agent) {
2556
- display.showWarning('No active agent session. Start one to inspect context summaries.');
2557
- return;
2558
- }
2559
- const contextManager = agent.getContextManager();
2560
- if (!contextManager?.getSummaryLog) {
2561
- display.showWarning('Context summary logging is unavailable in this session.');
2562
- return;
2563
- }
2564
- const tokens = input.trim().split(/\s+/).slice(1);
2565
- let limit = 5;
2566
- for (const token of tokens) {
2567
- if (!token)
2568
- continue;
2569
- const normalized = token.toLowerCase();
2570
- if (/^\d+$/.test(token)) {
2571
- limit = Math.min(Math.max(parseInt(token, 10), 1), 20);
2572
- }
2573
- else if (normalized.startsWith('limit=')) {
2574
- const value = parseInt(normalized.split('=')[1] ?? '', 10);
2575
- if (!Number.isNaN(value)) {
2576
- limit = Math.min(Math.max(value, 1), 20);
2577
- }
2578
- }
2579
- }
2580
- const entries = contextManager.getSummaryLog(limit);
2581
- if (!entries.length) {
2582
- display.showInfo('No context summaries captured yet.');
2583
- return;
2584
- }
2585
- const lines = [];
2586
- lines.push(`${theme.primary('Context summaries')} ${theme.ui.muted(`(latest ${entries.length})`)}`);
2587
- for (const entry of entries) {
2588
- const timestamp = new Date(entry.timestamp).toLocaleString();
2589
- const headerParts = [
2590
- theme.ui.muted(timestamp),
2591
- theme.info(entry.method),
2592
- `removed ${theme.error(String(entry.removed))}`,
2593
- `kept ${theme.success(String(entry.preserved))}`,
2594
- ];
2595
- if (entry.reason) {
2596
- headerParts.push(theme.ui.muted(entry.reason));
2597
- }
2598
- if (entry.model) {
2599
- headerParts.push(theme.ui.muted(`model=${entry.model}`));
2600
- }
2601
- lines.push(headerParts.join(' · '));
2602
- if (entry.summary) {
2603
- const trimmed = entry.summary.length > 600 ? `${entry.summary.slice(0, 600)}…` : entry.summary;
2604
- lines.push(trimmed);
2605
- }
2606
- else {
2607
- lines.push(theme.ui.muted('(no summary text — simple prune)'));
2608
- }
2609
- lines.push(''); // spacer
2610
- }
2611
- display.showSystemMessage(lines.join('\n'));
2612
- }
2613
1866
  parseContextOverrideTokens(input) {
2614
1867
  const overrides = {};
2615
1868
  let hasOverride = false;
@@ -2769,11 +2022,11 @@ export class InteractiveShell {
2769
2022
  handleThinkingCommand(input) {
2770
2023
  const value = input.slice('/thinking'.length).trim().toLowerCase();
2771
2024
  if (!value) {
2772
- display.showInfo(`Thinking mode is currently ${theme.info(this.thinkingMode)}. Usage: /thinking [concise|balanced|extended]`);
2025
+ display.showInfo(`Thinking mode is currently ${theme.info(this.thinkingMode)}. Usage: /thinking [balanced|extended]`);
2773
2026
  return;
2774
2027
  }
2775
- if (value !== 'concise' && value !== 'balanced' && value !== 'extended') {
2776
- display.showWarning('Usage: /thinking [concise|balanced|extended]');
2028
+ if (value !== 'balanced' && value !== 'extended') {
2029
+ display.showWarning('Usage: /thinking [balanced|extended]');
2777
2030
  return;
2778
2031
  }
2779
2032
  if (this.isProcessing) {
@@ -2786,30 +2039,11 @@ export class InteractiveShell {
2786
2039
  this.resetChatBoxAfterModelSwap();
2787
2040
  }
2788
2041
  const descriptions = {
2789
- concise: 'Hides internal reasoning and responds directly.',
2790
2042
  balanced: 'Shows short thoughts only when helpful.',
2791
2043
  extended: 'Always emits a <thinking> block before the final response.',
2792
2044
  };
2793
2045
  display.showInfo(`Thinking mode set to ${theme.info(value)} – ${descriptions[this.thinkingMode]}`);
2794
2046
  }
2795
- handleAlphaZeroCommand(input) {
2796
- const value = input.slice('/alphazero'.length).trim().toLowerCase();
2797
- if (!value || value === 'status') {
2798
- const status = this.alphaZeroModeEnabled ? theme.success('on') : theme.ui.muted('off');
2799
- const verification = this.verificationEnabled ? 'verification locked on' : 'verification optional';
2800
- display.showInfo(`AlphaZero RL mode is ${status}. When enabled, difficult prompts use duel/self-critique and human-style verification (${verification}).`);
2801
- return;
2802
- }
2803
- if (['on', 'enable', 'enabled'].includes(value)) {
2804
- this.setAlphaZeroMode(true, 'command');
2805
- return;
2806
- }
2807
- if (['off', 'disable', 'disabled'].includes(value)) {
2808
- this.setAlphaZeroMode(false, 'command');
2809
- return;
2810
- }
2811
- display.showWarning('Usage: /alphazero [on|off|status]');
2812
- }
2813
2047
  handleShortcutsCommand() {
2814
2048
  // Display keyboard shortcuts help (Claude Code style)
2815
2049
  display.showSystemMessage(formatShortcutsHelp());
@@ -2897,7 +2131,6 @@ export class InteractiveShell {
2897
2131
  const updated = toggleFeatureFlag(matchedKey, newValue);
2898
2132
  const status = updated[matchedKey] ? theme.success('enabled') : theme.ui.muted('disabled');
2899
2133
  display.showInfo(`Feature "${FEATURE_FLAG_INFO[matchedKey].label}" is now ${status}.`);
2900
- this.refreshFeatureStatusDisplay();
2901
2134
  display.showInfo('Changes will take effect on next launch or after /features refresh.');
2902
2135
  return;
2903
2136
  }
@@ -2910,7 +2143,6 @@ export class InteractiveShell {
2910
2143
  }
2911
2144
  saveFeatureFlags(updated);
2912
2145
  display.showInfo(`All features ${newValue ? theme.success('enabled') : theme.ui.muted('disabled')}.`);
2913
- this.refreshFeatureStatusDisplay();
2914
2146
  return;
2915
2147
  }
2916
2148
  else {
@@ -4083,7 +3315,9 @@ export class InteractiveShell {
4083
3315
  }
4084
3316
  display.showInfo(`Deleted session "${summary.title}".`);
4085
3317
  if (this.activeSessionId === summary.id) {
4086
- this.updateActiveSession(null, true);
3318
+ this.activeSessionId = null;
3319
+ this.activeSessionTitle = null;
3320
+ saveSessionPreferences({ lastSessionId: null });
4087
3321
  }
4088
3322
  }
4089
3323
  newSessionCommand(title) {
@@ -4103,7 +3337,6 @@ export class InteractiveShell {
4103
3337
  clearAutosaveSnapshot(this.profile);
4104
3338
  display.showInfo('Started a new empty session.');
4105
3339
  this.refreshContextGauge();
4106
- this.refreshFeatureStatusDisplay();
4107
3340
  }
4108
3341
  toggleAutosaveCommand(value) {
4109
3342
  if (!value) {
@@ -4158,6 +3391,7 @@ export class InteractiveShell {
4158
3391
  lines.push(' /rewind code Rewind code only (keep conversation)');
4159
3392
  lines.push(' /rewind conv Rewind conversation only (keep code)');
4160
3393
  lines.push('');
3394
+ lines.push(theme.ui.muted('Tip: Press Esc+Esc for quick access to rewind menu'));
4161
3395
  display.showSystemMessage(lines.join('\n'));
4162
3396
  }
4163
3397
  handleMemoryCommand(input) {
@@ -4176,6 +3410,7 @@ export class InteractiveShell {
4176
3410
  lines.push(' - Use # prefix to quickly add notes to project memory');
4177
3411
  lines.push(' - Import other files with @./relative/path syntax');
4178
3412
  lines.push('');
3413
+ lines.push(theme.ui.muted('Tip: Create EROSOLAR.md with project coding standards for better results'));
4179
3414
  display.showSystemMessage(lines.join('\n'));
4180
3415
  }
4181
3416
  handleVimCommand() {
@@ -4256,20 +3491,6 @@ export class InteractiveShell {
4256
3491
  }
4257
3492
  display.showSystemMessage(lines.join('\n'));
4258
3493
  }
4259
- async handleUpdateCommand() {
4260
- display.showInfo('Checking for updates...');
4261
- const lines = [];
4262
- lines.push(theme.bold('Update Check'));
4263
- lines.push('');
4264
- lines.push(`Current version: ${this.version ?? 'unknown'}`);
4265
- lines.push('');
4266
- lines.push(theme.secondary('To update:'));
4267
- lines.push(' npm install -g erosolar-cli@latest');
4268
- lines.push(' or: npm update erosolar-cli');
4269
- lines.push('');
4270
- lines.push(theme.ui.muted('Auto-updates will be checked periodically.'));
4271
- display.showSystemMessage(lines.join('\n'));
4272
- }
4273
3494
  handleClearCommand() {
4274
3495
  if (this.agent) {
4275
3496
  this.agent.clearHistory();
@@ -4278,7 +3499,7 @@ export class InteractiveShell {
4278
3499
  display.clear();
4279
3500
  clearAutosaveSnapshot(this.profile);
4280
3501
  display.showInfo('Conversation cleared. Starting fresh.');
4281
- this.renderPromptArea();
3502
+ this.terminalInput.render();
4282
3503
  }
4283
3504
  async handleResumeCommand(input) {
4284
3505
  const tokens = input.split(/\s+/).slice(1);
@@ -4419,7 +3640,6 @@ export class InteractiveShell {
4419
3640
  if (remember) {
4420
3641
  saveSessionPreferences({ lastSessionId: summary?.id ?? null });
4421
3642
  }
4422
- this.refreshFeatureStatusDisplay();
4423
3643
  }
4424
3644
  resolveSessionBySelector(selector) {
4425
3645
  const sessions = listSessions(this.profile);
@@ -4588,7 +3808,7 @@ export class InteractiveShell {
4588
3808
  if (!providerOptions.length) {
4589
3809
  display.showWarning('No providers are available.');
4590
3810
  this.pendingInteraction = null;
4591
- this.renderPromptArea();
3811
+ this.terminalInput.render();
4592
3812
  return;
4593
3813
  }
4594
3814
  const lines = [
@@ -4609,7 +3829,7 @@ export class InteractiveShell {
4609
3829
  catch (error) {
4610
3830
  display.showError('Failed to load model list. Try again in a moment.', error);
4611
3831
  this.pendingInteraction = null;
4612
- this.renderPromptArea();
3832
+ this.terminalInput.render();
4613
3833
  }
4614
3834
  }
4615
3835
  buildProviderOptions() {
@@ -4793,7 +4013,7 @@ export class InteractiveShell {
4793
4013
  }
4794
4014
  renderToolMenu(interaction) {
4795
4015
  const lines = [
4796
- theme.bold('Select which tools are enabled (changes apply on next launch):'),
4016
+ theme.bold('Select which tools are enabled (changes apply on next launch; default package is locked on).'),
4797
4017
  ...interaction.options.map((option, index) => this.formatToolOptionLine(option, index, interaction.selection)),
4798
4018
  '',
4799
4019
  'Enter the number to toggle, "save" to persist, "defaults" to restore recommended tools, or "cancel".',
@@ -4802,13 +4022,23 @@ export class InteractiveShell {
4802
4022
  }
4803
4023
  formatToolOptionLine(option, index, selection) {
4804
4024
  const enabled = selection.has(option.id);
4805
- const checkbox = enabled ? theme.primary('[x]') : theme.ui.muted('[ ]');
4025
+ const checkbox = option.locked
4026
+ ? theme.primary('[✓]')
4027
+ : enabled
4028
+ ? theme.primary('[x]')
4029
+ : theme.ui.muted('[ ]');
4806
4030
  const details = [option.description];
4807
4031
  if (option.requiresSecret) {
4808
4032
  const hasSecret = Boolean(getSecretValue(option.requiresSecret));
4809
4033
  const status = hasSecret ? theme.success('API key set') : theme.warning('API key missing');
4810
4034
  details.push(status);
4811
4035
  }
4036
+ if (option.locked) {
4037
+ details.push(theme.ui.muted('Locked default package'));
4038
+ }
4039
+ if (option.restartRequired) {
4040
+ details.push(theme.ui.muted('Restart required'));
4041
+ }
4812
4042
  const numberLabel = this.colorizeDropdownLine(`${index + 1}.`, index);
4813
4043
  const optionLabel = this.colorizeDropdownLine(option.label, index);
4814
4044
  const detailLine = this.colorizeDropdownLine(` ${details.join(' • ')}`, index);
@@ -5154,29 +4384,29 @@ export class InteractiveShell {
5154
4384
  const trimmed = input.trim();
5155
4385
  if (!trimmed) {
5156
4386
  display.showWarning('Enter a number or type cancel.');
5157
- this.renderPromptArea();
4387
+ this.terminalInput.render();
5158
4388
  return;
5159
4389
  }
5160
4390
  if (trimmed.toLowerCase() === 'cancel') {
5161
4391
  this.pendingInteraction = null;
5162
4392
  display.showInfo('Model selection cancelled.');
5163
- this.renderPromptArea();
4393
+ this.terminalInput.render();
5164
4394
  return;
5165
4395
  }
5166
4396
  const choice = Number.parseInt(trimmed, 10);
5167
4397
  if (!Number.isFinite(choice)) {
5168
4398
  display.showWarning('Please enter a valid number.');
5169
- this.renderPromptArea();
4399
+ this.terminalInput.render();
5170
4400
  return;
5171
4401
  }
5172
4402
  const option = pending.options[choice - 1];
5173
4403
  if (!option) {
5174
4404
  display.showWarning('That option is not available.');
5175
- this.renderPromptArea();
4405
+ this.terminalInput.render();
5176
4406
  return;
5177
4407
  }
5178
4408
  this.showProviderModels(option);
5179
- this.renderPromptArea();
4409
+ this.terminalInput.render();
5180
4410
  }
5181
4411
  async handleModelSelection(input) {
5182
4412
  const pending = this.pendingInteraction;
@@ -5186,35 +4416,35 @@ export class InteractiveShell {
5186
4416
  const trimmed = input.trim();
5187
4417
  if (!trimmed) {
5188
4418
  display.showWarning('Enter a number, type "back", or type "cancel".');
5189
- this.renderPromptArea();
4419
+ this.terminalInput.render();
5190
4420
  return;
5191
4421
  }
5192
4422
  if (trimmed.toLowerCase() === 'back') {
5193
4423
  this.showModelMenu();
5194
- this.renderPromptArea();
4424
+ this.terminalInput.render();
5195
4425
  return;
5196
4426
  }
5197
4427
  if (trimmed.toLowerCase() === 'cancel') {
5198
4428
  this.pendingInteraction = null;
5199
4429
  display.showInfo('Model selection cancelled.');
5200
- this.renderPromptArea();
4430
+ this.terminalInput.render();
5201
4431
  return;
5202
4432
  }
5203
4433
  const choice = Number.parseInt(trimmed, 10);
5204
4434
  if (!Number.isFinite(choice)) {
5205
4435
  display.showWarning('Please enter a valid number.');
5206
- this.renderPromptArea();
4436
+ this.terminalInput.render();
5207
4437
  return;
5208
4438
  }
5209
4439
  const preset = pending.options[choice - 1];
5210
4440
  if (!preset) {
5211
4441
  display.showWarning('That option is not available.');
5212
- this.renderPromptArea();
4442
+ this.terminalInput.render();
5213
4443
  return;
5214
4444
  }
5215
4445
  this.pendingInteraction = null;
5216
4446
  await this.applyModelPreset(preset);
5217
- this.renderPromptArea();
4447
+ this.terminalInput.render();
5218
4448
  }
5219
4449
  async applyModelPreset(preset) {
5220
4450
  try {
@@ -5247,30 +4477,30 @@ export class InteractiveShell {
5247
4477
  const trimmed = input.trim();
5248
4478
  if (!trimmed) {
5249
4479
  display.showWarning('Enter a number or type cancel.');
5250
- this.renderPromptArea();
4480
+ this.terminalInput.render();
5251
4481
  return;
5252
4482
  }
5253
4483
  if (trimmed.toLowerCase() === 'cancel') {
5254
4484
  this.pendingInteraction = null;
5255
4485
  display.showInfo('Secret management cancelled.');
5256
- this.renderPromptArea();
4486
+ this.terminalInput.render();
5257
4487
  return;
5258
4488
  }
5259
4489
  const choice = Number.parseInt(trimmed, 10);
5260
4490
  if (!Number.isFinite(choice)) {
5261
4491
  display.showWarning('Please enter a valid number.');
5262
- this.renderPromptArea();
4492
+ this.terminalInput.render();
5263
4493
  return;
5264
4494
  }
5265
4495
  const secret = pending.options[choice - 1];
5266
4496
  if (!secret) {
5267
4497
  display.showWarning('That option is not available.');
5268
- this.renderPromptArea();
4498
+ this.terminalInput.render();
5269
4499
  return;
5270
4500
  }
5271
4501
  display.showSystemMessage(`Enter a new value for ${secret.label} or type "cancel".`);
5272
4502
  this.pendingInteraction = { type: 'secret-input', secret };
5273
- this.renderPromptArea();
4503
+ this.terminalInput.render();
5274
4504
  }
5275
4505
  async handleSecretInput(input) {
5276
4506
  const pending = this.pendingInteraction;
@@ -5280,16 +4510,14 @@ export class InteractiveShell {
5280
4510
  const trimmed = input.trim();
5281
4511
  if (!trimmed) {
5282
4512
  display.showWarning('Enter a value or type cancel.');
5283
- this.renderPromptArea();
4513
+ this.terminalInput.render();
5284
4514
  return;
5285
4515
  }
5286
4516
  if (trimmed.toLowerCase() === 'cancel') {
5287
4517
  this.pendingInteraction = null;
5288
4518
  this.pendingSecretRetry = null;
5289
- this.apiKeyGateActive = false;
5290
4519
  display.showInfo('Secret unchanged.');
5291
- this.renderPromptArea();
5292
- this.scheduleQueueProcessing();
4520
+ this.terminalInput.render();
5293
4521
  return;
5294
4522
  }
5295
4523
  try {
@@ -5298,7 +4526,6 @@ export class InteractiveShell {
5298
4526
  this.pendingInteraction = null;
5299
4527
  const deferred = this.pendingSecretRetry;
5300
4528
  this.pendingSecretRetry = null;
5301
- this.apiKeyGateActive = false;
5302
4529
  if (pending.secret.providers.includes(this.sessionState.provider)) {
5303
4530
  if (this.rebuildAgent()) {
5304
4531
  this.resetChatBoxAfterModelSwap();
@@ -5313,14 +4540,12 @@ export class InteractiveShell {
5313
4540
  display.showError(message);
5314
4541
  this.pendingInteraction = null;
5315
4542
  this.pendingSecretRetry = null;
5316
- this.apiKeyGateActive = false;
5317
4543
  }
5318
- this.renderPromptArea();
5319
- this.scheduleQueueProcessing();
4544
+ this.terminalInput.render();
5320
4545
  }
5321
- async processRequest(userRequest) {
4546
+ async processRequest(request) {
5322
4547
  if (this.isProcessing) {
5323
- this.enqueueFollowUpAction({ type: 'request', text: userRequest });
4548
+ this.enqueueFollowUpAction({ type: 'request', text: request });
5324
4549
  return;
5325
4550
  }
5326
4551
  if (!this.agent && !this.rebuildAgent()) {
@@ -5331,57 +4556,29 @@ export class InteractiveShell {
5331
4556
  if (!agent) {
5332
4557
  return;
5333
4558
  }
5334
- this.hasShownThoughtProcess = false;
5335
- this.resetAssistantStreamTracking();
5336
- const alphaZeroEngaged = this.alphaZeroModeEnabled;
5337
- const alphaZeroDifficult = alphaZeroEngaged ? this.isDifficultProblem(userRequest) : false;
5338
- const requestForAgent = alphaZeroEngaged
5339
- ? this.buildAlphaZeroPrompt(userRequest, alphaZeroDifficult)
5340
- : userRequest;
5341
- const skipReflectionForThisRun = this.skipNextAutoReflection;
5342
- this.skipNextAutoReflection = false;
5343
- const scrollbackStartIndex = this.terminalInput.getScrollbackBuffer().length;
5344
- const alphaZeroStatusId = 'alpha-zero';
5345
- let alphaZeroStatusApplied = false;
5346
- let alphaZeroTaskStarted = false;
5347
- let alphaZeroTaskCompleted = false;
5348
- if (alphaZeroEngaged) {
5349
- const detail = alphaZeroDifficult ? 'Difficult request detected' : 'Reinforcement loop enabled';
5350
- this.statusTracker.pushOverride(alphaZeroStatusId, 'AlphaZero RL active', {
5351
- detail: `${detail} · full verification`,
5352
- tone: 'info',
5353
- });
5354
- alphaZeroStatusApplied = true;
5355
- display.showInfo(`AlphaZero RL mode engaged${alphaZeroDifficult ? ' for a difficult request' : ''}. Duel + self-critique with verification enabled.`);
5356
- this.alphaZeroMetrics.startAlphaZeroTask(userRequest);
5357
- alphaZeroTaskStarted = true;
5358
- }
5359
- this.logUserPrompt(userRequest);
4559
+ this.logUserPrompt(request);
5360
4560
  this.isProcessing = true;
5361
4561
  this.uiUpdates.setMode('processing');
5362
4562
  this.terminalInput.setStreaming(true);
5363
4563
  // Keep the persistent input/control bar active as we transition into streaming.
5364
- this.renderPromptArea(true);
4564
+ this.terminalInput.forceRender();
5365
4565
  const requestStartTime = Date.now(); // Alpha Zero 2 timing
5366
- this.lastRequestStartedAt = requestStartTime;
5367
- this.lastToolSummaryRenderedAt = null;
5368
4566
  // Clear previous parallel agents and start fresh for new request
5369
4567
  const parallelManager = getParallelAgentManager();
5370
4568
  parallelManager.clear();
5371
4569
  parallelManager.startBatch();
5372
4570
  // AlphaZero: Track task for learning
5373
- this.lastUserQuery = userRequest;
5374
- this.currentTaskType = classifyTaskType(userRequest);
4571
+ this.lastUserQuery = request;
4572
+ this.currentTaskType = classifyTaskType(request);
5375
4573
  this.currentToolCalls = [];
5376
4574
  this.uiAdapter.startProcessing('Working on your request');
5377
4575
  this.setProcessingStatus();
5378
4576
  let responseText = '';
5379
- let detectedFailure = null;
5380
- let hadUnhandledError = false;
5381
4577
  try {
5382
4578
  // Start streaming - no header needed, the input area already provides context
5383
- this.startStreamingHeartbeat(alphaZeroEngaged ? 'AlphaZero RL' : 'Streaming response');
5384
- responseText = await agent.send(requestForAgent, true);
4579
+ this.startStreamingHeartbeat('Streaming response');
4580
+ responseText = await agent.send(request, true);
4581
+ this.finishStreamingFormatter();
5385
4582
  await this.awaitPendingCleanup();
5386
4583
  this.captureHistorySnapshot();
5387
4584
  this.autosaveIfEnabled();
@@ -5400,18 +4597,14 @@ export class InteractiveShell {
5400
4597
  duration: 0,
5401
4598
  }));
5402
4599
  // AlphaZero: Check for failure in response
5403
- detectedFailure = detectFailure(responseText, {
4600
+ const failure = detectFailure(responseText, {
5404
4601
  toolCalls: this.currentToolCalls,
5405
- userMessage: userRequest,
4602
+ userMessage: request,
5406
4603
  });
5407
- if (alphaZeroEngaged && alphaZeroTaskStarted && !alphaZeroTaskCompleted) {
5408
- this.alphaZeroMetrics.completeAlphaZeroTask(!detectedFailure);
5409
- alphaZeroTaskCompleted = true;
5410
- }
5411
- if (detectedFailure) {
5412
- this.lastFailure = detectedFailure;
4604
+ if (failure) {
4605
+ this.lastFailure = failure;
5413
4606
  // Check if we have a recovery strategy
5414
- const strategy = findRecoveryStrategy(detectedFailure);
4607
+ const strategy = findRecoveryStrategy(failure);
5415
4608
  if (strategy) {
5416
4609
  display.showSystemMessage(`🔄 Found recovery strategy for this type of issue (success rate: ${Math.round(strategy.successRate * 100)}%)`);
5417
4610
  }
@@ -5434,55 +4627,28 @@ export class InteractiveShell {
5434
4627
  }
5435
4628
  }
5436
4629
  catch (error) {
5437
- const handled = this.handleProviderError(error, () => this.processRequest(userRequest));
5438
- hadUnhandledError = !handled;
5439
- if (alphaZeroEngaged && alphaZeroTaskStarted && !alphaZeroTaskCompleted) {
5440
- this.alphaZeroMetrics.completeAlphaZeroTask(false);
5441
- alphaZeroTaskCompleted = true;
5442
- }
4630
+ const handled = this.handleProviderError(error, () => this.processRequest(request));
5443
4631
  if (!handled) {
5444
4632
  // Pass full error object for enhanced formatting with stack trace
5445
4633
  display.showError(error instanceof Error ? error.message : String(error), error);
5446
4634
  }
5447
4635
  }
5448
4636
  finally {
5449
- const runLogEntry = this.buildRunLogEntry(userRequest, responseText, scrollbackStartIndex, {
5450
- alphaZeroEngaged,
5451
- alphaZeroDifficult,
5452
- failureType: detectedFailure?.type ?? (hadUnhandledError ? 'unhandled-error' : null),
5453
- });
5454
- this.lastRunLog = runLogEntry;
5455
- const allowAutoChain = alphaZeroEngaged && this.autoContinueEnabled && isErosolarRepo(this.workingDir);
5456
- let shouldStopAuto = false;
5457
- if (this.alphaZeroAutoImproveActive) {
5458
- shouldStopAuto = this.shouldStopAlphaZeroAutoImprove(responseText, allowAutoChain);
5459
- if (shouldStopAuto) {
5460
- this.alphaZeroAutoImproveActive = false;
5461
- this.alphaZeroAutoImproveIterations = 0;
5462
- }
5463
- }
5464
- if (!skipReflectionForThisRun && !shouldStopAuto) {
5465
- this.maybeQueueAlphaZeroSelfReflection(runLogEntry, alphaZeroEngaged, allowAutoChain);
5466
- }
5467
- if (alphaZeroEngaged && alphaZeroStatusApplied) {
5468
- this.statusTracker.clearOverride(alphaZeroStatusId);
5469
- }
5470
- if (alphaZeroEngaged && alphaZeroTaskStarted && !alphaZeroTaskCompleted) {
5471
- this.alphaZeroMetrics.completeAlphaZeroTask(false);
5472
- }
4637
+ this.finishStreamingFormatter();
5473
4638
  display.stopThinking(false);
5474
4639
  this.uiUpdates.setMode('processing');
5475
- this.stopStreamingHeartbeat({ skipRender: true });
4640
+ this.stopStreamingHeartbeat();
5476
4641
  this.isProcessing = false;
5477
4642
  this.terminalInput.setStreaming(false);
5478
4643
  this.uiAdapter.endProcessing('Ready for prompts');
5479
4644
  this.setIdleStatus();
4645
+ display.newLine();
5480
4646
  this.updateStatusMessage(null);
5481
4647
  queueMicrotask(() => this.uiUpdates.setMode('idle'));
5482
4648
  // CRITICAL: Ensure readline prompt is active for user input
5483
4649
  // Claude Code style: New prompt naturally appears at bottom
5484
4650
  this.ensureReadlineReady();
5485
- this.renderPromptArea();
4651
+ this.terminalInput.render();
5486
4652
  this.scheduleQueueProcessing();
5487
4653
  this.refreshQueueIndicators();
5488
4654
  }
@@ -5513,7 +4679,6 @@ export class InteractiveShell {
5513
4679
  if (!agent) {
5514
4680
  return;
5515
4681
  }
5516
- this.hasShownThoughtProcess = false;
5517
4682
  this.isProcessing = true;
5518
4683
  this.uiUpdates.setMode('processing');
5519
4684
  this.terminalInput.setStreaming(true);
@@ -5554,8 +4719,6 @@ When truly finished with ALL tasks, explicitly state "TASK_FULLY_COMPLETE".`;
5554
4719
  }
5555
4720
  while (iteration < MAX_ITERATIONS) {
5556
4721
  iteration++;
5557
- this.hasShownThoughtProcess = false;
5558
- this.resetAssistantStreamTracking();
5559
4722
  display.showSystemMessage(`\n📍 Iteration ${iteration}/${MAX_ITERATIONS}`);
5560
4723
  this.updateStatusMessage(`Working on iteration ${iteration}...`);
5561
4724
  try {
@@ -5563,6 +4726,7 @@ When truly finished with ALL tasks, explicitly state "TASK_FULLY_COMPLETE".`;
5563
4726
  display.showThinking('Responding...');
5564
4727
  this.refreshStatusLine(true);
5565
4728
  const response = await agent.send(currentPrompt, true);
4729
+ this.finishStreamingFormatter();
5566
4730
  await this.awaitPendingCleanup();
5567
4731
  this.captureHistorySnapshot();
5568
4732
  this.autosaveIfEnabled();
@@ -5695,6 +4859,7 @@ What's the next action?`;
5695
4859
  }
5696
4860
  }
5697
4861
  finally {
4862
+ this.finishStreamingFormatter();
5698
4863
  const totalElapsed = Date.now() - overallStartTime;
5699
4864
  const minutes = Math.floor(totalElapsed / 60000);
5700
4865
  const seconds = Math.floor((totalElapsed % 60000) / 1000);
@@ -5708,6 +4873,10 @@ What's the next action?`;
5708
4873
  this.uiAdapter.endProcessing('Ready for prompts');
5709
4874
  this.setIdleStatus();
5710
4875
  this.updateStatusMessage(null);
4876
+ display.newLine();
4877
+ // Claude Code style: Show unified status bar before prompt
4878
+ // This creates consistent UI between startup and post-streaming
4879
+ this.showUnifiedStatusBar();
5711
4880
  queueMicrotask(() => this.uiUpdates.setMode('idle'));
5712
4881
  // CRITICAL: Ensure readline prompt is active for user input
5713
4882
  // Claude Code style: New prompt naturally appears at bottom
@@ -5886,25 +5055,7 @@ What's the next action?`;
5886
5055
  }
5887
5056
  if (name === 'bash' || name === 'execute_bash') {
5888
5057
  const command = String(entry.args['command'] ?? '').toLowerCase();
5889
- const patterns = [
5890
- 'npm test',
5891
- 'yarn test',
5892
- 'pnpm test',
5893
- 'bun test',
5894
- 'go test',
5895
- 'cargo test',
5896
- 'pytest',
5897
- 'python -m pytest',
5898
- 'tox',
5899
- 'mvn test',
5900
- './mvnw test',
5901
- 'gradle test',
5902
- './gradlew test',
5903
- 'dotnet test',
5904
- 'make test',
5905
- 'swift test',
5906
- ];
5907
- return patterns.some((pattern) => command.includes(pattern));
5058
+ return command.includes('npm test') || command.includes('yarn test') || command.includes('pnpm test');
5908
5059
  }
5909
5060
  return false;
5910
5061
  }
@@ -5922,170 +5073,310 @@ What's the next action?`;
5922
5073
  const parts = [error?.stdout, error?.stderr, error?.message].filter((part) => typeof part === 'string' && part.trim());
5923
5074
  return parts.join('\n').trim();
5924
5075
  }
5925
- async enforceAutoTests(trigger, commandInfo) {
5926
- if (this.autoTestInFlight || !this.verificationEnabled) {
5927
- return 'skipped';
5076
+ runAutoQualityChecks(trigger, assistantResponse, verificationContext) {
5077
+ if (!this.verificationEnabled) {
5078
+ return;
5079
+ }
5080
+ void (async () => {
5081
+ try {
5082
+ const buildOk = await this.enforceAutoBuild(trigger);
5083
+ if (!buildOk) {
5084
+ return;
5085
+ }
5086
+ await this.enforceAutoTests(trigger, assistantResponse, verificationContext);
5087
+ }
5088
+ catch (error) {
5089
+ const message = error instanceof Error ? error.message : String(error);
5090
+ display.showWarning(`Auto quality checks failed: ${message}`);
5091
+ }
5092
+ })();
5093
+ }
5094
+ async enforceAutoTests(trigger, assistantResponse, verificationContext) {
5095
+ if (this.autoTestInFlight) {
5096
+ return;
5097
+ }
5098
+ if (!this.verificationEnabled) {
5099
+ return;
5928
5100
  }
5929
5101
  const latestChange = this.getLatestFileChangeTimestamp();
5930
5102
  if (!latestChange) {
5931
- return 'skipped';
5103
+ return;
5932
5104
  }
5933
5105
  const latestTest = this.getLatestTestTimestamp();
5934
5106
  if (latestTest && latestChange <= latestTest) {
5935
- return 'skipped';
5107
+ return;
5936
5108
  }
5937
- const detected = commandInfo ?? detectTestCommand(this.workingDir);
5938
- if (!detected) {
5939
- this.lastAutoTestRun = Date.now();
5940
- display.showSystemMessage('ℹ️ Skipping auto-tests: no test command detected for this repo.');
5941
- return 'skipped';
5109
+ if (!this.lastBuildSucceededAt || this.lastBuildSucceededAt < latestChange) {
5110
+ display.showSystemMessage('⏭️ Skipping auto-tests because no successful build is recorded for the latest changes.');
5111
+ return;
5942
5112
  }
5943
5113
  this.autoTestInFlight = true;
5944
- const command = detected.command;
5945
- const reasonSuffix = detected.reason ? ` [${detected.reason}]` : '';
5946
- display.showSystemMessage(`🧪 Auto-testing recent changes (${trigger}) with "${command}"${reasonSuffix}...`);
5114
+ const command = 'npm test -- --runInBand';
5115
+ display.showSystemMessage(`🧪 Auto-testing recent changes (${trigger}) with "${command}"...`);
5947
5116
  this.updateStatusMessage('Running tests automatically...');
5117
+ let combinedOutput = '';
5948
5118
  try {
5949
5119
  const { stdout, stderr } = await execAsync(command, {
5950
5120
  cwd: this.workingDir,
5951
5121
  timeout: 10 * 60 * 1000,
5952
5122
  maxBuffer: 10 * 1024 * 1024,
5953
5123
  });
5954
- this.lastAutoTestRun = Date.now();
5955
- const outputText = [stdout, stderr].filter(Boolean).join('\n').trim();
5124
+ combinedOutput = [stdout, stderr].filter(Boolean).join('\n').trim();
5956
5125
  display.showSystemMessage('✅ Auto-tests finished.');
5957
- if (outputText) {
5958
- this.writeLocked(`${outputText}\n`);
5959
- }
5960
5126
  this.statusTracker.clearOverride('tests');
5961
- return 'success';
5962
5127
  }
5963
5128
  catch (error) {
5964
- this.lastAutoTestRun = Date.now();
5965
- const message = this.formatCommandError(error);
5129
+ combinedOutput = this.formatCommandError(error);
5966
5130
  display.showWarning('⚠️ Auto-tests failed. Review output below.');
5967
- if (message) {
5968
- this.writeLocked(`${message}\n`);
5969
- }
5970
5131
  this.statusTracker.pushOverride('tests', 'Tests failing', {
5971
- detail: `Auto-run ${command} failed`,
5132
+ detail: 'Auto-run npm test failed',
5972
5133
  tone: 'danger',
5973
5134
  });
5974
- return 'failed';
5975
5135
  }
5976
5136
  finally {
5137
+ if (combinedOutput) {
5138
+ this.writeLocked(`${combinedOutput}\n`);
5139
+ }
5140
+ try {
5141
+ await this.runAIDesignedTests(trigger, assistantResponse, verificationContext);
5142
+ }
5143
+ catch (error) {
5144
+ const message = error instanceof Error ? error.message : String(error);
5145
+ display.showWarning(`AI-designed tests failed: ${message}`);
5146
+ }
5147
+ this.lastAutoTestRun = Date.now();
5977
5148
  this.updateStatusMessage(null);
5978
5149
  this.autoTestInFlight = false;
5979
- this.terminalInput.resetContentPosition();
5980
- this.terminalInput.forceRender();
5981
5150
  }
5982
5151
  }
5983
- isBuildToolCall(entry) {
5984
- const name = entry.toolName.toLowerCase();
5985
- if (name === 'run_build' || name === 'build') {
5152
+ /**
5153
+ * Auto-build verification after file edits.
5154
+ * Runs `npm run build` to catch TypeScript errors and feeds failures back to the agent.
5155
+ */
5156
+ async enforceAutoBuild(trigger) {
5157
+ if (this.autoBuildPromise) {
5158
+ return this.autoBuildPromise;
5159
+ }
5160
+ if (!this.verificationEnabled) {
5161
+ return false;
5162
+ }
5163
+ const latestChange = this.getLatestFileChangeTimestamp();
5164
+ if (!latestChange) {
5986
5165
  return true;
5987
5166
  }
5988
- if (name === 'bash' || name === 'execute_bash') {
5989
- const command = String(entry.args['command'] ?? '').toLowerCase();
5990
- const makeBuild = command.startsWith('make') && !command.includes('make test') && !command.includes('make lint');
5991
- const patterns = [
5992
- 'npm run build',
5993
- 'yarn build',
5994
- 'pnpm build',
5995
- 'bun run build',
5996
- 'bun build',
5997
- 'tsc',
5998
- 'cargo build',
5999
- 'go build',
6000
- 'python -m build',
6001
- 'mvn -b -dskiptests package',
6002
- 'mvn package',
6003
- 'mvn install',
6004
- './mvnw -b -dskiptests package',
6005
- './mvnw package',
6006
- './mvnw install',
6007
- 'gradle build',
6008
- 'gradle assemble',
6009
- './gradlew build',
6010
- './gradlew assemble',
6011
- 'dotnet build',
6012
- 'swift build',
6013
- ];
6014
- return makeBuild || patterns.some((pattern) => command.includes(pattern));
5167
+ if (this.lastBuildSucceededAt && latestChange <= this.lastBuildSucceededAt) {
5168
+ return true;
6015
5169
  }
6016
- return false;
5170
+ const command = 'npm run build';
5171
+ const runner = (async () => {
5172
+ this.autoBuildInFlight = true;
5173
+ display.showSystemMessage(`🔨 Auto-building to verify changes (${trigger})...`);
5174
+ this.updateStatusMessage('Running build automatically...');
5175
+ try {
5176
+ const { stdout, stderr } = await execAsync(command, {
5177
+ cwd: this.workingDir,
5178
+ timeout: 5 * 60 * 1000,
5179
+ maxBuffer: 10 * 1024 * 1024,
5180
+ });
5181
+ const finishedAt = Date.now();
5182
+ this.lastAutoBuildRun = finishedAt;
5183
+ this.lastBuildSucceededAt = finishedAt;
5184
+ const outputText = [stdout, stderr].filter(Boolean).join('\n').trim();
5185
+ display.showSystemMessage('✅ Build succeeded.');
5186
+ if (outputText && outputText.length < 500) {
5187
+ this.writeLocked(`${outputText}\n`);
5188
+ }
5189
+ this.statusTracker.clearOverride('build');
5190
+ return true;
5191
+ }
5192
+ catch (error) {
5193
+ const finishedAt = Date.now();
5194
+ this.lastAutoBuildRun = finishedAt;
5195
+ this.lastBuildSucceededAt = null;
5196
+ const errorOutput = this.formatCommandError(error);
5197
+ display.showWarning('⚠️ Build failed. Feeding errors back to agent...');
5198
+ if (errorOutput) {
5199
+ this.writeLocked(`${errorOutput}\n`);
5200
+ }
5201
+ this.statusTracker.pushOverride('build', 'Build failing', {
5202
+ detail: 'Auto-run npm run build failed',
5203
+ tone: 'danger',
5204
+ });
5205
+ // Feed build errors back to the agent so it can fix them
5206
+ await this.feedBuildErrorsToAgent(errorOutput);
5207
+ return false;
5208
+ }
5209
+ finally {
5210
+ this.updateStatusMessage(null);
5211
+ this.autoBuildInFlight = false;
5212
+ this.autoBuildPromise = null;
5213
+ }
5214
+ })();
5215
+ this.autoBuildPromise = runner;
5216
+ return runner;
6017
5217
  }
6018
- getLatestBuildTimestamp() {
6019
- const history = this.runtimeSession.toolRuntime.getToolHistory?.() ?? [];
6020
- let latest = this.lastAutoBuildRun;
6021
- for (const entry of history) {
6022
- if (this.isBuildToolCall(entry)) {
6023
- latest = latest ? Math.max(latest, entry.timestamp) : entry.timestamp;
5218
+ describeRecentChanges() {
5219
+ const changes = this._fileChangeTracker.getAllChanges();
5220
+ if (!changes.length) {
5221
+ return 'No tracked changes';
5222
+ }
5223
+ const items = changes.slice(0, 8).map((change) => {
5224
+ const additions = change.additions ? `+${change.additions}` : '';
5225
+ const removals = change.removals ? `-${change.removals}` : '';
5226
+ const delta = [additions, removals].filter(Boolean).join('/');
5227
+ const deltaLabel = delta ? ` ${delta}` : '';
5228
+ return `${change.path} (${change.type}${deltaLabel})`;
5229
+ });
5230
+ return items.join('; ');
5231
+ }
5232
+ describePackageScripts() {
5233
+ try {
5234
+ const pkgPath = join(this.workingDir, 'package.json');
5235
+ if (!existsSync(pkgPath)) {
5236
+ return 'package.json not found';
6024
5237
  }
5238
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
5239
+ const scripts = pkg.scripts ? Object.keys(pkg.scripts) : [];
5240
+ if (!scripts.length) {
5241
+ return 'package.json present without scripts';
5242
+ }
5243
+ return `package.json scripts: ${scripts.slice(0, 8).join(', ')}`;
5244
+ }
5245
+ catch {
5246
+ return 'package.json present but unreadable';
6025
5247
  }
6026
- return latest;
6027
5248
  }
6028
- /**
6029
- * Auto-build verification after file edits.
6030
- * Detects the build command for the current repo and feeds failures back to the agent.
6031
- */
6032
- async enforceAutoBuild(trigger, commandInfo) {
6033
- if (this.autoBuildInFlight || !this.verificationEnabled) {
6034
- return 'skipped';
5249
+ parseAIDesignedTests(plan) {
5250
+ if (!plan?.trim()) {
5251
+ return [];
6035
5252
  }
6036
- const latestChange = this.getLatestFileChangeTimestamp();
6037
- if (!latestChange) {
6038
- return 'skipped';
6039
- }
6040
- const latestBuild = this.getLatestBuildTimestamp();
6041
- if (latestBuild && latestChange <= latestBuild) {
6042
- return 'skipped';
6043
- }
6044
- const detected = commandInfo ?? detectBuildCommand(this.workingDir);
6045
- if (!detected) {
6046
- this.lastAutoBuildRun = Date.now();
6047
- display.showSystemMessage('ℹ️ Skipping auto-build: no build command detected for this repo.');
6048
- return 'skipped';
6049
- }
6050
- this.autoBuildInFlight = true;
6051
- const command = detected.command;
6052
- const reasonSuffix = detected.reason ? ` [${detected.reason}]` : '';
6053
- display.showSystemMessage(`🔨 Auto-building to verify changes (${trigger}) with "${command}"${reasonSuffix}...`);
6054
- this.updateStatusMessage('Running build automatically...');
5253
+ const match = plan.match(/\[[\s\S]*\]/);
5254
+ const payload = match ? match[0] : plan;
6055
5255
  try {
6056
- const { stdout, stderr } = await execAsync(command, {
6057
- cwd: this.workingDir,
6058
- timeout: 5 * 60 * 1000,
6059
- maxBuffer: 10 * 1024 * 1024,
5256
+ const parsed = JSON.parse(payload);
5257
+ if (!Array.isArray(parsed)) {
5258
+ return [];
5259
+ }
5260
+ return parsed
5261
+ .map((entry, index) => ({
5262
+ id: typeof entry.id === 'string' && entry.id.trim() ? entry.id.trim() : `ai-test-${index + 1}`,
5263
+ description: typeof entry.description === 'string' ? entry.description.trim() : `AI test ${index + 1}`,
5264
+ command: typeof entry.command === 'string' ? entry.command.trim() : '',
5265
+ expect: typeof entry.expect === 'string' ? entry.expect.trim() : undefined,
5266
+ timeoutMs: typeof entry.timeoutMs === 'number' ? entry.timeoutMs : undefined,
5267
+ }))
5268
+ .filter((test) => !!test.command);
5269
+ }
5270
+ catch {
5271
+ return [];
5272
+ }
5273
+ }
5274
+ isDangerousTestCommand(command) {
5275
+ const patterns = [
5276
+ /\brm\s+-/i,
5277
+ /\brm\s+/i,
5278
+ /\brmdir\b/i,
5279
+ /\bchmod\s+7/i,
5280
+ /\bsudo\b/i,
5281
+ /\bmkfs\b/i,
5282
+ /\bshutdown\b/i,
5283
+ /\breboot\b/i,
5284
+ /\bgit\s+(push|reset|checkout|clean)\b/i,
5285
+ /\bnpm\s+install\b/i,
5286
+ /\byarn\s+add\b/i,
5287
+ /\bpnpm\s+add\b/i,
5288
+ ];
5289
+ return patterns.some((pattern) => pattern.test(command));
5290
+ }
5291
+ async runAIDesignedTests(trigger, assistantResponse, verificationContext) {
5292
+ const userGoal = this.lastUserQuery?.trim() || 'Not provided';
5293
+ const implementationClaim = (assistantResponse ?? this.lastAssistantResponse ?? '').trim();
5294
+ const changeSummary = this.describeRecentChanges();
5295
+ const packageSummary = this.describePackageScripts();
5296
+ const recentConversation = verificationContext?.conversationHistory?.slice(-3).join('\n') ?? '';
5297
+ let provider;
5298
+ try {
5299
+ provider = createProvider({
5300
+ provider: this.sessionState.provider,
5301
+ model: this.sessionState.model,
5302
+ temperature: 0.15,
5303
+ maxTokens: 900,
6060
5304
  });
6061
- this.lastAutoBuildRun = Date.now();
6062
- const outputText = [stdout, stderr].filter(Boolean).join('\n').trim();
6063
- display.showSystemMessage('✅ Build succeeded.');
6064
- if (outputText && outputText.length < 500) {
6065
- this.writeLocked(`${outputText}\n`);
6066
- }
6067
- this.statusTracker.clearOverride('build');
6068
- return 'success';
6069
5305
  }
6070
5306
  catch (error) {
6071
- this.lastAutoBuildRun = Date.now();
6072
- const errorOutput = this.formatCommandError(error);
6073
- display.showWarning('⚠️ Build failed. Feeding errors back to agent...');
6074
- if (errorOutput) {
6075
- this.writeLocked(`${errorOutput}\n`);
6076
- }
6077
- this.statusTracker.pushOverride('build', 'Build failing', {
6078
- detail: `Auto-run ${command} failed`,
6079
- tone: 'danger',
6080
- });
6081
- // Feed build errors back to the agent so it can fix them
6082
- await this.feedBuildErrorsToAgent(errorOutput);
6083
- return 'failed';
5307
+ const message = error instanceof Error ? error.message : String(error);
5308
+ display.showWarning(`AI-designed tests skipped: ${message}`);
5309
+ return;
6084
5310
  }
6085
- finally {
6086
- this.updateStatusMessage(null);
6087
- this.autoBuildInFlight = false;
5311
+ const systemPrompt = 'You design high-value, repo-aware verification commands. Only emit JSON. Use read-only commands that run quickly.';
5312
+ const prompt = `Workspace: ${this.workingDir}
5313
+ User goal: ${userGoal.slice(0, 800)}
5314
+ Assistant claim: ${implementationClaim ? implementationClaim.slice(0, 1200) : 'No implementation summary captured.'}
5315
+ Recent file changes: ${changeSummary}
5316
+ Package signals: ${packageSummary}
5317
+ Recent conversation: ${recentConversation || 'n/a'}
5318
+
5319
+ Design 3-5 focused verification commands to confirm the requested functionality works after the successful build.
5320
+ Rules:
5321
+ - Commands must be safe and non-destructive (no rm, mv, chmod 7xx, sudo, package installs, or git mutations).
5322
+ - Prefer existing npm/yarn/pnpm scripts or targeted checks (node scripts, grep, curl localhost) that prove behavior.
5323
+ - Keep commands runnable from the repo root and finish quickly.
5324
+ Return ONLY JSON array:
5325
+ [{"id":"ai-test-1","description":"what it verifies","command":"npm test -- my-case","expect":"substring to find","timeoutMs":45000}]`;
5326
+ const plan = await provider.generate([
5327
+ { role: 'system', content: systemPrompt },
5328
+ { role: 'user', content: prompt },
5329
+ ], []);
5330
+ const planText = plan.type === 'message' ? plan.content : plan.content ?? '';
5331
+ const tests = this.parseAIDesignedTests(planText).slice(0, 5);
5332
+ if (!tests.length) {
5333
+ display.showWarning('AI-designed tests could not be generated. Skipping.');
5334
+ return;
5335
+ }
5336
+ const results = [];
5337
+ results.push(`🤖 Running ${tests.length} AI-designed verification tests (${trigger})...`);
5338
+ for (const test of tests) {
5339
+ const command = test.command.trim();
5340
+ if (!command) {
5341
+ continue;
5342
+ }
5343
+ if (this.isDangerousTestCommand(command)) {
5344
+ results.push(`⚠️ ${test.id} blocked (unsafe command): ${command}`);
5345
+ continue;
5346
+ }
5347
+ try {
5348
+ const { stdout, stderr } = await execAsync(command, {
5349
+ cwd: this.workingDir,
5350
+ timeout: Math.min(Math.max(test.timeoutMs ?? 45000, 5000), 120000),
5351
+ maxBuffer: 10 * 1024 * 1024,
5352
+ });
5353
+ const output = [stdout, stderr].filter(Boolean).join('\n');
5354
+ const expected = test.expect?.trim();
5355
+ const success = expected ? output.toLowerCase().includes(expected.toLowerCase()) : true;
5356
+ const snippet = output.replace(/\s+/g, ' ').trim().slice(0, 240);
5357
+ results.push(`${success ? '✅' : '❌'} ${test.description || test.id}`);
5358
+ results.push(` cmd: ${command}`);
5359
+ if (expected) {
5360
+ results.push(` expect: ${expected}`);
5361
+ }
5362
+ if (snippet) {
5363
+ results.push(` output: ${snippet}`);
5364
+ }
5365
+ if (!success && !snippet) {
5366
+ results.push(' output: [no output captured]');
5367
+ }
5368
+ }
5369
+ catch (error) {
5370
+ const message = this.formatCommandError(error) || (error instanceof Error ? error.message : String(error));
5371
+ results.push(`❌ ${test.description || test.id}`);
5372
+ results.push(` cmd: ${command}`);
5373
+ if (test.expect) {
5374
+ results.push(` expect: ${test.expect}`);
5375
+ }
5376
+ results.push(` error: ${message.slice(0, 240)}`);
5377
+ }
6088
5378
  }
5379
+ display.showSystemMessage(results.join('\n'));
6089
5380
  }
6090
5381
  /**
6091
5382
  * Feed build errors back to the agent conversation for automatic fixing.
@@ -6111,43 +5402,21 @@ What's the next action?`;
6111
5402
  // Send the error to the agent for fixing
6112
5403
  display.showThinking('Analyzing build errors');
6113
5404
  this.refreshStatusLine(true);
6114
- this.hasShownThoughtProcess = false;
6115
- await this.withStreamingUi('Fixing build errors', () => this.agent.send(prompt, true));
5405
+ const response = await this.agent.send(prompt, true);
5406
+ this.finishStreamingFormatter();
6116
5407
  display.stopThinking();
6117
5408
  this.refreshStatusLine(true);
5409
+ if (response) {
5410
+ display.showAssistantMessage(response, { isFinal: true });
5411
+ }
6118
5412
  // Recursively verify the fix worked
6119
5413
  await this.enforceAutoBuild('verification');
6120
5414
  }
6121
5415
  catch (agentError) {
6122
5416
  display.showWarning('Agent could not automatically fix build errors. Please review manually.');
6123
5417
  }
6124
- }
6125
- async enforceAutoVerification(trigger) {
6126
- if (this.autoVerificationInFlight || !this.verificationEnabled) {
6127
- return;
6128
- }
6129
- const latestChange = this.getLatestFileChangeTimestamp();
6130
- if (!latestChange) {
6131
- return;
6132
- }
6133
- this.autoVerificationInFlight = true;
6134
- try {
6135
- const buildInfo = detectBuildCommand(this.workingDir);
6136
- const testInfo = detectTestCommand(this.workingDir);
6137
- const buildResult = await this.enforceAutoBuild(trigger, buildInfo);
6138
- if (buildResult === 'failed') {
6139
- return;
6140
- }
6141
- if (testInfo) {
6142
- await this.enforceAutoTests(trigger, testInfo);
6143
- }
6144
- else {
6145
- this.lastAutoTestRun = Date.now();
6146
- display.showSystemMessage('ℹ️ Skipping auto-tests: no test command detected for this repo.');
6147
- }
6148
- }
6149
5418
  finally {
6150
- this.autoVerificationInFlight = false;
5419
+ this.finishStreamingFormatter();
6151
5420
  }
6152
5421
  }
6153
5422
  rebuildAgent() {
@@ -6165,35 +5434,29 @@ What's the next action?`;
6165
5434
  autoContinue: this.autoContinueEnabled,
6166
5435
  };
6167
5436
  this.agent = this.runtimeSession.createAgent(selection, {
6168
- onStreamChunk: (chunk, channel) => {
6169
- this.streamAssistantChunk(chunk, channel);
5437
+ onStreamChunk: (chunk) => {
5438
+ this.handleStreamChunk(chunk);
6170
5439
  },
6171
5440
  onStreamFallback: (info) => this.handleStreamingFallback(info),
6172
5441
  onAssistantMessage: (content, metadata) => {
6173
5442
  const enriched = this.buildDisplayMetadata(metadata);
6174
- const blocksActive = this.assistantBlocksEnabled && this.assistantBlockRenderer !== null;
6175
- const parsed = this.splitThinkingResponse(content);
6176
- const thinking = parsed?.thinking ?? null;
6177
- const responseContent = parsed ? parsed.response?.trim() ?? '' : content.trim();
6178
- const narrativeContent = (parsed?.response ?? content).trim();
6179
- const streamRendered = metadata.wasStreamed && this.assistantStreamHadContent;
6180
- if (thinking) {
6181
- this.presentThoughtProcess(thinking, enriched, {
6182
- wasStreamed: metadata.wasStreamed,
6183
- // Always render the thought block immediately when present so it appears first.
6184
- renderWhenStreamed: true,
6185
- });
6186
- }
5443
+ // Update spinner based on message type
6187
5444
  if (metadata.isFinal) {
6188
- if (metadata.wasStreamed) {
6189
- this.finalizeAssistantStream();
6190
- }
6191
- const body = responseContent || content;
6192
- // Always render the final response body even if it already streamed.
6193
- if (body) {
6194
- this.renderAssistantContent('response', body, { ...enriched, isFinal: true });
5445
+ const parsed = this.splitThinkingResponse(content);
5446
+ const finalContent = parsed?.response?.trim() || content;
5447
+ // Skip display if content was already streamed to avoid double-display
5448
+ if (!metadata.wasStreamed) {
5449
+ if (parsed?.thinking) {
5450
+ const summary = this.extractThoughtSummary(parsed.thinking);
5451
+ if (summary) {
5452
+ display.updateThinking(`💭 ${summary}`);
5453
+ }
5454
+ display.showAssistantMessage(parsed.thinking, { ...enriched, isFinal: false });
5455
+ }
5456
+ if (finalContent) {
5457
+ display.showAssistantMessage(finalContent, enriched);
5458
+ }
6195
5459
  }
6196
- this.renderToolUsageSummary({ ...enriched, isFinal: true });
6197
5460
  // Status shown in mode controls bar - no separate status line needed
6198
5461
  display.stopThinking();
6199
5462
  // Update context usage for mode controls display
@@ -6204,22 +5467,19 @@ What's the next action?`;
6204
5467
  this.updateContextUsage(percentage);
6205
5468
  }
6206
5469
  }
5470
+ if (finalContent) {
5471
+ this.lastAssistantResponse = finalContent;
5472
+ }
6207
5473
  // Auto-verify changes: build first (catches type errors), then tests
6208
- void this.enforceAutoVerification('final-response');
5474
+ void this.runAutoQualityChecks('final-response', finalContent);
6209
5475
  }
6210
5476
  else {
6211
5477
  // Non-final message = narrative text before tool calls (Claude Code style)
6212
5478
  // Stop spinner and show the narrative text directly
6213
5479
  display.stopThinking();
6214
- if (metadata.wasStreamed) {
6215
- this.finalizeAssistantStream();
6216
- }
6217
- const shouldRenderNarrative = Boolean(narrativeContent && (!metadata.wasStreamed || !blocksActive || !streamRendered));
6218
- if (shouldRenderNarrative) {
6219
- this.renderAssistantContent('thought', narrativeContent, {
6220
- ...enriched,
6221
- isFinal: false,
6222
- });
5480
+ // Skip display if content was already streamed to avoid double-display
5481
+ if (!metadata.wasStreamed) {
5482
+ display.showNarrative(content.trim());
6223
5483
  }
6224
5484
  // The isProcessing flag already shows "⏳ Processing..." - no need for duplicate status
6225
5485
  this.requestPromptRefresh();
@@ -6232,8 +5492,8 @@ What's the next action?`;
6232
5492
  this.requestPromptRefresh();
6233
5493
  },
6234
5494
  onContextSquishing: (message) => {
6235
- // Stream notification in UI when auto context squishing occurs
6236
- display.stream(`\n🔄 ${message}\n`);
5495
+ // Show notification in UI when auto context squishing occurs
5496
+ display.showSystemMessage(`🔄 ${message}`);
6237
5497
  this.statusTracker.pushOverride('context-squish', 'Auto-squishing context', {
6238
5498
  detail: 'Reducing conversation history to fit within token limits',
6239
5499
  tone: 'warning',
@@ -6241,7 +5501,7 @@ What's the next action?`;
6241
5501
  },
6242
5502
  onContextRecovery: (attempt, maxAttempts, message) => {
6243
5503
  // Show recovery progress in UI
6244
- display.stream(`\n⚡ Context Recovery (${attempt}/${maxAttempts}): ${message}\n`);
5504
+ display.showSystemMessage(`⚡ Context Recovery (${attempt}/${maxAttempts}): ${message}`);
6245
5505
  },
6246
5506
  onContextPruned: (removedCount, stats) => {
6247
5507
  // Clear squish overlay if active
@@ -6249,43 +5509,40 @@ What's the next action?`;
6249
5509
  // Show notification that context was pruned
6250
5510
  const method = stats['method'];
6251
5511
  const percentage = stats['percentage'];
6252
- const summarized = stats['summarized'] === true;
6253
5512
  if (method === 'emergency-recovery') {
6254
- display.stream(`\n✅ Context recovery complete. Removed ${removedCount} messages. Context now at ${percentage ?? 'unknown'}%\n`);
5513
+ display.showSystemMessage(`✅ Context recovery complete. Removed ${removedCount} messages. Context now at ${percentage ?? 'unknown'}%`);
6255
5514
  }
6256
5515
  // Update context usage in UI
6257
5516
  if (typeof percentage === 'number') {
6258
5517
  this.updateContextUsage(percentage);
6259
5518
  }
6260
- if (summarized) {
6261
- display.showSystemMessage('Context summary captured. View the latest summaries with /contextlog.');
6262
- }
6263
5519
  // Ensure prompt remains visible at bottom after context messages
6264
- this.renderPromptArea();
5520
+ this.terminalInput.render();
6265
5521
  },
6266
5522
  onContinueAfterRecovery: () => {
6267
5523
  // Update UI to show we're continuing after context recovery
6268
- display.stream(`\n🔄 Continuing after context recovery...\n`);
5524
+ display.showSystemMessage(`🔄 Continuing after context recovery...`);
6269
5525
  this.updateStatusMessage('Retrying with reduced context...');
6270
- this.renderPromptArea();
5526
+ this.terminalInput.render();
6271
5527
  },
6272
5528
  onAutoContinue: (attempt, maxAttempts, _message) => {
6273
5529
  // Show auto-continue progress in UI
6274
5530
  display.showSystemMessage(`🔄 Auto-continue (${attempt}/${maxAttempts}): Model expressed intent, prompting to act...`);
6275
5531
  this.updateStatusMessage('Auto-continuing...');
6276
- this.renderPromptArea();
5532
+ this.terminalInput.render();
6277
5533
  },
6278
5534
  onCancelled: () => {
6279
5535
  // Update UI to show operation was cancelled
6280
5536
  display.showWarning('Operation cancelled.');
6281
5537
  this.uiUpdates.setMode('processing');
6282
- this.stopStreamingHeartbeat({ skipRender: true });
5538
+ this.stopStreamingHeartbeat();
6283
5539
  this.updateStatusMessage(null);
6284
5540
  this.terminalInput.setStreaming(false);
6285
- this.renderPromptArea();
5541
+ this.terminalInput.render();
6286
5542
  },
6287
- onVerificationNeeded: () => {
6288
- void this.enforceAutoVerification('verification');
5543
+ onVerificationNeeded: (response, context) => {
5544
+ this.lastAssistantResponse = response;
5545
+ void this.runAutoQualityChecks('verification', response, context);
6289
5546
  },
6290
5547
  });
6291
5548
  // Register global AI enhancer for explore tool - uses active model by default
@@ -6319,7 +5576,7 @@ What's the next action?`;
6319
5576
  resetChatBoxAfterModelSwap() {
6320
5577
  this.updateStatusMessage(null);
6321
5578
  this.terminalInput.setStreaming(false);
6322
- this.renderPromptArea();
5579
+ this.terminalInput.render();
6323
5580
  this.ensureReadlineReady();
6324
5581
  }
6325
5582
  /**
@@ -6382,22 +5639,19 @@ What's the next action?`;
6382
5639
  }
6383
5640
  buildThinkingDirective() {
6384
5641
  switch (this.thinkingMode) {
6385
- case 'concise':
6386
- return 'Concise thinking mode: respond directly with the final answer and skip <thinking> blocks unless the user explicitly asks for reasoning.';
6387
5642
  case 'extended':
6388
5643
  return [
6389
- 'Extended thinking mode: include a <thinking> block followed by a <response> block.',
5644
+ 'Extended thinking mode is enabled. Format every reply as:',
6390
5645
  '<thinking>',
6391
- 'Brief, structured reasoning (cite tools/files when relevant; no secrets).',
5646
+ 'Detailed multi-step reasoning (reference tool runs/files when relevant, keep secrets redacted, no code blocks unless citing filenames).',
6392
5647
  '</thinking>',
6393
5648
  '<response>',
6394
- 'Final answer with requested code/commands and next steps.',
5649
+ 'Final answer with actionable next steps and any code/commands requested.',
6395
5650
  '</response>',
6396
5651
  ].join('\n');
6397
5652
  case 'balanced':
6398
5653
  default:
6399
- // Balanced mode: show thinking for complex reasoning, planning, or multi-step tasks
6400
- return 'When reasoning through complex tasks, planning approaches, or making decisions, wrap your thought process in <thinking> tags before your response. Keep thinking concise and focused on the decision-making process.';
5654
+ return 'Balanced thinking mode: include a short <thinking>...</thinking> block before <response> when the reasoning is non-trivial; skip it for simple answers.';
6401
5655
  }
6402
5656
  }
6403
5657
  buildDisplayMetadata(metadata) {
@@ -6406,58 +5660,6 @@ What's the next action?`;
6406
5660
  contextWindowTokens: this.activeContextWindowTokens,
6407
5661
  };
6408
5662
  }
6409
- presentThoughtProcess(thinking, metadata, options) {
6410
- if (!thinking || this.hasShownThoughtProcess) {
6411
- return;
6412
- }
6413
- const summary = this.extractThoughtSummary(thinking);
6414
- if (summary) {
6415
- display.updateThinking(`💭 ${summary}`);
6416
- this.latestThoughtSummary = summary;
6417
- this.streamingStatusDetail = summary;
6418
- this.terminalInput.recordRecentAction(`💭 ${summary}`);
6419
- this.rebuildStreamingStatusLabel();
6420
- this.refreshStatusLine(true);
6421
- }
6422
- const shouldRender = !options?.wasStreamed || options?.renderWhenStreamed;
6423
- if (shouldRender) {
6424
- this.renderAssistantContent('thought', thinking, { ...metadata, isFinal: false });
6425
- }
6426
- this.hasShownThoughtProcess = true;
6427
- }
6428
- renderToolUsageSummary(metadata) {
6429
- const since = this.lastToolSummaryRenderedAt ?? this.lastRequestStartedAt ?? Date.now();
6430
- const operations = this.uiAdapter.getToolOperationsSince(since);
6431
- if (!operations.length) {
6432
- return;
6433
- }
6434
- let summaryLine = compactRenderer.formatCompactLine(operations, {
6435
- showIcons: true,
6436
- showTimings: true,
6437
- });
6438
- const warnings = this.uiAdapter.getRecentWarnings(since);
6439
- if (warnings.length) {
6440
- const limited = warnings.slice(0, 3);
6441
- const formattedWarnings = limited
6442
- .map((warning) => `${icons.warning} ${warning.tool}: ${warning.text}`)
6443
- .join(' • ');
6444
- summaryLine = `${summaryLine} ${theme.warning('[warnings]')} ${formattedWarnings}`;
6445
- }
6446
- if (!summaryLine.trim()) {
6447
- this.lastToolSummaryRenderedAt = Date.now();
6448
- return;
6449
- }
6450
- this.lastToolSummaryRenderedAt =
6451
- operations[operations.length - 1]?.startedAt ?? Date.now();
6452
- const blockMetadata = { ...metadata, isFinal: true };
6453
- if (this.assistantBlocksEnabled && this.assistantBlockRenderer) {
6454
- this.renderAssistantBlock('tools', summaryLine, blockMetadata);
6455
- return;
6456
- }
6457
- const header = this.formatAssistantHeader('tools', blockMetadata);
6458
- const block = `\n${header}\n${summaryLine}\n\n`;
6459
- this.enqueueAssistantStream(block);
6460
- }
6461
5663
  handleContextTelemetry(metadata, displayMetadata) {
6462
5664
  if (!metadata.isFinal) {
6463
5665
  return null;
@@ -6818,7 +6020,7 @@ What's the next action?`;
6818
6020
  }
6819
6021
  handleAgentSetupError(error, retryAction, providerOverride) {
6820
6022
  this.pendingInteraction = null;
6821
- const provider = providerOverride === undefined ? this.sessionState.provider : providerOverride;
6023
+ const provider = providerOverride ?? this.sessionState.provider;
6822
6024
  const apiKeyIssue = detectApiKeyError(error, provider);
6823
6025
  if (apiKeyIssue) {
6824
6026
  this.handleApiKeyIssue(apiKeyIssue, retryAction);
@@ -6833,14 +6035,13 @@ What's the next action?`;
6833
6035
  const detail = detailText ? ` Error: ${detailText}` : '';
6834
6036
  const reason = info.reason ? ` (${info.reason.replace(/-/g, ' ')})` : '';
6835
6037
  const partialNote = info.partialResponse ? ' Received partial stream before failure.' : '';
6836
- this.finalizeAssistantStream();
6837
6038
  display.showWarning(`Streaming failed${reason}, retrying without streaming.${detail}${partialNote}`);
6039
+ this.finishStreamingFormatter('Stream interrupted - retrying without streaming');
6838
6040
  this.startStreamingHeartbeat('Fallback in progress');
6839
6041
  this.requestPromptRefresh(true);
6840
6042
  }
6841
6043
  handleProviderError(error, retryAction) {
6842
- const providerHint = error instanceof MissingSecretError ? null : this.sessionState.provider;
6843
- const apiKeyIssue = detectApiKeyError(error, providerHint);
6044
+ const apiKeyIssue = detectApiKeyError(error, this.sessionState.provider);
6844
6045
  if (!apiKeyIssue) {
6845
6046
  return false;
6846
6047
  }
@@ -6849,19 +6050,13 @@ What's the next action?`;
6849
6050
  }
6850
6051
  handleApiKeyIssue(info, retryAction) {
6851
6052
  const secret = info.secret ?? null;
6852
- const providerLabel = info.provider
6853
- ? this.providerLabel(info.provider)
6854
- : secret?.providers?.length
6855
- ? this.providerLabel(secret.providers[0])
6856
- : null;
6857
- const targetLabel = providerLabel ?? secret?.label ?? 'this tool';
6858
- this.apiKeyGateActive = !!secret;
6053
+ const providerLabel = info.provider ? this.providerLabel(info.provider) : 'the selected provider';
6859
6054
  if (!secret) {
6860
6055
  this.pendingSecretRetry = null;
6861
6056
  const guidance = 'Run "/secrets" to configure the required API key or export it (e.g., EXPORT KEY=value) before launching the CLI.';
6862
6057
  const baseMessage = info.type === 'missing'
6863
- ? `An API key is required before using ${targetLabel}.`
6864
- : `API authentication failed for ${targetLabel}.`;
6058
+ ? `An API key is required before using ${providerLabel}.`
6059
+ : `API authentication failed for ${providerLabel}.`;
6865
6060
  display.showWarning(`${baseMessage} ${guidance}`.trim());
6866
6061
  return;
6867
6062
  }
@@ -6870,8 +6065,8 @@ What's the next action?`;
6870
6065
  display.showWarning(info.message.trim());
6871
6066
  }
6872
6067
  const prefix = isMissing
6873
- ? `${secret.label} is required before you can use ${targetLabel}.`
6874
- : `${secret.label} appears to be invalid for ${targetLabel}.`;
6068
+ ? `${secret.label} is required before you can use ${providerLabel}.`
6069
+ : `${secret.label} appears to be invalid for ${providerLabel}.`;
6875
6070
  display.showWarning(prefix);
6876
6071
  this.pendingSecretRetry = retryAction ?? null;
6877
6072
  this.pendingInteraction = { type: 'secret-input', secret };
@@ -6885,7 +6080,7 @@ What's the next action?`;
6885
6080
  else {
6886
6081
  lines.push(`Update the stored value for ${secret.label} or type "cancel".`);
6887
6082
  }
6888
- lines.push(`Run "/secrets" anytime to manage credentials or export ${secret.envVar}=<value> before launching the CLI.`);
6083
+ lines.push(`Tip: run "/secrets" anytime to manage credentials or export ${secret.envVar}=<value> before launching the CLI.`);
6889
6084
  display.showSystemMessage(lines.join('\n'));
6890
6085
  }
6891
6086
  colorizeDropdownLine(text, index) {
@@ -6910,22 +6105,34 @@ What's the next action?`;
6910
6105
  /**
6911
6106
  * Build the session banner with comprehensive feature status.
6912
6107
  */
6913
- getSessionFrameProps() {
6914
- return {
6108
+ buildBanner() {
6109
+ const terminalWidth = output.columns ?? 100;
6110
+ const width = Math.min(terminalWidth - 4, 110);
6111
+ // Collect tool categories for display
6112
+ const toolCategories = this.collectToolCategories();
6113
+ // Load feature flags for banner display
6114
+ const featureFlags = loadFeatureFlags();
6115
+ return renderSessionFrame({
6915
6116
  profileLabel: this.profileLabel,
6916
6117
  profileName: this.profile,
6917
6118
  model: this.sessionState.model,
6918
6119
  provider: this.sessionState.provider,
6919
6120
  workspace: this.workingDir,
6920
6121
  version: this.version,
6921
- };
6922
- }
6923
- buildBanner() {
6924
- const terminalWidth = output.columns ?? 100;
6925
- const width = Math.min(terminalWidth - 4, 110);
6926
- return renderSessionFrame({
6927
- ...this.getSessionFrameProps(),
6928
6122
  width,
6123
+ features: {
6124
+ verification: this.verificationEnabled,
6125
+ autoContinue: this.autoContinueEnabled,
6126
+ thinkingMode: this.thinkingMode,
6127
+ plugins: this._enabledPlugins,
6128
+ tools: toolCategories,
6129
+ sessionId: this.activeSessionId ?? undefined,
6130
+ // Include feature flags
6131
+ alphaZeroDual: featureFlags.alphaZeroDual,
6132
+ autoCompact: featureFlags.autoCompact,
6133
+ mcpEnabled: featureFlags.mcpEnabled,
6134
+ metrics: featureFlags.metrics,
6135
+ },
6929
6136
  });
6930
6137
  }
6931
6138
  /**
@@ -6956,21 +6163,6 @@ What's the next action?`;
6956
6163
  }
6957
6164
  return categories;
6958
6165
  }
6959
- buildFeatureStatusSnapshot() {
6960
- const featureFlags = loadFeatureFlags();
6961
- const toolCategories = this.collectToolCategories();
6962
- const toolCount = toolCategories.reduce((sum, cat) => sum + cat.count, 0);
6963
- const pluginCount = this._enabledPlugins.length;
6964
- return {
6965
- pluginCount: pluginCount > 0 ? pluginCount : undefined,
6966
- toolCount: toolCount > 0 ? toolCount : undefined,
6967
- sessionId: this.activeSessionId,
6968
- mcpEnabled: featureFlags.mcpEnabled,
6969
- metricsEnabled: featureFlags.metrics,
6970
- autoCompact: featureFlags.autoCompact,
6971
- dualMode: featureFlags.alphaZeroDual,
6972
- };
6973
- }
6974
6166
  /**
6975
6167
  * Extract category from tool name.
6976
6168
  */
@@ -7034,7 +6226,8 @@ What's the next action?`;
7034
6226
  return;
7035
6227
  }
7036
6228
  this.refreshContextGauge();
7037
- // Banner is streamed once at launch; keep the control bar up to date without re-rendering it
6229
+ // Banner is no longer stored in display - it was streamed as content
6230
+ // Model/provider changes are visible in the control bar
7038
6231
  if (!this.isProcessing) {
7039
6232
  this.setIdleStatus();
7040
6233
  }
@@ -7339,6 +6532,20 @@ What's the next action?`;
7339
6532
  ];
7340
6533
  display.showSystemMessage(lines.join('\n'));
7341
6534
  }
6535
+ /**
6536
+ * Set the cached provider status for unified status bar display.
6537
+ * Called once at startup after checking providers.
6538
+ */
6539
+ setProviderStatus(providers) {
6540
+ this.cachedProviderStatus = providers;
6541
+ }
6542
+ /**
6543
+ * Show the unified status bar (Claude Code style).
6544
+ * Displays provider indicators and ready hints before the prompt.
6545
+ */
6546
+ showUnifiedStatusBar() {
6547
+ display.showUnifiedStatusBar(this.cachedProviderStatus);
6548
+ }
7342
6549
  }
7343
6550
  function setsEqual(first, second) {
7344
6551
  if (first.size !== second.size) {