erosolar-cli 1.7.367 → 1.7.368

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 (52) hide show
  1. package/dist/capabilities/learnCapability.d.ts +2 -0
  2. package/dist/capabilities/learnCapability.d.ts.map +1 -1
  3. package/dist/capabilities/learnCapability.js +9 -2
  4. package/dist/capabilities/learnCapability.js.map +1 -1
  5. package/dist/shell/interactiveShell.d.ts +2 -0
  6. package/dist/shell/interactiveShell.d.ts.map +1 -1
  7. package/dist/shell/interactiveShell.js +32 -15
  8. package/dist/shell/interactiveShell.js.map +1 -1
  9. package/dist/shell/terminalInput.d.ts +32 -20
  10. package/dist/shell/terminalInput.d.ts.map +1 -1
  11. package/dist/shell/terminalInput.js +203 -257
  12. package/dist/shell/terminalInput.js.map +1 -1
  13. package/dist/shell/terminalInputAdapter.d.ts.map +1 -1
  14. package/dist/shell/terminalInputAdapter.js +5 -2
  15. package/dist/shell/terminalInputAdapter.js.map +1 -1
  16. package/dist/subagents/taskRunner.d.ts.map +1 -1
  17. package/dist/subagents/taskRunner.js +7 -25
  18. package/dist/subagents/taskRunner.js.map +1 -1
  19. package/dist/tools/learnTools.js +127 -4
  20. package/dist/tools/learnTools.js.map +1 -1
  21. package/dist/tools/localExplore.d.ts +169 -0
  22. package/dist/tools/localExplore.d.ts.map +1 -0
  23. package/dist/tools/localExplore.js +1141 -0
  24. package/dist/tools/localExplore.js.map +1 -0
  25. package/dist/ui/ShellUIAdapter.d.ts +27 -0
  26. package/dist/ui/ShellUIAdapter.d.ts.map +1 -1
  27. package/dist/ui/ShellUIAdapter.js +175 -9
  28. package/dist/ui/ShellUIAdapter.js.map +1 -1
  29. package/dist/ui/compactRenderer.d.ts +139 -0
  30. package/dist/ui/compactRenderer.d.ts.map +1 -0
  31. package/dist/ui/compactRenderer.js +398 -0
  32. package/dist/ui/compactRenderer.js.map +1 -0
  33. package/dist/ui/inPlaceUpdater.d.ts +181 -0
  34. package/dist/ui/inPlaceUpdater.d.ts.map +1 -0
  35. package/dist/ui/inPlaceUpdater.js +515 -0
  36. package/dist/ui/inPlaceUpdater.js.map +1 -0
  37. package/dist/ui/theme.d.ts +108 -3
  38. package/dist/ui/theme.d.ts.map +1 -1
  39. package/dist/ui/theme.js +124 -3
  40. package/dist/ui/theme.js.map +1 -1
  41. package/dist/ui/toolDisplay.d.ts +44 -7
  42. package/dist/ui/toolDisplay.d.ts.map +1 -1
  43. package/dist/ui/toolDisplay.js +168 -84
  44. package/dist/ui/toolDisplay.js.map +1 -1
  45. package/dist/ui/unified/index.d.ts +11 -0
  46. package/dist/ui/unified/index.d.ts.map +1 -1
  47. package/dist/ui/unified/index.js +16 -0
  48. package/dist/ui/unified/index.js.map +1 -1
  49. package/dist/ui/unified/layout.d.ts.map +1 -1
  50. package/dist/ui/unified/layout.js +32 -47
  51. package/dist/ui/unified/layout.js.map +1 -1
  52. package/package.json +1 -1
