erosolar-cli 1.7.395 → 1.7.397

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 (78) hide show
  1. package/dist/browser/BrowserSessionManager.d.ts +3 -1
  2. package/dist/browser/BrowserSessionManager.d.ts.map +1 -1
  3. package/dist/browser/BrowserSessionManager.js +24 -4
  4. package/dist/browser/BrowserSessionManager.js.map +1 -1
  5. package/dist/contracts/agent-schemas.json +5 -0
  6. package/dist/contracts/unified-schema.json +2 -1
  7. package/dist/core/agent.d.ts +5 -0
  8. package/dist/core/agent.d.ts.map +1 -1
  9. package/dist/core/agent.js +99 -0
  10. package/dist/core/agent.js.map +1 -1
  11. package/dist/core/alphaZeroConfig.d.ts +11 -0
  12. package/dist/core/alphaZeroConfig.d.ts.map +1 -0
  13. package/dist/core/alphaZeroConfig.js +59 -0
  14. package/dist/core/alphaZeroConfig.js.map +1 -0
  15. package/dist/core/alphaZeroEngine.d.ts +8 -0
  16. package/dist/core/alphaZeroEngine.d.ts.map +1 -1
  17. package/dist/core/alphaZeroEngine.js +149 -35
  18. package/dist/core/alphaZeroEngine.js.map +1 -1
  19. package/dist/core/alphaZeroEnhanced.d.ts +125 -0
  20. package/dist/core/alphaZeroEnhanced.d.ts.map +1 -0
  21. package/dist/core/alphaZeroEnhanced.js +386 -0
  22. package/dist/core/alphaZeroEnhanced.js.map +1 -0
  23. package/dist/core/alphaZeroOrchestrator.d.ts +17 -0
  24. package/dist/core/alphaZeroOrchestrator.d.ts.map +1 -1
  25. package/dist/core/alphaZeroOrchestrator.js +95 -8
  26. package/dist/core/alphaZeroOrchestrator.js.map +1 -1
  27. package/dist/core/autonomousVerification.d.ts +103 -0
  28. package/dist/core/autonomousVerification.d.ts.map +1 -0
  29. package/dist/core/autonomousVerification.js +583 -0
  30. package/dist/core/autonomousVerification.js.map +1 -0
  31. package/dist/core/cliTestHarness.d.ts +5 -0
  32. package/dist/core/cliTestHarness.d.ts.map +1 -1
  33. package/dist/core/cliTestHarness.js +14 -3
  34. package/dist/core/cliTestHarness.js.map +1 -1
  35. package/dist/core/contextManager.d.ts +10 -0
  36. package/dist/core/contextManager.d.ts.map +1 -1
  37. package/dist/core/contextManager.js +18 -0
  38. package/dist/core/contextManager.js.map +1 -1
  39. package/dist/core/offsecAlphaZeroEnhanced.d.ts +98 -0
  40. package/dist/core/offsecAlphaZeroEnhanced.d.ts.map +1 -0
  41. package/dist/core/offsecAlphaZeroEnhanced.js +441 -0
  42. package/dist/core/offsecAlphaZeroEnhanced.js.map +1 -0
  43. package/dist/core/parallelAgentOrchestrator.d.ts +171 -0
  44. package/dist/core/parallelAgentOrchestrator.d.ts.map +1 -0
  45. package/dist/core/parallelAgentOrchestrator.js +459 -0
  46. package/dist/core/parallelAgentOrchestrator.js.map +1 -0
  47. package/dist/index.d.ts +5 -0
  48. package/dist/index.d.ts.map +1 -0
  49. package/dist/index.js +3 -0
  50. package/dist/index.js.map +1 -0
  51. package/dist/shell/interactiveShell.d.ts +33 -0
  52. package/dist/shell/interactiveShell.d.ts.map +1 -1
  53. package/dist/shell/interactiveShell.js +604 -244
  54. package/dist/shell/interactiveShell.js.map +1 -1
  55. package/dist/shell/shellApp.d.ts.map +1 -1
  56. package/dist/shell/shellApp.js +16 -2
  57. package/dist/shell/shellApp.js.map +1 -1
  58. package/dist/shell/terminalInput.d.ts +42 -1
  59. package/dist/shell/terminalInput.d.ts.map +1 -1
  60. package/dist/shell/terminalInput.js +276 -6
  61. package/dist/shell/terminalInput.js.map +1 -1
  62. package/dist/shell/terminalInputAdapter.d.ts +14 -2
  63. package/dist/shell/terminalInputAdapter.d.ts.map +1 -1
  64. package/dist/shell/terminalInputAdapter.js +19 -1
  65. package/dist/shell/terminalInputAdapter.js.map +1 -1
  66. package/dist/ui/assistantBlockRenderer.d.ts +28 -0
  67. package/dist/ui/assistantBlockRenderer.d.ts.map +1 -0
  68. package/dist/ui/assistantBlockRenderer.js +99 -0
  69. package/dist/ui/assistantBlockRenderer.js.map +1 -0
  70. package/dist/ui/display.d.ts +11 -0
  71. package/dist/ui/display.d.ts.map +1 -1
  72. package/dist/ui/display.js +37 -0
  73. package/dist/ui/display.js.map +1 -1
  74. package/dist/ui/unified/layout.d.ts +0 -23
  75. package/dist/ui/unified/layout.d.ts.map +1 -1
  76. package/dist/ui/unified/layout.js +11 -114
  77. package/dist/ui/unified/layout.js.map +1 -1
  78. package/package.json +35 -21
@@ -1,10 +1,12 @@
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 { createInterface } from 'node:readline/promises';
5
+ import { AssistantBlockRenderer } from '../ui/assistantBlockRenderer.js';
4
6
  import { display } from '../ui/display.js';
5
7
  import { theme } from '../ui/theme.js';
6
8
  import { getContextWindowTokens } from '../core/contextWindow.js';
7
- import { ensureSecretForProvider, getSecretDefinitionForProvider, getSecretValue, listSecretDefinitions, maskSecret, setSecretValue, } from '../core/secretStore.js';
9
+ import { ensureSecretForProvider, getSecretDefinitionForProvider, getSecretValue, listSecretDefinitions, MissingSecretError, maskSecret, setSecretValue, } from '../core/secretStore.js';
8
10
  import { saveActiveProfilePreference, saveModelPreference, loadToolSettings, saveToolSettings, clearToolSettings, clearActiveProfilePreference, loadSessionPreferences, saveSessionPreferences, loadFeatureFlags, saveFeatureFlags, toggleFeatureFlag, FEATURE_FLAG_INFO, } from '../core/preferences.js';
9
11
  import { getLearningSummary, getRecentLearning, commitLearning, exportAllLearning, getLearningDir, } from '../core/learningPersistence.js';
10
12
  import { buildEnabledToolSet, evaluateToolPermissions, getToolToggleOptions, } from '../capabilities/toolRegistry.js';
