erosolar-cli 1.7.410 → 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 (205) 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/askUserCapability.d.ts.map +1 -1
  11. package/dist/capabilities/askUserCapability.js +64 -10
  12. package/dist/capabilities/askUserCapability.js.map +1 -1
  13. package/dist/capabilities/toolRegistry.d.ts +2 -0
  14. package/dist/capabilities/toolRegistry.d.ts.map +1 -1
  15. package/dist/capabilities/toolRegistry.js +40 -5
  16. package/dist/capabilities/toolRegistry.js.map +1 -1
  17. package/dist/contracts/agent-profiles.schema.json +5 -5
  18. package/dist/contracts/agent-schemas.json +6 -16
  19. package/dist/contracts/schemas/agent.schema.json +1 -5
  20. package/dist/contracts/schemas/tool-selection.schema.json +1 -7
  21. package/dist/contracts/tools.schema.json +80 -207
  22. package/dist/contracts/unified-schema.json +4 -5
  23. package/dist/contracts/v1/agent.d.ts +0 -3
  24. package/dist/contracts/v1/agent.d.ts.map +1 -1
  25. package/dist/contracts/v1/provider.d.ts +1 -2
  26. package/dist/contracts/v1/provider.d.ts.map +1 -1
  27. package/dist/contracts/v1/toolAccess.d.ts +1 -1
  28. package/dist/contracts/v1/toolAccess.d.ts.map +1 -1
  29. package/dist/core/agent.d.ts +1 -7
  30. package/dist/core/agent.d.ts.map +1 -1
  31. package/dist/core/agent.js +2 -131
  32. package/dist/core/agent.js.map +1 -1
  33. package/dist/core/alphaZeroEngine.d.ts +0 -8
  34. package/dist/core/alphaZeroEngine.d.ts.map +1 -1
  35. package/dist/core/alphaZeroEngine.js +35 -149
  36. package/dist/core/alphaZeroEngine.js.map +1 -1
  37. package/dist/core/alphaZeroOrchestrator.d.ts +0 -17
  38. package/dist/core/alphaZeroOrchestrator.d.ts.map +1 -1
  39. package/dist/core/alphaZeroOrchestrator.js +8 -95
  40. package/dist/core/alphaZeroOrchestrator.js.map +1 -1
  41. package/dist/core/claudeCodeFeatures.d.ts +2 -1
  42. package/dist/core/claudeCodeFeatures.d.ts.map +1 -1
  43. package/dist/core/claudeCodeFeatures.js +2 -1
  44. package/dist/core/claudeCodeFeatures.js.map +1 -1
  45. package/dist/core/cliTestHarness.d.ts +0 -5
  46. package/dist/core/cliTestHarness.d.ts.map +1 -1
  47. package/dist/core/cliTestHarness.js +3 -14
  48. package/dist/core/cliTestHarness.js.map +1 -1
  49. package/dist/core/contextManager.d.ts +0 -30
  50. package/dist/core/contextManager.d.ts.map +1 -1
  51. package/dist/core/contextManager.js +5 -87
  52. package/dist/core/contextManager.js.map +1 -1
  53. package/dist/core/contextWindow.d.ts +4 -4
  54. package/dist/core/contextWindow.js +9 -9
  55. package/dist/core/contextWindow.js.map +1 -1
  56. package/dist/core/modelDiscovery.js +3 -3
  57. package/dist/core/modelDiscovery.js.map +1 -1
  58. package/dist/core/preferences.d.ts +2 -3
  59. package/dist/core/preferences.d.ts.map +1 -1
  60. package/dist/core/preferences.js +11 -18
  61. package/dist/core/preferences.js.map +1 -1
  62. package/dist/core/secretStore.d.ts.map +1 -1
  63. package/dist/core/secretStore.js +31 -0
  64. package/dist/core/secretStore.js.map +1 -1
  65. package/dist/core/toolPreconditions.d.ts.map +1 -1
  66. package/dist/core/toolPreconditions.js +0 -60
  67. package/dist/core/toolPreconditions.js.map +1 -1
  68. package/dist/core/toolRuntime.d.ts.map +1 -1
  69. package/dist/core/toolRuntime.js +0 -17
  70. package/dist/core/toolRuntime.js.map +1 -1
  71. package/dist/core/types.d.ts +1 -1
  72. package/dist/core/types.d.ts.map +1 -1
  73. package/dist/headless/headlessApp.d.ts.map +1 -1
  74. package/dist/headless/headlessApp.js +6 -22
  75. package/dist/headless/headlessApp.js.map +1 -1
  76. package/dist/plugins/providers/google/index.js +3 -2
  77. package/dist/plugins/providers/google/index.js.map +1 -1
  78. package/dist/providers/openaiChatCompletionsProvider.d.ts.map +1 -1
  79. package/dist/providers/openaiChatCompletionsProvider.js +6 -60
  80. package/dist/providers/openaiChatCompletionsProvider.js.map +1 -1
  81. package/dist/runtime/agentController.d.ts.map +1 -1
  82. package/dist/runtime/agentController.js +6 -27
  83. package/dist/runtime/agentController.js.map +1 -1
  84. package/dist/shell/interactiveShell.d.ts +30 -79
  85. package/dist/shell/interactiveShell.d.ts.map +1 -1
  86. package/dist/shell/interactiveShell.js +726 -1511
  87. package/dist/shell/interactiveShell.js.map +1 -1
  88. package/dist/shell/shellApp.d.ts.map +1 -1
  89. package/dist/shell/shellApp.js +41 -15
  90. package/dist/shell/shellApp.js.map +1 -1
  91. package/dist/shell/systemPrompt.d.ts.map +1 -1
  92. package/dist/shell/systemPrompt.js +0 -1
  93. package/dist/shell/systemPrompt.js.map +1 -1
  94. package/dist/shell/terminalInput.d.ts +21 -85
  95. package/dist/shell/terminalInput.d.ts.map +1 -1
  96. package/dist/shell/terminalInput.js +60 -517
  97. package/dist/shell/terminalInput.js.map +1 -1
  98. package/dist/shell/terminalInputAdapter.d.ts +16 -37
  99. package/dist/shell/terminalInputAdapter.d.ts.map +1 -1
  100. package/dist/shell/terminalInputAdapter.js +22 -44
  101. package/dist/shell/terminalInputAdapter.js.map +1 -1
  102. package/dist/shell/updateManager.d.ts.map +1 -1
  103. package/dist/shell/updateManager.js +17 -1
  104. package/dist/shell/updateManager.js.map +1 -1
  105. package/dist/subagents/parallelAgentManager.d.ts.map +1 -1
  106. package/dist/subagents/parallelAgentManager.js +2 -1
  107. package/dist/subagents/parallelAgentManager.js.map +1 -1
  108. package/dist/tools/buildTools.d.ts.map +1 -1
  109. package/dist/tools/buildTools.js +76 -19
  110. package/dist/tools/buildTools.js.map +1 -1
  111. package/dist/tools/editTools.js +1 -1
  112. package/dist/tools/editTools.js.map +1 -1
  113. package/dist/tools/enhancedCodeIntelligenceTools.js +2 -1
  114. package/dist/tools/enhancedCodeIntelligenceTools.js.map +1 -1
  115. package/dist/tools/fileTools.js +0 -3
  116. package/dist/tools/fileTools.js.map +1 -1
  117. package/dist/tools/frontendTestingTools.js +1 -1
  118. package/dist/tools/frontendTestingTools.js.map +1 -1
  119. package/dist/tools/interactionTools.d.ts.map +1 -1
  120. package/dist/tools/interactionTools.js +82 -15
  121. package/dist/tools/interactionTools.js.map +1 -1
  122. package/dist/tools/learnTools.d.ts +0 -2
  123. package/dist/tools/learnTools.d.ts.map +1 -1
  124. package/dist/tools/learnTools.js +81 -29
  125. package/dist/tools/learnTools.js.map +1 -1
  126. package/dist/tools/localExplore.d.ts.map +1 -1
  127. package/dist/tools/localExplore.js +1 -0
  128. package/dist/tools/localExplore.js.map +1 -1
  129. package/dist/tools/notebookEditTools.js.map +1 -1
  130. package/dist/tools/repoChecksTools.js +3 -4
  131. package/dist/tools/repoChecksTools.js.map +1 -1
  132. package/dist/tools/searchTools.js +0 -4
  133. package/dist/tools/searchTools.js.map +1 -1
  134. package/dist/tools/softwareEngineeringTools.d.ts.map +1 -1
  135. package/dist/tools/softwareEngineeringTools.js +0 -1
  136. package/dist/tools/softwareEngineeringTools.js.map +1 -1
  137. package/dist/tools/webTools.d.ts.map +1 -1
  138. package/dist/tools/webTools.js.map +1 -1
  139. package/dist/ui/ShellUIAdapter.d.ts +17 -54
  140. package/dist/ui/ShellUIAdapter.d.ts.map +1 -1
  141. package/dist/ui/ShellUIAdapter.js +90 -378
  142. package/dist/ui/ShellUIAdapter.js.map +1 -1
  143. package/dist/ui/display.d.ts +38 -19
  144. package/dist/ui/display.d.ts.map +1 -1
  145. package/dist/ui/display.js +311 -96
  146. package/dist/ui/display.js.map +1 -1
  147. package/dist/ui/orchestration/UIUpdateCoordinator.d.ts.map +1 -1
  148. package/dist/ui/orchestration/UIUpdateCoordinator.js +3 -5
  149. package/dist/ui/orchestration/UIUpdateCoordinator.js.map +1 -1
  150. package/dist/ui/shortcutsHelp.d.ts +11 -1
  151. package/dist/ui/shortcutsHelp.d.ts.map +1 -1
  152. package/dist/ui/shortcutsHelp.js +54 -8
  153. package/dist/ui/shortcutsHelp.js.map +1 -1
  154. package/dist/ui/streamingFormatter.d.ts +16 -0
  155. package/dist/ui/streamingFormatter.d.ts.map +1 -0
  156. package/dist/ui/streamingFormatter.js +62 -0
  157. package/dist/ui/streamingFormatter.js.map +1 -0
  158. package/dist/ui/theme.d.ts +100 -100
  159. package/dist/ui/theme.d.ts.map +1 -1
  160. package/dist/ui/theme.js.map +1 -1
  161. package/dist/ui/toolDisplay.d.ts +3 -3
  162. package/dist/ui/toolDisplay.d.ts.map +1 -1
  163. package/dist/ui/toolDisplay.js +8 -7
  164. package/dist/ui/toolDisplay.js.map +1 -1
  165. package/dist/ui/unified/index.d.ts +3 -2
  166. package/dist/ui/unified/index.d.ts.map +1 -1
  167. package/dist/ui/unified/index.js +1 -0
  168. package/dist/ui/unified/index.js.map +1 -1
  169. package/dist/ui/unified/layout.d.ts +23 -0
  170. package/dist/ui/unified/layout.d.ts.map +1 -1
  171. package/dist/ui/unified/layout.js +113 -11
  172. package/dist/ui/unified/layout.js.map +1 -1
  173. package/package.json +24 -37
  174. package/dist/core/alphaZeroConfig.d.ts +0 -11
  175. package/dist/core/alphaZeroConfig.d.ts.map +0 -1
  176. package/dist/core/alphaZeroConfig.js +0 -59
  177. package/dist/core/alphaZeroConfig.js.map +0 -1
  178. package/dist/core/alphaZeroEnhanced.d.ts +0 -125
  179. package/dist/core/alphaZeroEnhanced.d.ts.map +0 -1
  180. package/dist/core/alphaZeroEnhanced.js +0 -386
  181. package/dist/core/alphaZeroEnhanced.js.map +0 -1
  182. package/dist/core/autonomousVerification.d.ts +0 -103
  183. package/dist/core/autonomousVerification.d.ts.map +0 -1
  184. package/dist/core/autonomousVerification.js +0 -583
  185. package/dist/core/autonomousVerification.js.map +0 -1
  186. package/dist/core/offsecAlphaZeroEnhanced.d.ts +0 -98
  187. package/dist/core/offsecAlphaZeroEnhanced.d.ts.map +0 -1
  188. package/dist/core/offsecAlphaZeroEnhanced.js +0 -441
  189. package/dist/core/offsecAlphaZeroEnhanced.js.map +0 -1
  190. package/dist/core/parallelAgentOrchestrator.d.ts +0 -171
  191. package/dist/core/parallelAgentOrchestrator.d.ts.map +0 -1
  192. package/dist/core/parallelAgentOrchestrator.js +0 -459
  193. package/dist/core/parallelAgentOrchestrator.js.map +0 -1
  194. package/dist/index.d.ts +0 -5
  195. package/dist/index.d.ts.map +0 -1
  196. package/dist/index.js +0 -3
  197. package/dist/index.js.map +0 -1
  198. package/dist/tools/detectCommands.d.ts +0 -8
  199. package/dist/tools/detectCommands.d.ts.map +0 -1
  200. package/dist/tools/detectCommands.js +0 -183
  201. package/dist/tools/detectCommands.js.map +0 -1
  202. package/dist/ui/assistantBlockRenderer.d.ts +0 -30
  203. package/dist/ui/assistantBlockRenderer.d.ts.map +0 -1
  204. package/dist/ui/assistantBlockRenderer.js +0 -121
  205. 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,310 +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;
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);
1349
1164
  return;