@@ -92,7 +92,6 @@ export class TerminalInput extends EventEmitter {
92
92
  metaTokenLimit = null; // Optional token window
93
93
  metaThinkingMs = null; // Optional thinking duration
94
94
  metaThinkingHasContent = false; // Whether collapsed thinking content exists
95
- toolCount = null; // Total tool count for status display
96
95
  lastRenderContent = '';
97
96
  lastRenderCursor = -1;
98
97
  renderDirty = false;
@@ -118,7 +117,7 @@ export class TerminalInput extends EventEmitter {
118
117
  editMode = 'display-edits';
119
118
  verificationEnabled = true;
120
119
  autoContinueEnabled = false;
121
- verificationHotkey = 'alt+d';
120
+ verificationHotkey = 'alt+v';
122
121
  autoContinueHotkey = 'alt+c';
123
122
  thinkingHotkey = '/thinking';
124
123
  modelLabel = null;
@@ -174,60 +173,63 @@ export class TerminalInput extends EventEmitter {
174
173
  }
175
174
  }
176
175
  /**
177
- * Process raw terminal data (handles bracketed paste sequences, mouse events, and escape sequences)
178
- * Returns true if the data was consumed (paste sequence, mouse event, etc.)
176
+ * Process raw terminal data (handles bracketed paste sequences and mouse events)
177
+ * Returns true if the data was consumed (paste sequence)
179
178
  */
180
179
  processRawData(data) {
181
- // Check for mouse events (SGR mode: \x1b[<button;x;yM or m)
182
- const mouseMatch = data.match(/\x1b\[<(\d+);(\d+);(\d+)([Mm])/);
183
- if (mouseMatch) {
184
- const button = parseInt(mouseMatch[1], 10);
185
- const x = parseInt(mouseMatch[2], 10);
186
- const y = parseInt(mouseMatch[3], 10);
187
- const action = mouseMatch[4]; // 'M' = press, 'm' = release
188
- this.handleMouseEvent(button, x, y, action);
189
- // Remove mouse event from data and return remaining
190
- const mouseEventEnd = data.indexOf(action) + 1;
191
- const remaining = data.slice(mouseEventEnd);
192
- return { consumed: true, passthrough: remaining };
193
- }
194
- // Filter out arrow key escape sequences (they're handled by keypress events)
195
- // Arrow keys: \x1b[A (up), \x1b[B (down), \x1b[C (right), \x1b[D (left)
196
- const arrowMatch = data.match(/\x1b\[[ABCD]/);
197
- if (arrowMatch) {
198
- // Arrow keys should be handled by keypress handler, strip them from passthrough
199
- const filtered = data.replace(/\x1b\[[ABCD]/g, '');
200
- return { consumed: filtered.length !== data.length, passthrough: filtered };
180
+ // Handle ALL mouse events in the data (SGR mode: \x1b[<button;x;yM or m)
181
+ // Process repeatedly until no more mouse events
182
+ const mousePattern = /\x1b\[<(\d+);(\d+);(\d+)([Mm])/g;
183
+ let remaining = data;
184
+ let hadMouseEvents = false;
185
+ // Remove all mouse escape sequences from the data
186
+ let match;
187
+ while ((match = mousePattern.exec(data)) !== null) {
188
+ const button = parseInt(match[1], 10);
189
+ this.handleMouseEvent(button, 0, 0, match[4]);
190
+ hadMouseEvents = true;
191
+ }
192
+ if (hadMouseEvents) {
193
+ // Strip all mouse sequences from the data
194
+ remaining = data.replace(/\x1b\[<\d+;\d+;\d+[Mm]/g, '');
195
+ if (!remaining) {
196
+ return { consumed: true, passthrough: '' };
197
+ }
198
+ // If there's remaining content, continue processing it
201
199
  }
202
200
  // Check for paste start
203
- if (data.includes(ESC.PASTE_START)) {
201
+ if (remaining.includes(ESC.PASTE_START)) {
204
202
  this.isPasting = true;
205
203
  this.pasteBuffer = '';
206
204
  // Extract content after paste start
207
- const startIdx = data.indexOf(ESC.PASTE_START) + ESC.PASTE_START.length;
208
- const remaining = data.slice(startIdx);
205
+ const startIdx = remaining.indexOf(ESC.PASTE_START) + ESC.PASTE_START.length;
206
+ const afterStart = remaining.slice(startIdx);
209
207
  // Check if paste end is also in this chunk
210
- if (remaining.includes(ESC.PASTE_END)) {
211
- const endIdx = remaining.indexOf(ESC.PASTE_END);
212
- this.pasteBuffer = remaining.slice(0, endIdx);
208
+ if (afterStart.includes(ESC.PASTE_END)) {
209
+ const endIdx = afterStart.indexOf(ESC.PASTE_END);
210
+ this.pasteBuffer = afterStart.slice(0, endIdx);
213
211
  this.finishPaste();
214
- return { consumed: true, passthrough: remaining.slice(endIdx + ESC.PASTE_END.length) };
212
+ return { consumed: true, passthrough: afterStart.slice(endIdx + ESC.PASTE_END.length) };
215
213
  }
216
- this.pasteBuffer = remaining;
214
+ this.pasteBuffer = afterStart;
217
215
  return { consumed: true, passthrough: '' };
218
216
  }
219
217
  // Accumulating paste
220
218
  if (this.isPasting) {
221
- if (data.includes(ESC.PASTE_END)) {
222
- const endIdx = data.indexOf(ESC.PASTE_END);
223
- this.pasteBuffer += data.slice(0, endIdx);
219
+ if (remaining.includes(ESC.PASTE_END)) {
220
+ const endIdx = remaining.indexOf(ESC.PASTE_END);
221
+ this.pasteBuffer += remaining.slice(0, endIdx);
224
222
  this.finishPaste();
225
- return { consumed: true, passthrough: data.slice(endIdx + ESC.PASTE_END.length) };
223
+ return { consumed: true, passthrough: remaining.slice(endIdx + ESC.PASTE_END.length) };
226
224
  }
227
- this.pasteBuffer += data;
225
+ this.pasteBuffer += remaining;
228
226
  return { consumed: true, passthrough: '' };
229
227
  }
230
- return { consumed: false, passthrough: data };
228
+ // If we processed mouse events but have remaining text, return it as passthrough
229
+ if (hadMouseEvents && remaining) {
230
+ return { consumed: true, passthrough: remaining };
231
+ }
232
+ return { consumed: false, passthrough: remaining };
231
233
  }
232
234
  /**
233
235
  * Handle mouse events (button, x, y coordinates, action)
@@ -237,12 +239,30 @@ export class TerminalInput extends EventEmitter {
237
239
  if (button === 64) {
238
240
  // Scroll up (3 lines per wheel tick)
239
241
  this.scrollUp(3);
242
+ return;
240
243
  }
241
- else if (button === 65) {
244
+ if (button === 65) {
242
245
  // Scroll down (3 lines per wheel tick)
243
246
  this.scrollDown(3);
247
+ return;
248
+ }
249
+ // Left button (0), middle button (1), right button (2)
250
+ // These are captured by SGR mouse tracking but we don't need to handle them
251
+ // Just consume silently - the escape sequence has already been processed
252
+ // The 'M' action is press, 'm' is release
253
+ if (button <= 2) {
254
+ // Silently consume click events to prevent artifacts
255
+ // Left clicks (button=0) in the input area could focus the input
256
+ // but terminal apps typically handle this natively
257
+ return;
258
+ }
259
+ // Button with motion flag (button + 32) - drag events
260
+ if (button >= 32 && button <= 34) {
261
+ // Drag events - consume silently
262
+ return;
244
263
  }
245
- // Ignore other mouse events (clicks, drags, etc.) for now
264
+ // Any other unrecognized button - log for debugging in dev mode
265
+ // but don't output anything to prevent artifacts
246
266
  }
247
267
  /**
248
268
  * Handle a keypress event
@@ -448,14 +468,12 @@ export class TerminalInput extends EventEmitter {
448
468
  const nextAutoHotkey = options.autoContinueHotkey ?? this.autoContinueHotkey;
449
469
  const nextThinkingHotkey = options.thinkingHotkey ?? this.thinkingHotkey;
450
470
  const nextThinkingLabel = options.thinkingModeLabel === undefined ? this.thinkingModeLabel : (options.thinkingModeLabel || null);
451
- const nextToolCount = options.toolCount === undefined ? this.toolCount : (options.toolCount ?? null);
452
471
  if (this.verificationEnabled === nextVerification &&
453
472
  this.autoContinueEnabled === nextAutoContinue &&
454
473
  this.verificationHotkey === nextVerifyHotkey &&
455
474
  this.autoContinueHotkey === nextAutoHotkey &&
456
475
  this.thinkingHotkey === nextThinkingHotkey &&
457
- this.thinkingModeLabel === nextThinkingLabel &&
458
- this.toolCount === nextToolCount) {
476
+ this.thinkingModeLabel === nextThinkingLabel) {
459
477
  return;
460
478
  }
461
479
  this.verificationEnabled = nextVerification;
@@ -464,7 +482,6 @@ export class TerminalInput extends EventEmitter {
464
482
  this.autoContinueHotkey = nextAutoHotkey;
465
483
  this.thinkingHotkey = nextThinkingHotkey;
466
484
  this.thinkingModeLabel = nextThinkingLabel;
467
- this.toolCount = nextToolCount;
468
485
  this.scheduleRender();
469
486
  }
470
487
  /**
@@ -700,103 +717,123 @@ export class TerminalInput extends EventEmitter {
700
717
  return [`${leftText}${' '.repeat(spacing)}${rightText}`];
701
718
  }
702
719
  /**
703
- * Build mode controls line with all keyboard shortcuts and toggle states.
704
- * Shows below the chat box input area with fully spelled-out key names.
705
- * Always visible before and during streaming.
720
+ * Build mode controls line with all keyboard shortcuts.
721
+ * Shows status, all toggles, and contextual information.
722
+ * Enhanced with comprehensive feature status display.
723
+ *
724
+ * Format: [Key] Label:status · [Key] Label:status · context% · model
706
725
  */
707
726
  buildModeControls(cols) {
708
727
  const width = Math.max(8, cols - 2);
709
- const parts = [];
710
- // Streaming status with stop hint (if streaming)
728
+ const leftParts = [];
729
+ const rightParts = [];
730
+ // Streaming indicator with animated spinner
711
731
  if (this.streamingLabel) {
712
- parts.push({ text: `◐ ${this.streamingLabel}`, tone: 'info' });
713
- parts.push({ text: `Esc:stop`, tone: 'warn' });
732
+ leftParts.push({ text: `◐ ${this.streamingLabel}`, tone: 'info' });
714
733
  }
715
- // === ALL MODE TOGGLES WITH FULL KEY NAMES (always shown) ===
716
- // Edit mode toggle (Shift+Tab)
717
- const editIcon = this.editMode === 'display-edits' ? '✓' : '○';
718
- const editTone = this.editMode === 'display-edits' ? 'success' : 'muted';
719
- parts.push({ text: `Shift+Tab:edits${editIcon}`, tone: editTone });
720
- // Verify mode toggle (Alt+D)
721
- const verifyIcon = this.verificationEnabled ? '✓' : '○';
722
- const verifyTone = this.verificationEnabled ? 'success' : 'muted';
723
- parts.push({ text: `Alt+D:verify${verifyIcon}`, tone: verifyTone });
724
- // Auto-continue toggle (Alt+C)
725
- const autoIcon = this.autoContinueEnabled ? '✓' : '○';
726
- const autoTone = this.autoContinueEnabled ? 'success' : 'muted';
727
- parts.push({ text: `Alt+C:auto${autoIcon}`, tone: autoTone });
728
- // Thinking mode toggle (Alt+T)
729
- if (this.thinkingModeLabel) {
730
- const thinkingShort = this.thinkingModeLabel === 'minimal' ? 'min' :
731
- this.thinkingModeLabel === 'balanced' ? 'bal' :
732
- this.thinkingModeLabel === 'extended' ? 'ext' : this.thinkingModeLabel;
733
- parts.push({ text: `Alt+T:think[${thinkingShort}]`, tone: 'info' });
734
+ // Override status (warnings, errors)
735
+ if (this.overrideStatusMessage) {
736
+ leftParts.push({ text: `⚠ ${this.overrideStatusMessage}`, tone: 'warn' });
734
737
  }
735
- else {
736
- parts.push({ text: `Alt+T:think`, tone: 'muted' });
737
- }
738
- // Clear context (Alt+X)
739
- parts.push({ text: `Alt+X:clear`, tone: 'muted' });
740
- // Scroll hint
741
- parts.push({ text: `PgUp/Dn:scroll`, tone: 'muted' });
742
- // === STATUS INFO ===
743
- // Context remaining percentage
744
- const contextRemaining = this.computeContextRemaining();
745
- if (contextRemaining !== null) {
746
- const tone = contextRemaining <= 10 ? 'warn' : contextRemaining <= 30 ? 'info' : 'muted';
747
- parts.push({ text: `ctx:${contextRemaining}%`, tone });
748
- }
749
- // Token usage
750
- if (this.metaTokensUsed !== null) {
751
- const used = this.formatTokenCount(this.metaTokensUsed);
752
- const limit = this.metaTokenLimit ? `/${this.formatTokenCount(this.metaTokenLimit)}` : '';
753
- parts.push({ text: `${used}${limit}tk`, tone: 'muted' });
738
+ // Main status message
739
+ if (this.statusMessage) {
740
+ leftParts.push({ text: this.statusMessage, tone: this.streamingLabel ? 'muted' : 'info' });
754
741
  }
755
- // Elapsed time (always show if available)
756
- if (this.metaElapsedSeconds !== null) {
757
- parts.push({ text: `time:${this.formatElapsedLabel(this.metaElapsedSeconds)}`, tone: 'muted' });
742
+ // === KEYBOARD SHORTCUTS (Compact format: Key·Status) ===
743
+ // Interrupt shortcut (during streaming)
744
+ if (this.mode === 'streaming' || this.scrollRegionActive) {
745
+ leftParts.push({ text: `Esc·Stop`, tone: 'warn' });
758
746
  }
759
- // Tool count if available
760
- if (this.toolCount !== null && this.toolCount > 0) {
761
- parts.push({ text: `tools:${this.toolCount}`, tone: 'info' });
747
+ // Edit mode toggle (Shift+Tab) - Compact display
748
+ const editStatus = this.editMode === 'display-edits' ? '✓' : '?';
749
+ const editTone = this.editMode === 'display-edits' ? 'success' : 'muted';
750
+ leftParts.push({ text: `⇧Tab·${editStatus}edits`, tone: editTone });
751
+ // Verification toggle (Alt+V) - Compact display
752
+ const verifyStatus = this.verificationEnabled ? '✓' : '○';
753
+ leftParts.push({
754
+ text: `⌥V·${verifyStatus}verify`,
755
+ tone: this.verificationEnabled ? 'success' : 'muted'
756
+ });
757
+ // Auto-continue toggle (Alt+C) - Compact display
758
+ const autoStatus = this.autoContinueEnabled ? '↻' : '○';
759
+ leftParts.push({
760
+ text: `⌥C·${autoStatus}cont`,
761
+ tone: this.autoContinueEnabled ? 'info' : 'muted'
762
+ });
763
+ // Thinking mode toggle (if available)
764
+ if (this.thinkingModeLabel) {
765
+ const shortThinking = this.thinkingModeLabel.length > 8
766
+ ? this.thinkingModeLabel.slice(0, 6) + '..'
767
+ : this.thinkingModeLabel;
768
+ rightParts.push({ text: `⌥T·${shortThinking}`, tone: 'info' });
769
+ }
770
+ // === CONTEXTUAL INFO ===
771
+ // Queued commands
772
+ if (this.queue.length > 0) {
773
+ const queueIcon = this.mode === 'streaming' ? '⏳' : '▸';
774
+ leftParts.push({ text: `${queueIcon}${this.queue.length} queued`, tone: 'info' });
775
+ }
776
+ // Multi-line indicator
777
+ if (this.buffer.includes('\n')) {
778
+ const lineCount = this.buffer.split('\n').length;
779
+ rightParts.push({ text: `${lineCount}L`, tone: 'muted' });
780
+ }
781
+ // Paste indicator
782
+ if (this.pastePlaceholders.length > 0) {
783
+ const latest = this.pastePlaceholders[this.pastePlaceholders.length - 1];
784
+ rightParts.push({
785
+ text: `📋${latest.lineCount}L`,
786
+ tone: 'info',
787
+ });
788
+ }
789
+ // Note: Model, timer, and context are shown in the meta line above
790
+ // Controls line focuses on keyboard shortcuts only
791
+ // Render: left-aligned shortcuts, right-aligned context info
792
+ if (!rightParts.length || width < 60) {
793
+ const merged = rightParts.length ? [...leftParts, ...rightParts] : leftParts;
794
+ return renderStatusLine(merged, width);
762
795
  }
763
- return renderStatusLine(parts, width);
796
+ const leftWidth = Math.max(12, Math.floor(width * 0.6));
797
+ const rightWidth = Math.max(14, width - leftWidth - 1);
798
+ const leftText = renderStatusLine(leftParts, leftWidth);
799
+ const rightText = renderStatusLine(rightParts, rightWidth);
800
+ const spacing = Math.max(1, width - this.visibleLength(leftText) - this.visibleLength(rightText));
801
+ return `${leftText}${' '.repeat(spacing)}${rightText}`;
802
+ }
803
+ /**
804
+ * Render a mini context usage bar (5 chars)
805
+ */
806
+ renderMiniContextBar(percentRemaining) {
807
+ const bars = 5;
808
+ const filled = Math.round((percentRemaining / 100) * bars);
809
+ const empty = bars - filled;
810
+ return '█'.repeat(filled) + '░'.repeat(empty);
764
811
  }
765
812
  formatHotkey(hotkey) {
766
813
  const normalized = hotkey.trim().toLowerCase();
767
814
  if (!normalized)
768
815
  return hotkey;
769
816
  const parts = normalized.split('+').filter(Boolean);
770
- // Use readable key names instead of symbols for better terminal compatibility
771
817
  const map = {
772
- shift: 'Shift',
773
- sh: 'Shift',
774
- alt: 'Alt',
775
- option: 'Alt',
776
- opt: 'Alt',
777
- ctrl: 'Ctrl',
778
- control: 'Ctrl',
779
- cmd: 'Cmd',
780
- meta: 'Cmd',
781
- esc: 'Esc',
782
- escape: 'Esc',
783
- tab: 'Tab',
784
- return: 'Enter',
785
- enter: 'Enter',
786
- pageup: 'PgUp',
787
- pagedown: 'PgDn',
788
- home: 'Home',
789
- end: 'End',
818
+ shift: '',
819
+ sh: '',
820
+ alt: '',
821
+ option: '',
822
+ opt: '',
823
+ ctrl: '',
824
+ control: '',
825
+ cmd: '',
826
+ meta: '',
790
827
  };
791
828
  const formatted = parts
792
829
  .map((part) => {
793
- const label = map[part];
794
- if (label)
795
- return label;
796
- return part.length === 1 ? part.toUpperCase() : part.charAt(0).toUpperCase() + part.slice(1);
830
+ const symbol = map[part];
831
+ if (symbol)
832
+ return symbol;
833
+ return part.length === 1 ? part.toUpperCase() : part.toUpperCase();
797
834
  })
798
- .join('+');
799
- return `[${formatted}]`;
835
+ .join('');
836
+ return formatted || hotkey;
800
837
  }
801
838
  computeContextRemaining() {
802
839
  if (this.contextUsage === null) {
@@ -962,7 +999,7 @@ export class TerminalInput extends EventEmitter {
962
999
  * Calculate chat box height.
963
1000
  */
964
1001
  getChatBoxHeight() {
965
- return 5; // Fixed: divider + input + status + buffer
1002
+ return 6; // Fixed: meta + divider + input + controls + buffer
966
1003
  }
967
1004
  /**
968
1005
  * @deprecated Use streamContent() instead
@@ -998,41 +1035,26 @@ export class TerminalInput extends EventEmitter {
998
1035
  }
999
1036
  }
1000
1037
  /**
1001
- * Enter alternate screen buffer and clear it.
1002
- * This gives us full control over the terminal without affecting user's history.
1038
+ * Enter alternate screen buffer.
1039
+ * DISABLED: Using terminal-native mode for proper scrollback and text selection.
1003
1040
  */
1004
1041
  enterAlternateScreen() {
1005
- writeLock.lock('enterAltScreen');
1006
- try {
1007
- this.write(ESC.ENTER_ALT_SCREEN);
1008
- this.write(ESC.HOME);
1009
- this.write(ESC.CLEAR_SCREEN);
1010
- this.contentRow = 1;
1011
- this.alternateScreenActive = true;
1012
- }
1013
- finally {
1014
- writeLock.unlock();
1015
- }
1042
+ // Disabled - using terminal-native mode
1043
+ this.contentRow = 1;
1016
1044
  }
1017
1045
  /**
1018
1046
  * Exit alternate screen buffer.
1019
- * Restores the user's previous terminal content.
1047
+ * DISABLED: Using terminal-native mode.
1020
1048
  */
1021
1049
  exitAlternateScreen() {
1022
- writeLock.lock('exitAltScreen');
1023
- try {
1024
- this.write(ESC.EXIT_ALT_SCREEN);
1025
- this.alternateScreenActive = false;
1026
- }
1027
- finally {
1028
- writeLock.unlock();
1029
- }
1050
+ // Disabled - using terminal-native mode
1030
1051
  }
1031
1052
  /**
1032
1053
  * Check if alternate screen buffer is currently active.
1054
+ * Always returns false - using terminal-native mode.
1033
1055
  */
1034
1056
  isAlternateScreenActive() {
1035
- return this.alternateScreenActive;
1057
+ return false;
1036
1058
  }
1037
1059
  /**
1038
1060
  * Get a snapshot of the scrollback buffer (for display on exit).
@@ -1041,14 +1063,17 @@ export class TerminalInput extends EventEmitter {
1041
1063
  return [...this.scrollbackBuffer];
1042
1064
  }
1043
1065
  /**
1044
- * Clear the entire terminal screen and reset content position.
1045
- * This removes all content including the launching command.
1066
+ * Clear the visible terminal area and reset content position.
1067
+ * In terminal-native mode, this just adds newlines to scroll past content
1068
+ * rather than clearing history (preserving scrollback).
1046
1069
  */
1047
1070
  clearScreen() {
1048
1071
  writeLock.lock('clearScreen');
1049
1072
  try {
1073
+ // In native mode, scroll past existing content rather than clearing
1074
+ const { rows } = this.getSize();
1075
+ this.write('\n'.repeat(rows));
1050
1076
  this.write(ESC.HOME);
1051
- this.write(ESC.CLEAR_SCREEN);
1052
1077
  this.contentRow = 1;
1053
1078
  }
1054
1079
  finally {
@@ -1148,8 +1173,8 @@ export class TerminalInput extends EventEmitter {
1148
1173
  this.insertNewline();
1149
1174
  break;
1150
1175
  // === MODE TOGGLES ===
1151
- case 'd':
1152
- // Alt+D: Toggle verification/double-check mode (auto-tests after edits)
1176
+ case 'v':
1177
+ // Alt+V: Toggle verification mode (auto-tests after edits)
1153
1178
  this.emit('toggleVerify');
1154
1179
  break;
1155
1180
  case 'c':
@@ -1216,25 +1241,12 @@ export class TerminalInput extends EventEmitter {
1216
1241
  return Math.max(5, rows - chatBoxHeight - 2);
1217
1242
  }
1218
1243
  /**
1219
- * Build scroll indicator for the divider line (Claude Code style).
1220
- * Shows scroll position when in scrollback mode, or history size hint when idle.
1244
+ * Build scroll indicator for the divider line.
1245
+ * Scrollback navigation is disabled in alternate screen mode.
1246
+ * This returns null - no scroll indicator is shown.
1221
1247
  */
1222
1248
  buildScrollIndicator() {
1223
- const bufferSize = this.scrollbackBuffer.length;
1224
- // In scrollback mode - show position
1225
- if (this.isInScrollbackMode && this.scrollbackOffset > 0) {
1226
- const { rows } = this.getSize();
1227
- const chatBoxHeight = this.getChatBoxHeight();
1228
- const viewportHeight = Math.max(1, rows - chatBoxHeight);
1229
- const currentPos = Math.max(0, bufferSize - this.scrollbackOffset - viewportHeight);
1230
- const pct = bufferSize > 0 ? Math.round((currentPos / bufferSize) * 100) : 0;
1231
- return `↑${this.scrollbackOffset} · ${pct}% · PgUp/Dn`;
1232
- }
1233
- // Not in scrollback - show hint if there's history
1234
- if (bufferSize > 20) {
1235
- const sizeLabel = bufferSize >= 1000 ? `${Math.floor(bufferSize / 1000)}k` : `${bufferSize}`;
1236
- return `↕${sizeLabel}L · PgUp`;
1237
- }
1249
+ // Scrollback navigation disabled - no indicator needed
1238
1250
  return null;
1239
1251
  }
1240
1252
  handleSpecialKey(_str, key) {
@@ -1296,10 +1308,11 @@ export class TerminalInput extends EventEmitter {
1296
1308
  }
1297
1309
  return true;
1298
1310
  case 'pageup':
1299
- this.scrollUp(20); // Scroll up by 20 lines
1311
+ // Scrollback disabled in alternate screen mode
1312
+ // Users should use terminal's native scrollback if available
1300
1313
  return true;
1301
1314
  case 'pagedown':
1302
- this.scrollDown(20); // Scroll down by 20 lines
1315
+ // Scrollback disabled in alternate screen mode
1303
1316
  return true;
1304
1317
  case 'tab':
1305
1318
  if (key.shift) {
@@ -1693,120 +1706,53 @@ export class TerminalInput extends EventEmitter {
1693
1706
  }
1694
1707
  /**
1695
1708
  * Scroll up by a number of lines (PageUp)
1709
+ * Note: Scrollback is disabled in alternate screen mode to avoid display corruption.
1710
+ * Users should use their terminal's native scrollback or copy/paste features.
1696
1711
  */
1697
- scrollUp(lines = 10) {
1698
- const { rows } = this.getSize();
1699
- const chatBoxHeight = this.getChatBoxHeight();
1700
- const visibleLines = Math.max(1, rows - chatBoxHeight);
1701
- // Calculate max scroll offset
1702
- const maxOffset = Math.max(0, this.scrollbackBuffer.length - visibleLines);
1703
- this.scrollbackOffset = Math.min(this.scrollbackOffset + lines, maxOffset);
1704
- this.isInScrollbackMode = this.scrollbackOffset > 0;
1705
- if (this.isInScrollbackMode) {
1706
- this.renderScrollbackView();
1707
- }
1712
+ scrollUp(_lines = 10) {
1713
+ // Scrollback disabled - alternate screen buffer doesn't support it well
1714
+ // The scrollback buffer is still maintained for potential future use
1715
+ // Users can select and copy text normally since mouse tracking is off
1708
1716
  }
1709
1717
  /**
1710
1718
  * Scroll down by a number of lines (PageDown)
1719
+ * Note: Scrollback disabled - see scrollUp comment
1711
1720
  */
1712
- scrollDown(lines = 10) {
1713
- this.scrollbackOffset = Math.max(0, this.scrollbackOffset - lines);
1714
- this.isInScrollbackMode = this.scrollbackOffset > 0;
1715
- if (this.isInScrollbackMode) {
1716
- this.renderScrollbackView();
1717
- }
1718
- else {
1719
- // Returned to live mode - force re-render
1720
- this.forceRender();
1721
- }
1721
+ scrollDown(_lines = 10) {
1722
+ // Scrollback disabled
1722
1723
  }
1723
1724
  /**
1724
1725
  * Jump to the top of scrollback buffer
1726
+ * DISABLED: Scrollback navigation causes display corruption in alternate screen mode.
1727
+ * The scrollback buffer is maintained but cannot be rendered properly.
1725
1728
  */
1726
1729
  scrollToTop() {
1727
- const { rows } = this.getSize();
1728
- const chatBoxHeight = this.getChatBoxHeight();
1729
- const visibleLines = Math.max(1, rows - chatBoxHeight);
1730
- const maxOffset = Math.max(0, this.scrollbackBuffer.length - visibleLines);
1731
- this.scrollbackOffset = maxOffset;
1732
- this.isInScrollbackMode = true;
1733
- this.renderScrollbackView();
1730
+ // Disabled - causes display corruption in alternate screen buffer
1734
1731
  }
1735
1732
  /**
1736
1733
  * Jump to the bottom (live mode)
1734
+ * DISABLED: Scrollback navigation causes display corruption.
1737
1735
  */
1738
1736
  scrollToBottom() {
1737
+ // Reset scrollback state in case it was somehow enabled
1739
1738
  this.scrollbackOffset = 0;
1740
1739
  this.isInScrollbackMode = false;
1741
- this.forceRender();
1742
1740
  }
1743
1741
  /**
1744
1742
  * Toggle scrollback mode on/off (Alt+S hotkey)
1743
+ * DISABLED: Scrollback navigation causes display corruption in alternate screen mode.
1745
1744
  */
1746
1745
  toggleScrollbackMode() {
1747
- if (this.isInScrollbackMode) {
1748
- this.scrollToBottom();
1749
- }
1750
- else if (this.scrollbackBuffer.length > 0) {
1751
- this.scrollUp(20);
1752
- }
1746
+ // Disabled - alternate screen buffer doesn't support manual scrollback rendering
1753
1747
  }
1754
1748
  /**
1755
- * Render the scrollback buffer view with enhanced visuals
1756
- * Features:
1757
- * - Visual scroll position indicator
1758
- * - Progress bar showing position in history
1759
- * - Keyboard navigation hints
1760
- * - Animated indicators
1749
+ * Render the scrollback buffer view.
1750
+ * DISABLED: This causes display corruption in alternate screen mode.
1751
+ * The alternate screen buffer has its own rendering model that conflicts with
1752
+ * manual scroll region manipulation.
1761
1753
  */
1762
1754
  renderScrollbackView() {
1763
- const { rows, cols } = this.getSize();
1764
- const chatBoxHeight = this.getChatBoxHeight();
1765
- const contentHeight = Math.max(1, rows - chatBoxHeight);
1766
- writeLock.lock('renderScrollback');
1767
- try {
1768
- this.write(ESC.SAVE);
1769
- this.write(ESC.HIDE);
1770
- // Clear content area
1771
- for (let i = 1; i <= contentHeight; i++) {
1772
- this.write(ESC.TO(i, 1));
1773
- this.write(ESC.CLEAR_LINE);
1774
- }
1775
- // Calculate which lines to show
1776
- const totalLines = this.scrollbackBuffer.length;
1777
- const startIdx = Math.max(0, totalLines - this.scrollbackOffset - contentHeight);
1778
- const endIdx = Math.max(0, totalLines - this.scrollbackOffset);
1779
- const visibleLines = this.scrollbackBuffer.slice(startIdx, endIdx);
1780
- // Build header bar with navigation hints
1781
- const headerInfo = this.buildScrollbackHeader(cols, totalLines, startIdx, endIdx);
1782
- this.write(ESC.TO(1, 1));
1783
- this.write(headerInfo);
1784
- // Render visible lines with line numbers and visual guides
1785
- const lineNumWidth = String(totalLines).length + 1;
1786
- const contentStart = 2; // Start after header
1787
- for (let i = 0; i < Math.min(visibleLines.length, contentHeight - 1); i++) {
1788
- const line = visibleLines[i] ?? '';
1789
- const lineNum = startIdx + i + 1;
1790
- this.write(ESC.TO(contentStart + i, 1));
1791
- // Line number gutter
1792
- const numStr = String(lineNum).padStart(lineNumWidth, ' ');
1793
- this.write(theme.ui.muted(`${numStr} │ `));
1794
- // Content with truncation
1795
- const gutterWidth = lineNumWidth + 4;
1796
- const maxLen = cols - gutterWidth - 2;
1797
- const displayLine = line.length > maxLen ? line.slice(0, maxLen - 3) + '...' : line;
1798
- this.write(displayLine);
1799
- }
1800
- // Add visual scroll track on the right edge
1801
- this.renderScrollTrack(cols, contentHeight, totalLines, startIdx, endIdx);
1802
- this.write(ESC.RESTORE);
1803
- this.write(ESC.SHOW);
1804
- }
1805
- finally {
1806
- writeLock.unlock();
1807
- }
1808
- // Re-render chat box
1809
- this.forceRender();
1755
+ // Disabled - causes display corruption
1810
1756
  }
1811
1757
  /**
1812
1758
  * Build scrollback header with navigation hints