@@ -113,6 +115,8 @@ export class InteractiveShell {
113
115
  ui;
114
116
  uiAdapter;
115
117
  uiUpdates;
118
+ assistantBlocksEnabled;
119
+ assistantBlockRenderer = null;
116
120
  _fileChangeTracker = new FileChangeTracker(); // Reserved for future file tracking features
117
121
  alphaZeroMetrics; // Alpha Zero 2 performance tracking
118
122
  statusSubscription = null;
@@ -151,6 +155,13 @@ export class InteractiveShell {
151
155
  lastUserQuery = '';
152
156
  lastFailure = null;
153
157
  lastAutoTestRun = null;
158
+ runIdCounter = 0;
159
+ lastRunLog = null;
160
+ lastReflectedRunId = null;
161
+ skipNextAutoReflection = false;
162
+ alphaZeroAutoImproveActive = false;
163
+ alphaZeroAutoImproveIterations = 0;
164
+ alphaZeroAutoImproveMaxIterations = 6;
154
165
  // Auto-build tracking
155
166
  autoBuildInFlight = false;
156
167
  lastAutoBuildRun = null;
@@ -160,14 +171,19 @@ export class InteractiveShell {
160
171
  streamingHeartbeatStart = null;
161
172
  streamingHeartbeatFrame = 0;
162
173
  streamingStatusLabel = null;
174
+ streamingStatusBase = null;
175
+ streamingStatusDetail = null;
163
176
  lastStreamingElapsedSeconds = null; // Preserve final elapsed time
164
177
  statusLineState = null;
165
178
  statusMessageOverride = null;
179
+ latestThoughtSummary = null;
166
180
  hasShownThoughtProcess = false;
167
181
  promptRefreshTimer = null;
168
182
  launchPaletteShown = false;
169
183
  version;
170
184
  alternateScreenEnabled;
185
+ inputInitialized = false;
186
+ assistantStreamBuffer = '';
171
187
  constructor(config) {
172
188
  this.profile = config.profile;
173
189
  this.profileLabel = config.profileLabel;
@@ -183,6 +199,7 @@ export class InteractiveShell {
183
199
  this.sessionRestoreConfig = config.sessionRestore ?? { mode: 'none' };
184
200
  this._enabledPlugins = config.enabledPlugins ?? [];
185
201
  this.version = config.version ?? '0.0.0';
202
+ this.assistantBlocksEnabled = Boolean(config.assistantBlocksEnabled);
186
203
  // Alternate screen disabled - use terminal-native mode for proper scrollback and text selection
187
204
  this.alternateScreenEnabled = false;
188
205
  this.initializeSessionHistory();
@@ -278,12 +295,16 @@ export class InteractiveShell {
278
295
  onToggleAlphaZero: () => this.toggleAlphaZeroMode('shortcut'),
279
296
  onClearContext: () => this.handleClearContext(),
280
297
  });
298
+ if (this.assistantBlocksEnabled) {
299
+ this.assistantBlockRenderer = new AssistantBlockRenderer({
300
+ write: (text) => this.terminalInput.streamContent(text),
301
+ updates: this.uiUpdates,
302
+ });
303
+ }
281
304
  // Initialize Alpha Zero 2 metrics tracking
282
305
  this.alphaZeroMetrics = new MetricsTracker(`${this.profile}-${Date.now()}`);
283
306
  this.setupStatusTracking();
284
307
  this.refreshContextGauge();
285
- // Start terminal input (sets up handlers)
286
- this.terminalInput.start();
287
308
  // Allow planning tools (e.g., ProposePlan) to open the interactive approval UI just like Codex CLI
288
309
  this.registerPlanApprovalBridge();
289
310
  // Capture display output into the scrollback/chat log so system messages
@@ -301,9 +322,6 @@ export class InteractiveShell {
301
322
  // Stream banner first - this sets up scroll region dynamically
302
323
  const banner = this.buildBanner();
303
324
  this.terminalInput.streamContent(banner + '\n\n');
304
- // Render chat box after banner is streamed
305
- this.refreshControlBar();
306
- this.renderPromptArea(true);
307
325
  this.rebuildAgent();
308
326
  this.setupHandlers();
309
327
  this.refreshBannerSessionInfo();
@@ -395,10 +413,8 @@ export class InteractiveShell {
395
413
  this.sessionResumeNotice = null;
396
414
  }
397
415
  async start(initialPrompt) {
398
- // Check for updates in background (non-blocking)
399
- if (this.version) {
400
- void maybeOfferCliUpdate(this.version);
401
- }
416
+ await this.runStartupUpdatePrompt();
417
+ this.ensureInputInitialized();
402
418
  if (initialPrompt) {
403
419
  await this.processInputBlock(initialPrompt);
404
420
  return;
@@ -407,6 +423,79 @@ export class InteractiveShell {
407
423
  // Ensure the terminal input is visible
408
424
  this.renderPromptArea();
409
425
  }
426
+ async runStartupUpdatePrompt() {
427
+ if (this.version) {
428
+ try {
429
+ await maybeOfferCliUpdate(this.version, {
430
+ prompt: (context) => this.promptForCliUpdate(context),
431
+ });
432
+ }
433
+ catch {
434
+ // Ignore update check failures at startup
435
+ }
436
+ }
437
+ this.showStartupSecretGuidance();
438
+ }
439
+ ensureInputInitialized() {
440
+ if (this.inputInitialized) {
441
+ return;
442
+ }
443
+ this.terminalInput.start();
444
+ this.refreshControlBar();
445
+ this.renderPromptArea(true);
446
+ this.inputInitialized = true;
447
+ }
448
+ async promptForCliUpdate(context) {
449
+ if (!input.isTTY || !output.isTTY) {
450
+ return true;
451
+ }
452
+ const lines = [
453
+ theme.gradient.primary('⬆️ Update available'),
454
+ `${theme.ui.muted('Current')}: ${theme.info(context.currentVersion)}`,
455
+ `${theme.ui.muted('Latest')}: ${theme.success(context.latestVersion)}`,
456
+ '',
457
+ `${theme.primary('>')} Update now (recommended)`,
458
+ `${theme.ui.muted(' Skip for now')}`,
459
+ '',
460
+ 'Press Enter to accept the highlighted option, or type Y/n (1/2).',
461
+ ];
462
+ display.showSystemMessage(lines.join('\n'));
463
+ const prompt = createInterface({ input, output });
464
+ try {
465
+ const raw = (await prompt.question('> ')).trim().toLowerCase();
466
+ if (!raw || raw === 'y' || raw === 'yes' || raw === '1' || raw === 'update') {
467
+ return true;
468
+ }
469
+ if (raw === 'n' || raw === 'no' || raw === '2' || raw === 'skip') {
470
+ return false;
471
+ }
472
+ return false;
473
+ }
474
+ finally {
475
+ prompt.close();
476
+ }
477
+ }
478
+ showStartupSecretGuidance() {
479
+ const definitions = listSecretDefinitions();
480
+ const providerSecret = definitions.find((definition) => definition.providers.includes(this.sessionState.provider));
481
+ const missingProviderKey = providerSecret ? !getSecretValue(providerSecret.id) : false;
482
+ const hasSearchKey = Boolean(getSecretValue('TAVILY_API_KEY')) ||
483
+ Boolean(getSecretValue('BRAVE_SEARCH_API_KEY')) ||
484
+ Boolean(getSecretValue('SERPAPI_API_KEY'));
485
+ if (!missingProviderKey && hasSearchKey) {
486
+ return;
487
+ }
488
+ const lines = [];
489
+ lines.push(theme.gradient.primary('Quick setup needed:'));
490
+ if (missingProviderKey && providerSecret) {
491
+ lines.push(`${theme.primary('•')} Set ${providerSecret.label} to use ${this.providerLabel(this.sessionState.provider)}.`);
492
+ }
493
+ if (!hasSearchKey) {
494
+ lines.push(`${theme.primary('•')} Add a web search API key (Tavily recommended) for web tools.`);
495
+ }
496
+ lines.push(theme.ui.muted('Run /secrets to configure keys now. Stored values override env vars.'));
497
+ display.showSystemMessage(lines.join('\n'));
498
+ }
410
499
  showLaunchCommandPalette() {
411
500
  // Disabled: Quick commands palette takes up too much space
412
501
  // Users can type /help to see available commands
@@ -463,7 +552,8 @@ export class InteractiveShell {
463
552
  // Keep adapter queue trimmed so hints stay accurate
464
553
  this.terminalInput.dequeue();
465
554
  this.followUpQueue.push({ type: 'request', text });
466
- display.showInfo(`Queued: "${text}"`);
555
+ // Record the queued action in the pinned recent strip instead of the scroll log
556
+ this.terminalInput.recordRecentAction(`queued: ${text}`);
467
557
  this.refreshQueueIndicators();
468
558
  this.scheduleQueueProcessing();
469
559
  this.handleInputChange('');
@@ -524,15 +614,30 @@ export class InteractiveShell {
524
614
  * Execute a command immediately during streaming.
525
615
  */
526
616
  async executeImmediateCommand(text) {
527
- // Pause streaming display briefly to show command output
528
- display.showInfo(`Running command during stream: ${text}`);
529
- await this.processSlashCommand(text);
617
+ const label = text.trim() || 'command';
618
+ // Keep the action visible in the pinned input area instead of the scroll log
619
+ this.terminalInput.recordRecentAction(label);
620
+ // Surface a lightweight inline status while the command runs
621
+ const previousOverride = this.statusMessageOverride;
622
+ this.statusMessageOverride = `Running ${label}`;
623
+ this.refreshStatusLine(true);
624
+ try {
625
+ await this.processSlashCommand(text);
626
+ }
627
+ finally {
628
+ this.statusMessageOverride = previousOverride;
629
+ this.refreshStatusLine(true);
630
+ }
530
631
  }
531
632
  /**
532
633
  * TerminalInputAdapter change handler
533
634
  */
534
635
  handleInputChange(text) {
636
+ const previous = this.currentInput;
535
637
  this.currentInput = text;
638
+ if (previous !== text) {
639
+ this.terminalInput.clearInlineCommandPanel();
640
+ }
536
641
  if (text.length > 0) {
537
642
  this.resetCtrlCSequence();
538
643
  }
@@ -1094,12 +1199,13 @@ export class InteractiveShell {
1094
1199
  setupHandlers() {
1095
1200
  // Handle terminal resize
1096
1201
  output.on('resize', () => {
1202
+ if (!this.inputInitialized) {
1203
+ return;
1204
+ }
1097
1205
  this.terminalInput.resetContentPosition();
1098
1206
  this.terminalInput.handleResize();
1099
1207
  this.terminalInput.forceRender();
1100
1208
  });
1101
- // Show initial input UI
1102
- this.renderPromptArea();
1103
1209
  }
1104
1210
  /**
1105
1211
  * Set up command autocomplete with all available slash commands.
@@ -1222,9 +1328,13 @@ export class InteractiveShell {
1222
1328
  alphaZeroHotkey: 'ctrl+shift+a',
1223
1329
  alphaZeroLabel: 'AlphaZero RL',
1224
1330
  });
1331
+ this.refreshFeatureStatusDisplay();
1225
1332
  this.refreshStatusLine();
1226
1333
  this.renderPromptArea();
1227
1334
  }
1335
+ refreshFeatureStatusDisplay() {
1336
+ this.terminalInput.setFeatureStatus(this.buildFeatureStatusSnapshot());
1337
+ }
1228
1338
  writeLocked(content) {
1229
1339
  if (!content) {
1230
1340
  return;
@@ -1232,6 +1342,29 @@ export class InteractiveShell {
1232
1342
  // Route through display stream so scroll regions and streaming locks stay in sync
1233
1343
  display.stream(content);
1234
1344
  }
1345
+ resetAssistantStreamBuffer() {
1346
+ this.assistantStreamBuffer = '';
1347
+ }
1348
+ appendAssistantStreamChunk(chunk) {
1349
+ if (!this.assistantBlocksEnabled || !chunk) {
1350
+ return;
1351
+ }
1352
+ this.assistantStreamBuffer += chunk;
1353
+ }
1354
+ consumeAssistantStreamBuffer(fallback) {
1355
+ const buffered = this.assistantStreamBuffer;
1356
+ this.assistantStreamBuffer = '';
1357
+ if (buffered.trim()) {
1358
+ return buffered;
1359
+ }
1360
+ return fallback ?? '';
1361
+ }
1362
+ renderAssistantBlock(type, content, metadata) {
1363
+ if (!this.assistantBlockRenderer) {
1364
+ return;
1365
+ }
1366
+ this.assistantBlockRenderer.renderBlock(type, content, metadata);
1367
+ }
1235
1368
  isStreamingUiActive() {
1236
1369
  return this.streamingHeartbeatStart !== null;
1237
1370
  }
@@ -1280,19 +1413,23 @@ export class InteractiveShell {
1280
1413
  // Surface meta header (elapsed + context usage) above the divider
1281
1414
  // Use streaming elapsed time if available, otherwise fall back to status line state
1282
1415
  let elapsedSeconds = null;
1283
- if (this.streamingHeartbeatStart) {
1416
+ const shouldShowElapsed = this.streamingHeartbeatStart !== null || this.isProcessing;
1417
+ if (this.streamingHeartbeatStart && shouldShowElapsed) {
1284
1418
  // Actively streaming - compute live elapsed
1285
1419
  elapsedSeconds = Math.max(0, Math.floor((Date.now() - this.streamingHeartbeatStart) / 1000));
1286
1420
  }
1287
- else if (this.lastStreamingElapsedSeconds !== null) {
1421
+ else if (shouldShowElapsed && this.lastStreamingElapsedSeconds !== null) {
1288
1422
  // Just finished streaming - use preserved final time
1289
1423
  elapsedSeconds = this.lastStreamingElapsedSeconds;
1290
1424
  }
1291
- else if (this.statusLineState) {
1425
+ else if (shouldShowElapsed && this.statusLineState) {
1292
1426
  // Fallback to status line state elapsed
1293
1427
  elapsedSeconds = Math.max(0, Math.floor((Date.now() - this.statusLineState.startedAt) / 1000));
1294
1428
  }
1295
- const thinkingMs = display.isSpinnerActive() ? display.getThinkingElapsedMs() : null;
1429
+ const hasThoughtSummary = !!this.latestThoughtSummary;
1430
+ const thinkingMs = hasThoughtSummary && display.isSpinnerActive()
1431
+ ? display.getThinkingElapsedMs()
1432
+ : null;
1296
1433
  const tokensUsed = this.latestTokenUsage.used;
1297
1434
  const tokenLimit = this.latestTokenUsage.limit ?? this.activeContextWindowTokens;
1298
1435
  this.terminalInput.setMetaStatus({
@@ -1300,7 +1437,7 @@ export class InteractiveShell {
1300
1437
  tokensUsed,
1301
1438
  tokenLimit,
1302
1439
  thinkingMs,
1303
- thinkingHasContent: display.isSpinnerActive(),
1440
+ thinkingHasContent: hasThoughtSummary,
1304
1441
  });
1305
1442
  // Keep model/provider visible in the controls bar
1306
1443
  this.terminalInput.setModelContext({
@@ -1407,14 +1544,17 @@ export class InteractiveShell {
1407
1544
  this.stopStreamingHeartbeat();
1408
1545
  // Enter global streaming mode - blocks all non-streaming UI output
1409
1546
  enterStreamingMode();
1547
+ this.resetAssistantStreamBuffer();
1548
+ this.streamingStatusBase = label;
1549
+ this.streamingStatusDetail = null;
1550
+ this.latestThoughtSummary = null;
1551
+ this.lastStreamingElapsedSeconds = null;
1410
1552
  // Set up scroll region for streaming content
1411
1553
  this.terminalInput.enterStreamingScrollRegion();
1412
1554
  this.uiUpdates.setMode('streaming');
1413
1555
  this.streamingHeartbeatStart = Date.now();
1414
1556
  this.streamingHeartbeatFrame = 0;
1415
- const initialFrame = STREAMING_SPINNER_FRAMES[this.streamingHeartbeatFrame];
1416
- this.streamingStatusLabel = this.buildStreamingStatus(`${initialFrame} ${label}`, 0);
1417
- display.updateStreamingStatus(this.streamingStatusLabel);
1557
+ this.rebuildStreamingStatusLabel();
1418
1558
  this.refreshStatusLine(true);
1419
1559
  // Periodically refresh the pinned input/status region while streaming so
1420
1560
  // elapsed time remains visible without interrupting the scroll region.
@@ -1424,14 +1564,9 @@ export class InteractiveShell {
1424
1564
  mode: ['streaming', 'processing'],
1425
1565
  coalesceKey: 'streaming:heartbeat',
1426
1566
  run: () => {
1427
- const elapsedSeconds = this.streamingHeartbeatStart
1428
- ? Math.max(0, Math.floor((Date.now() - this.streamingHeartbeatStart) / 1000))
1429
- : 0;
1430
1567
  this.streamingHeartbeatFrame =
1431
1568
  (this.streamingHeartbeatFrame + 1) % STREAMING_SPINNER_FRAMES.length;
1432
- const frame = STREAMING_SPINNER_FRAMES[this.streamingHeartbeatFrame];
1433
- this.streamingStatusLabel = this.buildStreamingStatus(`${frame} ${label}`, elapsedSeconds);
1434
- display.updateStreamingStatus(this.streamingStatusLabel);
1569
+ this.rebuildStreamingStatusLabel();
1435
1570
  // Update parallel agent display during streaming
1436
1571
  const manager = getParallelAgentManager();
1437
1572
  if (manager.isRunning()) {
@@ -1455,6 +1590,9 @@ export class InteractiveShell {
1455
1590
  this.streamingHeartbeatStart = null;
1456
1591
  this.streamingHeartbeatFrame = 0;
1457
1592
  this.streamingStatusLabel = null;
1593
+ this.streamingStatusBase = null;
1594
+ this.streamingStatusDetail = null;
1595
+ this.latestThoughtSummary = null;
1458
1596
  // Clear streaming label specifically (keeps override and main status if set)
1459
1597
  this.terminalInput.setStreamingLabel(null);
1460
1598
  // Clear streaming status from display
@@ -1464,9 +1602,25 @@ export class InteractiveShell {
1464
1602
  }
1465
1603
  buildStreamingStatus(label, _elapsedSeconds) {
1466
1604
  // Model + elapsed time already live in the pinned meta header; keep the streaming
1467
- // status focused on the activity to avoid duplicate info.
1605
+ // status focused on the activity and most recent thought summary.
1468
1606
  const prefix = theme.info('⏺');
1469
- return `${prefix} ${label}`.trim();
1607
+ const parts = [label.trim()];
1608
+ if (this.streamingStatusDetail) {
1609
+ const detail = this.streamingStatusDetail.length > 52
1610
+ ? `${this.streamingStatusDetail.slice(0, 51)}…`
1611
+ : this.streamingStatusDetail;
1612
+ parts.push(theme.ui.muted(detail));
1613
+ }
1614
+ return `${prefix} ${parts.join(' · ')}`.trim();
1615
+ }
1616
+ rebuildStreamingStatusLabel() {
1617
+ if (this.streamingHeartbeatStart === null) {
1618
+ return;
1619
+ }
1620
+ const frame = STREAMING_SPINNER_FRAMES[this.streamingHeartbeatFrame];
1621
+ const base = this.streamingStatusBase ?? 'Streaming';
1622
+ this.streamingStatusLabel = this.buildStreamingStatus(`${frame} ${base}`);
1623
+ display.updateStreamingStatus(this.streamingStatusLabel);
1470
1624
  }
1471
1625
  formatElapsedShort(seconds) {
1472
1626
  if (seconds < 60) {
@@ -1509,7 +1663,14 @@ export class InteractiveShell {
1509
1663
  this.refreshQueueIndicators();
1510
1664
  return;
1511
1665
  }
1666
+ if (this.apiKeyGateActive) {
1667
+ this.refreshQueueIndicators();
1668
+ return;
1669
+ }
1512
1670
  queueMicrotask(() => {
1671
+ if (this.apiKeyGateActive) {
1672
+ return;
1673
+ }
1513
1674
  void this.processQueuedActions();
1514
1675
  });
1515
1676
  }
@@ -1517,12 +1678,12 @@ export class InteractiveShell {
1517
1678
  * Process queued follow-up actions.
1518
1679
  */
1519
1680
  async processQueuedActions() {
1520
- if (this.isDrainingQueue || this.isProcessing || !this.followUpQueue.length) {
1681
+ if (this.apiKeyGateActive || this.isDrainingQueue || this.isProcessing || !this.followUpQueue.length) {
1521
1682
  return;
1522
1683
  }
1523
1684
  this.isDrainingQueue = true;
1524
1685
  try {
1525
- while (!this.isProcessing && this.followUpQueue.length) {
1686
+ while (!this.isProcessing && !this.apiKeyGateActive && this.followUpQueue.length) {
1526
1687
  const next = this.followUpQueue.shift();
1527
1688
  const remaining = this.followUpQueue.length;
1528
1689
  const label = next.type === 'continuous' ? 'continuous command' : 'follow-up';
@@ -1669,6 +1830,99 @@ export class InteractiveShell {
1669
1830
  ];
1670
1831
  return `${playbook.join('\n')}\n\nPrimary user request:\n${request.trim()}`;
1671
1832
  }
1833
+ buildRunLogExcerpt(startIndex) {
1834
+ const buffer = this.terminalInput.getScrollbackBuffer();
1835
+ const sinceStart = buffer.slice(Math.max(0, startIndex));
1836
+ const excerpt = sinceStart.slice(-200); // Cap prompt size
1837
+ return excerpt.join('\n').trim();
1838
+ }
1839
+ buildRunLogEntry(request, response, scrollbackStartIndex, meta) {
1840
+ return {
1841
+ id: ++this.runIdCounter,
1842
+ request,
1843
+ response,
1844
+ timestamp: new Date().toISOString(),
1845
+ alphaZero: meta.alphaZeroEngaged,
1846
+ difficult: meta.alphaZeroDifficult,
1847
+ failureType: meta.failureType,
1848
+ outputExcerpt: this.buildRunLogExcerpt(scrollbackStartIndex),
1849
+ };
1850
+ }
1851
+ buildAlphaZeroReflectionPrompt(runLog, options) {
1852
+ const lines = [];
1853
+ lines.push('AlphaZero Post-Run Self-Reflection (erosolar-cli)');
1854
+ lines.push(`Timestamp: ${runLog.timestamp}`);
1855
+ lines.push(`AlphaZero: ${runLog.alphaZero ? 'on' : 'off'}${runLog.difficult ? ' | difficult' : ''}${runLog.failureType ? ` | signal: ${runLog.failureType}` : ''}`);
1856
+ lines.push('');
1857
+ lines.push('User request:');
1858
+ lines.push(runLog.request);
1859
+ lines.push('');
1860
+ lines.push('Previous run log excerpt:');
1861
+ lines.push(runLog.outputExcerpt || '[empty]');
1862
+ lines.push('');
1863
+ lines.push('Instructions:');
1864
+ lines.push('- Reflect on the log to spot erosolar-cli bugs, UX issues, or reliability gaps.');
1865
+ lines.push('- Propose and apply targeted fixes in this repository only (no user workspace edits).');
1866
+ lines.push('- Prefer small, test-backed changes; run any relevant checks you invoke.');
1867
+ lines.push('- Keep notes concise and finish with applied changes plus follow-ups.');
1868
+ if (options.autoChain) {
1869
+ lines.push('- Continue iterating automatically while meaningful improvements remain.');
1870
+ lines.push('- When no further improvements are possible, reply with NO_MORE_IMPROVEMENTS on its own line.');
1871
+ }
1872
+ return lines.join('\n');
1873
+ }
1874
+ maybeQueueAlphaZeroSelfReflection(runLog, alphaZeroEngaged, allowAutoChain) {
1875
+ if (!alphaZeroEngaged) {
1876
+ return;
1877
+ }
1878
+ if (!isErosolarRepo(this.workingDir)) {
1879
+ return;
1880
+ }
1881
+ if (!runLog.outputExcerpt) {
1882
+ return;
1883
+ }
1884
+ if (this.lastReflectedRunId === runLog.id) {
1885
+ return;
1886
+ }
1887
+ if (allowAutoChain) {
1888
+ if (!this.autoContinueEnabled) {
1889
+ return;
1890
+ }
1891
+ if (this.alphaZeroAutoImproveIterations >= this.alphaZeroAutoImproveMaxIterations) {
1892
+ display.showInfo('AlphaZero auto-improvement limit reached; stopping.');
1893
+ this.alphaZeroAutoImproveActive = false;
1894
+ this.alphaZeroAutoImproveIterations = 0;
1895
+ return;
1896
+ }
1897
+ if (!this.alphaZeroAutoImproveActive) {
1898
+ this.alphaZeroAutoImproveIterations = 0;
1899
+ }
1900
+ this.alphaZeroAutoImproveActive = true;
1901
+ this.alphaZeroAutoImproveIterations++;
1902
+ }
1903
+ const prompt = this.buildAlphaZeroReflectionPrompt(runLog, { autoChain: allowAutoChain });
1904
+ this.lastReflectedRunId = runLog.id;
1905
+ this.skipNextAutoReflection = !allowAutoChain; // Prevent reflection-on-reflection unless auto-chaining
1906
+ this.enqueueFollowUpAction({ type: 'request', text: prompt });
1907
+ display.showInfo(allowAutoChain
1908
+ ? 'Auto AlphaZero self-improvement queued (auto-continue enabled).'
1909
+ : 'Queued AlphaZero self-reflection to improve erosolar-cli from the latest run log.');
1910
+ }
1911
+ shouldStopAlphaZeroAutoImprove(responseText, allowAutoChain) {
1912
+ if (!this.alphaZeroAutoImproveActive) {
1913
+ return false;
1914
+ }
1915
+ if (!allowAutoChain) {
1916
+ return true;
1917
+ }
1918
+ if (!responseText) {
1919
+ return false;
1920
+ }
1921
+ const normalized = responseText.toLowerCase();
1922
+ return (normalized.includes('no_more_improvements') ||
1923
+ normalized.includes('no more improvements') ||
1924
+ normalized.includes('stop_auto_improve'));
1925
+ }
1672
1926
  async handlePendingInteraction(input) {
1673
1927
  if (!this.pendingInteraction) {
1674
1928
  return false;
@@ -1710,170 +1964,197 @@ export class InteractiveShell {
1710
1964
  this.renderPromptArea();
1711
1965
  return;
1712
1966
  }
1713
- switch (command) {
1714
- case '/help':
1715
- case '/?':
1716
- this.showHelp();
1717
- break;
1718
- case '/features':
1719
- this.showFeaturesMenu(input);
1720
- break;
1721
- case '/learn':
1722
- this.showLearningStatus(input);
1723
- break;
1724
- case '/improve':
1725
- void this.handleImprovementCommand(input);
1726
- break;
1727
- case '/model':
1728
- this.showModelMenu();
1729
- break;
1730
- case '/exit':
1731
- case '/quit':
1732
- case '/q':
1733
- this.shutdown();
1734
- break;
1735
- case '/secrets':
1736
- this.showSecretsMenu();
1737
- break;
1738
- case '/tools':
1739
- this.showToolsMenu();
1740
- break;
1741
- case '/mcp':
1742
- await this.showMcpStatus();
1743
- break;
1744
- case '/doctor':
1745
- this.runDoctor();
1746
- break;
1747
- case '/checks':
1748
- await this.runRepoChecksCommand();
1749
- break;
1750
- case '/context':
1751
- await this.refreshWorkspaceContextCommand(input);
1752
- break;
1753
- case '/agents':
1754
- this.showAgentsMenu();
1755
- break;
1756
- case '/sessions':
1757
- await this.handleSessionCommand(input);
1758
- break;
1759
- case '/skills':
1760
- await this.handleSkillsCommand(input);
1761
- break;
1762
- case '/thinking':
1763
- this.handleThinkingCommand(input);
1764
- break;
1765
- case '/autocontinue':
1766
- this.handleAutoContinueCommand(input);
1767
- break;
1768
- case '/alphazero':
1769
- this.handleAlphaZeroCommand(input);
1770
- break;
1771
- case '/shortcuts':
1772
- case '/keys':
1773
- this.handleShortcutsCommand();
1774
- break;
1775
- case '/changes':
1776
- case '/summary':
1777
- this.showFileChangeSummary();
1778
- break;
1779
- case '/metrics':
1780
- case '/stats':
1781
- case '/perf':
1782
- this.showAlphaZeroMetrics();
1783
- break;
1784
- case '/suggestions':
1785
- case '/improve':
1786
- this.showImprovementSuggestions();
1787
- break;
1788
- case '/plugins':
1789
- this.showPluginStatus();
1790
- break;
1791
- case '/evolve':
1792
- void this.handleEvolveCommand(input);
1793
- break;
1794
- case '/modular':
1795
- case '/a0':
1796
- void this.handleModularCommand(input);
1797
- break;
1798
- case '/offsec':
1799
- void this.handleOffsecCommand(input);
1800
- break;
1801
- case '/test':
1802
- case '/tests':
1803
- void this.handleTestCommand(input);
1804
- break;
1805
- case '/provider':
1806
- await this.handleProviderCommand(input);
1807
- break;
1808
- case '/providers':
1809
- this.showConfiguredProviders();
1810
- break;
1811
- case '/local':
1812
- await this.handleLocalCommand(input);
1813
- break;
1814
- case '/discover':
1815
- await this.discoverModelsCommand();
1816
- break;
1817
- // Claude Code style commands
1818
- case '/rewind':
1819
- await this.handleRewindCommand(input);
1820
- break;
1821
- case '/memory':
1822
- this.handleMemoryCommand(input);
1823
- break;
1824
- case '/vim':
1825
- this.handleVimCommand();
1826
- break;
1827
- case '/output-style':
1828
- this.handleOutputStyleCommand(input);
1829
- break;
1830
- case '/cost':
1831
- this.handleCostCommand();
1832
- break;
1833
- case '/usage':
1834
- this.handleUsageCommand();
1835
- break;
1836
- case '/update':
1837
- await this.handleUpdateCommand();
1838
- break;
1839
- case '/clear':
1840
- this.handleClearCommand();
1841
- break;
1842
- case '/resume':
1843
- await this.handleResumeCommand(input);
1844
- break;
1845
- case '/export':
1846
- this.handleExportCommand(input);
1847
- break;
1848
- case '/review':
1849
- await this.handleReviewCommand();
1850
- break;
1851
- case '/security-review':
1852
- await this.handleSecurityReviewCommand();
1853
- break;
1854
- case '/bug':
1855
- this.handleBugCommand();
1856
- break;
1857
- case '/terminal-setup':
1858
- this.handleTerminalSetupCommand();
1859
- break;
1860
- case '/permissions':
1861
- this.handlePermissionsCommand();
1862
- break;
1863
- case '/init':
1864
- this.handleInitCommand();
1865
- break;
1866
- case '/compact':
1867
- await this.handleCompactCommand();
1868
- break;
1869
- default:
1870
- if (!(await this.tryCustomSlashCommand(command, input))) {
1871
- display.showWarning(`Unknown command "${command}".`);
1872
- }
1873
- break;
1967
+ // Keep the slash action visible in the pinned recent strip
1968
+ this.terminalInput.recordRecentAction(command);
1969
+ const runCommand = async () => {
1970
+ switch (command) {
1971
+ case '/help':
1972
+ case '/?':
1973
+ this.showHelp();
1974
+ break;
1975
+ case '/features':
1976
+ this.showFeaturesMenu(input);
1977
+ break;
1978
+ case '/learn':
1979
+ this.showLearningStatus(input);
1980
+ break;
1981
+ case '/improve':
1982
+ void this.handleImprovementCommand(input);
1983
+ break;
1984
+ case '/model':
1985
+ this.showModelMenu();
1986
+ break;
1987
+ case '/exit':
1988
+ case '/quit':
1989
+ case '/q':
1990
+ this.shutdown();
1991
+ break;
1992
+ case '/secrets':
1993
+ this.showSecretsMenu();
1994
+ break;
1995
+ case '/tools':
1996
+ this.showToolsMenu();
1997
+ break;
1998
+ case '/mcp':
1999
+ await this.showMcpStatus();
2000
+ break;
2001
+ case '/doctor':
2002
+ this.runDoctor();
2003
+ break;
2004
+ case '/checks':
2005
+ await this.runRepoChecksCommand();
2006
+ break;
2007
+ case '/context':
2008
+ await this.refreshWorkspaceContextCommand(input);
2009
+ break;
2010
+ case '/agents':
2011
+ this.showAgentsMenu();
2012
+ break;
2013
+ case '/sessions':
2014
+ await this.handleSessionCommand(input);
2015
+ break;
2016
+ case '/skills':
2017
+ await this.handleSkillsCommand(input);
2018
+ break;
2019
+ case '/thinking':
2020
+ this.handleThinkingCommand(input);
2021
+ break;
2022
+ case '/autocontinue':
2023
+ this.handleAutoContinueCommand(input);
2024
+ break;
2025
+ case '/alphazero':
2026
+ this.handleAlphaZeroCommand(input);
2027
+ break;
2028
+ case '/shortcuts':
2029
+ case '/keys':
2030
+ this.handleShortcutsCommand();
2031
+ break;
2032
+ case '/changes':
2033
+ case '/summary':
2034
+ this.showFileChangeSummary();
2035
+ break;
2036
+ case '/metrics':
2037
+ case '/stats':
2038
+ case '/perf':
2039
+ this.showAlphaZeroMetrics();
2040
+ break;
2041
+ case '/suggestions':
2042
+ case '/improve':
2043
+ this.showImprovementSuggestions();
2044
+ break;
2045
+ case '/plugins':
2046
+ this.showPluginStatus();
2047
+ break;
2048
+ case '/evolve':
2049
+ void this.handleEvolveCommand(input);
2050
+ break;
2051
+ case '/modular':
2052
+ case '/a0':
2053
+ void this.handleModularCommand(input);
2054
+ break;
2055
+ case '/offsec':
2056
+ void this.handleOffsecCommand(input);
2057
+ break;
2058
+ case '/test':
2059
+ case '/tests':
2060
+ void this.handleTestCommand(input);
2061
+ break;
2062
+ case '/provider':
2063
+ await this.handleProviderCommand(input);
2064
+ break;
2065
+ case '/providers':
2066
+ this.showConfiguredProviders();
2067
+ break;
2068
+ case '/local':
2069
+ await this.handleLocalCommand(input);
2070
+ break;
2071
+ case '/discover':
2072
+ await this.discoverModelsCommand();
2073
+ break;
2074
+ // Claude Code style commands
2075
+ case '/rewind':
2076
+ await this.handleRewindCommand(input);
2077
+ break;
2078
+ case '/memory':
2079
+ this.handleMemoryCommand(input);
2080
+ break;
2081
+ case '/vim':
2082
+ this.handleVimCommand();
2083
+ break;
2084
+ case '/output-style':
2085
+ this.handleOutputStyleCommand(input);
2086
+ break;
2087
+ case '/cost':
2088
+ this.handleCostCommand();
2089
+ break;
2090
+ case '/usage':
2091
+ this.handleUsageCommand();
2092
+ break;
2093
+ case '/update':
2094
+ await this.handleUpdateCommand();
2095
+ break;
2096
+ case '/clear':
2097
+ this.handleClearCommand();
2098
+ break;
2099
+ case '/resume':
2100
+ await this.handleResumeCommand(input);
2101
+ break;
2102
+ case '/export':
2103
+ this.handleExportCommand(input);
2104
+ break;
2105
+ case '/review':
2106
+ await this.handleReviewCommand();
2107
+ break;
2108
+ case '/security-review':
2109
+ await this.handleSecurityReviewCommand();
2110
+ break;
2111
+ case '/bug':
2112
+ this.handleBugCommand();
2113
+ break;
2114
+ case '/terminal-setup':
2115
+ this.handleTerminalSetupCommand();
2116
+ break;
2117
+ case '/permissions':
2118
+ this.handlePermissionsCommand();
2119
+ break;
2120
+ case '/init':
2121
+ this.handleInitCommand();
2122
+ break;
2123
+ case '/compact':
2124
+ await this.handleCompactCommand();
2125
+ break;
2126
+ default:
2127
+ if (!(await this.tryCustomSlashCommand(command, input))) {
2128
+ display.showWarning(`Unknown command "${command}".`);
2129
+ }
2130
+ break;
2131
+ }
2132
+ };
2133
+ let capturedOutput = '';
2134
+ try {
2135
+ const { output: outputBuffer } = await display.captureOutput(runCommand);
2136
+ capturedOutput = outputBuffer;
2137
+ }
2138
+ catch (error) {
2139
+ capturedOutput = error?.capturedOutput ?? capturedOutput;
2140
+ display.showError(error instanceof Error ? error.message : String(error), error);
1874
2141
  }
2142
+ const panelContent = this.buildInlineCommandPanel(command, capturedOutput);
2143
+ this.terminalInput.setInlineCommandPanel(panelContent);
1875
2144
  this.renderPromptArea();
1876
2145
  }
2146
+ buildInlineCommandPanel(command, output) {
2147
+ const normalized = output ? output.replace(/\r\n/g, '\n').replace(/\r/g, '\n').trimEnd() : '';
2148
+ if (!normalized) {
2149
+ return null;
2150
+ }
2151
+ const header = theme.ui.muted(command);
2152
+ const body = normalized.split('\n');
2153
+ if (body.length === 1) {
2154
+ return [`${header} ${body[0] ?? ''}`.trimEnd()];
2155
+ }
2156
+ return [header, ...body];
2157
+ }
1877
2158
  async tryCustomSlashCommand(command, fullInput) {
1878
2159
  const custom = this.customCommandMap.get(command);
1879
2160
  if (!custom) {
@@ -2300,6 +2581,7 @@ export class InteractiveShell {
2300
2581
  const updated = toggleFeatureFlag(matchedKey, newValue);
2301
2582
  const status = updated[matchedKey] ? theme.success('enabled') : theme.ui.muted('disabled');
2302
2583
  display.showInfo(`Feature "${FEATURE_FLAG_INFO[matchedKey].label}" is now ${status}.`);
2584
+ this.refreshFeatureStatusDisplay();
2303
2585
  display.showInfo('Changes will take effect on next launch or after /features refresh.');
2304
2586
  return;
2305
2587
  }
@@ -2312,6 +2594,7 @@ export class InteractiveShell {
2312
2594
  }
2313
2595
  saveFeatureFlags(updated);
2314
2596
  display.showInfo(`All features ${newValue ? theme.success('enabled') : theme.ui.muted('disabled')}.`);
2597
+ this.refreshFeatureStatusDisplay();
2315
2598
  return;
2316
2599
  }
2317
2600
  else {
@@ -3484,9 +3767,7 @@ export class InteractiveShell {
3484
3767
  }
3485
3768
  display.showInfo(`Deleted session "${summary.title}".`);
3486
3769
  if (this.activeSessionId === summary.id) {
3487
- this.activeSessionId = null;
3488
- this.activeSessionTitle = null;
3489
- saveSessionPreferences({ lastSessionId: null });
3770
+ this.updateActiveSession(null, true);
3490
3771
  }
3491
3772
  }
3492
3773
  newSessionCommand(title) {
@@ -3506,6 +3787,7 @@ export class InteractiveShell {
3506
3787
  clearAutosaveSnapshot(this.profile);
3507
3788
  display.showInfo('Started a new empty session.');
3508
3789
  this.refreshContextGauge();
3790
+ this.refreshFeatureStatusDisplay();
3509
3791
  }
3510
3792
  toggleAutosaveCommand(value) {
3511
3793
  if (!value) {
@@ -3823,6 +4105,7 @@ export class InteractiveShell {
3823
4105
  if (remember) {
3824
4106
  saveSessionPreferences({ lastSessionId: summary?.id ?? null });
3825
4107
  }
4108
+ this.refreshFeatureStatusDisplay();
3826
4109
  }
3827
4110
  resolveSessionBySelector(selector) {
3828
4111
  const sessions = listSessions(this.profile);
@@ -4689,8 +4972,10 @@ export class InteractiveShell {
4689
4972
  if (trimmed.toLowerCase() === 'cancel') {
4690
4973
  this.pendingInteraction = null;
4691
4974
  this.pendingSecretRetry = null;
4975
+ this.apiKeyGateActive = false;
4692
4976
  display.showInfo('Secret unchanged.');
4693
4977
  this.renderPromptArea();
4978
+ this.scheduleQueueProcessing();
4694
4979
  return;
4695
4980
  }
4696
4981
  try {
@@ -4699,6 +4984,7 @@ export class InteractiveShell {
4699
4984
  this.pendingInteraction = null;
4700
4985
  const deferred = this.pendingSecretRetry;
4701
4986
  this.pendingSecretRetry = null;
4987
+ this.apiKeyGateActive = false;
4702
4988
  if (pending.secret.providers.includes(this.sessionState.provider)) {
4703
4989
  if (this.rebuildAgent()) {
4704
4990
  this.resetChatBoxAfterModelSwap();
@@ -4713,8 +4999,10 @@ export class InteractiveShell {
4713
4999
  display.showError(message);
4714
5000
  this.pendingInteraction = null;
4715
5001
  this.pendingSecretRetry = null;
5002
+ this.apiKeyGateActive = false;
4716
5003
  }
4717
5004
  this.renderPromptArea();
5005
+ this.scheduleQueueProcessing();
4718
5006
  }
4719
5007
  async processRequest(userRequest) {
4720
5008
  if (this.isProcessing) {
@@ -4729,11 +5017,15 @@ export class InteractiveShell {
4729
5017
  if (!agent) {
4730
5018
  return;
4731
5019
  }
5020
+ this.hasShownThoughtProcess = false;
4732
5021
  const alphaZeroEngaged = this.alphaZeroModeEnabled;
4733
5022
  const alphaZeroDifficult = alphaZeroEngaged ? this.isDifficultProblem(userRequest) : false;
4734
5023
  const requestForAgent = alphaZeroEngaged
4735
5024
  ? this.buildAlphaZeroPrompt(userRequest, alphaZeroDifficult)
4736
5025
  : userRequest;
5026
+ const skipReflectionForThisRun = this.skipNextAutoReflection;
5027
+ this.skipNextAutoReflection = false;
5028
+ const scrollbackStartIndex = this.terminalInput.getScrollbackBuffer().length;
4737
5029
  const alphaZeroStatusId = 'alpha-zero';
4738
5030
  let alphaZeroStatusApplied = false;
4739
5031
  let alphaZeroTaskStarted = false;
@@ -4767,6 +5059,8 @@ export class InteractiveShell {
4767
5059
  this.uiAdapter.startProcessing('Working on your request');
4768
5060
  this.setProcessingStatus();
4769
5061
  let responseText = '';
5062
+ let detectedFailure = null;
5063
+ let hadUnhandledError = false;
4770
5064
  try {
4771
5065
  // Start streaming - no header needed, the input area already provides context
4772
5066
  this.startStreamingHeartbeat(alphaZeroEngaged ? 'AlphaZero RL' : 'Streaming response');
@@ -4789,18 +5083,18 @@ export class InteractiveShell {
4789
5083
  duration: 0,
4790
5084
  }));
4791
5085
  // AlphaZero: Check for failure in response
4792
- const failure = detectFailure(responseText, {
5086
+ detectedFailure = detectFailure(responseText, {
4793
5087
  toolCalls: this.currentToolCalls,
4794
5088
  userMessage: userRequest,
4795
5089
  });
4796
5090
  if (alphaZeroEngaged && alphaZeroTaskStarted && !alphaZeroTaskCompleted) {
4797
- this.alphaZeroMetrics.completeAlphaZeroTask(!failure);
5091
+ this.alphaZeroMetrics.completeAlphaZeroTask(!detectedFailure);
4798
5092
  alphaZeroTaskCompleted = true;
4799
5093
  }
4800
- if (failure) {
4801
- this.lastFailure = failure;
5094
+ if (detectedFailure) {
5095
+ this.lastFailure = detectedFailure;
4802
5096
  // Check if we have a recovery strategy
4803
- const strategy = findRecoveryStrategy(failure);
5097
+ const strategy = findRecoveryStrategy(detectedFailure);
4804
5098
  if (strategy) {
4805
5099
  display.showSystemMessage(`🔄 Found recovery strategy for this type of issue (success rate: ${Math.round(strategy.successRate * 100)}%)`);
4806
5100
  }
@@ -4824,6 +5118,7 @@ export class InteractiveShell {
4824
5118
  }
4825
5119
  catch (error) {
4826
5120
  const handled = this.handleProviderError(error, () => this.processRequest(userRequest));
5121
+ hadUnhandledError = !handled;
4827
5122
  if (alphaZeroEngaged && alphaZeroTaskStarted && !alphaZeroTaskCompleted) {
4828
5123
  this.alphaZeroMetrics.completeAlphaZeroTask(false);
4829
5124
  alphaZeroTaskCompleted = true;
@@ -4834,6 +5129,24 @@ export class InteractiveShell {
4834
5129
  }
4835
5130
  }
4836
5131
  finally {
5132
+ const runLogEntry = this.buildRunLogEntry(userRequest, responseText, scrollbackStartIndex, {
5133
+ alphaZeroEngaged,
5134
+ alphaZeroDifficult,
5135
+ failureType: detectedFailure?.type ?? (hadUnhandledError ? 'unhandled-error' : null),
5136
+ });
5137
+ this.lastRunLog = runLogEntry;
5138
+ const allowAutoChain = alphaZeroEngaged && this.autoContinueEnabled && isErosolarRepo(this.workingDir);
5139
+ let shouldStopAuto = false;
5140
+ if (this.alphaZeroAutoImproveActive) {
5141
+ shouldStopAuto = this.shouldStopAlphaZeroAutoImprove(responseText, allowAutoChain);
5142
+ if (shouldStopAuto) {
5143
+ this.alphaZeroAutoImproveActive = false;
5144
+ this.alphaZeroAutoImproveIterations = 0;
5145
+ }
5146
+ }
5147
+ if (!skipReflectionForThisRun && !shouldStopAuto) {
5148
+ this.maybeQueueAlphaZeroSelfReflection(runLogEntry, alphaZeroEngaged, allowAutoChain);
5149
+ }
4837
5150
  if (alphaZeroEngaged && alphaZeroStatusApplied) {
4838
5151
  this.statusTracker.clearOverride(alphaZeroStatusId);
4839
5152
  }
@@ -4884,6 +5197,7 @@ export class InteractiveShell {
4884
5197
  if (!agent) {
4885
5198
  return;
4886
5199
  }
5200
+ this.hasShownThoughtProcess = false;
4887
5201
  this.isProcessing = true;
4888
5202
  this.uiUpdates.setMode('processing');
4889
5203
  this.terminalInput.setStreaming(true);
@@ -4924,6 +5238,7 @@ When truly finished with ALL tasks, explicitly state "TASK_FULLY_COMPLETE".`;
4924
5238
  }
4925
5239
  while (iteration < MAX_ITERATIONS) {
4926
5240
  iteration++;
5241
+ this.hasShownThoughtProcess = false;
4927
5242
  display.showSystemMessage(`\n📍 Iteration ${iteration}/${MAX_ITERATIONS}`);
4928
5243
  this.updateStatusMessage(`Working on iteration ${iteration}...`);
4929
5244
  try {
@@ -5432,6 +5747,7 @@ What's the next action?`;
5432
5747
  // Send the error to the agent for fixing
5433
5748
  display.showThinking('Analyzing build errors');
5434
5749
  this.refreshStatusLine(true);
5750
+ this.hasShownThoughtProcess = false;
5435
5751
  const response = await this.withStreamingUi('Fixing build errors', () => this.agent.send(prompt, true));
5436
5752
  display.stopThinking();
5437
5753
  this.refreshStatusLine(true);
@@ -5461,29 +5777,40 @@ What's the next action?`;
5461
5777
  };
5462
5778
  this.agent = this.runtimeSession.createAgent(selection, {
5463
5779
  onStreamChunk: (chunk) => {
5780
+ if (this.assistantBlocksEnabled) {
5781
+ this.appendAssistantStreamChunk(chunk);
5782
+ return;
5783
+ }
5464
5784
  // Stream output using clean streamContent() - chat box floats below
5465
5785
  this.terminalInput.streamContent(chunk);
5466
5786
  },
5467
5787
  onStreamFallback: (info) => this.handleStreamingFallback(info),
5468
5788
  onAssistantMessage: (content, metadata) => {
5469
5789
  const enriched = this.buildDisplayMetadata(metadata);
5470
- // Update spinner based on message type
5790
+ const usingBlocks = this.assistantBlocksEnabled && this.assistantBlockRenderer !== null;
5791
+ const sourceContent = usingBlocks && metadata.wasStreamed ? this.consumeAssistantStreamBuffer(content) : content;
5792
+ const parsed = this.splitThinkingResponse(sourceContent);
5793
+ const thinking = parsed?.thinking ?? null;
5794
+ const shouldRenderThoughtBlock = Boolean(thinking) && !this.hasShownThoughtProcess;
5795
+ const responseContent = parsed ? parsed.response?.trim() ?? '' : sourceContent.trim();
5796
+ const narrativeContent = parsed?.response ?? sourceContent;
5471
5797
  if (metadata.isFinal) {
5472
- // Skip display if content was already streamed to avoid double-display
5473
- if (!metadata.wasStreamed) {
5474
- const parsed = this.splitThinkingResponse(content);
5475
- if (parsed?.thinking) {
5476
- const summary = this.extractThoughtSummary(parsed.thinking);
5477
- if (summary) {
5478
- display.updateThinking(`💭 ${summary}`);
5479
- }
5480
- display.showAssistantMessage(parsed.thinking, { ...enriched, isFinal: false });
5481
- }
5482
- const finalContent = parsed?.response?.trim() || content;
5483
- if (finalContent) {
5484
- display.showAssistantMessage(finalContent, enriched);
5798
+ if (thinking) {
5799
+ // Update status + summary, but skip the legacy UI output when blocks are enabled
5800
+ this.presentThoughtProcess(thinking, enriched, {
5801
+ wasStreamed: usingBlocks || metadata.wasStreamed,
5802
+ });
5803
+ if (usingBlocks && shouldRenderThoughtBlock) {
5804
+ this.renderAssistantBlock('thought', thinking, enriched);
5485
5805
  }
5486
5806
  }
5807
+ if (usingBlocks) {
5808
+ const body = responseContent || sourceContent;
5809
+ this.renderAssistantBlock('response', body, enriched);
5810
+ }
5811
+ else if (!metadata.wasStreamed && responseContent) {
5812
+ display.showAssistantMessage(responseContent, enriched);
5813
+ }
5487
5814
  // Status shown in mode controls bar - no separate status line needed
5488
5815
  display.stopThinking();
5489
5816
  // Update context usage for mode controls display
@@ -5499,12 +5826,21 @@ What's the next action?`;
5499
5826
  void this.enforceAutoTests('final-response');
5500
5827
  }
5501
5828
  else {
5829
+ if (thinking) {
5830
+ this.presentThoughtProcess(thinking, enriched, {
5831
+ wasStreamed: usingBlocks || metadata.wasStreamed,
5832
+ });
5833
+ }
5502
5834
  // Non-final message = narrative text before tool calls (Claude Code style)
5503
5835
  // Stop spinner and show the narrative text directly
5504
5836
  display.stopThinking();
5505
- // Skip display if content was already streamed to avoid double-display
5506
- if (!metadata.wasStreamed) {
5507
- display.showNarrative(content.trim());
5837
+ if (usingBlocks) {
5838
+ const body = metadata.wasStreamed ? responseContent || sourceContent : narrativeContent;
5839
+ this.renderAssistantBlock('thought', body, enriched);
5840
+ }
5841
+ else if (!metadata.wasStreamed) {
5842
+ const narrative = parsed?.response ?? content;
5843
+ display.showNarrative(narrative.trim());
5508
5844
  }
5509
5845
  // The isProcessing flag already shows "⏳ Processing..." - no need for duplicate status
5510
5846
  this.requestPromptRefresh();
@@ -5687,6 +6023,24 @@ What's the next action?`;
5687
6023
  contextWindowTokens: this.activeContextWindowTokens,
5688
6024
  };
5689
6025
  }
6026
+ presentThoughtProcess(thinking, metadata, options) {
6027
+ if (!thinking || this.hasShownThoughtProcess) {
6028
+ return;
6029
+ }
6030
+ const summary = this.extractThoughtSummary(thinking);
6031
+ if (summary) {
6032
+ display.updateThinking(`💭 ${summary}`);
6033
+ this.latestThoughtSummary = summary;
6034
+ this.streamingStatusDetail = summary;
6035
+ this.terminalInput.recordRecentAction(`💭 ${summary}`);
6036
+ this.rebuildStreamingStatusLabel();
6037
+ this.refreshStatusLine(true);
6038
+ }
6039
+ if (!options?.wasStreamed) {
6040
+ display.showAssistantMessage(thinking, { ...metadata, isFinal: false });
6041
+ }
6042
+ this.hasShownThoughtProcess = true;
6043
+ }
5690
6044
  handleContextTelemetry(metadata, displayMetadata) {
5691
6045
  if (!metadata.isFinal) {
5692
6046
  return null;
@@ -6047,7 +6401,7 @@ What's the next action?`;
6047
6401
  }
6048
6402
  handleAgentSetupError(error, retryAction, providerOverride) {
6049
6403
  this.pendingInteraction = null;
6050
- const provider = providerOverride ?? this.sessionState.provider;
6404
+ const provider = providerOverride === undefined ? this.sessionState.provider : providerOverride;
6051
6405
  const apiKeyIssue = detectApiKeyError(error, provider);
6052
6406
  if (apiKeyIssue) {
6053
6407
  this.handleApiKeyIssue(apiKeyIssue, retryAction);
@@ -6062,12 +6416,14 @@ What's the next action?`;
6062
6416
  const detail = detailText ? ` Error: ${detailText}` : '';
6063
6417
  const reason = info.reason ? ` (${info.reason.replace(/-/g, ' ')})` : '';
6064
6418
  const partialNote = info.partialResponse ? ' Received partial stream before failure.' : '';
6419
+ this.resetAssistantStreamBuffer();
6065
6420
  display.showWarning(`Streaming failed${reason}, retrying without streaming.${detail}${partialNote}`);
6066
6421
  this.startStreamingHeartbeat('Fallback in progress');
6067
6422
  this.requestPromptRefresh(true);
6068
6423
  }
6069
6424
  handleProviderError(error, retryAction) {
6070
- const apiKeyIssue = detectApiKeyError(error, this.sessionState.provider);
6425
+ const providerHint = error instanceof MissingSecretError ? null : this.sessionState.provider;
6426
+ const apiKeyIssue = detectApiKeyError(error, providerHint);
6071
6427
  if (!apiKeyIssue) {
6072
6428
  return false;
6073
6429
  }
@@ -6076,13 +6432,19 @@ What's the next action?`;
6076
6432
  }
6077
6433
  handleApiKeyIssue(info, retryAction) {
6078
6434
  const secret = info.secret ?? null;
6079
- const providerLabel = info.provider ? this.providerLabel(info.provider) : 'the selected provider';
6435
+ const providerLabel = info.provider
6436
+ ? this.providerLabel(info.provider)
6437
+ : secret?.providers?.length
6438
+ ? this.providerLabel(secret.providers[0])
6439
+ : null;
6440
+ const targetLabel = providerLabel ?? secret?.label ?? 'this tool';
6441
+ this.apiKeyGateActive = !!secret;
6080
6442
  if (!secret) {
6081
6443
  this.pendingSecretRetry = null;
6082
6444
  const guidance = 'Run "/secrets" to configure the required API key or export it (e.g., EXPORT KEY=value) before launching the CLI.';
6083
6445
  const baseMessage = info.type === 'missing'
6084
- ? `An API key is required before using ${providerLabel}.`
6085
- : `API authentication failed for ${providerLabel}.`;
6446
+ ? `An API key is required before using ${targetLabel}.`
6447
+ : `API authentication failed for ${targetLabel}.`;
6086
6448
  display.showWarning(`${baseMessage} ${guidance}`.trim());
6087
6449
  return;
6088
6450
  }
@@ -6091,8 +6453,8 @@ What's the next action?`;
6091
6453
  display.showWarning(info.message.trim());
6092
6454
  }
6093
6455
  const prefix = isMissing
6094
- ? `${secret.label} is required before you can use ${providerLabel}.`
6095
- : `${secret.label} appears to be invalid for ${providerLabel}.`;
6456
+ ? `${secret.label} is required before you can use ${targetLabel}.`
6457
+ : `${secret.label} appears to be invalid for ${targetLabel}.`;
6096
6458
  display.showWarning(prefix);
6097
6459
  this.pendingSecretRetry = retryAction ?? null;
6098
6460
  this.pendingInteraction = { type: 'secret-input', secret };
@@ -6134,10 +6496,6 @@ What's the next action?`;
6134
6496
  buildBanner() {
6135
6497
  const terminalWidth = output.columns ?? 100;
6136
6498
  const width = Math.min(terminalWidth - 4, 110);
6137
- // Collect tool categories for display
6138
- const toolCategories = this.collectToolCategories();
6139
- // Load feature flags for banner display
6140
- const featureFlags = loadFeatureFlags();
6141
6499
  return renderSessionFrame({
6142
6500
  profileLabel: this.profileLabel,
6143
6501
  profileName: this.profile,
@@ -6146,19 +6504,6 @@ What's the next action?`;
6146
6504
  workspace: this.workingDir,
6147
6505
  version: this.version,
6148
6506
  width,
6149
- features: {
6150
- verification: this.verificationEnabled,
6151
- autoContinue: this.autoContinueEnabled,
6152
- thinkingMode: this.thinkingMode,
6153
- plugins: this._enabledPlugins,
6154
- tools: toolCategories,
6155
- sessionId: this.activeSessionId ?? undefined,
6156
- // Include feature flags
6157
- alphaZeroDual: featureFlags.alphaZeroDual,
6158
- autoCompact: featureFlags.autoCompact,
6159
- mcpEnabled: featureFlags.mcpEnabled,
6160
- metrics: featureFlags.metrics,
6161
- },
6162
6507
  });
6163
6508
  }
6164
6509
  /**
@@ -6189,6 +6534,21 @@ What's the next action?`;
6189
6534
  }
6190
6535
  return categories;
6191
6536
  }
6537
+ buildFeatureStatusSnapshot() {
6538
+ const featureFlags = loadFeatureFlags();
6539
+ const toolCategories = this.collectToolCategories();
6540
+ const toolCount = toolCategories.reduce((sum, cat) => sum + cat.count, 0);
6541
+ const pluginCount = this._enabledPlugins.length;
6542
+ return {
6543
+ pluginCount: pluginCount > 0 ? pluginCount : undefined,
6544
+ toolCount: toolCount > 0 ? toolCount : undefined,
6545
+ sessionId: this.activeSessionId,
6546
+ mcpEnabled: featureFlags.mcpEnabled,
6547
+ metricsEnabled: featureFlags.metrics,
6548
+ autoCompact: featureFlags.autoCompact,
6549
+ dualMode: featureFlags.alphaZeroDual,
6550
+ };
6551
+ }
6192
6552
  /**
6193
6553
  * Extract category from tool name.
6194
6554
  */