1350
1165
  }
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) {
1361
- return;
1362
- }
1363
- if (!this.assistantStreamActive) {
1364
- this.startAssistantStream({
1365
- contextWindowTokens: this.activeContextWindowTokens,
1366
- });
1367
- }
1368
- // Agent wraps reasoning chunks in <thinking> tags via openThinking()/closeThinking()
1369
- // Always use tag parsing to properly handle the tags
1370
- this.processAssistantStreamChunk(chunk);
1371
- }
1372
- finalizeAssistantStream() {
1373
- if (!this.assistantStreamActive) {
1374
- return;
1375
- }
1376
- if (this.assistantStreamBuffer) {
1377
- this.writeAssistantStream(this.assistantStreamBuffer);
1378
- this.assistantStreamBuffer = '';
1379
- }
1380
- this.assistantStreamActive = false;
1381
- this.assistantStreamPhase = null;
1382
- this.assistantStreamHeaderShown = false;
1383
- this.assistantStreamMetadata = undefined;
1384
- }
1385
- processAssistantStreamChunk(chunk) {
1386
- this.assistantStreamBuffer += chunk;
1387
- const tagRegex = /<(\/?)(thinking|response)>/i;
1388
- while (this.assistantStreamBuffer.length > 0) {
1389
- const match = tagRegex.exec(this.assistantStreamBuffer);
1390
- if (!match || match.index === undefined) {
1391
- // No more full tags - flush what we can but keep any partial tag prefix buffered
1392
- const { flushable, remainder } = this.splitAssistantStreamRemainder(this.assistantStreamBuffer);
1393
- if (flushable) {
1394
- this.writeAssistantStream(flushable);
1395
- }
1396
- this.assistantStreamBuffer = remainder;
1397
- return;
1398
- }
1399
- const tagIndex = match.index;
1400
- const beforeTag = this.assistantStreamBuffer.slice(0, tagIndex);
1401
- if (beforeTag) {
1402
- this.writeAssistantStream(beforeTag);
1403
- }
1404
- // Advance buffer past the tag
1405
- this.assistantStreamBuffer = this.assistantStreamBuffer.slice(tagIndex + match[0].length);
1406
- const isClosing = match[1] === '/';
1407
- const rawTagType = (match[2] ?? '').toLowerCase();
1408
- // Map 'thinking' to 'thought' for AssistantBlockType compatibility
1409
- const tagType = (rawTagType === 'thinking' ? 'thought' : rawTagType);
1410
- if (isClosing) {
1411
- if (this.assistantStreamPhase === tagType) {
1412
- this.assistantStreamPhase = null;
1413
- this.assistantStreamHeaderShown = false;
1414
- }
1415
- }
1416
- else {
1417
- this.setAssistantStreamPhase(tagType);
1418
- }
1419
- }
1420
- }
1421
- writeAssistantStream(content) {
1422
- if (!content) {
1423
- return;
1424
- }
1425
- if (content.trim().length > 0) {
1426
- this.assistantStreamHadContent = true;
1427
- }
1428
- // If no explicit phase has been set yet, keep the stream labeled as "thought"
1429
- // until a <response> tag arrives so operators see thinking first.
1430
- this.setAssistantStreamPhase(this.assistantStreamPhase ?? 'thought');
1431
- if (this.assistantStreamPhase) {
1432
- this.renderAssistantStreamHeader(this.assistantStreamPhase);
1433
- }
1434
- let output = content;
1435
- if (this.assistantStreamPhase === 'thought') {
1436
- output = this.formatThoughtDisplay(content, { streaming: true });
1437
- }
1438
- this.enqueueAssistantStream(output);
1439
- }
1440
- setAssistantStreamPhase(phase) {
1441
- if (this.assistantStreamPhase === phase) {
1442
- return;
1443
- }
1444
- this.assistantStreamPhase = phase;
1445
- this.assistantStreamHeaderShown = false;
1446
- if (phase) {
1447
- this.renderAssistantStreamHeader(phase);
1448
- }
1449
- }
1450
- renderAssistantStreamHeader(type) {
1451
- if (this.assistantStreamHeaderShown) {
1452
- return;
1453
- }
1454
- const header = this.formatAssistantHeader(type, this.assistantStreamMetadata);
1455
- if (header.trim()) {
1456
- this.enqueueAssistantStream(`\n${header}\n`);
1457
- }
1458
- this.assistantStreamHeaderShown = true;
1459
- }
1460
- splitAssistantStreamRemainder(buffer) {
1461
- if (!buffer) {
1462
- return { flushable: '', remainder: '' };
1463
- }
1464
- const tags = ['<thinking>', '</thinking>', '<response>', '</response>'];
1465
- let longest = '';
1466
- for (const tag of tags) {
1467
- for (let i = 1; i < tag.length; i += 1) {
1468
- const prefix = tag.slice(0, i);
1469
- if (buffer.endsWith(prefix) && prefix.length > longest.length) {
1470
- longest = prefix;
1471
- }
1472
- }
1473
- }
1474
- if (!longest) {
1475
- return { flushable: buffer, remainder: '' };
1476
- }
1477
- return {
1478
- flushable: buffer.slice(0, -longest.length),
1479
- remainder: longest,
1480
- };
1481
- }
1482
- enqueueAssistantStream(content) {
1483
- if (!content) {
1484
- return;
1485
- }
1486
- const run = () => this.writeAssistantBlock(content);
1487
- this.uiUpdates.enqueue({
1488
- lane: 'stream',
1489
- mode: ['streaming', 'processing', 'idle'],
1490
- priority: 'high',
1491
- description: 'assistant stream flush',
1492
- run,
1493
- });
1494
- }
1495
- writeAssistantBlock(content) {
1496
- if (!content) {
1497
- return;
1498
- }
1499
- // Ensure assistant block writes are serialized with terminal input renders
1500
- writeLock.safeWrite(() => {
1501
- this.terminalInput.streamContent(content);
1502
- });
1503
- }
1504
- renderAssistantBlock(type, content, metadata) {
1505
- if (!this.assistantBlocksEnabled || !this.assistantBlockRenderer) {
1506
- return;
1507
- }
1508
- this.assistantBlockRenderer.renderBlock(type, content, metadata);
1509
- // Ensure the prompt stays pinned below freshly written blocks
1510
- this.renderPromptArea();
1511
- }
1512
- renderAssistantContent(type, content, metadata) {
1513
- const normalized = content.replace(/\r\n/g, '\n').trim();
1514
- if (!normalized) {
1515
- return;
1516
- }
1517
- const formatted = type === 'thought' ? this.formatThoughtDisplay(normalized) : normalized;
1518
- if (this.assistantBlocksEnabled && this.assistantBlockRenderer) {
1519
- this.renderAssistantBlock(type, formatted, metadata);
1520
- return;
1521
- }
1522
- this.renderAssistantFallback(type, formatted, metadata);
1523
- }
1524
- renderAssistantFallback(type, content, metadata) {
1525
- const header = this.formatAssistantHeader(type, metadata);
1526
- const compact = content.trimEnd().replace(/\n{3,}/g, '\n\n');
1527
- const block = `\n${header}\n${compact}\n\n`;
1528
- this.enqueueAssistantStream(block);
1529
- }
1530
- formatThoughtDisplay(content, options = {}) {
1531
- const normalized = content.replace(/\r\n/g, '\n');
1532
- if (options.streaming) {
1533
- return formatThinkingContent(normalized);
1534
- }
1535
- const lines = normalized.split('\n');
1536
- return lines
1537
- .map((line, index) => {
1538
- const prefix = index === 0 ? theme.info(icons.action) : theme.ui.muted(icons.subaction);
1539
- const trimmed = line.trim();
1540
- const body = trimmed ? formatThinkingContent(trimmed) : '';
1541
- return body ? `${prefix} ${body}` : `${prefix}`;
1542
- })
1543
- .join('\n');
1544
- }
1545
- formatAssistantHeader(type, metadata) {
1546
- if (this.assistantBlocksEnabled && this.assistantBlockRenderer) {
1547
- return this.assistantBlockRenderer.formatHeader(type, metadata);
1548
- }
1549
- const badges = [];
1550
- badges.push(this.buildAssistantTypeBadge(type));
1551
- const timestamp = new Date().toLocaleTimeString('en-US', {
1552
- hour12: false,
1553
- hour: '2-digit',
1554
- minute: '2-digit',
1555
- second: '2-digit',
1556
- });
1557
- badges.push({ text: timestamp, style: 'muted', icon: '🕑' });
1558
- const total = metadata?.usage ? this.totalTokens(metadata.usage) : null;
1559
- const windowTokens = metadata?.contextWindowTokens;
1560
- if (typeof total === 'number') {
1561
- badges.push({ text: `${total} tok`, style: 'muted', icon: '⎍' });
1562
- }
1563
- if (typeof total === 'number' && typeof windowTokens === 'number' && windowTokens > 0) {
1564
- const percentage = Math.round((total / windowTokens) * 100);
1565
- const style = percentage > 85 ? 'error' : percentage > 70 ? 'warning' : 'info';
1566
- badges.push({ text: `${percentage}% ctx`, style, icon: '⊛' });
1567
- }
1568
- const elapsedBadge = this.buildElapsedBadge(metadata);
1569
- if (elapsedBadge) {
1570
- badges.push(elapsedBadge);
1571
- }
1572
- return compactRenderer.formatBadges(badges, theme.ui.muted(' │ '));
1573
- }
1574
- buildAssistantTypeBadge(type) {
1575
- switch (type) {
1576
- case 'thought':
1577
- return { text: 'Thought', style: 'info', icon: '💭' };
1578
- case 'tools':
1579
- return { text: 'Tools', style: 'primary', icon: '🛠' };
1580
- case 'response':
1581
- default:
1582
- return { text: 'Response', style: 'success', icon: '💬' };
1583
- }
1584
- }
1585
- buildElapsedBadge(metadata) {
1586
- const elapsed = metadata?.elapsedMs;
1587
- if (typeof elapsed !== 'number' || elapsed <= 0) {
1588
- return null;
1589
- }
1590
- const formatted = elapsed < 1000 ? `${Math.round(elapsed)}ms` : `${Math.round(elapsed / 1000)}s`;
1591
- return { text: formatted, style: 'muted', icon: '⏱' };
1592
- }
1593
- isStreamingUiActive() {
1594
- return this.streamingHeartbeatStart !== null;
1595
- }
1596
- /**
1597
- * Render the prompt/control bar. During streaming, rely on the streaming frame
1598
- * renderer and enqueue through the UIUpdateCoordinator to avoid fighting the
1599
- * scroll region or duplicating the prompt.
1600
- */
1601
- renderPromptArea(force = false) {
1602
- if (this.isStreamingUiActive()) {
1603
- this.uiUpdates.enqueue({
1604
- lane: 'prompt',
1605
- mode: ['streaming', 'processing'],
1606
- coalesceKey: 'prompt:streaming-frame',
1607
- description: 'render streaming prompt frame',
1608
- run: () => {
1609
- if (force) {
1610
- this.terminalInput.renderStreamingFrame(true);
1611
- return;
1612
- }
1613
- this.terminalInput.renderStreamingFrame();
1614
- },
1615
- });
1616
- return;
1617
- }
1618
- if (force) {
1619
- this.terminalInput.forceRender();
1620
- }
1621
- else {
1622
- this.terminalInput.render();
1623
- }
1166
+ writeLock.withLock(() => {
1167
+ process.stdout.write(content);
1168
+ }, 'interactiveShell.stdout');
1624
1169
  }
1625
1170
  /**
1626
1171
  * Refresh the status line in the persistent input area.
@@ -1638,23 +1183,19 @@ export class InteractiveShell {
1638
1183
  // Surface meta header (elapsed + context usage) above the divider
1639
1184
  // Use streaming elapsed time if available, otherwise fall back to status line state
1640
1185
  let elapsedSeconds = null;
1641
- const shouldShowElapsed = this.streamingHeartbeatStart !== null || this.isProcessing;
1642
- if (this.streamingHeartbeatStart && shouldShowElapsed) {
1186
+ if (this.streamingHeartbeatStart) {
1643
1187
  // Actively streaming - compute live elapsed
1644
1188
  elapsedSeconds = Math.max(0, Math.floor((Date.now() - this.streamingHeartbeatStart) / 1000));
1645
1189
  }
1646
- else if (shouldShowElapsed && this.lastStreamingElapsedSeconds !== null) {
1190
+ else if (this.lastStreamingElapsedSeconds !== null) {
1647
1191
  // Just finished streaming - use preserved final time
1648
1192
  elapsedSeconds = this.lastStreamingElapsedSeconds;
1649
1193
  }
1650
- else if (shouldShowElapsed && this.statusLineState) {
1194
+ else if (this.statusLineState) {
1651
1195
  // Fallback to status line state elapsed
1652
1196
  elapsedSeconds = Math.max(0, Math.floor((Date.now() - this.statusLineState.startedAt) / 1000));
1653
1197
  }
1654
- const hasThoughtSummary = !!this.latestThoughtSummary;
1655
- const thinkingMs = hasThoughtSummary && display.isSpinnerActive()
1656
- ? display.getThinkingElapsedMs()
1657
- : null;
1198
+ const thinkingMs = display.isSpinnerActive() ? display.getThinkingElapsedMs() : null;
1658
1199
  const tokensUsed = this.latestTokenUsage.used;
1659
1200
  const tokenLimit = this.latestTokenUsage.limit ?? this.activeContextWindowTokens;
1660
1201
  this.terminalInput.setMetaStatus({
@@ -1662,7 +1203,7 @@ export class InteractiveShell {
1662
1203
  tokensUsed,
1663
1204
  tokenLimit,
1664
1205
  thinkingMs,
1665
- thinkingHasContent: hasThoughtSummary,
1206
+ thinkingHasContent: display.isSpinnerActive(),
1666
1207
  });
1667
1208
  // Keep model/provider visible in the controls bar
1668
1209
  this.terminalInput.setModelContext({
@@ -1670,7 +1211,7 @@ export class InteractiveShell {
1670
1211
  provider: this.providerLabel(this.sessionState.provider),
1671
1212
  });
1672
1213
  if (forceRender) {
1673
- this.renderPromptArea(true);
1214
+ this.terminalInput.render();
1674
1215
  }
1675
1216
  }
1676
1217
  /**
@@ -1707,7 +1248,7 @@ export class InteractiveShell {
1707
1248
  * Ensure the terminal input is ready for interactive input.
1708
1249
  */
1709
1250
  ensureReadlineReady() {
1710
- this.renderPromptArea();
1251
+ this.terminalInput.render();
1711
1252
  }
