erosolar-cli 1.7.367 → 1.7.369
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.
- package/dist/capabilities/learnCapability.d.ts +9 -0
- package/dist/capabilities/learnCapability.d.ts.map +1 -1
- package/dist/capabilities/learnCapability.js +15 -2
- package/dist/capabilities/learnCapability.js.map +1 -1
- package/dist/shell/interactiveShell.d.ts +2 -0
- package/dist/shell/interactiveShell.d.ts.map +1 -1
- package/dist/shell/interactiveShell.js +32 -15
- package/dist/shell/interactiveShell.js.map +1 -1
- package/dist/shell/terminalInput.d.ts +44 -22
- package/dist/shell/terminalInput.d.ts.map +1 -1
- package/dist/shell/terminalInput.js +235 -260
- package/dist/shell/terminalInput.js.map +1 -1
- package/dist/shell/terminalInputAdapter.d.ts.map +1 -1
- package/dist/shell/terminalInputAdapter.js +5 -2
- package/dist/shell/terminalInputAdapter.js.map +1 -1
- package/dist/subagents/taskRunner.d.ts.map +1 -1
- package/dist/subagents/taskRunner.js +7 -25
- package/dist/subagents/taskRunner.js.map +1 -1
- package/dist/tools/learnTools.js +127 -4
- package/dist/tools/learnTools.js.map +1 -1
- package/dist/tools/localExplore.d.ts +225 -0
- package/dist/tools/localExplore.d.ts.map +1 -0
- package/dist/tools/localExplore.js +1295 -0
- package/dist/tools/localExplore.js.map +1 -0
- package/dist/ui/ShellUIAdapter.d.ts +28 -0
- package/dist/ui/ShellUIAdapter.d.ts.map +1 -1
- package/dist/ui/ShellUIAdapter.js +215 -9
- package/dist/ui/ShellUIAdapter.js.map +1 -1
- package/dist/ui/compactRenderer.d.ts +139 -0
- package/dist/ui/compactRenderer.d.ts.map +1 -0
- package/dist/ui/compactRenderer.js +398 -0
- package/dist/ui/compactRenderer.js.map +1 -0
- package/dist/ui/inPlaceUpdater.d.ts +181 -0
- package/dist/ui/inPlaceUpdater.d.ts.map +1 -0
- package/dist/ui/inPlaceUpdater.js +515 -0
- package/dist/ui/inPlaceUpdater.js.map +1 -0
- package/dist/ui/theme.d.ts +108 -3
- package/dist/ui/theme.d.ts.map +1 -1
- package/dist/ui/theme.js +124 -3
- package/dist/ui/theme.js.map +1 -1
- package/dist/ui/toolDisplay.d.ts +44 -7
- package/dist/ui/toolDisplay.d.ts.map +1 -1
- package/dist/ui/toolDisplay.js +201 -85
- package/dist/ui/toolDisplay.js.map +1 -1
- package/dist/ui/unified/index.d.ts +11 -0
- package/dist/ui/unified/index.d.ts.map +1 -1
- package/dist/ui/unified/index.js +16 -0
- package/dist/ui/unified/index.js.map +1 -1
- package/dist/ui/unified/layout.d.ts.map +1 -1
- package/dist/ui/unified/layout.js +32 -47
- package/dist/ui/unified/layout.js.map +1 -1
- 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+
|
|
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
|
|
178
|
-
* Returns true if the data was consumed (paste sequence
|
|
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
|
-
//
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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 (
|
|
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 =
|
|
208
|
-
const
|
|
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 (
|
|
211
|
-
const endIdx =
|
|
212
|
-
this.pasteBuffer =
|
|
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:
|
|
212
|
+
return { consumed: true, passthrough: afterStart.slice(endIdx + ESC.PASTE_END.length) };
|
|
215
213
|
}
|
|
216
|
-
this.pasteBuffer =
|
|
214
|
+
this.pasteBuffer = afterStart;
|
|
217
215
|
return { consumed: true, passthrough: '' };
|
|
218
216
|
}
|
|
219
217
|
// Accumulating paste
|
|
220
218
|
if (this.isPasting) {
|
|
221
|
-
if (
|
|
222
|
-
const endIdx =
|
|
223
|
-
this.pasteBuffer +=
|
|
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:
|
|
223
|
+
return { consumed: true, passthrough: remaining.slice(endIdx + ESC.PASTE_END.length) };
|
|
226
224
|
}
|
|
227
|
-
this.pasteBuffer +=
|
|
225
|
+
this.pasteBuffer += remaining;
|
|
228
226
|
return { consumed: true, passthrough: '' };
|
|
229
227
|
}
|
|
230
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
/**
|
|
@@ -620,10 +637,15 @@ export class TerminalInput extends EventEmitter {
|
|
|
620
637
|
this.write(' '.repeat(padding));
|
|
621
638
|
this.write(ESC.RESET);
|
|
622
639
|
}
|
|
623
|
-
// Mode controls
|
|
624
|
-
|
|
625
|
-
this.
|
|
626
|
-
|
|
640
|
+
// Mode controls lines with all keyboard shortcuts
|
|
641
|
+
// Can be multiple lines during streaming (status + toggles)
|
|
642
|
+
const controlLines = this.buildModeControls(cols);
|
|
643
|
+
let controlRow = currentRow + visibleLines.length;
|
|
644
|
+
for (const controlLine of controlLines) {
|
|
645
|
+
this.write(ESC.TO(controlRow, 1));
|
|
646
|
+
this.write(controlLine);
|
|
647
|
+
controlRow += 1;
|
|
648
|
+
}
|
|
627
649
|
// Restore scroll region and cursor
|
|
628
650
|
if (this.scrollRegionActive) {
|
|
629
651
|
// Restore scroll region
|
|
@@ -700,103 +722,133 @@ export class TerminalInput extends EventEmitter {
|
|
|
700
722
|
return [`${leftText}${' '.repeat(spacing)}${rightText}`];
|
|
701
723
|
}
|
|
702
724
|
/**
|
|
703
|
-
* Build mode controls
|
|
704
|
-
* Shows
|
|
705
|
-
*
|
|
725
|
+
* Build mode controls lines with all keyboard shortcuts.
|
|
726
|
+
* Shows status, all toggles, and contextual information.
|
|
727
|
+
* Enhanced with comprehensive feature status display.
|
|
728
|
+
*
|
|
729
|
+
* Returns multiple lines to ensure all context is visible during streaming:
|
|
730
|
+
* - Line 1 (during streaming): Status/spinner + interrupt shortcut
|
|
731
|
+
* - Line 2: Mode toggles (edit, verify, auto-continue, thinking)
|
|
732
|
+
*
|
|
733
|
+
* Format: [Key] Label:status · [Key] Label:status · context% · model
|
|
706
734
|
*/
|
|
707
735
|
buildModeControls(cols) {
|
|
708
736
|
const width = Math.max(8, cols - 2);
|
|
709
|
-
const
|
|
710
|
-
//
|
|
737
|
+
const streamingActive = this.mode === 'streaming' || this.scrollRegionActive;
|
|
738
|
+
// === LINE 1: STATUS LINE (streaming status + interrupt) ===
|
|
739
|
+
const statusParts = [];
|
|
740
|
+
// Streaming indicator with animated spinner
|
|
711
741
|
if (this.streamingLabel) {
|
|
712
|
-
|
|
713
|
-
|
|
742
|
+
statusParts.push({ text: `◐ ${this.streamingLabel}`, tone: 'info' });
|
|
743
|
+
}
|
|
744
|
+
// Override status (warnings, errors)
|
|
745
|
+
if (this.overrideStatusMessage) {
|
|
746
|
+
statusParts.push({ text: `⚠ ${this.overrideStatusMessage}`, tone: 'warn' });
|
|
714
747
|
}
|
|
715
|
-
//
|
|
716
|
-
|
|
717
|
-
|
|
748
|
+
// Main status message
|
|
749
|
+
if (this.statusMessage) {
|
|
750
|
+
statusParts.push({ text: this.statusMessage, tone: this.streamingLabel ? 'muted' : 'info' });
|
|
751
|
+
}
|
|
752
|
+
// Interrupt shortcut (during streaming)
|
|
753
|
+
if (streamingActive) {
|
|
754
|
+
statusParts.push({ text: `Escape:stop`, tone: 'warn' });
|
|
755
|
+
}
|
|
756
|
+
// Queued commands
|
|
757
|
+
if (this.queue.length > 0) {
|
|
758
|
+
const queueIcon = streamingActive ? '⏳' : '▸';
|
|
759
|
+
statusParts.push({ text: `${queueIcon}${this.queue.length} queued`, tone: 'info' });
|
|
760
|
+
}
|
|
761
|
+
// === LINE 2: MODE TOGGLES (always visible, same as before streaming) ===
|
|
762
|
+
const toggleParts = [];
|
|
763
|
+
// Edit mode toggle (Shift+Tab) - Full English key names for clarity
|
|
764
|
+
const editStatus = this.editMode === 'display-edits' ? '✓auto' : '?ask';
|
|
718
765
|
const editTone = this.editMode === 'display-edits' ? 'success' : 'muted';
|
|
719
|
-
|
|
720
|
-
//
|
|
721
|
-
const
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
766
|
+
toggleParts.push({ text: `Shift+Tab:${editStatus}`, tone: editTone });
|
|
767
|
+
// Verification toggle (Alt+V) - Full English key names
|
|
768
|
+
const verifyStatus = this.verificationEnabled ? '✓on' : '○off';
|
|
769
|
+
toggleParts.push({
|
|
770
|
+
text: `Alt+V:${verifyStatus}`,
|
|
771
|
+
tone: this.verificationEnabled ? 'success' : 'muted'
|
|
772
|
+
});
|
|
773
|
+
// Auto-continue toggle (Alt+C) - Full English key names
|
|
774
|
+
const autoStatus = this.autoContinueEnabled ? '↻on' : '○off';
|
|
775
|
+
toggleParts.push({
|
|
776
|
+
text: `Alt+C:${autoStatus}`,
|
|
777
|
+
tone: this.autoContinueEnabled ? 'info' : 'muted'
|
|
778
|
+
});
|
|
779
|
+
// Thinking mode toggle (if available)
|
|
729
780
|
if (this.thinkingModeLabel) {
|
|
730
|
-
const
|
|
731
|
-
this.thinkingModeLabel
|
|
732
|
-
|
|
733
|
-
|
|
781
|
+
const shortThinking = this.thinkingModeLabel.length > 10
|
|
782
|
+
? this.thinkingModeLabel.slice(0, 8) + '..'
|
|
783
|
+
: this.thinkingModeLabel;
|
|
784
|
+
toggleParts.push({ text: `Alt+T:${shortThinking}`, tone: 'info' });
|
|
785
|
+
}
|
|
786
|
+
// Multi-line indicator
|
|
787
|
+
if (this.buffer.includes('\n')) {
|
|
788
|
+
const lineCount = this.buffer.split('\n').length;
|
|
789
|
+
toggleParts.push({ text: `${lineCount}L`, tone: 'muted' });
|
|
790
|
+
}
|
|
791
|
+
// Paste indicator
|
|
792
|
+
if (this.pastePlaceholders.length > 0) {
|
|
793
|
+
const latest = this.pastePlaceholders[this.pastePlaceholders.length - 1];
|
|
794
|
+
toggleParts.push({
|
|
795
|
+
text: `📋${latest.lineCount}L`,
|
|
796
|
+
tone: 'info',
|
|
797
|
+
});
|
|
734
798
|
}
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
//
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
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' });
|
|
799
|
+
// Build the output lines
|
|
800
|
+
const lines = [];
|
|
801
|
+
// During streaming: show status line first, then toggles line
|
|
802
|
+
// Not streaming: show combined or just toggles
|
|
803
|
+
if (streamingActive && statusParts.length > 0) {
|
|
804
|
+
lines.push(renderStatusLine(statusParts, width));
|
|
805
|
+
lines.push(renderStatusLine(toggleParts, width));
|
|
754
806
|
}
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
807
|
+
else if (statusParts.length > 0) {
|
|
808
|
+
// Not streaming but have status - combine on one line if possible
|
|
809
|
+
const combined = [...statusParts, ...toggleParts];
|
|
810
|
+
lines.push(renderStatusLine(combined, width));
|
|
758
811
|
}
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
812
|
+
else {
|
|
813
|
+
// No streaming, no status - just show toggles
|
|
814
|
+
lines.push(renderStatusLine(toggleParts, width));
|
|
762
815
|
}
|
|
763
|
-
return
|
|
816
|
+
return lines;
|
|
817
|
+
}
|
|
818
|
+
/**
|
|
819
|
+
* Render a mini context usage bar (5 chars)
|
|
820
|
+
*/
|
|
821
|
+
renderMiniContextBar(percentRemaining) {
|
|
822
|
+
const bars = 5;
|
|
823
|
+
const filled = Math.round((percentRemaining / 100) * bars);
|
|
824
|
+
const empty = bars - filled;
|
|
825
|
+
return '█'.repeat(filled) + '░'.repeat(empty);
|
|
764
826
|
}
|
|
765
827
|
formatHotkey(hotkey) {
|
|
766
828
|
const normalized = hotkey.trim().toLowerCase();
|
|
767
829
|
if (!normalized)
|
|
768
830
|
return hotkey;
|
|
769
831
|
const parts = normalized.split('+').filter(Boolean);
|
|
770
|
-
// Use readable key names instead of symbols for better terminal compatibility
|
|
771
832
|
const map = {
|
|
772
|
-
shift: '
|
|
773
|
-
sh: '
|
|
774
|
-
alt: '
|
|
775
|
-
option: '
|
|
776
|
-
opt: '
|
|
777
|
-
ctrl: '
|
|
778
|
-
control: '
|
|
779
|
-
cmd: '
|
|
780
|
-
meta: '
|
|
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',
|
|
833
|
+
shift: '⇧',
|
|
834
|
+
sh: '⇧',
|
|
835
|
+
alt: '⌥',
|
|
836
|
+
option: '⌥',
|
|
837
|
+
opt: '⌥',
|
|
838
|
+
ctrl: '⌃',
|
|
839
|
+
control: '⌃',
|
|
840
|
+
cmd: '⌘',
|
|
841
|
+
meta: '⌘',
|
|
790
842
|
};
|
|
791
843
|
const formatted = parts
|
|
792
844
|
.map((part) => {
|
|
793
|
-
const
|
|
794
|
-
if (
|
|
795
|
-
return
|
|
796
|
-
return part.length === 1 ? part.toUpperCase() : part.
|
|
845
|
+
const symbol = map[part];
|
|
846
|
+
if (symbol)
|
|
847
|
+
return symbol;
|
|
848
|
+
return part.length === 1 ? part.toUpperCase() : part.toUpperCase();
|
|
797
849
|
})
|
|
798
|
-
.join('
|
|
799
|
-
return
|
|
850
|
+
.join('');
|
|
851
|
+
return formatted || hotkey;
|
|
800
852
|
}
|
|
801
853
|
computeContextRemaining() {
|
|
802
854
|
if (this.contextUsage === null) {
|
|
@@ -959,10 +1011,24 @@ export class TerminalInput extends EventEmitter {
|
|
|
959
1011
|
// No-op: using pure floating approach
|
|
960
1012
|
}
|
|
961
1013
|
/**
|
|
962
|
-
* Calculate chat box height.
|
|
1014
|
+
* Calculate chat box height dynamically.
|
|
1015
|
+
* Accounts for:
|
|
1016
|
+
* - Meta lines (model, elapsed, tokens)
|
|
1017
|
+
* - Divider
|
|
1018
|
+
* - Input line(s)
|
|
1019
|
+
* - Control lines (1 or 2 during streaming)
|
|
1020
|
+
* - Buffer
|
|
963
1021
|
*/
|
|
964
1022
|
getChatBoxHeight() {
|
|
965
|
-
|
|
1023
|
+
const streamingActive = this.mode === 'streaming' || this.scrollRegionActive;
|
|
1024
|
+
const hasStreamingStatus = this.streamingLabel || this.statusMessage || this.overrideStatusMessage;
|
|
1025
|
+
// Base: meta (1) + divider (1) + input (1) + buffer (1) = 4
|
|
1026
|
+
// Add control lines: 1 normally, 2 during streaming with status
|
|
1027
|
+
const controlLines = (streamingActive && hasStreamingStatus) ? 2 : 1;
|
|
1028
|
+
// Meta line only if we have model/elapsed info
|
|
1029
|
+
const hasMetaInfo = this.modelLabel || this.metaElapsedSeconds !== null || this.metaTokensUsed !== null;
|
|
1030
|
+
const metaLines = hasMetaInfo ? 1 : 0;
|
|
1031
|
+
return metaLines + 1 + 1 + controlLines + 1; // meta + divider + input + controls + buffer
|
|
966
1032
|
}
|
|
967
1033
|
/**
|
|
968
1034
|
* @deprecated Use streamContent() instead
|
|
@@ -998,41 +1064,26 @@ export class TerminalInput extends EventEmitter {
|
|
|
998
1064
|
}
|
|
999
1065
|
}
|
|
1000
1066
|
/**
|
|
1001
|
-
* Enter alternate screen buffer
|
|
1002
|
-
*
|
|
1067
|
+
* Enter alternate screen buffer.
|
|
1068
|
+
* DISABLED: Using terminal-native mode for proper scrollback and text selection.
|
|
1003
1069
|
*/
|
|
1004
1070
|
enterAlternateScreen() {
|
|
1005
|
-
|
|
1006
|
-
|
|
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
|
-
}
|
|
1071
|
+
// Disabled - using terminal-native mode
|
|
1072
|
+
this.contentRow = 1;
|
|
1016
1073
|
}
|
|
1017
1074
|
/**
|
|
1018
1075
|
* Exit alternate screen buffer.
|
|
1019
|
-
*
|
|
1076
|
+
* DISABLED: Using terminal-native mode.
|
|
1020
1077
|
*/
|
|
1021
1078
|
exitAlternateScreen() {
|
|
1022
|
-
|
|
1023
|
-
try {
|
|
1024
|
-
this.write(ESC.EXIT_ALT_SCREEN);
|
|
1025
|
-
this.alternateScreenActive = false;
|
|
1026
|
-
}
|
|
1027
|
-
finally {
|
|
1028
|
-
writeLock.unlock();
|
|
1029
|
-
}
|
|
1079
|
+
// Disabled - using terminal-native mode
|
|
1030
1080
|
}
|
|
1031
1081
|
/**
|
|
1032
1082
|
* Check if alternate screen buffer is currently active.
|
|
1083
|
+
* Always returns false - using terminal-native mode.
|
|
1033
1084
|
*/
|
|
1034
1085
|
isAlternateScreenActive() {
|
|
1035
|
-
return
|
|
1086
|
+
return false;
|
|
1036
1087
|
}
|
|
1037
1088
|
/**
|
|
1038
1089
|
* Get a snapshot of the scrollback buffer (for display on exit).
|
|
@@ -1041,14 +1092,17 @@ export class TerminalInput extends EventEmitter {
|
|
|
1041
1092
|
return [...this.scrollbackBuffer];
|
|
1042
1093
|
}
|
|
1043
1094
|
/**
|
|
1044
|
-
* Clear the
|
|
1045
|
-
*
|
|
1095
|
+
* Clear the visible terminal area and reset content position.
|
|
1096
|
+
* In terminal-native mode, this just adds newlines to scroll past content
|
|
1097
|
+
* rather than clearing history (preserving scrollback).
|
|
1046
1098
|
*/
|
|
1047
1099
|
clearScreen() {
|
|
1048
1100
|
writeLock.lock('clearScreen');
|
|
1049
1101
|
try {
|
|
1102
|
+
// In native mode, scroll past existing content rather than clearing
|
|
1103
|
+
const { rows } = this.getSize();
|
|
1104
|
+
this.write('\n'.repeat(rows));
|
|
1050
1105
|
this.write(ESC.HOME);
|
|
1051
|
-
this.write(ESC.CLEAR_SCREEN);
|
|
1052
1106
|
this.contentRow = 1;
|
|
1053
1107
|
}
|
|
1054
1108
|
finally {
|
|
@@ -1148,8 +1202,8 @@ export class TerminalInput extends EventEmitter {
|
|
|
1148
1202
|
this.insertNewline();
|
|
1149
1203
|
break;
|
|
1150
1204
|
// === MODE TOGGLES ===
|
|
1151
|
-
case '
|
|
1152
|
-
// Alt+
|
|
1205
|
+
case 'v':
|
|
1206
|
+
// Alt+V: Toggle verification mode (auto-tests after edits)
|
|
1153
1207
|
this.emit('toggleVerify');
|
|
1154
1208
|
break;
|
|
1155
1209
|
case 'c':
|
|
@@ -1216,25 +1270,12 @@ export class TerminalInput extends EventEmitter {
|
|
|
1216
1270
|
return Math.max(5, rows - chatBoxHeight - 2);
|
|
1217
1271
|
}
|
|
1218
1272
|
/**
|
|
1219
|
-
* Build scroll indicator for the divider line
|
|
1220
|
-
*
|
|
1273
|
+
* Build scroll indicator for the divider line.
|
|
1274
|
+
* Scrollback navigation is disabled in alternate screen mode.
|
|
1275
|
+
* This returns null - no scroll indicator is shown.
|
|
1221
1276
|
*/
|
|
1222
1277
|
buildScrollIndicator() {
|
|
1223
|
-
|
|
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
|
-
}
|
|
1278
|
+
// Scrollback navigation disabled - no indicator needed
|
|
1238
1279
|
return null;
|
|
1239
1280
|
}
|
|
1240
1281
|
handleSpecialKey(_str, key) {
|
|
@@ -1296,10 +1337,11 @@ export class TerminalInput extends EventEmitter {
|
|
|
1296
1337
|
}
|
|
1297
1338
|
return true;
|
|
1298
1339
|
case 'pageup':
|
|
1299
|
-
|
|
1340
|
+
// Scrollback disabled in alternate screen mode
|
|
1341
|
+
// Users should use terminal's native scrollback if available
|
|
1300
1342
|
return true;
|
|
1301
1343
|
case 'pagedown':
|
|
1302
|
-
|
|
1344
|
+
// Scrollback disabled in alternate screen mode
|
|
1303
1345
|
return true;
|
|
1304
1346
|
case 'tab':
|
|
1305
1347
|
if (key.shift) {
|
|
@@ -1693,120 +1735,53 @@ export class TerminalInput extends EventEmitter {
|
|
|
1693
1735
|
}
|
|
1694
1736
|
/**
|
|
1695
1737
|
* Scroll up by a number of lines (PageUp)
|
|
1738
|
+
* Note: Scrollback is disabled in alternate screen mode to avoid display corruption.
|
|
1739
|
+
* Users should use their terminal's native scrollback or copy/paste features.
|
|
1696
1740
|
*/
|
|
1697
|
-
scrollUp(
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
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
|
-
}
|
|
1741
|
+
scrollUp(_lines = 10) {
|
|
1742
|
+
// Scrollback disabled - alternate screen buffer doesn't support it well
|
|
1743
|
+
// The scrollback buffer is still maintained for potential future use
|
|
1744
|
+
// Users can select and copy text normally since mouse tracking is off
|
|
1708
1745
|
}
|
|
1709
1746
|
/**
|
|
1710
1747
|
* Scroll down by a number of lines (PageDown)
|
|
1748
|
+
* Note: Scrollback disabled - see scrollUp comment
|
|
1711
1749
|
*/
|
|
1712
|
-
scrollDown(
|
|
1713
|
-
|
|
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
|
-
}
|
|
1750
|
+
scrollDown(_lines = 10) {
|
|
1751
|
+
// Scrollback disabled
|
|
1722
1752
|
}
|
|
1723
1753
|
/**
|
|
1724
1754
|
* Jump to the top of scrollback buffer
|
|
1755
|
+
* DISABLED: Scrollback navigation causes display corruption in alternate screen mode.
|
|
1756
|
+
* The scrollback buffer is maintained but cannot be rendered properly.
|
|
1725
1757
|
*/
|
|
1726
1758
|
scrollToTop() {
|
|
1727
|
-
|
|
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();
|
|
1759
|
+
// Disabled - causes display corruption in alternate screen buffer
|
|
1734
1760
|
}
|
|
1735
1761
|
/**
|
|
1736
1762
|
* Jump to the bottom (live mode)
|
|
1763
|
+
* DISABLED: Scrollback navigation causes display corruption.
|
|
1737
1764
|
*/
|
|
1738
1765
|
scrollToBottom() {
|
|
1766
|
+
// Reset scrollback state in case it was somehow enabled
|
|
1739
1767
|
this.scrollbackOffset = 0;
|
|
1740
1768
|
this.isInScrollbackMode = false;
|
|
1741
|
-
this.forceRender();
|
|
1742
1769
|
}
|
|
1743
1770
|
/**
|
|
1744
1771
|
* Toggle scrollback mode on/off (Alt+S hotkey)
|
|
1772
|
+
* DISABLED: Scrollback navigation causes display corruption in alternate screen mode.
|
|
1745
1773
|
*/
|
|
1746
1774
|
toggleScrollbackMode() {
|
|
1747
|
-
|
|
1748
|
-
this.scrollToBottom();
|
|
1749
|
-
}
|
|
1750
|
-
else if (this.scrollbackBuffer.length > 0) {
|
|
1751
|
-
this.scrollUp(20);
|
|
1752
|
-
}
|
|
1775
|
+
// Disabled - alternate screen buffer doesn't support manual scrollback rendering
|
|
1753
1776
|
}
|
|
1754
1777
|
/**
|
|
1755
|
-
* Render the scrollback buffer view
|
|
1756
|
-
*
|
|
1757
|
-
*
|
|
1758
|
-
*
|
|
1759
|
-
* - Keyboard navigation hints
|
|
1760
|
-
* - Animated indicators
|
|
1778
|
+
* Render the scrollback buffer view.
|
|
1779
|
+
* DISABLED: This causes display corruption in alternate screen mode.
|
|
1780
|
+
* The alternate screen buffer has its own rendering model that conflicts with
|
|
1781
|
+
* manual scroll region manipulation.
|
|
1761
1782
|
*/
|
|
1762
1783
|
renderScrollbackView() {
|
|
1763
|
-
|
|
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();
|
|
1784
|
+
// Disabled - causes display corruption
|
|
1810
1785
|
}
|
|
1811
1786
|
/**
|
|
1812
1787
|
* Build scrollback header with navigation hints
|