erosolar-cli 1.7.428 → 1.7.430

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 (83) hide show
  1. package/dist/capabilities/enhancedGitCapability.js +3 -3
  2. package/dist/capabilities/enhancedGitCapability.js.map +1 -1
  3. package/dist/capabilities/learnCapability.d.ts +1 -1
  4. package/dist/capabilities/learnCapability.d.ts.map +1 -1
  5. package/dist/capabilities/learnCapability.js +1 -1
  6. package/dist/capabilities/learnCapability.js.map +1 -1
  7. package/dist/core/checkpoint.d.ts +1 -1
  8. package/dist/core/checkpoint.js +1 -1
  9. package/dist/core/costTracker.d.ts +1 -1
  10. package/dist/core/costTracker.js +1 -1
  11. package/dist/core/hooks.d.ts +1 -1
  12. package/dist/core/hooks.js +1 -1
  13. package/dist/core/memorySystem.d.ts +2 -2
  14. package/dist/core/memorySystem.js +2 -2
  15. package/dist/core/outputStyles.d.ts +2 -2
  16. package/dist/core/outputStyles.js +2 -2
  17. package/dist/core/validationRunner.d.ts +1 -1
  18. package/dist/core/validationRunner.js +1 -1
  19. package/dist/shell/interactiveShell.d.ts +6 -1
  20. package/dist/shell/interactiveShell.d.ts.map +1 -1
  21. package/dist/shell/interactiveShell.js +129 -92
  22. package/dist/shell/interactiveShell.js.map +1 -1
  23. package/dist/shell/systemPrompt.d.ts.map +1 -1
  24. package/dist/shell/systemPrompt.js +13 -33
  25. package/dist/shell/systemPrompt.js.map +1 -1
  26. package/dist/shell/terminalInputAdapter.d.ts +75 -83
  27. package/dist/shell/terminalInputAdapter.d.ts.map +1 -1
  28. package/dist/shell/terminalInputAdapter.js +159 -220
  29. package/dist/shell/terminalInputAdapter.js.map +1 -1
  30. package/dist/shell/vimMode.d.ts +1 -1
  31. package/dist/shell/vimMode.js +1 -1
  32. package/dist/tools/buildTools.d.ts +1 -1
  33. package/dist/tools/buildTools.js +1 -1
  34. package/dist/tools/diffUtils.d.ts +2 -2
  35. package/dist/tools/diffUtils.js +2 -2
  36. package/dist/tools/editTools.js +4 -4
  37. package/dist/tools/editTools.js.map +1 -1
  38. package/dist/tools/localExplore.d.ts +3 -3
  39. package/dist/tools/localExplore.js +3 -3
  40. package/dist/tools/skillTools.js +2 -2
  41. package/dist/tools/skillTools.js.map +1 -1
  42. package/dist/tools/validationTools.js +1 -1
  43. package/dist/ui/DisplayEventQueue.d.ts +99 -0
  44. package/dist/ui/DisplayEventQueue.d.ts.map +1 -0
  45. package/dist/ui/DisplayEventQueue.js +167 -0
  46. package/dist/ui/DisplayEventQueue.js.map +1 -0
  47. package/dist/ui/SequentialRenderer.d.ts +69 -0
  48. package/dist/ui/SequentialRenderer.d.ts.map +1 -0
  49. package/dist/ui/SequentialRenderer.js +137 -0
  50. package/dist/ui/SequentialRenderer.js.map +1 -0
  51. package/dist/ui/ShellUIAdapter.d.ts +19 -12
  52. package/dist/ui/ShellUIAdapter.d.ts.map +1 -1
  53. package/dist/ui/ShellUIAdapter.js +73 -56
  54. package/dist/ui/ShellUIAdapter.js.map +1 -1
  55. package/dist/ui/UnifiedUIRenderer.d.ts +184 -0
  56. package/dist/ui/UnifiedUIRenderer.d.ts.map +1 -0
  57. package/dist/ui/UnifiedUIRenderer.js +567 -0
  58. package/dist/ui/UnifiedUIRenderer.js.map +1 -0
  59. package/dist/ui/display.d.ts +100 -173
  60. package/dist/ui/display.d.ts.map +1 -1
  61. package/dist/ui/display.js +359 -926
  62. package/dist/ui/display.js.map +1 -1
  63. package/dist/ui/errorFormatter.d.ts +1 -1
  64. package/dist/ui/errorFormatter.js +1 -1
  65. package/dist/ui/shortcutsHelp.d.ts +6 -6
  66. package/dist/ui/shortcutsHelp.js +6 -6
  67. package/dist/ui/streamingFormatter.d.ts +2 -10
  68. package/dist/ui/streamingFormatter.d.ts.map +1 -1
  69. package/dist/ui/streamingFormatter.js +9 -59
  70. package/dist/ui/streamingFormatter.js.map +1 -1
  71. package/dist/ui/textHighlighter.d.ts +8 -8
  72. package/dist/ui/textHighlighter.js +9 -9
  73. package/dist/ui/textHighlighter.js.map +1 -1
  74. package/dist/ui/theme.d.ts +2 -2
  75. package/dist/ui/theme.js +4 -4
  76. package/dist/ui/theme.js.map +1 -1
  77. package/dist/ui/toolDisplay.d.ts +8 -8
  78. package/dist/ui/toolDisplay.js +8 -8
  79. package/package.json +1 -1
  80. package/dist/shell/terminalInput.d.ts +0 -619
  81. package/dist/shell/terminalInput.d.ts.map +0 -1
  82. package/dist/shell/terminalInput.js +0 -2699
  83. package/dist/shell/terminalInput.js.map +0 -1