1712
1253
  /**
1713
1254
  * Log user prompt to the scroll region so it's part of the conversation flow.
@@ -1729,7 +1270,7 @@ export class InteractiveShell {
1729
1270
  }
1730
1271
  requestPromptRefresh(force = false) {
1731
1272
  if (force) {
1732
- this.renderPromptArea(true);
1273
+ this.terminalInput.forceRender();
1733
1274
  return;
1734
1275
  }
1735
1276
  if (this.promptRefreshTimer) {
@@ -1737,7 +1278,7 @@ export class InteractiveShell {
1737
1278
  }
1738
1279
  this.promptRefreshTimer = setTimeout(() => {
1739
1280
  this.promptRefreshTimer = null;
1740
- this.renderPromptArea();
1281
+ this.terminalInput.render();
1741
1282
  }, 48);
1742
1283
  }
1743
1284
  clearPromptRefreshTimer() {
@@ -1746,53 +1287,35 @@ export class InteractiveShell {
1746
1287
  this.promptRefreshTimer = null;
1747
1288
  }
1748
1289
  }
1749
- async withStreamingUi(label, run) {
1750
- if (this.isStreamingUiActive()) {
1751
- return run();
1752
- }
1753
- this.resetAssistantStreamTracking();
1754
- this.terminalInput.setStreaming(true);
1755
- this.startStreamingHeartbeat(label);
1756
- try {
1757
- return await run();
1758
- }
1759
- finally {
1760
- this.stopStreamingHeartbeat();
1761
- this.terminalInput.setStreaming(false);
1762
- const nextMode = this.isProcessing ? 'processing' : 'idle';
1763
- this.uiUpdates.setMode(nextMode);
1764
- if (nextMode === 'processing') {
1765
- queueMicrotask(() => this.uiUpdates.setMode('idle'));
1766
- }
1767
- }
1768
- }
1769
1290
  startStreamingHeartbeat(label = 'Streaming') {
1770
- this.stopStreamingHeartbeat({ skipRender: true });
1291
+ this.stopStreamingHeartbeat();
1771
1292
  // Enter global streaming mode - blocks all non-streaming UI output
1772
1293
  enterStreamingMode();
1773
- this.streamingStatusBase = label;
1774
- this.streamingStatusDetail = null;
1775
- this.latestThoughtSummary = null;
1776
- this.lastStreamingElapsedSeconds = null;
1777
1294
  // Set up scroll region for streaming content
1778
1295
  this.terminalInput.enterStreamingScrollRegion();
1779
1296
  this.uiUpdates.setMode('streaming');
1780
1297
  this.streamingHeartbeatStart = Date.now();
1781
1298
  this.streamingHeartbeatFrame = 0;
1782
- this.rebuildStreamingStatusLabel();
1299
+ const initialFrame = STREAMING_SPINNER_FRAMES[this.streamingHeartbeatFrame];
1300
+ this.streamingStatusLabel = this.buildStreamingStatus(`${initialFrame} ${label}`, 0);
1301
+ display.updateStreamingStatus(this.streamingStatusLabel);
1783
1302
  this.refreshStatusLine(true);
1784
1303
  // Periodically refresh the pinned input/status region while streaming so
1785
1304
  // elapsed time remains visible without interrupting the scroll region.
1786
1305
  this.uiUpdates.startHeartbeat('streaming', {
1787
1306
  intervalMs: 1000,
1788
1307
  lane: 'heartbeat',
1789
- priority: 'high',
1790
1308
  mode: ['streaming', 'processing'],
1791
1309
  coalesceKey: 'streaming:heartbeat',
1792
1310
  run: () => {
1311
+ const elapsedSeconds = this.streamingHeartbeatStart
1312
+ ? Math.max(0, Math.floor((Date.now() - this.streamingHeartbeatStart) / 1000))
1313
+ : 0;
1793
1314
  this.streamingHeartbeatFrame =
1794
1315
  (this.streamingHeartbeatFrame + 1) % STREAMING_SPINNER_FRAMES.length;
1795
- this.rebuildStreamingStatusLabel();
1316
+ const frame = STREAMING_SPINNER_FRAMES[this.streamingHeartbeatFrame];
1317
+ this.streamingStatusLabel = this.buildStreamingStatus(`${frame} ${label}`, elapsedSeconds);
1318
+ display.updateStreamingStatus(this.streamingStatusLabel);
1796
1319
  // Update parallel agent display during streaming
1797
1320
  const manager = getParallelAgentManager();
1798
1321
  if (manager.isRunning()) {
@@ -1803,13 +1326,7 @@ export class InteractiveShell {
1803
1326
  },
1804
1327
  });
1805
1328
  }
1806
- stopStreamingHeartbeat(options = {}) {
1807
- const skipRender = !!options.skipRender;
1808
- const streamingActive = this.isStreamingUiActive();
1809
- const scrollRegionActive = this.terminalInput.isScrollRegionActive();
1810
- if (!streamingActive && !scrollRegionActive) {
1811
- return;
1812
- }
1329
+ stopStreamingHeartbeat() {
1813
1330
  // Exit global streaming mode - allows UI to render again
1814
1331
  exitStreamingMode();
1815
1332
  // Preserve final elapsed time before clearing heartbeat start
@@ -1817,44 +1334,51 @@ export class InteractiveShell {
1817
1334
  this.lastStreamingElapsedSeconds = Math.max(0, Math.floor((Date.now() - this.streamingHeartbeatStart) / 1000));
1818
1335
  }
1819
1336
  // Exit scroll region mode
1820
- this.terminalInput.exitStreamingScrollRegion({ skipRender });
1337
+ this.terminalInput.exitStreamingScrollRegion();
1821
1338
  this.uiUpdates.stopHeartbeat('streaming');
1822
1339
  this.streamingHeartbeatStart = null;
1823
1340
  this.streamingHeartbeatFrame = 0;
1824
1341
  this.streamingStatusLabel = null;
1825
- this.streamingStatusBase = null;
1826
- this.streamingStatusDetail = null;
1827
- this.latestThoughtSummary = null;
1828
1342
  // Clear streaming label specifically (keeps override and main status if set)
1829
1343
  this.terminalInput.setStreamingLabel(null);
1830
1344
  // Clear streaming status from display
1831
1345
  display.updateStreamingStatus(null);
1832
1346
  // Force refresh to update the input area now that streaming has ended
1833
- if (!skipRender) {
1834
- this.refreshStatusLine(true);
1835
- }
1347
+ this.refreshStatusLine(true);
1836
1348
  }
1837
- buildStreamingStatus(label, _elapsedSeconds) {
1838
- // Model + elapsed time already live in the pinned meta header; keep the streaming
1839
- // status focused on the activity and most recent thought summary.
1840
- const prefix = theme.info('⏺');
1841
- const parts = [label.trim()];
1842
- if (this.streamingStatusDetail) {
1843
- const detail = this.streamingStatusDetail.length > 52
1844
- ? `${this.streamingStatusDetail.slice(0, 51)}…`
1845
- : this.streamingStatusDetail;
1846
- 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);
1847
1365
  }
1848
- return `${prefix} ${parts.join(' · ')}`.trim();
1849
1366
  }
1850
- rebuildStreamingStatusLabel() {
1851
- if (this.streamingHeartbeatStart === null) {
1367
+ finishStreamingFormatter(note) {
1368
+ if (!this.streamingFormatter) {
1852
1369
  return;
1853
1370
  }
1854
- const frame = STREAMING_SPINNER_FRAMES[this.streamingHeartbeatFrame];
1855
- const base = this.streamingStatusBase ?? 'Streaming';
1856
- this.streamingStatusLabel = this.buildStreamingStatus(`${frame} ${base}`);
1857
- 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();
1858
1382
  }
1859
1383
  formatElapsedShort(seconds) {
1860
1384
  if (seconds < 60) {
@@ -1871,7 +1395,7 @@ export class InteractiveShell {
1871
1395
  else {
1872
1396
  this.setIdleStatus();
1873
1397
  }
1874
- this.renderPromptArea();
1398
+ this.terminalInput.render();
1875
1399
  }
1876
1400
  enqueueFollowUpAction(action) {
1877
1401
  this.followUpQueue.push(action);
@@ -1890,21 +1414,14 @@ export class InteractiveShell {
1890
1414
  this.refreshQueueIndicators();
1891
1415
  this.scheduleQueueProcessing();
1892
1416
  // Re-show the prompt so user can continue typing more follow-ups
1893
- this.renderPromptArea();
1417
+ this.terminalInput.render();
1894
1418
  }
1895
1419
  scheduleQueueProcessing() {
1896
1420
  if (!this.followUpQueue.length) {
1897
1421
  this.refreshQueueIndicators();
1898
1422
  return;
1899
1423
  }
1900
- if (this.apiKeyGateActive) {
1901
- this.refreshQueueIndicators();
1902
- return;
1903
- }
1904
1424
  queueMicrotask(() => {
1905
- if (this.apiKeyGateActive) {
1906
- return;
1907
- }
1908
1425
  void this.processQueuedActions();
1909
1426
  });
1910
1427
  }
@@ -1912,12 +1429,12 @@ export class InteractiveShell {
1912
1429
  * Process queued follow-up actions.
1913
1430
  */
1914
1431
  async processQueuedActions() {
1915
- if (this.apiKeyGateActive || this.isDrainingQueue || this.isProcessing || !this.followUpQueue.length) {
1432
+ if (this.isDrainingQueue || this.isProcessing || !this.followUpQueue.length) {
1916
1433
  return;
1917
1434
  }
1918
1435
  this.isDrainingQueue = true;
1919
1436
  try {
1920
- while (!this.isProcessing && !this.apiKeyGateActive && this.followUpQueue.length) {
1437
+ while (!this.isProcessing && this.followUpQueue.length) {
1921
1438
  const next = this.followUpQueue.shift();
1922
1439
  const remaining = this.followUpQueue.length;
1923
1440
  const label = next.type === 'continuous' ? 'continuous command' : 'follow-up';
@@ -1952,12 +1469,12 @@ export class InteractiveShell {
1952
1469
  }
1953
1470
  if (lower === 'clear') {
1954
1471
  display.clear();
1955
- this.renderPromptArea();
1472
+ this.terminalInput.render();
1956
1473
  return;
1957
1474
  }
1958
1475
  if (lower === 'help') {
1959
1476
  this.showHelp();
1960
- this.renderPromptArea();
1477
+ this.terminalInput.render();
1961
1478
  return;
1962
1479
  }
1963
1480
  if (trimmed.startsWith('/')) {
@@ -1967,12 +1484,12 @@ export class InteractiveShell {
1967
1484
  // Check for continuous/infinite loop commands
1968
1485
  if (this.isContinuousCommand(trimmed)) {
1969
1486
  await this.processContinuousRequest(trimmed);
1970
- this.renderPromptArea();
1487
+ this.terminalInput.render();
1971
1488
  return;
1972
1489
  }
1973
1490
  // Direct execution for all inputs, including multi-line pastes
1974
1491
  await this.processRequest(trimmed);
1975
- this.renderPromptArea();
1492
+ this.terminalInput.render();
1976
1493
  }
1977
1494
  /**
1978
1495
  * Check if the command is a continuous/infinite loop command
@@ -2010,153 +1527,6 @@ export class InteractiveShell {
2010
1527
  ];
2011
1528
  return patterns.some(pattern => pattern.test(lower));
2012
1529
  }
2013
- isDifficultProblem(input) {
2014
- const normalized = input.toLowerCase();
2015
- const wordCount = normalized.split(/\s+/).filter(Boolean).length;
2016
- if (normalized.length > 600 || wordCount > 80) {
2017
- return true;
2018
- }
2019
- const signals = [
2020
- 'root cause',
2021
- 'postmortem',
2022
- 'crash',
2023
- 'incident',
2024
- 'outage',
2025
- 'optimiz',
2026
- 'performance',
2027
- 'throughput',
2028
- 'latency',
2029
- 'scalab',
2030
- 'architecture',
2031
- 'rewrite',
2032
- 'migration',
2033
- 'refactor',
2034
- 'reverse engineer',
2035
- 'security',
2036
- 'exploit',
2037
- 'injection',
2038
- 'vulnerability',
2039
- 'compliance',
2040
- 'multi-step',
2041
- 'complex',
2042
- 'difficult',
2043
- 'hard problem',
2044
- 'debug',
2045
- 'trace',
2046
- 'profil',
2047
- 'bottleneck',
2048
- ];
2049
- return signals.some((signal) => normalized.includes(signal));
2050
- }
2051
- buildAlphaZeroPrompt(request, flaggedDifficult) {
2052
- const playbook = [
2053
- 'AlphaZero RL MODE is ACTIVE. Operate as a self-play reinforcement loop.',
2054
- flaggedDifficult
2055
- ? 'Treat this as a difficult, high-risk task and over-verify the result.'
2056
- : 'Apply the reinforcement loop even if the task looks small.',
2057
- 'Follow this closed-loop playbook:',
2058
- '- Draft two competing solution strategies and merge the strongest ideas before executing.',
2059
- '- Execute with tools while logging decisions and evidence.',
2060
- '- Self-critique and repair until the quality is excellent (aim ≥90/100).',
2061
- '- Run full-lifecycle verification like a human reviewer: build/tests, manual sanity checks, edge cases, performance/safety/security probes, docs/UX/readiness notes.',
2062
- '- Keep a verification ledger: each check with PASS/FAIL, evidence, and remaining risks. If anything fails, fix and re-verify before claiming completion.',
2063
- 'Finish with a concise sign-off that lists what was achieved and the proof of completion.',
2064
- ];
2065
- return `${playbook.join('\n')}\n\nPrimary user request:\n${request.trim()}`;
2066
- }
2067
- buildRunLogExcerpt(startIndex) {
2068
- const buffer = this.terminalInput.getScrollbackBuffer();
2069
- const sinceStart = buffer.slice(Math.max(0, startIndex));
2070
- const excerpt = sinceStart.slice(-200); // Cap prompt size
2071
- return excerpt.join('\n').trim();
2072
- }
2073
- buildRunLogEntry(request, response, scrollbackStartIndex, meta) {
2074
- return {
2075
- id: ++this.runIdCounter,
2076
- request,
2077
- response,
2078
- timestamp: new Date().toISOString(),
2079
- alphaZero: meta.alphaZeroEngaged,
2080
- difficult: meta.alphaZeroDifficult,
2081
- failureType: meta.failureType,
2082
- outputExcerpt: this.buildRunLogExcerpt(scrollbackStartIndex),
2083
- };
2084
- }
2085
- buildAlphaZeroReflectionPrompt(runLog, options) {
2086
- const lines = [];
2087
- lines.push('AlphaZero Post-Run Self-Reflection (erosolar-cli)');
2088
- lines.push(`Timestamp: ${runLog.timestamp}`);
2089
- lines.push(`AlphaZero: ${runLog.alphaZero ? 'on' : 'off'}${runLog.difficult ? ' | difficult' : ''}${runLog.failureType ? ` | signal: ${runLog.failureType}` : ''}`);
2090
- lines.push('');
2091
- lines.push('User request:');
2092
- lines.push(runLog.request);
2093
- lines.push('');
2094
- lines.push('Previous run log excerpt:');
2095
- lines.push(runLog.outputExcerpt || '[empty]');
2096
- lines.push('');
2097
- lines.push('Instructions:');
2098
- lines.push('- Reflect on the log to spot erosolar-cli bugs, UX issues, or reliability gaps.');
2099
- lines.push('- Propose and apply targeted fixes in this repository only (no user workspace edits).');
2100
- lines.push('- Prefer small, test-backed changes; run any relevant checks you invoke.');
2101
- lines.push('- Keep notes concise and finish with applied changes plus follow-ups.');
2102
- if (options.autoChain) {
2103
- lines.push('- Continue iterating automatically while meaningful improvements remain.');
2104
- lines.push('- When no further improvements are possible, reply with NO_MORE_IMPROVEMENTS on its own line.');
2105
- }
2106
- return lines.join('\n');
2107
- }
2108
- maybeQueueAlphaZeroSelfReflection(runLog, alphaZeroEngaged, allowAutoChain) {
2109
- if (!alphaZeroEngaged) {
2110
- return;
2111
- }
2112
- if (!isErosolarRepo(this.workingDir)) {
2113
- return;
2114
- }
2115
- if (!runLog.outputExcerpt) {
2116
- return;
2117
- }
2118
- if (this.lastReflectedRunId === runLog.id) {
2119
- return;
2120
- }
2121
- if (allowAutoChain) {
2122
- if (!this.autoContinueEnabled) {
2123
- return;
2124
- }
2125
- if (this.alphaZeroAutoImproveIterations >= this.alphaZeroAutoImproveMaxIterations) {
2126
- display.showInfo('AlphaZero auto-improvement limit reached; stopping.');
2127
- this.alphaZeroAutoImproveActive = false;
2128
- this.alphaZeroAutoImproveIterations = 0;
2129
- return;
2130
- }
2131
- if (!this.alphaZeroAutoImproveActive) {
2132
- this.alphaZeroAutoImproveIterations = 0;
2133
- }
2134
- this.alphaZeroAutoImproveActive = true;
2135
- this.alphaZeroAutoImproveIterations++;
2136
- }
2137
- const prompt = this.buildAlphaZeroReflectionPrompt(runLog, { autoChain: allowAutoChain });
2138
- this.lastReflectedRunId = runLog.id;
2139
- this.skipNextAutoReflection = !allowAutoChain; // Prevent reflection-on-reflection unless auto-chaining
2140
- this.enqueueFollowUpAction({ type: 'request', text: prompt });
2141
- display.showInfo(allowAutoChain
2142
- ? 'Auto AlphaZero self-improvement queued (auto-continue enabled).'
2143
- : 'Queued AlphaZero self-reflection to improve erosolar-cli from the latest run log.');
2144
- }
2145
- shouldStopAlphaZeroAutoImprove(responseText, allowAutoChain) {
2146
- if (!this.alphaZeroAutoImproveActive) {
2147
- return false;
2148
- }
2149
- if (!allowAutoChain) {
2150
- return true;
2151
- }
2152
- if (!responseText) {
2153
- return false;
2154
- }
2155
- const normalized = responseText.toLowerCase();
2156
- return (normalized.includes('no_more_improvements') ||
2157
- normalized.includes('no more improvements') ||
2158
- normalized.includes('stop_auto_improve'));
2159
- }
2160
1530
  async handlePendingInteraction(input) {
2161
1531
  if (!this.pendingInteraction) {
2162
1532
  return false;
@@ -2164,7 +1534,7 @@ export class InteractiveShell {
2164
1534
  switch (this.pendingInteraction.type) {
2165
1535
  case 'model-loading':
2166
1536
  display.showInfo('Still fetching model options. Please wait a moment.');
2167
- this.renderPromptArea();
1537
+ this.terminalInput.render();
2168
1538
  return true;
2169
1539
  case 'model-provider':
2170
1540
  await this.handleModelProviderSelection(input);
@@ -2195,213 +1565,166 @@ export class InteractiveShell {
2195
1565
  const [command] = input.split(/\s+/);
2196
1566
  if (!command) {
2197
1567
  display.showWarning('Enter a slash command.');
2198
- this.renderPromptArea();
1568
+ this.terminalInput.render();
2199
1569
  return;
2200
1570
  }
2201
- // Keep the slash action visible in the pinned recent strip
2202
- this.terminalInput.recordRecentAction(command);
2203
- const runCommand = async () => {
2204
- switch (command) {
2205
- case '/help':
2206
- case '/?':
2207
- this.showHelp();
2208
- break;
2209
- case '/features':
2210
- this.showFeaturesMenu(input);
2211
- break;
2212
- case '/learn':
2213
- this.showLearningStatus(input);
2214
- break;
2215
- case '/improve':
2216
- void this.handleImprovementCommand(input);
2217
- break;
2218
- case '/model':
2219
- this.showModelMenu();
2220
- break;
2221
- case '/exit':
2222
- case '/quit':
2223
- case '/q':
2224
- this.shutdown();
2225
- break;
2226
- case '/secrets':
2227
- this.showSecretsMenu();
2228
- break;
2229
- case '/tools':
2230
- this.showToolsMenu();
2231
- break;
2232
- case '/mcp':
2233
- await this.showMcpStatus();
2234
- break;
2235
- case '/doctor':
2236
- this.runDoctor();
2237
- break;
2238
- case '/checks':
2239
- await this.runRepoChecksCommand();
2240
- break;
2241
- case '/context':
2242
- await this.refreshWorkspaceContextCommand(input);
2243
- break;
2244
- case '/contextlog':
2245
- this.showContextSummaryLog(input);
2246
- break;
2247
- case '/agents':
2248
- this.showAgentsMenu();
2249
- break;
2250
- case '/sessions':
2251
- await this.handleSessionCommand(input);
2252
- break;
2253
- case '/skills':
2254
- await this.handleSkillsCommand(input);
2255
- break;
2256
- case '/thinking':
2257
- this.handleThinkingCommand(input);
2258
- break;
2259
- case '/autocontinue':
2260
- this.handleAutoContinueCommand(input);
2261
- break;
2262
- case '/alphazero':
2263
- this.handleAlphaZeroCommand(input);
2264
- break;
2265
- case '/shortcuts':
2266
- case '/keys':
2267
- this.handleShortcutsCommand();
2268
- break;
2269
- case '/changes':
2270
- case '/summary':
2271
- this.showFileChangeSummary();
2272
- break;
2273
- case '/metrics':
2274
- case '/stats':
2275
- case '/perf':
2276
- this.showAlphaZeroMetrics();
2277
- break;
2278
- case '/suggestions':
2279
- case '/improve':
2280
- this.showImprovementSuggestions();
2281
- break;
2282
- case '/plugins':
2283
- this.showPluginStatus();
2284
- break;
2285
- case '/evolve':
2286
- void this.handleEvolveCommand(input);
2287
- break;
2288
- case '/modular':
2289
- case '/a0':
2290
- void this.handleModularCommand(input);
2291
- break;
2292
- case '/offsec':
2293
- void this.handleOffsecCommand(input);
2294
- break;
2295
- case '/test':
2296
- case '/tests':
2297
- void this.handleTestCommand(input);
2298
- break;
2299
- case '/provider':
2300
- await this.handleProviderCommand(input);
2301
- break;
2302
- case '/providers':
2303
- this.showConfiguredProviders();
2304
- break;
2305
- case '/local':
2306
- await this.handleLocalCommand(input);
2307
- break;
2308
- case '/discover':
2309
- await this.discoverModelsCommand();
2310
- break;
2311
- // Claude Code style commands
2312
- case '/rewind':
2313
- await this.handleRewindCommand(input);
2314
- break;
2315
- case '/memory':
2316
- this.handleMemoryCommand(input);
2317
- break;
2318
- case '/vim':
2319
- this.handleVimCommand();
2320
- break;
2321
- case '/output-style':
2322
- this.handleOutputStyleCommand(input);
2323
- break;
2324
- case '/cost':
2325
- this.handleCostCommand();
2326
- break;
2327
- case '/usage':
2328
- this.handleUsageCommand();
2329
- break;
2330
- case '/update':
2331
- await this.handleUpdateCommand();
2332
- break;
2333
- case '/clear':
2334
- this.handleClearCommand();
2335
- break;
2336
- case '/resume':
2337
- await this.handleResumeCommand(input);
2338
- break;
2339
- case '/export':
2340
- this.handleExportCommand(input);
2341
- break;
2342
- case '/review':
2343
- await this.handleReviewCommand();
2344
- break;
2345
- case '/security-review':
2346
- await this.handleSecurityReviewCommand();
2347
- break;
2348
- case '/bug':
2349
- this.handleBugCommand();
2350
- break;
2351
- case '/terminal-setup':
2352
- this.handleTerminalSetupCommand();
2353
- break;
2354
- case '/permissions':
2355
- this.handlePermissionsCommand();
2356
- break;
2357
- case '/init':
2358
- this.handleInitCommand();
2359
- break;
2360
- case '/compact':
2361
- await this.handleCompactCommand();
2362
- break;
2363
- default:
2364
- if (!(await this.tryCustomSlashCommand(command, input))) {
2365
- display.showWarning(`Unknown command "${command}".`);
2366
- }
2367
- break;
2368
- }
2369
- };
2370
- const streamingUiActive = this.isStreamingUiActive();
2371
- const captureOptions = streamingUiActive
2372
- ? { includeStreaming: false, suppressTypes: ['normal'] }
2373
- : undefined;
2374
- let capturedOutput = '';
2375
- try {
2376
- const { output: outputBuffer } = await display.captureOutput(runCommand, captureOptions);
2377
- capturedOutput = outputBuffer;
2378
- }
2379
- catch (error) {
2380
- capturedOutput = error?.capturedOutput ?? capturedOutput;
2381
- if (streamingUiActive) {
2382
- const message = error instanceof Error ? error.message : String(error);
2383
- const { output: errorOutput } = await display.captureOutput(() => display.showError(message, error), { includeStreaming: false, suppressTypes: ['normal'] });
2384
- capturedOutput = capturedOutput || errorOutput || message;
2385
- }
2386
- else {
2387
- display.showError(error instanceof Error ? error.message : String(error), error);
2388
- }
2389
- }
2390
- const panelContent = this.buildInlineCommandPanel(command, capturedOutput);
2391
- this.terminalInput.setInlineCommandPanel(panelContent);
2392
- this.renderPromptArea();
2393
- }
2394
- buildInlineCommandPanel(command, output) {
2395
- const normalized = output ? output.replace(/\r\n/g, '\n').replace(/\r/g, '\n').trimEnd() : '';
2396
- if (!normalized) {
2397
- return null;
2398
- }
2399
- const header = theme.ui.muted(command);
2400
- const body = normalized.split('\n');
2401
- if (body.length === 1) {
2402
- 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;
2403
1726
  }
2404
- return [header, ...body];
1727
+ this.terminalInput.render();
2405
1728
  }
2406
1729
  async tryCustomSlashCommand(command, fullInput) {
2407
1730
  const custom = this.customCommandMap.get(command);
@@ -2438,13 +1761,11 @@ export class InteractiveShell {
2438
1761
  ` ${theme.info('Option+V')} ${theme.ui.muted('Toggle verification')}`,
2439
1762
  ` ${theme.info('Option+C')} ${theme.ui.muted('Toggle auto-continue')}`,
2440
1763
  ` ${theme.info('Option+T')} ${theme.ui.muted('Cycle thinking mode')}`,
2441
- ` ${theme.info('Option+A')} ${theme.ui.muted('Toggle AlphaZero RL mode')}`,
2442
1764
  ` ${theme.info('Option+E')} ${theme.ui.muted('Toggle edit permission mode')}`,
2443
1765
  ` ${theme.info('Option+X')} ${theme.ui.muted('Clear/compact context')}`,
2444
1766
  '',
2445
1767
  theme.bold(' Navigation'),
2446
1768
  ` ${theme.info('PageUp/PageDown')} ${theme.ui.muted('Scroll through output')}`,
2447
- ` ${theme.info('Escape')} ${theme.ui.muted('Stop streaming')}`,
2448
1769
  ` ${theme.info('Ctrl+C')} ${theme.ui.muted('Clear input or interrupt')}`,
2449
1770
  ` ${theme.info('Up/Down')} ${theme.ui.muted('Navigate command history')}`,
2450
1771
  '',
@@ -2542,66 +1863,6 @@ export class InteractiveShell {
2542
1863
  display.showWarning('Workspace snapshot refreshed, but the agent failed to rebuild. Run /doctor for details.');
2543
1864
  }
2544
1865
  }
2545
- showContextSummaryLog(input) {
2546
- const agent = this.agent;
2547
- if (!agent) {
2548
- display.showWarning('No active agent session. Start one to inspect context summaries.');
2549
- return;
2550
- }
2551
- const contextManager = agent.getContextManager();
2552
- if (!contextManager?.getSummaryLog) {
2553
- display.showWarning('Context summary logging is unavailable in this session.');
2554
- return;
2555
- }
2556
- const tokens = input.trim().split(/\s+/).slice(1);
2557
- let limit = 5;
2558
- for (const token of tokens) {
2559
- if (!token)
2560
- continue;
2561
- const normalized = token.toLowerCase();
2562
- if (/^\d+$/.test(token)) {
2563
- limit = Math.min(Math.max(parseInt(token, 10), 1), 20);
2564
- }
2565
- else if (normalized.startsWith('limit=')) {
2566
- const value = parseInt(normalized.split('=')[1] ?? '', 10);
2567
- if (!Number.isNaN(value)) {
2568
- limit = Math.min(Math.max(value, 1), 20);
2569
- }
2570
- }
2571
- }
2572
- const entries = contextManager.getSummaryLog(limit);
2573
- if (!entries.length) {
2574
- display.showInfo('No context summaries captured yet.');
2575
- return;
2576
- }
2577
- const lines = [];
2578
- lines.push(`${theme.primary('Context summaries')} ${theme.ui.muted(`(latest ${entries.length})`)}`);
2579
- for (const entry of entries) {
2580
- const timestamp = new Date(entry.timestamp).toLocaleString();
2581
- const headerParts = [
2582
- theme.ui.muted(timestamp),
2583
- theme.info(entry.method),
2584
- `removed ${theme.error(String(entry.removed))}`,
2585
- `kept ${theme.success(String(entry.preserved))}`,
2586
- ];
2587
- if (entry.reason) {
2588
- headerParts.push(theme.ui.muted(entry.reason));
2589
- }
2590
- if (entry.model) {
2591
- headerParts.push(theme.ui.muted(`model=${entry.model}`));
2592
- }
2593
- lines.push(headerParts.join(' · '));
2594
- if (entry.summary) {
2595
- const trimmed = entry.summary.length > 600 ? `${entry.summary.slice(0, 600)}…` : entry.summary;
2596
- lines.push(trimmed);
2597
- }
2598
- else {
2599
- lines.push(theme.ui.muted('(no summary text — simple prune)'));
2600
- }
2601
- lines.push(''); // spacer
2602
- }
2603
- display.showSystemMessage(lines.join('\n'));
2604
- }
2605
1866
  parseContextOverrideTokens(input) {
2606
1867
  const overrides = {};
2607
1868
  let hasOverride = false;
@@ -2761,11 +2022,11 @@ export class InteractiveShell {
2761
2022
  handleThinkingCommand(input) {
2762
2023
  const value = input.slice('/thinking'.length).trim().toLowerCase();
2763
2024
  if (!value) {
2764
- 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]`);
2765
2026
  return;
2766
2027
  }
2767
- if (value !== 'concise' && value !== 'balanced' && value !== 'extended') {
2768
- display.showWarning('Usage: /thinking [concise|balanced|extended]');
2028
+ if (value !== 'balanced' && value !== 'extended') {
2029
+ display.showWarning('Usage: /thinking [balanced|extended]');
2769
2030
  return;
2770
2031
  }
2771
2032
  if (this.isProcessing) {
@@ -2778,30 +2039,11 @@ export class InteractiveShell {
2778
2039
  this.resetChatBoxAfterModelSwap();
2779
2040
  }
2780
2041
  const descriptions = {
2781
- concise: 'Hides internal reasoning and responds directly.',
2782
2042
  balanced: 'Shows short thoughts only when helpful.',
2783
2043
  extended: 'Always emits a <thinking> block before the final response.',
2784
2044
  };
2785
2045
  display.showInfo(`Thinking mode set to ${theme.info(value)} – ${descriptions[this.thinkingMode]}`);
2786
2046
  }
2787
- handleAlphaZeroCommand(input) {
2788
- const value = input.slice('/alphazero'.length).trim().toLowerCase();
2789
- if (!value || value === 'status') {
2790
- const status = this.alphaZeroModeEnabled ? theme.success('on') : theme.ui.muted('off');
2791
- const verification = this.verificationEnabled ? 'verification locked on' : 'verification optional';
2792
- display.showInfo(`AlphaZero RL mode is ${status}. When enabled, difficult prompts use duel/self-critique and human-style verification (${verification}).`);
2793
- return;
2794
- }
2795
- if (['on', 'enable', 'enabled'].includes(value)) {
2796
- this.setAlphaZeroMode(true, 'command');
2797
- return;
2798
- }
2799
- if (['off', 'disable', 'disabled'].includes(value)) {
2800
- this.setAlphaZeroMode(false, 'command');
2801
- return;
2802
- }
2803
- display.showWarning('Usage: /alphazero [on|off|status]');
2804
- }
2805
2047
  handleShortcutsCommand() {
2806
2048
  // Display keyboard shortcuts help (Claude Code style)
2807
2049
  display.showSystemMessage(formatShortcutsHelp());
@@ -2889,7 +2131,6 @@ export class InteractiveShell {
2889
2131
  const updated = toggleFeatureFlag(matchedKey, newValue);
2890
2132
  const status = updated[matchedKey] ? theme.success('enabled') : theme.ui.muted('disabled');
2891
2133
  display.showInfo(`Feature "${FEATURE_FLAG_INFO[matchedKey].label}" is now ${status}.`);
2892
- this.refreshFeatureStatusDisplay();
2893
2134
  display.showInfo('Changes will take effect on next launch or after /features refresh.');
2894
2135
  return;
2895
2136
  }
@@ -2902,7 +2143,6 @@ export class InteractiveShell {
2902
2143
  }
2903
2144
  saveFeatureFlags(updated);
2904
2145
  display.showInfo(`All features ${newValue ? theme.success('enabled') : theme.ui.muted('disabled')}.`);
2905
- this.refreshFeatureStatusDisplay();
2906
2146
  return;
2907
2147
  }
2908
2148
  else {
@@ -4075,7 +3315,9 @@ export class InteractiveShell {
4075
3315
  }
4076
3316
  display.showInfo(`Deleted session "${summary.title}".`);
4077
3317
  if (this.activeSessionId === summary.id) {
4078
- this.updateActiveSession(null, true);
3318
+ this.activeSessionId = null;
3319
+ this.activeSessionTitle = null;
3320
+ saveSessionPreferences({ lastSessionId: null });
4079
3321
  }
4080
3322
  }
4081
3323
  newSessionCommand(title) {
@@ -4095,7 +3337,6 @@ export class InteractiveShell {
4095
3337
  clearAutosaveSnapshot(this.profile);
4096
3338
  display.showInfo('Started a new empty session.');
4097
3339
  this.refreshContextGauge();
4098
- this.refreshFeatureStatusDisplay();
4099
3340
  }
4100
3341
  toggleAutosaveCommand(value) {
4101
3342
  if (!value) {
@@ -4150,6 +3391,7 @@ export class InteractiveShell {
4150
3391
  lines.push(' /rewind code Rewind code only (keep conversation)');
4151
3392
  lines.push(' /rewind conv Rewind conversation only (keep code)');
4152
3393
  lines.push('');
3394
+ lines.push(theme.ui.muted('Tip: Press Esc+Esc for quick access to rewind menu'));
4153
3395
  display.showSystemMessage(lines.join('\n'));
4154
3396
  }
4155
3397
  handleMemoryCommand(input) {
@@ -4168,6 +3410,7 @@ export class InteractiveShell {
4168
3410
  lines.push(' - Use # prefix to quickly add notes to project memory');
4169
3411
  lines.push(' - Import other files with @./relative/path syntax');
4170
3412
  lines.push('');
3413
+ lines.push(theme.ui.muted('Tip: Create EROSOLAR.md with project coding standards for better results'));
4171
3414
  display.showSystemMessage(lines.join('\n'));
4172
3415
  }
4173
3416
  handleVimCommand() {
@@ -4248,20 +3491,6 @@ export class InteractiveShell {
4248
3491
  }
4249
3492
  display.showSystemMessage(lines.join('\n'));
4250
3493
  }
4251
- async handleUpdateCommand() {
4252
- display.showInfo('Checking for updates...');
4253
- const lines = [];
4254
- lines.push(theme.bold('Update Check'));
4255
- lines.push('');
4256
- lines.push(`Current version: ${this.version ?? 'unknown'}`);
4257
- lines.push('');
4258
- lines.push(theme.secondary('To update:'));
4259
- lines.push(' npm install -g erosolar-cli@latest');
4260
- lines.push(' or: npm update erosolar-cli');
4261
- lines.push('');
4262
- lines.push(theme.ui.muted('Auto-updates will be checked periodically.'));
4263
- display.showSystemMessage(lines.join('\n'));
4264
- }
4265
3494
  handleClearCommand() {
4266
3495
  if (this.agent) {
4267
3496
  this.agent.clearHistory();
@@ -4270,7 +3499,7 @@ export class InteractiveShell {
4270
3499
  display.clear();
4271
3500
  clearAutosaveSnapshot(this.profile);
4272
3501
  display.showInfo('Conversation cleared. Starting fresh.');
4273
- this.renderPromptArea();
3502
+ this.terminalInput.render();
4274
3503
  }
4275
3504
  async handleResumeCommand(input) {
4276
3505
  const tokens = input.split(/\s+/).slice(1);
@@ -4411,7 +3640,6 @@ export class InteractiveShell {
4411
3640
  if (remember) {
4412
3641
  saveSessionPreferences({ lastSessionId: summary?.id ?? null });
4413
3642
  }
4414
- this.refreshFeatureStatusDisplay();
4415
3643
  }
4416
3644
  resolveSessionBySelector(selector) {
4417
3645
  const sessions = listSessions(this.profile);
@@ -4580,7 +3808,7 @@ export class InteractiveShell {
4580
3808
  if (!providerOptions.length) {
4581
3809
  display.showWarning('No providers are available.');
4582
3810
  this.pendingInteraction = null;
4583
- this.renderPromptArea();
3811
+ this.terminalInput.render();
4584
3812
  return;
4585
3813
  }
4586
3814
  const lines = [
@@ -4601,7 +3829,7 @@ export class InteractiveShell {
4601
3829
  catch (error) {
4602
3830
  display.showError('Failed to load model list. Try again in a moment.', error);
4603
3831
  this.pendingInteraction = null;
4604
- this.renderPromptArea();
3832
+ this.terminalInput.render();
4605
3833
  }
4606
3834
  }
4607
3835
  buildProviderOptions() {
@@ -4785,7 +4013,7 @@ export class InteractiveShell {
4785
4013
  }
4786
4014
  renderToolMenu(interaction) {
4787
4015
  const lines = [
4788
- 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).'),
4789
4017
  ...interaction.options.map((option, index) => this.formatToolOptionLine(option, index, interaction.selection)),
4790
4018
  '',
4791
4019
  'Enter the number to toggle, "save" to persist, "defaults" to restore recommended tools, or "cancel".',
@@ -4794,13 +4022,23 @@ export class InteractiveShell {
4794
4022
  }
4795
4023
  formatToolOptionLine(option, index, selection) {
4796
4024
  const enabled = selection.has(option.id);
4797
- 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('[ ]');
4798
4030
  const details = [option.description];
4799
4031
  if (option.requiresSecret) {
4800
4032
  const hasSecret = Boolean(getSecretValue(option.requiresSecret));
4801
4033
  const status = hasSecret ? theme.success('API key set') : theme.warning('API key missing');
4802
4034
  details.push(status);
4803
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
+ }
4804
4042
  const numberLabel = this.colorizeDropdownLine(`${index + 1}.`, index);
4805
4043
  const optionLabel = this.colorizeDropdownLine(option.label, index);
4806
4044
  const detailLine = this.colorizeDropdownLine(` ${details.join(' • ')}`, index);
@@ -5146,29 +4384,29 @@ export class InteractiveShell {
5146
4384
  const trimmed = input.trim();
5147
4385
  if (!trimmed) {
5148
4386
  display.showWarning('Enter a number or type cancel.');
5149
- this.renderPromptArea();
4387
+ this.terminalInput.render();
5150
4388
  return;
5151
4389
  }
5152
4390
  if (trimmed.toLowerCase() === 'cancel') {
5153
4391
  this.pendingInteraction = null;
5154
4392
  display.showInfo('Model selection cancelled.');
5155
- this.renderPromptArea();
4393
+ this.terminalInput.render();
5156
4394
  return;
5157
4395
  }
5158
4396
  const choice = Number.parseInt(trimmed, 10);
5159
4397
  if (!Number.isFinite(choice)) {
5160
4398
  display.showWarning('Please enter a valid number.');
5161
- this.renderPromptArea();
4399
+ this.terminalInput.render();
5162
4400
  return;
5163
4401
  }
5164
4402
  const option = pending.options[choice - 1];
5165
4403
  if (!option) {
5166
4404
  display.showWarning('That option is not available.');
5167
- this.renderPromptArea();
4405
+ this.terminalInput.render();
5168
4406
  return;
5169
4407
  }
5170
4408
  this.showProviderModels(option);
5171
- this.renderPromptArea();
4409
+ this.terminalInput.render();
5172
4410
  }
5173
4411
  async handleModelSelection(input) {
5174
4412
  const pending = this.pendingInteraction;
@@ -5178,35 +4416,35 @@ export class InteractiveShell {
5178
4416
  const trimmed = input.trim();
5179
4417
  if (!trimmed) {
5180
4418
  display.showWarning('Enter a number, type "back", or type "cancel".');
5181
- this.renderPromptArea();
4419
+ this.terminalInput.render();
5182
4420
  return;
5183
4421
  }
5184
4422
  if (trimmed.toLowerCase() === 'back') {
5185
4423
  this.showModelMenu();
5186
- this.renderPromptArea();
4424
+ this.terminalInput.render();
5187
4425
  return;
5188
4426
  }
5189
4427
  if (trimmed.toLowerCase() === 'cancel') {
5190
4428
  this.pendingInteraction = null;
5191
4429
  display.showInfo('Model selection cancelled.');
5192
- this.renderPromptArea();
4430
+ this.terminalInput.render();
5193
4431
  return;
5194
4432
  }
5195
4433
  const choice = Number.parseInt(trimmed, 10);
5196
4434
  if (!Number.isFinite(choice)) {
5197
4435
  display.showWarning('Please enter a valid number.');
5198
- this.renderPromptArea();
4436
+ this.terminalInput.render();
5199
4437
  return;
5200
4438
  }
5201
4439
  const preset = pending.options[choice - 1];
5202
4440
  if (!preset) {
5203
4441
  display.showWarning('That option is not available.');
5204
- this.renderPromptArea();
4442
+ this.terminalInput.render();
5205
4443
  return;
5206
4444
  }
5207
4445
  this.pendingInteraction = null;
5208
4446
  await this.applyModelPreset(preset);
5209
- this.renderPromptArea();
4447
+ this.terminalInput.render();
5210
4448
  }
5211
4449
  async applyModelPreset(preset) {
5212
4450
  try {
@@ -5239,30 +4477,30 @@ export class InteractiveShell {
5239
4477
  const trimmed = input.trim();
5240
4478
  if (!trimmed) {
5241
4479
  display.showWarning('Enter a number or type cancel.');
5242
- this.renderPromptArea();
4480
+ this.terminalInput.render();
5243
4481
  return;
5244
4482
  }
5245
4483
  if (trimmed.toLowerCase() === 'cancel') {
5246
4484
  this.pendingInteraction = null;
5247
4485
  display.showInfo('Secret management cancelled.');
5248
- this.renderPromptArea();
4486
+ this.terminalInput.render();
5249
4487
  return;
5250
4488
  }
5251
4489
  const choice = Number.parseInt(trimmed, 10);
5252
4490
  if (!Number.isFinite(choice)) {
5253
4491
  display.showWarning('Please enter a valid number.');
5254
- this.renderPromptArea();
4492
+ this.terminalInput.render();
5255
4493
  return;
5256
4494
  }
5257
4495
  const secret = pending.options[choice - 1];
5258
4496
  if (!secret) {
5259
4497
  display.showWarning('That option is not available.');
5260
- this.renderPromptArea();
4498
+ this.terminalInput.render();
5261
4499
  return;
5262
4500
  }
5263
4501
  display.showSystemMessage(`Enter a new value for ${secret.label} or type "cancel".`);
5264
4502
  this.pendingInteraction = { type: 'secret-input', secret };
5265
- this.renderPromptArea();
4503
+ this.terminalInput.render();
5266
4504
  }
5267
4505
  async handleSecretInput(input) {
5268
4506
  const pending = this.pendingInteraction;
@@ -5272,16 +4510,14 @@ export class InteractiveShell {
5272
4510
  const trimmed = input.trim();
5273
4511
  if (!trimmed) {
5274
4512
  display.showWarning('Enter a value or type cancel.');
5275
- this.renderPromptArea();
4513
+ this.terminalInput.render();
5276
4514
  return;
5277
4515
  }
5278
4516
  if (trimmed.toLowerCase() === 'cancel') {
5279
4517
  this.pendingInteraction = null;
5280
4518
  this.pendingSecretRetry = null;
5281
- this.apiKeyGateActive = false;
5282
4519
  display.showInfo('Secret unchanged.');
5283
- this.renderPromptArea();
5284
- this.scheduleQueueProcessing();
4520
+ this.terminalInput.render();
5285
4521
  return;
5286
4522
  }
5287
4523
  try {
@@ -5290,7 +4526,6 @@ export class InteractiveShell {
5290
4526
  this.pendingInteraction = null;
5291
4527
  const deferred = this.pendingSecretRetry;
5292
4528
  this.pendingSecretRetry = null;
5293
- this.apiKeyGateActive = false;
5294
4529
  if (pending.secret.providers.includes(this.sessionState.provider)) {
5295
4530
  if (this.rebuildAgent()) {
5296
4531
  this.resetChatBoxAfterModelSwap();
@@ -5305,14 +4540,12 @@ export class InteractiveShell {
5305
4540
  display.showError(message);
5306
4541
  this.pendingInteraction = null;
5307
4542
  this.pendingSecretRetry = null;
5308
- this.apiKeyGateActive = false;
5309
4543
  }
5310
- this.renderPromptArea();
5311
- this.scheduleQueueProcessing();
4544
+ this.terminalInput.render();
5312
4545
  }
5313
- async processRequest(userRequest) {
4546
+ async processRequest(request) {
5314
4547
  if (this.isProcessing) {
5315
- this.enqueueFollowUpAction({ type: 'request', text: userRequest });
4548
+ this.enqueueFollowUpAction({ type: 'request', text: request });
5316
4549
  return;
5317
4550
  }
5318
4551
  if (!this.agent && !this.rebuildAgent()) {
@@ -5323,57 +4556,29 @@ export class InteractiveShell {
5323
4556
  if (!agent) {
5324
4557
  return;
5325
4558
  }
5326
- this.hasShownThoughtProcess = false;
5327
- this.resetAssistantStreamTracking();
5328
- const alphaZeroEngaged = this.alphaZeroModeEnabled;
5329
- const alphaZeroDifficult = alphaZeroEngaged ? this.isDifficultProblem(userRequest) : false;
5330
- const requestForAgent = alphaZeroEngaged
5331
- ? this.buildAlphaZeroPrompt(userRequest, alphaZeroDifficult)
5332
- : userRequest;
5333
- const skipReflectionForThisRun = this.skipNextAutoReflection;
5334
- this.skipNextAutoReflection = false;
5335
- const scrollbackStartIndex = this.terminalInput.getScrollbackBuffer().length;
5336
- const alphaZeroStatusId = 'alpha-zero';
5337
- let alphaZeroStatusApplied = false;
5338
- let alphaZeroTaskStarted = false;
5339
- let alphaZeroTaskCompleted = false;
5340
- if (alphaZeroEngaged) {
5341
- const detail = alphaZeroDifficult ? 'Difficult request detected' : 'Reinforcement loop enabled';
5342
- this.statusTracker.pushOverride(alphaZeroStatusId, 'AlphaZero RL active', {
5343
- detail: `${detail} · full verification`,
5344
- tone: 'info',
5345
- });
5346
- alphaZeroStatusApplied = true;
5347
- display.showInfo(`AlphaZero RL mode engaged${alphaZeroDifficult ? ' for a difficult request' : ''}. Duel + self-critique with verification enabled.`);
5348
- this.alphaZeroMetrics.startAlphaZeroTask(userRequest);
5349
- alphaZeroTaskStarted = true;
5350
- }
5351
- this.logUserPrompt(userRequest);
4559
+ this.logUserPrompt(request);
5352
4560
  this.isProcessing = true;
5353
4561
  this.uiUpdates.setMode('processing');
5354
4562
  this.terminalInput.setStreaming(true);
5355
4563
  // Keep the persistent input/control bar active as we transition into streaming.
5356
- this.renderPromptArea(true);
4564
+ this.terminalInput.forceRender();
5357
4565
  const requestStartTime = Date.now(); // Alpha Zero 2 timing
5358
- this.lastRequestStartedAt = requestStartTime;
5359
- this.lastToolSummaryRenderedAt = null;
5360
4566
  // Clear previous parallel agents and start fresh for new request
5361
4567
  const parallelManager = getParallelAgentManager();
5362
4568
  parallelManager.clear();
5363
4569
  parallelManager.startBatch();
5364
4570
  // AlphaZero: Track task for learning
5365
- this.lastUserQuery = userRequest;
5366
- this.currentTaskType = classifyTaskType(userRequest);
4571
+ this.lastUserQuery = request;
4572
+ this.currentTaskType = classifyTaskType(request);
5367
4573
  this.currentToolCalls = [];
5368
4574
  this.uiAdapter.startProcessing('Working on your request');
5369
4575
  this.setProcessingStatus();
5370
4576
  let responseText = '';
5371
- let detectedFailure = null;
5372
- let hadUnhandledError = false;
5373
4577
  try {
5374
4578
  // Start streaming - no header needed, the input area already provides context
5375
- this.startStreamingHeartbeat(alphaZeroEngaged ? 'AlphaZero RL' : 'Streaming response');
5376
- responseText = await agent.send(requestForAgent, true);
4579
+ this.startStreamingHeartbeat('Streaming response');
4580
+ responseText = await agent.send(request, true);
4581
+ this.finishStreamingFormatter();
5377
4582
  await this.awaitPendingCleanup();
5378
4583
  this.captureHistorySnapshot();
5379
4584
  this.autosaveIfEnabled();
@@ -5392,18 +4597,14 @@ export class InteractiveShell {
5392
4597
  duration: 0,
5393
4598
  }));
5394
4599
  // AlphaZero: Check for failure in response
5395
- detectedFailure = detectFailure(responseText, {
4600
+ const failure = detectFailure(responseText, {
5396
4601
  toolCalls: this.currentToolCalls,
5397
- userMessage: userRequest,
4602
+ userMessage: request,
5398
4603
  });
5399
- if (alphaZeroEngaged && alphaZeroTaskStarted && !alphaZeroTaskCompleted) {
5400
- this.alphaZeroMetrics.completeAlphaZeroTask(!detectedFailure);
5401
- alphaZeroTaskCompleted = true;
5402
- }
5403
- if (detectedFailure) {
5404
- this.lastFailure = detectedFailure;
4604
+ if (failure) {
4605
+ this.lastFailure = failure;
5405
4606
  // Check if we have a recovery strategy
5406
- const strategy = findRecoveryStrategy(detectedFailure);
4607
+ const strategy = findRecoveryStrategy(failure);
5407
4608
  if (strategy) {
5408
4609
  display.showSystemMessage(`🔄 Found recovery strategy for this type of issue (success rate: ${Math.round(strategy.successRate * 100)}%)`);
5409
4610
  }
@@ -5426,55 +4627,28 @@ export class InteractiveShell {
5426
4627
  }
5427
4628
  }
5428
4629
  catch (error) {
5429
- const handled = this.handleProviderError(error, () => this.processRequest(userRequest));
5430
- hadUnhandledError = !handled;
5431
- if (alphaZeroEngaged && alphaZeroTaskStarted && !alphaZeroTaskCompleted) {
5432
- this.alphaZeroMetrics.completeAlphaZeroTask(false);
5433
- alphaZeroTaskCompleted = true;
5434
- }
4630
+ const handled = this.handleProviderError(error, () => this.processRequest(request));
5435
4631
  if (!handled) {
5436
4632
  // Pass full error object for enhanced formatting with stack trace
5437
4633
  display.showError(error instanceof Error ? error.message : String(error), error);
5438
4634
  }
5439
4635
  }
5440
4636
  finally {
5441
- const runLogEntry = this.buildRunLogEntry(userRequest, responseText, scrollbackStartIndex, {
5442
- alphaZeroEngaged,
5443
- alphaZeroDifficult,
5444
- failureType: detectedFailure?.type ?? (hadUnhandledError ? 'unhandled-error' : null),
5445
- });
5446
- this.lastRunLog = runLogEntry;
5447
- const allowAutoChain = alphaZeroEngaged && this.autoContinueEnabled && isErosolarRepo(this.workingDir);
5448
- let shouldStopAuto = false;
5449
- if (this.alphaZeroAutoImproveActive) {
5450
- shouldStopAuto = this.shouldStopAlphaZeroAutoImprove(responseText, allowAutoChain);
5451
- if (shouldStopAuto) {
5452
- this.alphaZeroAutoImproveActive = false;
5453
- this.alphaZeroAutoImproveIterations = 0;
5454
- }
5455
- }
5456
- if (!skipReflectionForThisRun && !shouldStopAuto) {
5457
- this.maybeQueueAlphaZeroSelfReflection(runLogEntry, alphaZeroEngaged, allowAutoChain);
5458
- }
5459
- if (alphaZeroEngaged && alphaZeroStatusApplied) {
5460
- this.statusTracker.clearOverride(alphaZeroStatusId);
5461
- }
5462
- if (alphaZeroEngaged && alphaZeroTaskStarted && !alphaZeroTaskCompleted) {
5463
- this.alphaZeroMetrics.completeAlphaZeroTask(false);
5464
- }
4637
+ this.finishStreamingFormatter();
5465
4638
  display.stopThinking(false);
5466
4639
  this.uiUpdates.setMode('processing');
5467
- this.stopStreamingHeartbeat({ skipRender: true });
4640
+ this.stopStreamingHeartbeat();
5468
4641
  this.isProcessing = false;
5469
4642
  this.terminalInput.setStreaming(false);
5470
4643
  this.uiAdapter.endProcessing('Ready for prompts');
5471
4644
  this.setIdleStatus();
4645
+ display.newLine();
5472
4646
  this.updateStatusMessage(null);
5473
4647
  queueMicrotask(() => this.uiUpdates.setMode('idle'));
5474
4648
  // CRITICAL: Ensure readline prompt is active for user input
5475
4649
  // Claude Code style: New prompt naturally appears at bottom
5476
4650
  this.ensureReadlineReady();
5477
- this.renderPromptArea();
4651
+ this.terminalInput.render();
5478
4652
  this.scheduleQueueProcessing();
5479
4653
  this.refreshQueueIndicators();
5480
4654
  }
@@ -5505,7 +4679,6 @@ export class InteractiveShell {
5505
4679
  if (!agent) {
5506
4680
  return;
5507
4681
  }
5508
- this.hasShownThoughtProcess = false;
5509
4682
  this.isProcessing = true;
5510
4683
  this.uiUpdates.setMode('processing');
5511
4684
  this.terminalInput.setStreaming(true);
@@ -5546,8 +4719,6 @@ When truly finished with ALL tasks, explicitly state "TASK_FULLY_COMPLETE".`;
5546
4719
  }
5547
4720
  while (iteration < MAX_ITERATIONS) {
5548
4721
  iteration++;
5549
- this.hasShownThoughtProcess = false;
5550
- this.resetAssistantStreamTracking();
5551
4722
  display.showSystemMessage(`\n📍 Iteration ${iteration}/${MAX_ITERATIONS}`);
5552
4723
  this.updateStatusMessage(`Working on iteration ${iteration}...`);
5553
4724
  try {
@@ -5555,6 +4726,7 @@ When truly finished with ALL tasks, explicitly state "TASK_FULLY_COMPLETE".`;
5555
4726
  display.showThinking('Responding...');
5556
4727
  this.refreshStatusLine(true);
5557
4728
  const response = await agent.send(currentPrompt, true);
4729
+ this.finishStreamingFormatter();
5558
4730
  await this.awaitPendingCleanup();
5559
4731
  this.captureHistorySnapshot();
5560
4732
  this.autosaveIfEnabled();
@@ -5687,6 +4859,7 @@ What's the next action?`;
5687
4859
  }
5688
4860
  }
5689
4861
  finally {
4862
+ this.finishStreamingFormatter();
5690
4863
  const totalElapsed = Date.now() - overallStartTime;
5691
4864
  const minutes = Math.floor(totalElapsed / 60000);
5692
4865
  const seconds = Math.floor((totalElapsed % 60000) / 1000);
@@ -5700,6 +4873,10 @@ What's the next action?`;
5700
4873
  this.uiAdapter.endProcessing('Ready for prompts');
5701
4874
  this.setIdleStatus();
5702
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();
5703
4880
  queueMicrotask(() => this.uiUpdates.setMode('idle'));
5704
4881
  // CRITICAL: Ensure readline prompt is active for user input
5705
4882
  // Claude Code style: New prompt naturally appears at bottom
@@ -5878,25 +5055,7 @@ What's the next action?`;
5878
5055
  }
5879
5056
  if (name === 'bash' || name === 'execute_bash') {
5880
5057
  const command = String(entry.args['command'] ?? '').toLowerCase();
5881
- const patterns = [
5882
- 'npm test',
5883
- 'yarn test',
5884
- 'pnpm test',
5885
- 'bun test',
5886
- 'go test',
5887
- 'cargo test',
5888
- 'pytest',
5889
- 'python -m pytest',
5890
- 'tox',
5891
- 'mvn test',
5892
- './mvnw test',
5893
- 'gradle test',
5894
- './gradlew test',
5895
- 'dotnet test',
5896
- 'make test',
5897
- 'swift test',
5898
- ];
5899
- return patterns.some((pattern) => command.includes(pattern));
5058
+ return command.includes('npm test') || command.includes('yarn test') || command.includes('pnpm test');
5900
5059
  }
5901
5060
  return false;
5902
5061
  }
@@ -5914,170 +5073,310 @@ What's the next action?`;
5914
5073
  const parts = [error?.stdout, error?.stderr, error?.message].filter((part) => typeof part === 'string' && part.trim());
5915
5074
  return parts.join('\n').trim();
5916
5075
  }
5917
- async enforceAutoTests(trigger, commandInfo) {
5918
- if (this.autoTestInFlight || !this.verificationEnabled) {
5919
- 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;
5920
5100
  }
5921
5101
  const latestChange = this.getLatestFileChangeTimestamp();
5922
5102
  if (!latestChange) {
5923
- return 'skipped';
5103
+ return;
5924
5104
  }
5925
5105
  const latestTest = this.getLatestTestTimestamp();
5926
5106
  if (latestTest && latestChange <= latestTest) {
5927
- return 'skipped';
5107
+ return;
5928
5108
  }
5929
- const detected = commandInfo ?? detectTestCommand(this.workingDir);
5930
- if (!detected) {
5931
- this.lastAutoTestRun = Date.now();
5932
- display.showSystemMessage('ℹ️ Skipping auto-tests: no test command detected for this repo.');
5933
- 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;
5934
5112
  }
5935
5113
  this.autoTestInFlight = true;
5936
- const command = detected.command;
5937
- const reasonSuffix = detected.reason ? ` [${detected.reason}]` : '';
5938
- 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}"...`);
5939
5116
  this.updateStatusMessage('Running tests automatically...');
5117
+ let combinedOutput = '';
5940
5118
  try {
5941
5119
  const { stdout, stderr } = await execAsync(command, {
5942
5120
  cwd: this.workingDir,
5943
5121
  timeout: 10 * 60 * 1000,
5944
5122
  maxBuffer: 10 * 1024 * 1024,
5945
5123
  });
5946
- this.lastAutoTestRun = Date.now();
5947
- const outputText = [stdout, stderr].filter(Boolean).join('\n').trim();
5124
+ combinedOutput = [stdout, stderr].filter(Boolean).join('\n').trim();
5948
5125
  display.showSystemMessage('✅ Auto-tests finished.');
5949
- if (outputText) {
5950
- this.writeLocked(`${outputText}\n`);
5951
- }
5952
5126
  this.statusTracker.clearOverride('tests');
5953
- return 'success';
5954
5127
  }
5955
5128
  catch (error) {
5956
- this.lastAutoTestRun = Date.now();
5957
- const message = this.formatCommandError(error);
5129
+ combinedOutput = this.formatCommandError(error);
5958
5130
  display.showWarning('⚠️ Auto-tests failed. Review output below.');
5959
- if (message) {
5960
- this.writeLocked(`${message}\n`);
5961
- }
5962
5131
  this.statusTracker.pushOverride('tests', 'Tests failing', {
5963
- detail: `Auto-run ${command} failed`,
5132
+ detail: 'Auto-run npm test failed',
5964
5133
  tone: 'danger',
5965
5134
  });
5966
- return 'failed';
5967
5135
  }
5968
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();
5969
5148
  this.updateStatusMessage(null);
5970
5149
  this.autoTestInFlight = false;
5971
- this.terminalInput.resetContentPosition();
5972
- this.terminalInput.forceRender();
5973
5150
  }
5974
5151
  }
5975
- isBuildToolCall(entry) {
5976
- const name = entry.toolName.toLowerCase();
5977
- 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) {
5978
5165
  return true;
5979
5166
  }
5980
- if (name === 'bash' || name === 'execute_bash') {
5981
- const command = String(entry.args['command'] ?? '').toLowerCase();
5982
- const makeBuild = command.startsWith('make') && !command.includes('make test') && !command.includes('make lint');
5983
- const patterns = [
5984
- 'npm run build',
5985
- 'yarn build',
5986
- 'pnpm build',
5987
- 'bun run build',
5988
- 'bun build',
5989
- 'tsc',
5990
- 'cargo build',
5991
- 'go build',
5992
- 'python -m build',
5993
- 'mvn -b -dskiptests package',
5994
- 'mvn package',
5995
- 'mvn install',
5996
- './mvnw -b -dskiptests package',
5997
- './mvnw package',
5998
- './mvnw install',
5999
- 'gradle build',
6000
- 'gradle assemble',
6001
- './gradlew build',
6002
- './gradlew assemble',
6003
- 'dotnet build',
6004
- 'swift build',
6005
- ];
6006
- return makeBuild || patterns.some((pattern) => command.includes(pattern));
5167
+ if (this.lastBuildSucceededAt && latestChange <= this.lastBuildSucceededAt) {
5168
+ return true;
6007
5169
  }
6008
- 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;
6009
5217
  }
6010
- getLatestBuildTimestamp() {
6011
- const history = this.runtimeSession.toolRuntime.getToolHistory?.() ?? [];
6012
- let latest = this.lastAutoBuildRun;
6013
- for (const entry of history) {
6014
- if (this.isBuildToolCall(entry)) {
6015
- 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';
6016
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';
6017
5247
  }
6018
- return latest;
6019
5248
  }
6020
- /**
6021
- * Auto-build verification after file edits.
6022
- * Detects the build command for the current repo and feeds failures back to the agent.
6023
- */
6024
- async enforceAutoBuild(trigger, commandInfo) {
6025
- if (this.autoBuildInFlight || !this.verificationEnabled) {
6026
- return 'skipped';
5249
+ parseAIDesignedTests(plan) {
5250
+ if (!plan?.trim()) {
5251
+ return [];
6027
5252
  }
6028
- const latestChange = this.getLatestFileChangeTimestamp();
6029
- if (!latestChange) {
6030
- return 'skipped';
6031
- }
6032
- const latestBuild = this.getLatestBuildTimestamp();
6033
- if (latestBuild && latestChange <= latestBuild) {
6034
- return 'skipped';
6035
- }
6036
- const detected = commandInfo ?? detectBuildCommand(this.workingDir);
6037
- if (!detected) {
6038
- this.lastAutoBuildRun = Date.now();
6039
- display.showSystemMessage('ℹ️ Skipping auto-build: no build command detected for this repo.');
6040
- return 'skipped';
6041
- }
6042
- this.autoBuildInFlight = true;
6043
- const command = detected.command;
6044
- const reasonSuffix = detected.reason ? ` [${detected.reason}]` : '';
6045
- display.showSystemMessage(`🔨 Auto-building to verify changes (${trigger}) with "${command}"${reasonSuffix}...`);
6046
- this.updateStatusMessage('Running build automatically...');
5253
+ const match = plan.match(/\[[\s\S]*\]/);
5254
+ const payload = match ? match[0] : plan;
6047
5255
  try {
6048
- const { stdout, stderr } = await execAsync(command, {
6049
- cwd: this.workingDir,
6050
- timeout: 5 * 60 * 1000,
6051
- 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,
6052
5304
  });
6053
- this.lastAutoBuildRun = Date.now();
6054
- const outputText = [stdout, stderr].filter(Boolean).join('\n').trim();
6055
- display.showSystemMessage('✅ Build succeeded.');
6056
- if (outputText && outputText.length < 500) {
6057
- this.writeLocked(`${outputText}\n`);
6058
- }
6059
- this.statusTracker.clearOverride('build');
6060
- return 'success';
6061
5305
  }
6062
5306
  catch (error) {
6063
- this.lastAutoBuildRun = Date.now();
6064
- const errorOutput = this.formatCommandError(error);
6065
- display.showWarning('⚠️ Build failed. Feeding errors back to agent...');
6066
- if (errorOutput) {
6067
- this.writeLocked(`${errorOutput}\n`);
6068
- }
6069
- this.statusTracker.pushOverride('build', 'Build failing', {
6070
- detail: `Auto-run ${command} failed`,
6071
- tone: 'danger',
6072
- });
6073
- // Feed build errors back to the agent so it can fix them
6074
- await this.feedBuildErrorsToAgent(errorOutput);
6075
- return 'failed';
5307
+ const message = error instanceof Error ? error.message : String(error);
5308
+ display.showWarning(`AI-designed tests skipped: ${message}`);
5309
+ return;
6076
5310
  }
6077
- finally {
6078
- this.updateStatusMessage(null);
6079
- 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
+ }
6080
5378
  }
5379
+ display.showSystemMessage(results.join('\n'));
6081
5380
  }
6082
5381
  /**
6083
5382
  * Feed build errors back to the agent conversation for automatic fixing.
@@ -6103,43 +5402,21 @@ What's the next action?`;
6103
5402
  // Send the error to the agent for fixing
6104
5403
  display.showThinking('Analyzing build errors');
6105
5404
  this.refreshStatusLine(true);
6106
- this.hasShownThoughtProcess = false;
6107
- await this.withStreamingUi('Fixing build errors', () => this.agent.send(prompt, true));
5405
+ const response = await this.agent.send(prompt, true);
5406
+ this.finishStreamingFormatter();
6108
5407
  display.stopThinking();
6109
5408
  this.refreshStatusLine(true);
5409
+ if (response) {
5410
+ display.showAssistantMessage(response, { isFinal: true });
5411
+ }
6110
5412
  // Recursively verify the fix worked
6111
5413
  await this.enforceAutoBuild('verification');
6112
5414
  }
6113
5415
  catch (agentError) {
6114
5416
  display.showWarning('Agent could not automatically fix build errors. Please review manually.');
6115
5417
  }
6116
- }
6117
- async enforceAutoVerification(trigger) {
6118
- if (this.autoVerificationInFlight || !this.verificationEnabled) {
6119
- return;
6120
- }
6121
- const latestChange = this.getLatestFileChangeTimestamp();
6122
- if (!latestChange) {
6123
- return;
6124
- }
6125
- this.autoVerificationInFlight = true;
6126
- try {
6127
- const buildInfo = detectBuildCommand(this.workingDir);
6128
- const testInfo = detectTestCommand(this.workingDir);
6129
- const buildResult = await this.enforceAutoBuild(trigger, buildInfo);
6130
- if (buildResult === 'failed') {
6131
- return;
6132
- }
6133
- if (testInfo) {
6134
- await this.enforceAutoTests(trigger, testInfo);
6135
- }
6136
- else {
6137
- this.lastAutoTestRun = Date.now();
6138
- display.showSystemMessage('ℹ️ Skipping auto-tests: no test command detected for this repo.');
6139
- }
6140
- }
6141
5418
  finally {
6142
- this.autoVerificationInFlight = false;
5419
+ this.finishStreamingFormatter();
6143
5420
  }
6144
5421
  }
6145
5422
  rebuildAgent() {
@@ -6157,35 +5434,29 @@ What's the next action?`;
6157
5434
  autoContinue: this.autoContinueEnabled,
6158
5435
  };
6159
5436
  this.agent = this.runtimeSession.createAgent(selection, {
6160
- onStreamChunk: (chunk, channel) => {
6161
- this.streamAssistantChunk(chunk, channel);
5437
+ onStreamChunk: (chunk) => {
5438
+ this.handleStreamChunk(chunk);
6162
5439
  },
6163
5440
  onStreamFallback: (info) => this.handleStreamingFallback(info),
6164
5441
  onAssistantMessage: (content, metadata) => {
6165
5442
  const enriched = this.buildDisplayMetadata(metadata);
6166
- const blocksActive = this.assistantBlocksEnabled && this.assistantBlockRenderer !== null;
6167
- const parsed = this.splitThinkingResponse(content);
6168
- const thinking = parsed?.thinking ?? null;
6169
- const responseContent = parsed ? parsed.response?.trim() ?? '' : content.trim();
6170
- const narrativeContent = (parsed?.response ?? content).trim();
6171
- const streamRendered = metadata.wasStreamed && this.assistantStreamHadContent;
6172
- if (thinking) {
6173
- this.presentThoughtProcess(thinking, enriched, {
6174
- wasStreamed: metadata.wasStreamed,
6175
- // Always render the thought block immediately when present so it appears first.
6176
- renderWhenStreamed: true,
6177
- });
6178
- }
5443
+ // Update spinner based on message type
6179
5444
  if (metadata.isFinal) {
6180
- if (metadata.wasStreamed) {
6181
- this.finalizeAssistantStream();
6182
- }
6183
- const body = responseContent || content;
6184
- // Always render the final response body even if it already streamed.
6185
- if (body) {
6186
- 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
+ }
6187
5459
  }
6188
- this.renderToolUsageSummary({ ...enriched, isFinal: true });
6189
5460
  // Status shown in mode controls bar - no separate status line needed
6190
5461
  display.stopThinking();
6191
5462
  // Update context usage for mode controls display
@@ -6196,22 +5467,19 @@ What's the next action?`;
6196
5467
  this.updateContextUsage(percentage);
6197
5468
  }
6198
5469
  }
5470
+ if (finalContent) {
5471
+ this.lastAssistantResponse = finalContent;
5472
+ }
6199
5473
  // Auto-verify changes: build first (catches type errors), then tests
6200
- void this.enforceAutoVerification('final-response');
5474
+ void this.runAutoQualityChecks('final-response', finalContent);
6201
5475
  }
6202
5476
  else {
6203
5477
  // Non-final message = narrative text before tool calls (Claude Code style)
6204
5478
  // Stop spinner and show the narrative text directly
6205
5479
  display.stopThinking();
6206
- if (metadata.wasStreamed) {
6207
- this.finalizeAssistantStream();
6208
- }
6209
- const shouldRenderNarrative = Boolean(narrativeContent && (!metadata.wasStreamed || !blocksActive || !streamRendered));
6210
- if (shouldRenderNarrative) {
6211
- this.renderAssistantContent('thought', narrativeContent, {
6212
- ...enriched,
6213
- isFinal: false,
6214
- });
5480
+ // Skip display if content was already streamed to avoid double-display
5481
+ if (!metadata.wasStreamed) {
5482
+ display.showNarrative(content.trim());
6215
5483
  }
6216
5484
  // The isProcessing flag already shows "⏳ Processing..." - no need for duplicate status
6217
5485
  this.requestPromptRefresh();
@@ -6224,8 +5492,8 @@ What's the next action?`;
6224
5492
  this.requestPromptRefresh();
6225
5493
  },
6226
5494
  onContextSquishing: (message) => {
6227
- // Stream notification in UI when auto context squishing occurs
6228
- display.stream(`\n🔄 ${message}\n`);
5495
+ // Show notification in UI when auto context squishing occurs
5496
+ display.showSystemMessage(`🔄 ${message}`);
6229
5497
  this.statusTracker.pushOverride('context-squish', 'Auto-squishing context', {
6230
5498
  detail: 'Reducing conversation history to fit within token limits',
6231
5499
  tone: 'warning',
@@ -6233,7 +5501,7 @@ What's the next action?`;
6233
5501
  },
6234
5502
  onContextRecovery: (attempt, maxAttempts, message) => {
6235
5503
  // Show recovery progress in UI
6236
- display.stream(`\n⚡ Context Recovery (${attempt}/${maxAttempts}): ${message}\n`);
5504
+ display.showSystemMessage(`⚡ Context Recovery (${attempt}/${maxAttempts}): ${message}`);
6237
5505
  },
6238
5506
  onContextPruned: (removedCount, stats) => {
6239
5507
  // Clear squish overlay if active
@@ -6241,43 +5509,40 @@ What's the next action?`;
6241
5509
  // Show notification that context was pruned
6242
5510
  const method = stats['method'];
6243
5511
  const percentage = stats['percentage'];
6244
- const summarized = stats['summarized'] === true;
6245
5512
  if (method === 'emergency-recovery') {
6246
- 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'}%`);
6247
5514
  }
6248
5515
  // Update context usage in UI
6249
5516
  if (typeof percentage === 'number') {
6250
5517
  this.updateContextUsage(percentage);
6251
5518
  }
6252
- if (summarized) {
6253
- display.showSystemMessage('Context summary captured. View the latest summaries with /contextlog.');
6254
- }
6255
5519
  // Ensure prompt remains visible at bottom after context messages
6256
- this.renderPromptArea();
5520
+ this.terminalInput.render();
6257
5521
  },
6258
5522
  onContinueAfterRecovery: () => {
6259
5523
  // Update UI to show we're continuing after context recovery
6260
- display.stream(`\n🔄 Continuing after context recovery...\n`);
5524
+ display.showSystemMessage(`🔄 Continuing after context recovery...`);
6261
5525
  this.updateStatusMessage('Retrying with reduced context...');
6262
- this.renderPromptArea();
5526
+ this.terminalInput.render();
6263
5527
  },
6264
5528
  onAutoContinue: (attempt, maxAttempts, _message) => {
6265
5529
  // Show auto-continue progress in UI
6266
5530
  display.showSystemMessage(`🔄 Auto-continue (${attempt}/${maxAttempts}): Model expressed intent, prompting to act...`);
6267
5531
  this.updateStatusMessage('Auto-continuing...');
6268
- this.renderPromptArea();
5532
+ this.terminalInput.render();
6269
5533
  },
6270
5534
  onCancelled: () => {
6271
5535
  // Update UI to show operation was cancelled
6272
5536
  display.showWarning('Operation cancelled.');
6273
5537
  this.uiUpdates.setMode('processing');
6274
- this.stopStreamingHeartbeat({ skipRender: true });
5538
+ this.stopStreamingHeartbeat();
6275
5539
  this.updateStatusMessage(null);
6276
5540
  this.terminalInput.setStreaming(false);
6277
- this.renderPromptArea();
5541
+ this.terminalInput.render();
6278
5542
  },
6279
- onVerificationNeeded: () => {
6280
- void this.enforceAutoVerification('verification');
5543
+ onVerificationNeeded: (response, context) => {
5544
+ this.lastAssistantResponse = response;
5545
+ void this.runAutoQualityChecks('verification', response, context);
6281
5546
  },
6282
5547
  });
6283
5548
  // Register global AI enhancer for explore tool - uses active model by default
@@ -6311,7 +5576,7 @@ What's the next action?`;
6311
5576
  resetChatBoxAfterModelSwap() {
6312
5577
  this.updateStatusMessage(null);
6313
5578
  this.terminalInput.setStreaming(false);
6314
- this.renderPromptArea();
5579
+ this.terminalInput.render();
6315
5580
  this.ensureReadlineReady();
6316
5581
  }
6317
5582
  /**
@@ -6374,22 +5639,19 @@ What's the next action?`;
6374
5639
  }
6375
5640
  buildThinkingDirective() {
6376
5641
  switch (this.thinkingMode) {
6377
- case 'concise':
6378
- return 'Concise thinking mode: respond directly with the final answer and skip <thinking> blocks unless the user explicitly asks for reasoning.';
6379
5642
  case 'extended':
6380
5643
  return [
6381
- 'Extended thinking mode: include a <thinking> block followed by a <response> block.',
5644
+ 'Extended thinking mode is enabled. Format every reply as:',
6382
5645
  '<thinking>',
6383
- '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).',
6384
5647
  '</thinking>',
6385
5648
  '<response>',
6386
- 'Final answer with requested code/commands and next steps.',
5649
+ 'Final answer with actionable next steps and any code/commands requested.',
6387
5650
  '</response>',
6388
5651
  ].join('\n');
6389
5652
  case 'balanced':
6390
5653
  default:
6391
- // Balanced mode: show thinking for complex reasoning, planning, or multi-step tasks
6392
- 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.';
6393
5655
  }
6394
5656
  }
6395
5657
  buildDisplayMetadata(metadata) {
@@ -6398,58 +5660,6 @@ What's the next action?`;
6398
5660
  contextWindowTokens: this.activeContextWindowTokens,
6399
5661
  };
6400
5662
  }
6401
- presentThoughtProcess(thinking, metadata, options) {
6402
- if (!thinking || this.hasShownThoughtProcess) {
6403
- return;
6404
- }
6405
- const summary = this.extractThoughtSummary(thinking);
6406
- if (summary) {
6407
- display.updateThinking(`💭 ${summary}`);
6408
- this.latestThoughtSummary = summary;
6409
- this.streamingStatusDetail = summary;
6410
- this.terminalInput.recordRecentAction(`💭 ${summary}`);
6411
- this.rebuildStreamingStatusLabel();
6412
- this.refreshStatusLine(true);
6413
- }
6414
- const shouldRender = !options?.wasStreamed || options?.renderWhenStreamed;
6415
- if (shouldRender) {
6416
- this.renderAssistantContent('thought', thinking, { ...metadata, isFinal: false });
6417
- }
6418
- this.hasShownThoughtProcess = true;
6419
- }
6420
- renderToolUsageSummary(metadata) {
6421
- const since = this.lastToolSummaryRenderedAt ?? this.lastRequestStartedAt ?? Date.now();
6422
- const operations = this.uiAdapter.getToolOperationsSince(since);
6423
- if (!operations.length) {
6424
- return;
6425
- }
6426
- let summaryLine = compactRenderer.formatCompactLine(operations, {
6427
- showIcons: true,
6428
- showTimings: true,
6429
- });
6430
- const warnings = this.uiAdapter.getRecentWarnings(since);
6431
- if (warnings.length) {
6432
- const limited = warnings.slice(0, 3);
6433
- const formattedWarnings = limited
6434
- .map((warning) => `${icons.warning} ${warning.tool}: ${warning.text}`)
6435
- .join(' • ');
6436
- summaryLine = `${summaryLine} ${theme.warning('[warnings]')} ${formattedWarnings}`;
6437
- }
6438
- if (!summaryLine.trim()) {
6439
- this.lastToolSummaryRenderedAt = Date.now();
6440
- return;
6441
- }
6442
- this.lastToolSummaryRenderedAt =
6443
- operations[operations.length - 1]?.startedAt ?? Date.now();
6444
- const blockMetadata = { ...metadata, isFinal: true };
6445
- if (this.assistantBlocksEnabled && this.assistantBlockRenderer) {
6446
- this.renderAssistantBlock('tools', summaryLine, blockMetadata);
6447
- return;
6448
- }
6449
- const header = this.formatAssistantHeader('tools', blockMetadata);
6450
- const block = `\n${header}\n${summaryLine}\n\n`;
6451
- this.enqueueAssistantStream(block);
6452
- }
6453
5663
  handleContextTelemetry(metadata, displayMetadata) {
6454
5664
  if (!metadata.isFinal) {
6455
5665
  return null;
@@ -6810,7 +6020,7 @@ What's the next action?`;
6810
6020
  }
6811
6021
  handleAgentSetupError(error, retryAction, providerOverride) {
6812
6022
  this.pendingInteraction = null;
6813
- const provider = providerOverride === undefined ? this.sessionState.provider : providerOverride;
6023
+ const provider = providerOverride ?? this.sessionState.provider;
6814
6024
  const apiKeyIssue = detectApiKeyError(error, provider);
6815
6025
  if (apiKeyIssue) {
6816
6026
  this.handleApiKeyIssue(apiKeyIssue, retryAction);
@@ -6825,14 +6035,13 @@ What's the next action?`;
6825
6035
  const detail = detailText ? ` Error: ${detailText}` : '';
6826
6036
  const reason = info.reason ? ` (${info.reason.replace(/-/g, ' ')})` : '';
6827
6037
  const partialNote = info.partialResponse ? ' Received partial stream before failure.' : '';
6828
- this.finalizeAssistantStream();
6829
6038
  display.showWarning(`Streaming failed${reason}, retrying without streaming.${detail}${partialNote}`);
6039
+ this.finishStreamingFormatter('Stream interrupted - retrying without streaming');
6830
6040
  this.startStreamingHeartbeat('Fallback in progress');
6831
6041
  this.requestPromptRefresh(true);
6832
6042
  }
6833
6043
  handleProviderError(error, retryAction) {
6834
- const providerHint = error instanceof MissingSecretError ? null : this.sessionState.provider;
6835
- const apiKeyIssue = detectApiKeyError(error, providerHint);
6044
+ const apiKeyIssue = detectApiKeyError(error, this.sessionState.provider);
6836
6045
  if (!apiKeyIssue) {
6837
6046
  return false;
6838
6047
  }
@@ -6841,19 +6050,13 @@ What's the next action?`;
6841
6050
  }
6842
6051
  handleApiKeyIssue(info, retryAction) {
6843
6052
  const secret = info.secret ?? null;
6844
- const providerLabel = info.provider
6845
- ? this.providerLabel(info.provider)
6846
- : secret?.providers?.length
6847
- ? this.providerLabel(secret.providers[0])
6848
- : null;
6849
- const targetLabel = providerLabel ?? secret?.label ?? 'this tool';
6850
- this.apiKeyGateActive = !!secret;
6053
+ const providerLabel = info.provider ? this.providerLabel(info.provider) : 'the selected provider';
6851
6054
  if (!secret) {
6852
6055
  this.pendingSecretRetry = null;
6853
6056
  const guidance = 'Run "/secrets" to configure the required API key or export it (e.g., EXPORT KEY=value) before launching the CLI.';
6854
6057
  const baseMessage = info.type === 'missing'
6855
- ? `An API key is required before using ${targetLabel}.`
6856
- : `API authentication failed for ${targetLabel}.`;
6058
+ ? `An API key is required before using ${providerLabel}.`
6059
+ : `API authentication failed for ${providerLabel}.`;
6857
6060
  display.showWarning(`${baseMessage} ${guidance}`.trim());
6858
6061
  return;
6859
6062
  }
@@ -6862,8 +6065,8 @@ What's the next action?`;
6862
6065
  display.showWarning(info.message.trim());
6863
6066
  }
6864
6067
  const prefix = isMissing
6865
- ? `${secret.label} is required before you can use ${targetLabel}.`
6866
- : `${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}.`;
6867
6070
  display.showWarning(prefix);
6868
6071
  this.pendingSecretRetry = retryAction ?? null;
6869
6072
  this.pendingInteraction = { type: 'secret-input', secret };
@@ -6877,7 +6080,7 @@ What's the next action?`;
6877
6080
  else {
6878
6081
  lines.push(`Update the stored value for ${secret.label} or type "cancel".`);
6879
6082
  }
6880
- 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.`);
6881
6084
  display.showSystemMessage(lines.join('\n'));
6882
6085
  }
6883
6086
  colorizeDropdownLine(text, index) {
@@ -6902,22 +6105,34 @@ What's the next action?`;
6902
6105
  /**
6903
6106
  * Build the session banner with comprehensive feature status.
6904
6107
  */
6905
- getSessionFrameProps() {
6906
- 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({
6907
6116
  profileLabel: this.profileLabel,
6908
6117
  profileName: this.profile,
6909
6118
  model: this.sessionState.model,
6910
6119
  provider: this.sessionState.provider,
6911
6120
  workspace: this.workingDir,
6912
6121
  version: this.version,
6913
- };
6914
- }
6915
- buildBanner() {
6916
- const terminalWidth = output.columns ?? 100;
6917
- const width = Math.min(terminalWidth - 4, 110);
6918
- return renderSessionFrame({
6919
- ...this.getSessionFrameProps(),
6920
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
+ },
6921
6136
  });
6922
6137
  }
6923
6138
  /**
@@ -6948,21 +6163,6 @@ What's the next action?`;
6948
6163
  }
6949
6164
  return categories;
6950
6165
  }
6951
- buildFeatureStatusSnapshot() {
6952
- const featureFlags = loadFeatureFlags();
6953
- const toolCategories = this.collectToolCategories();
6954
- const toolCount = toolCategories.reduce((sum, cat) => sum + cat.count, 0);
6955
- const pluginCount = this._enabledPlugins.length;
6956
- return {
6957
- pluginCount: pluginCount > 0 ? pluginCount : undefined,
6958
- toolCount: toolCount > 0 ? toolCount : undefined,
6959
- sessionId: this.activeSessionId,
6960
- mcpEnabled: featureFlags.mcpEnabled,
6961
- metricsEnabled: featureFlags.metrics,
6962
- autoCompact: featureFlags.autoCompact,
6963
- dualMode: featureFlags.alphaZeroDual,
6964
- };
6965
- }
6966
6166
  /**
6967
6167
  * Extract category from tool name.
6968
6168
  */
@@ -7026,7 +6226,8 @@ What's the next action?`;
7026
6226
  return;
7027
6227
  }
7028
6228
  this.refreshContextGauge();
7029
- // 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
7030
6231
  if (!this.isProcessing) {
7031
6232
  this.setIdleStatus();
7032
6233
  }
@@ -7331,6 +6532,20 @@ What's the next action?`;
7331
6532
  ];
7332
6533
  display.showSystemMessage(lines.join('\n'));
7333
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
+ }
7334
6549
  }
7335
6550
  function setsEqual(first, second) {
7336
6551
  if (first.size !== second.size) {