@@ -4,9 +4,9 @@ import { promisify } from 'node:util';
4
4
  import { existsSync, readFileSync } from 'node:fs';
5
5
  import { join } from 'node:path';
6
6
  import { display } from '../ui/display.js';
7
- import { isPlainOutputMode } from '../ui/outputMode.js';
8
7
  import { theme } from '../ui/theme.js';
9
8
  import { StreamingResponseFormatter } from '../ui/streamingFormatter.js';
9
+ import { UnifiedUIRenderer } from '../ui/UnifiedUIRenderer.js';
10
10
  import { getContextWindowTokens } from '../core/contextWindow.js';
11
11
  import { ensureSecretForProvider, getSecretDefinitionForProvider, getSecretValue, listSecretDefinitions, maskSecret, setSecretValue, } from '../core/secretStore.js';
12
12
  import { saveActiveProfilePreference, saveModelPreference, loadToolSettings, saveToolSettings, clearToolSettings, clearActiveProfilePreference, loadSessionPreferences, saveSessionPreferences, loadFeatureFlags, saveFeatureFlags, toggleFeatureFlag, FEATURE_FLAG_INFO, } from '../core/preferences.js';
@@ -174,6 +174,8 @@ export class InteractiveShell {
174
174
  alternateScreenEnabled;
175
175
  welcomeShown = false;
176
176
  scrollbackRestored = false;
177
+ uiRenderer;
178
+ useUnifiedRenderer;
177
179
  constructor(config) {
178
180
  this.profile = config.profile;
179
181
  this.profileLabel = config.profileLabel;
@@ -270,6 +272,10 @@ export class InteractiveShell {
270
272
  onToggleThinking: () => this.cycleThinkingMode(),
271
273
  onClearContext: () => this.handleClearContext(),
272
274
  });
275
+ const canUseRenderer = output.isTTY;
276
+ this.uiRenderer = canUseRenderer ? new UnifiedUIRenderer(output, input) : null;
277
+ this.useUnifiedRenderer = !!this.uiRenderer;
278
+ display.setRenderer(this.uiRenderer);
273
279
  // Initialize Alpha Zero 2 metrics tracking
274
280
  this.alphaZeroMetrics = new MetricsTracker(`${this.profile}-${Date.now()}`);
275
281
  this.setupStatusTracking();
@@ -292,7 +298,8 @@ export class InteractiveShell {
292
298
  }
293
299
  // Render chat box immediately using the streaming UI lifecycle
294
300
  this.refreshControlBar();
295
- this.terminalInput.forceRender();
301
+ this.syncRendererInput();
302
+ this.uiRenderer?.render();
296
303
  this.rebuildAgent();
297
304
  this.setupHandlers();
298
305
  this.refreshSessionContext();
@@ -383,19 +390,38 @@ export class InteractiveShell {
383
390
  if (this.welcomeShown && !force) {
384
391
  return;
385
392
  }
386
- const header = theme.gradient.primary('Erosolar CLI');
387
- const modelLine = `${theme.fields.model(this.sessionState.model)} ${theme.ui.muted('@')} ${theme.fields.agent(this.providerLabel(this.sessionState.provider))}`;
388
- const profileLine = `${theme.fields.profile(this.profileLabel)} ${theme.ui.muted(`(${this.profile})`)}`;
389
- const workspaceLine = theme.fields.workspace(this.workingDir);
390
- const versionLine = this.version ? theme.ui.muted(`v${this.version} · support@ero.solar`) : null;
391
- const hintLine = theme.ui.muted('/help for commands · /model to switch · /secrets for keys');
392
- const message = [header, modelLine, profileLine, workspaceLine, versionLine, hintLine]
393
- .filter(Boolean)
394
- .join('\n');
395
- this.streamEventBlock(message);
393
+ const banner = this.buildClaudeStyleBanner();
394
+ this.streamEventBlock(banner);
396
395
  this.welcomeShown = true;
397
396
  this.requestPromptRefresh(true);
398
397
  }
398
+ buildClaudeStyleBanner() {
399
+ const welcome = `Welcome back ${this.profileLabel}!`;
400
+ const model = `${this.sessionState.model} @ ${this.providerLabel(this.sessionState.provider)}`;
401
+ const workspace = this.workingDir;
402
+ const lines = [
403
+ 'Erosolar-CLI',
404
+ welcome,
405
+ model,
406
+ workspace,
407
+ '/help for commands · /model to switch · /secrets for keys',
408
+ ];
409
+ return lines.join('\n');
410
+ }
411
+ syncRendererInput() {
412
+ // No-op: UnifiedUIRenderer handles its own input state now
413
+ // The new architecture doesn't require separate input syncing
414
+ }
415
+ pushUiEvent(type, content) {
416
+ if (!content) {
417
+ return;
418
+ }
419
+ if (this.useUnifiedRenderer && this.uiRenderer) {
420
+ this.uiRenderer.addEvent(type, content);
421
+ return;
422
+ }
423
+ display.stream(content);
424
+ }
399
425
  /**
400
426
  * Stream a content block as a single event, isolating it from adjacent output.
401
427
  */
@@ -404,6 +430,10 @@ export class InteractiveShell {
404
430
  if (!normalized) {
405
431
  return;
406
432
  }
433
+ if (this.useUnifiedRenderer && this.uiRenderer) {
434
+ this.uiRenderer.addEvent('banner', normalized);
435
+ return;
436
+ }
407
437
  const bullet = `${theme.info('⏺')} `;
408
438
  const indent = ' ';
409
439
  const lines = normalized.split('\n').map(line => line.trimEnd());
@@ -411,11 +441,18 @@ export class InteractiveShell {
411
441
  .map((line, index) => (index === 0 ? `${bullet}${line}` : `${indent}${line}`))
412
442
  .join('\n');
413
443
  const block = `\n${formatted}\n`;
414
- this.terminalInput.streamContent(block);
444
+ this.pushUiEvent('raw', block);
415
445
  }
416
446
  async start(initialPrompt) {
417
447
  // Always use the scroll-region streaming layout from the outset
418
- this.terminalInput.enterStreamingScrollRegion({ statusMessage: 'Ready for prompts' });
448
+ if (this.useUnifiedRenderer && this.uiRenderer) {
449
+ this.uiRenderer.initialize();
450
+ }
451
+ else {
452
+ this.terminalInput.clearScreen();
453
+ this.terminalInput.resetContentPosition();
454
+ }
455
+ this.syncRendererInput();
419
456
  this.showWelcomeBanner();
420
457
  if (initialPrompt) {
421
458
  await this.processInputBlock(initialPrompt);
@@ -423,7 +460,7 @@ export class InteractiveShell {
423
460
  }
424
461
  this.showLaunchCommandPalette();
425
462
  // Ensure the terminal input is visible
426
- this.terminalInput.render();
463
+ this.syncRendererInput();
427
464
  }
428
465
  showLaunchCommandPalette() {
429
466
  // Disabled: Quick commands palette takes up too much space
@@ -542,6 +579,7 @@ export class InteractiveShell {
542
579
  */
543
580
  handleInputChange(text) {
544
581
  this.currentInput = text;
582
+ this.syncRendererInput();
545
583
  if (text.length > 0) {
546
584
  this.resetCtrlCSequence();
547
585
  }
@@ -563,7 +601,7 @@ export class InteractiveShell {
563
601
  display.showSystemMessage('✏️ Display edits mode enabled.');
564
602
  }
565
603
  }
566
- this.terminalInput.render();
604
+ this.syncRendererInput();
567
605
  }
568
606
  toggleVerificationMode() {
569
607
  this.setVerificationMode(!this.verificationEnabled, 'shortcut');
@@ -684,7 +722,7 @@ export class InteractiveShell {
684
722
  if (['n', 'no', 'cancel', '/cancel'].includes(lower)) {
685
723
  this.pendingPermissionInput = null;
686
724
  display.showInfo('Request cancelled.');
687
- this.terminalInput.render();
725
+ this.syncRendererInput();
688
726
  return null;
689
727
  }
690
728
  // Treat any other input as a replacement request that also needs confirmation
@@ -702,7 +740,7 @@ export class InteractiveShell {
702
740
  ]
703
741
  .filter(Boolean)
704
742
  .join('\n'));
705
- this.terminalInput.render();
743
+ this.syncRendererInput();
706
744
  }
707
745
  /**
708
746
  * Handle Ctrl+C presses in three stages:
@@ -767,6 +805,7 @@ export class InteractiveShell {
767
805
  this.stopStreamingHeartbeat('quit', { quiet: true });
768
806
  this.endAiRuntime();
769
807
  this.uiUpdates.dispose();
808
+ this.uiRenderer?.cleanup();
770
809
  this.clearPromptRefreshTimer();
771
810
  this.teardownStatusTracking();
772
811
  // Clear global AI enhancer (explore tool will work offline after this)
@@ -826,26 +865,26 @@ export class InteractiveShell {
826
865
  const trimmed = input.trim();
827
866
  if (!trimmed) {
828
867
  display.showWarning('Enter a number, "save", "defaults", or "cancel".');
829
- this.terminalInput.render();
868
+ this.syncRendererInput();
830
869
  return;
831
870
  }
832
871
  const normalized = trimmed.toLowerCase();
833
872
  if (normalized === 'cancel') {
834
873
  this.pendingInteraction = null;
835
874
  display.showInfo('Tool selection cancelled.');
836
- this.terminalInput.render();
875
+ this.syncRendererInput();
837
876
  return;
838
877
  }
839
878
  if (normalized === 'defaults') {
840
879
  pending.selection = buildEnabledToolSet(null);
841
880
  this.renderToolMenu(pending);
842
- this.terminalInput.render();
881
+ this.syncRendererInput();
843
882
  return;
844
883
  }
845
884
  if (normalized === 'save') {
846
885
  await this.persistToolSelection(pending);
847
886
  this.pendingInteraction = null;
848
- this.terminalInput.render();
887
+ this.syncRendererInput();
849
888
  return;
850
889
  }
851
890
  const choice = Number.parseInt(trimmed, 10);
@@ -868,11 +907,11 @@ export class InteractiveShell {
868
907
  }
869
908
  this.renderToolMenu(pending);
870
909
  }
871
- this.terminalInput.render();
910
+ this.syncRendererInput();
872
911
  return;
873
912
  }
874
913
  display.showWarning('Enter a number, "save", "defaults", or "cancel".');
875
- this.terminalInput.render();
914
+ this.syncRendererInput();
876
915
  }
877
916
  async persistToolSelection(interaction) {
878
917
  if (setsEqual(interaction.selection, interaction.initialSelection)) {
@@ -900,36 +939,36 @@ export class InteractiveShell {
900
939
  if (!this.agentMenu) {
901
940
  this.pendingInteraction = null;
902
941
  display.showWarning('Agent selection is unavailable in this CLI.');
903
- this.terminalInput.render();
942
+ this.syncRendererInput();
904
943
  return;
905
944
  }
906
945
  const trimmed = input.trim();
907
946
  if (!trimmed) {
908
947
  display.showWarning('Enter a number or type "cancel".');
909
- this.terminalInput.render();
948
+ this.syncRendererInput();
910
949
  return;
911
950
  }
912
951
  if (trimmed.toLowerCase() === 'cancel') {
913
952
  this.pendingInteraction = null;
914
953
  display.showInfo('Agent selection cancelled.');
915
- this.terminalInput.render();
954
+ this.syncRendererInput();
916
955
  return;
917
956
  }
918
957
  const choice = Number.parseInt(trimmed, 10);
919
958
  if (!Number.isFinite(choice)) {
920
959
  display.showWarning('Please enter a valid number.');
921
- this.terminalInput.render();
960
+ this.syncRendererInput();
922
961
  return;
923
962
  }
924
963
  const option = pending.options[choice - 1];
925
964
  if (!option) {
926
965
  display.showWarning('That option is not available.');
927
- this.terminalInput.render();
966
+ this.syncRendererInput();
928
967
  return;
929
968
  }
930
969
  await this.persistAgentSelection(option.name);
931
970
  this.pendingInteraction = null;
932
- this.terminalInput.render();
971
+ this.syncRendererInput();
933
972
  }
934
973
  async persistAgentSelection(profileName) {
935
974
  if (!this.agentMenu) {
@@ -1002,7 +1041,7 @@ export class InteractiveShell {
1002
1041
  lines.push(` ${theme.primary('[text]')} Submit your own solution instead`);
1003
1042
  lines.push('');
1004
1043
  display.showSystemMessage(lines.join('\n'));
1005
- this.terminalInput.render();
1044
+ this.syncRendererInput();
1006
1045
  }
1007
1046
  async handlePlanApprovalInput(input) {
1008
1047
  const pending = this.pendingInteraction;
@@ -1011,7 +1050,7 @@ export class InteractiveShell {
1011
1050
  const trimmed = input.trim();
1012
1051
  if (!trimmed) {
1013
1052
  display.showWarning('Enter a command or your own solution.');
1014
- this.terminalInput.render();
1053
+ this.syncRendererInput();
1015
1054
  return;
1016
1055
  }
1017
1056
  const lower = trimmed.toLowerCase();
@@ -1019,7 +1058,7 @@ export class InteractiveShell {
1019
1058
  if (lower === 'cancel' || lower === 'c') {
1020
1059
  this.pendingInteraction = null;
1021
1060
  display.showInfo('Plan cancelled. You can continue with a different approach.');
1022
- this.terminalInput.render();
1061
+ this.syncRendererInput();
1023
1062
  return;
1024
1063
  }
1025
1064
  // Select all
@@ -1039,7 +1078,7 @@ export class InteractiveShell {
1039
1078
  const selectedSteps = pending.steps.filter(s => pending.selectedSteps.has(s.id));
1040
1079
  if (selectedSteps.length === 0) {
1041
1080
  display.showWarning('No steps selected. Select steps or enter your own solution.');
1042
- this.terminalInput.render();
1081
+ this.syncRendererInput();
1043
1082
  return;
1044
1083
  }
1045
1084
  this.pendingInteraction = null;
@@ -1072,7 +1111,7 @@ export class InteractiveShell {
1072
1111
  return;
1073
1112
  }
1074
1113
  display.showWarning('Invalid input. Enter a step number, command (go/cancel/all/none), or your own solution.');
1075
- this.terminalInput.render();
1114
+ this.syncRendererInput();
1076
1115
  }
1077
1116
  setupHandlers() {
1078
1117
  // Handle terminal resize
@@ -1080,7 +1119,7 @@ export class InteractiveShell {
1080
1119
  this.terminalInput.handleResize();
1081
1120
  });
1082
1121
  // Show initial input UI
1083
- this.terminalInput.render();
1122
+ this.syncRendererInput();
1084
1123
  }
1085
1124
  /**
1086
1125
  * Set up command autocomplete with all available slash commands.
@@ -1193,7 +1232,7 @@ export class InteractiveShell {
1193
1232
  thinkingHotkey: 'tab',
1194
1233
  });
1195
1234
  this.refreshStatusLine();
1196
- this.terminalInput.render();
1235
+ this.syncRendererInput();
1197
1236
  }
1198
1237
  writeLocked(content) {
1199
1238
  if (!content) {
@@ -1231,7 +1270,7 @@ export class InteractiveShell {
1231
1270
  /**
1232
1271
  * Refresh the status line in the persistent input area.
1233
1272
  * Uses combined status display: streaming label + override + main status.
1234
- * All three can be shown simultaneously (Claude Code style).
1273
+ * All three can be shown simultaneously (Erosolar-CLI style).
1235
1274
  */
1236
1275
  refreshStatusLine(forceRender = false) {
1237
1276
  // Set streaming label (spinner + label) - shows during streaming
@@ -1258,8 +1297,10 @@ export class InteractiveShell {
1258
1297
  model: this.sessionState.model,
1259
1298
  provider: this.providerLabel(this.sessionState.provider),
1260
1299
  });
1300
+ this.syncRendererInput();
1261
1301
  if (forceRender) {
1262
- this.terminalInput.render();
1302
+ this.syncRendererInput();
1303
+ this.uiRenderer?.render();
1263
1304
  }
1264
1305
  }
1265
1306
  /**
@@ -1318,7 +1359,8 @@ export class InteractiveShell {
1318
1359
  }
1319
1360
  requestPromptRefresh(force = false) {
1320
1361
  if (force) {
1321
- this.terminalInput.forceRender();
1362
+ this.syncRendererInput();
1363
+ this.uiRenderer?.render();
1322
1364
  return;
1323
1365
  }
1324
1366
  if (this.promptRefreshTimer) {
@@ -1326,7 +1368,7 @@ export class InteractiveShell {
1326
1368
  }
1327
1369
  this.promptRefreshTimer = setTimeout(() => {
1328
1370
  this.promptRefreshTimer = null;
1329
- this.terminalInput.render();
1371
+ this.syncRendererInput();
1330
1372
  }, 48);
1331
1373
  }
1332
1374
  clearPromptRefreshTimer() {
@@ -1340,7 +1382,6 @@ export class InteractiveShell {
1340
1382
  // Enter global streaming mode - blocks all non-streaming UI output
1341
1383
  enterStreamingMode();
1342
1384
  // Set up scroll region for streaming content
1343
- this.terminalInput.enterStreamingScrollRegion();
1344
1385
  this.uiUpdates.setMode('streaming');
1345
1386
  this.streamingHeartbeatStart = Date.now();
1346
1387
  this.streamingHeartbeatFrame = 0;
@@ -1407,19 +1448,14 @@ export class InteractiveShell {
1407
1448
  if (!chunk) {
1408
1449
  return;
1409
1450
  }
1410
- // Preserve raw output in plain/CI modes or non-TTY environments
1411
- if (isPlainOutputMode() || !output.isTTY) {
1412
- this.terminalInput.streamContent(chunk);
1413
- return;
1414
- }
1415
1451
  if (!this.streamingFormatter) {
1416
1452
  this.streamingFormatter = new StreamingResponseFormatter(output.columns ?? undefined);
1417
- this.terminalInput.streamContent(this.streamingFormatter.header());
1453
+ this.pushUiEvent('streaming', this.streamingFormatter.header());
1418
1454
  }
1419
1455
  const formatted = this.streamingFormatter.formatChunk(chunk);
1420
1456
  this.captureStreamingThought(chunk);
1421
1457
  if (formatted) {
1422
- this.terminalInput.streamContent(formatted);
1458
+ this.pushUiEvent('streaming', formatted);
1423
1459
  }
1424
1460
  }
1425
1461
  finishStreamingFormatter(note, options) {
@@ -1431,7 +1467,7 @@ export class InteractiveShell {
1431
1467
  mode: options?.mode ?? 'complete',
1432
1468
  });
1433
1469
  if (closing) {
1434
- this.terminalInput.streamContent(closing);
1470
+ this.pushUiEvent('streaming', closing);
1435
1471
  }
1436
1472
  if (this.streamingThoughtBuffer.trim()) {
1437
1473
  this.ui.controller.recordAssistantThought(this.streamingThoughtBuffer.trim());
@@ -1483,7 +1519,7 @@ export class InteractiveShell {
1483
1519
  else {
1484
1520
  this.setIdleStatus();
1485
1521
  }
1486
- this.terminalInput.render();
1522
+ this.syncRendererInput();
1487
1523
  }
1488
1524
  enqueueFollowUpAction(action) {
1489
1525
  this.followUpQueue.push(action);
@@ -1502,7 +1538,7 @@ export class InteractiveShell {
1502
1538
  this.refreshQueueIndicators();
1503
1539
  this.scheduleQueueProcessing();
1504
1540
  // Re-show the prompt so user can continue typing more follow-ups
1505
- this.terminalInput.render();
1541
+ this.syncRendererInput();
1506
1542
  }
1507
1543
  scheduleQueueProcessing() {
1508
1544
  if (!this.followUpQueue.length) {
@@ -1557,12 +1593,12 @@ export class InteractiveShell {
1557
1593
  }
1558
1594
  if (lower === 'clear') {
1559
1595
  display.clear();
1560
- this.terminalInput.render();
1596
+ this.syncRendererInput();
1561
1597
  return;
1562
1598
  }
1563
1599
  if (lower === 'help') {
1564
1600
  this.showHelp();
1565
- this.terminalInput.render();
1601
+ this.syncRendererInput();
1566
1602
  return;
1567
1603
  }
1568
1604
  if (trimmed.startsWith('/')) {
@@ -1572,12 +1608,12 @@ export class InteractiveShell {
1572
1608
  // Check for continuous/infinite loop commands
1573
1609
  if (this.isContinuousCommand(trimmed)) {
1574
1610
  await this.processContinuousRequest(trimmed);
1575
- this.terminalInput.render();
1611
+ this.syncRendererInput();
1576
1612
  return;
1577
1613
  }
1578
1614
  // Direct execution for all inputs, including multi-line pastes
1579
1615
  await this.processRequest(trimmed);
1580
- this.terminalInput.render();
1616
+ this.syncRendererInput();
1581
1617
  }
1582
1618
  /**
1583
1619
  * Check if the command is a continuous/infinite loop command
@@ -1622,7 +1658,7 @@ export class InteractiveShell {
1622
1658
  switch (this.pendingInteraction.type) {
1623
1659
  case 'model-loading':
1624
1660
  display.showInfo('Still fetching model options. Please wait a moment.');
1625
- this.terminalInput.render();
1661
+ this.syncRendererInput();
1626
1662
  return true;
1627
1663
  case 'model-provider':
1628
1664
  await this.handleModelProviderSelection(input);
@@ -1653,7 +1689,7 @@ export class InteractiveShell {
1653
1689
  const [command] = input.split(/\s+/);
1654
1690
  if (!command) {
1655
1691
  display.showWarning('Enter a slash command.');
1656
- this.terminalInput.render();
1692
+ this.syncRendererInput();
1657
1693
  return;
1658
1694
  }
1659
1695
  switch (command) {
@@ -1756,7 +1792,7 @@ export class InteractiveShell {
1756
1792
  case '/discover':
1757
1793
  await this.discoverModelsCommand();
1758
1794
  break;
1759
- // Claude Code style commands
1795
+ // Erosolar-CLI style commands
1760
1796
  case '/rewind':
1761
1797
  await this.handleRewindCommand(input);
1762
1798
  break;
@@ -1811,7 +1847,7 @@ export class InteractiveShell {
1811
1847
  }
1812
1848
  break;
1813
1849
  }
1814
- this.terminalInput.render();
1850
+ this.syncRendererInput();
1815
1851
  }
1816
1852
  async tryCustomSlashCommand(command, fullInput) {
1817
1853
  const custom = this.customCommandMap.get(command);
@@ -2137,7 +2173,7 @@ export class InteractiveShell {
2137
2173
  display.showSystemMessage(`${headline}\n${theme.ui.muted(descriptions[this.thinkingMode])}`);
2138
2174
  }
2139
2175
  handleShortcutsCommand() {
2140
- // Display keyboard shortcuts help (Claude Code style)
2176
+ // Display keyboard shortcuts help (Erosolar-CLI style)
2141
2177
  display.showSystemMessage(formatShortcutsHelp());
2142
2178
  }
2143
2179
  showFileChangeSummary() {
@@ -3460,7 +3496,7 @@ export class InteractiveShell {
3460
3496
  }
3461
3497
  this.setAutoContinueMode(value === 'on', 'command');
3462
3498
  }
3463
- // ==================== Claude Code Style Commands ====================
3499
+ // ==================== Erosolar-CLI Style Commands ====================
3464
3500
  async handleRewindCommand(_input) {
3465
3501
  const lines = [];
3466
3502
  lines.push(theme.bold('Rewind / Checkpoint System'));
@@ -3619,7 +3655,7 @@ export class InteractiveShell {
3619
3655
  display.clear();
3620
3656
  clearAutosaveSnapshot(this.profile);
3621
3657
  display.showInfo('Conversation cleared. Starting fresh.');
3622
- this.terminalInput.render();
3658
+ this.syncRendererInput();
3623
3659
  }
3624
3660
  async handleResumeCommand(input) {
3625
3661
  const tokens = input.split(/\s+/).slice(1);
@@ -3747,7 +3783,7 @@ export class InteractiveShell {
3747
3783
  display.showInfo('Compacting conversation context...');
3748
3784
  await this.performContextCompaction('Manual /compact request');
3749
3785
  }
3750
- // ==================== End Claude Code Style Commands ====================
3786
+ // ==================== End Erosolar-CLI Style Commands ====================
3751
3787
  updateActiveSession(summary, remember = false) {
3752
3788
  this.activeSessionId = summary?.id ?? null;
3753
3789
  this.activeSessionTitle = summary?.title ?? null;
@@ -3922,7 +3958,7 @@ export class InteractiveShell {
3922
3958
  if (!providerOptions.length) {
3923
3959
  display.showWarning('No providers are available.');
3924
3960
  this.pendingInteraction = null;
3925
- this.terminalInput.render();
3961
+ this.syncRendererInput();
3926
3962
  return;
3927
3963
  }
3928
3964
  const lines = [
@@ -3943,7 +3979,7 @@ export class InteractiveShell {
3943
3979
  catch (error) {
3944
3980
  display.showError('Failed to load model list. Try again in a moment.', error);
3945
3981
  this.pendingInteraction = null;
3946
- this.terminalInput.render();
3982
+ this.syncRendererInput();
3947
3983
  }
3948
3984
  }
3949
3985
  buildProviderOptions() {
@@ -4498,29 +4534,29 @@ export class InteractiveShell {
4498
4534
  const trimmed = input.trim();
4499
4535
  if (!trimmed) {
4500
4536
  display.showWarning('Enter a number or type cancel.');
4501
- this.terminalInput.render();
4537
+ this.syncRendererInput();
4502
4538
  return;
4503
4539
  }
4504
4540
  if (trimmed.toLowerCase() === 'cancel') {
4505
4541
  this.pendingInteraction = null;
4506
4542
  display.showInfo('Model selection cancelled.');
4507
- this.terminalInput.render();
4543
+ this.syncRendererInput();
4508
4544
  return;
4509
4545
  }
4510
4546
  const choice = Number.parseInt(trimmed, 10);
4511
4547
  if (!Number.isFinite(choice)) {
4512
4548
  display.showWarning('Please enter a valid number.');
4513
- this.terminalInput.render();
4549
+ this.syncRendererInput();
4514
4550
  return;
4515
4551
  }
4516
4552
  const option = pending.options[choice - 1];
4517
4553
  if (!option) {
4518
4554
  display.showWarning('That option is not available.');
4519
- this.terminalInput.render();
4555
+ this.syncRendererInput();
4520
4556
  return;
4521
4557
  }
4522
4558
  this.showProviderModels(option);
4523
- this.terminalInput.render();
4559
+ this.syncRendererInput();
4524
4560
  }
4525
4561
  async handleModelSelection(input) {
4526
4562
  const pending = this.pendingInteraction;
@@ -4530,35 +4566,35 @@ export class InteractiveShell {
4530
4566
  const trimmed = input.trim();
4531
4567
  if (!trimmed) {
4532
4568
  display.showWarning('Enter a number, type "back", or type "cancel".');
4533
- this.terminalInput.render();
4569
+ this.syncRendererInput();
4534
4570
  return;
4535
4571
  }
4536
4572
  if (trimmed.toLowerCase() === 'back') {
4537
4573
  this.showModelMenu();
4538
- this.terminalInput.render();
4574
+ this.syncRendererInput();
4539
4575
  return;
4540
4576
  }
4541
4577
  if (trimmed.toLowerCase() === 'cancel') {
4542
4578
  this.pendingInteraction = null;
4543
4579
  display.showInfo('Model selection cancelled.');
4544
- this.terminalInput.render();
4580
+ this.syncRendererInput();
4545
4581
  return;
4546
4582
  }
4547
4583
  const choice = Number.parseInt(trimmed, 10);
4548
4584
  if (!Number.isFinite(choice)) {
4549
4585
  display.showWarning('Please enter a valid number.');
4550
- this.terminalInput.render();
4586
+ this.syncRendererInput();
4551
4587
  return;
4552
4588
  }
4553
4589
  const preset = pending.options[choice - 1];
4554
4590
  if (!preset) {
4555
4591
  display.showWarning('That option is not available.');
4556
- this.terminalInput.render();
4592
+ this.syncRendererInput();
4557
4593
  return;
4558
4594
  }
4559
4595
  this.pendingInteraction = null;
4560
4596
  await this.applyModelPreset(preset);
4561
- this.terminalInput.render();
4597
+ this.syncRendererInput();
4562
4598
  }
4563
4599
  async applyModelPreset(preset) {
4564
4600
  try {
@@ -4591,30 +4627,30 @@ export class InteractiveShell {
4591
4627
  const trimmed = input.trim();
4592
4628
  if (!trimmed) {
4593
4629
  display.showWarning('Enter a number or type cancel.');
4594
- this.terminalInput.render();
4630
+ this.syncRendererInput();
4595
4631
  return;
4596
4632
  }
4597
4633
  if (trimmed.toLowerCase() === 'cancel') {
4598
4634
  this.pendingInteraction = null;
4599
4635
  display.showInfo('Secret management cancelled.');
4600
- this.terminalInput.render();
4636
+ this.syncRendererInput();
4601
4637
  return;
4602
4638
  }
4603
4639
  const choice = Number.parseInt(trimmed, 10);
4604
4640
  if (!Number.isFinite(choice)) {
4605
4641
  display.showWarning('Please enter a valid number.');
4606
- this.terminalInput.render();
4642
+ this.syncRendererInput();
4607
4643
  return;
4608
4644
  }
4609
4645
  const secret = pending.options[choice - 1];
4610
4646
  if (!secret) {
4611
4647
  display.showWarning('That option is not available.');
4612
- this.terminalInput.render();
4648
+ this.syncRendererInput();
4613
4649
  return;
4614
4650
  }
4615
4651
  display.showSystemMessage(`Enter a new value for ${secret.label} or type "cancel".`);
4616
4652
  this.pendingInteraction = { type: 'secret-input', secret };
4617
- this.terminalInput.render();
4653
+ this.syncRendererInput();
4618
4654
  }
4619
4655
  async handleSecretInput(input) {
4620
4656
  const pending = this.pendingInteraction;
@@ -4624,14 +4660,14 @@ export class InteractiveShell {
4624
4660
  const trimmed = input.trim();
4625
4661
  if (!trimmed) {
4626
4662
  display.showWarning('Enter a value or type cancel.');
4627
- this.terminalInput.render();
4663
+ this.syncRendererInput();
4628
4664
  return;
4629
4665
  }
4630
4666
  if (trimmed.toLowerCase() === 'cancel') {
4631
4667
  this.pendingInteraction = null;
4632
4668
  this.pendingSecretRetry = null;
4633
4669
  display.showInfo('Secret unchanged.');
4634
- this.terminalInput.render();
4670
+ this.syncRendererInput();
4635
4671
  return;
4636
4672
  }
4637
4673
  try {
@@ -4655,7 +4691,7 @@ export class InteractiveShell {
4655
4691
  this.pendingInteraction = null;
4656
4692
  this.pendingSecretRetry = null;
4657
4693
  }
4658
- this.terminalInput.render();
4694
+ this.syncRendererInput();
4659
4695
  }
4660
4696
  async processRequest(request) {
4661
4697
  if (this.isProcessing) {
@@ -4675,7 +4711,8 @@ export class InteractiveShell {
4675
4711
  this.uiUpdates.setMode('processing');
4676
4712
  this.terminalInput.setStreaming(true);
4677
4713
  // Keep the persistent input/control bar active as we transition into streaming.
4678
- this.terminalInput.forceRender();
4714
+ this.syncRendererInput();
4715
+ this.uiRenderer?.render();
4679
4716
  const requestStartTime = Date.now(); // Alpha Zero 2 timing
4680
4717
  // Clear previous parallel agents and start fresh for new request
4681
4718
  const parallelManager = getParallelAgentManager();
@@ -4761,7 +4798,7 @@ export class InteractiveShell {
4761
4798
  this.updateStatusMessage(null);
4762
4799
  queueMicrotask(() => this.uiUpdates.setMode('idle'));
4763
4800
  // CRITICAL: Ensure readline prompt is active for user input
4764
- // Claude Code style: New prompt naturally appears at bottom
4801
+ // Erosolar-CLI style: New prompt naturally appears at bottom
4765
4802
  this.ensureReadlineReady();
4766
4803
  this.scheduleQueueProcessing();
4767
4804
  this.refreshQueueIndicators();
@@ -4991,7 +5028,7 @@ What's the next action?`;
4991
5028
  this.updateStatusMessage(null);
4992
5029
  queueMicrotask(() => this.uiUpdates.setMode('idle'));
4993
5030
  // CRITICAL: Ensure readline prompt is active for user input
4994
- // Claude Code style: New prompt naturally appears at bottom
5031
+ // Erosolar-CLI style: New prompt naturally appears at bottom
4995
5032
  this.ensureReadlineReady();
4996
5033
  this.scheduleQueueProcessing();
4997
5034
  this.refreshQueueIndicators();
@@ -5602,7 +5639,7 @@ Return ONLY JSON array:
5602
5639
  void this.runAutoQualityChecks('final-response', finalContent);
5603
5640
  }
5604
5641
  else {
5605
- // Non-final message = narrative text before tool calls (Claude Code style)
5642
+ // Non-final message = narrative text before tool calls (Erosolar-CLI style)
5606
5643
  // Stop spinner and show the narrative text directly
5607
5644
  display.stopThinking();
5608
5645
  // Skip display if content was already streamed to avoid double-display
@@ -5649,19 +5686,19 @@ Return ONLY JSON array:
5649
5686
  this.updateContextUsage(percentage);
5650
5687
  }
5651
5688
  // Ensure prompt remains visible at bottom after context messages
5652
- this.terminalInput.render();
5689
+ this.syncRendererInput();
5653
5690
  },
5654
5691
  onContinueAfterRecovery: () => {
5655
5692
  // Update UI to show we're continuing after context recovery
5656
5693
  display.showSystemMessage(`🔄 Continuing after context recovery...`);
5657
5694
  this.updateStatusMessage('Retrying with reduced context...');
5658
- this.terminalInput.render();
5695
+ this.syncRendererInput();
5659
5696
  },
5660
5697
  onAutoContinue: (attempt, maxAttempts, _message) => {
5661
5698
  // Show auto-continue progress in UI
5662
5699
  display.showSystemMessage(`🔄 Auto-continue (${attempt}/${maxAttempts}): Model expressed intent, prompting to act...`);
5663
5700
  this.updateStatusMessage('Auto-continuing...');
5664
- this.terminalInput.render();
5701
+ this.syncRendererInput();
5665
5702
  },
5666
5703
  onCancelled: () => {
5667
5704
  // Update UI to show operation was cancelled