erosolar-cli 1.7.155 → 1.7.157
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/README.md +7 -17
- package/agents/erosolar-code.rules.json +1 -111
- package/agents/general.rules.json +0 -6
- package/dist/bin/erosolar.js +0 -22
- package/dist/bin/erosolar.js.map +1 -1
- package/dist/bin/selfTest.js +2 -190
- package/dist/bin/selfTest.js.map +1 -1
- package/dist/core/agent.d.ts +0 -12
- package/dist/core/agent.d.ts.map +1 -1
- package/dist/core/agent.js +12 -75
- package/dist/core/agent.js.map +1 -1
- package/dist/core/contextManager.d.ts +1 -2
- package/dist/core/contextManager.d.ts.map +1 -1
- package/dist/core/contextManager.js +2 -11
- package/dist/core/contextManager.js.map +1 -1
- package/dist/core/multilinePasteHandler.d.ts +4 -8
- package/dist/core/multilinePasteHandler.d.ts.map +1 -1
- package/dist/core/multilinePasteHandler.js +17 -67
- package/dist/core/multilinePasteHandler.js.map +1 -1
- package/dist/core/preferences.js +2 -8
- package/dist/core/preferences.js.map +1 -1
- package/dist/core/resultVerification.d.ts +1 -1
- package/dist/core/resultVerification.d.ts.map +1 -1
- package/dist/core/resultVerification.js +10 -11
- package/dist/core/resultVerification.js.map +1 -1
- package/dist/core/schemaValidator.d.ts.map +1 -1
- package/dist/core/schemaValidator.js +1 -36
- package/dist/core/schemaValidator.js.map +1 -1
- package/dist/core/toolRuntime.d.ts +0 -61
- package/dist/core/toolRuntime.d.ts.map +1 -1
- package/dist/core/toolRuntime.js +1 -303
- package/dist/core/toolRuntime.js.map +1 -1
- package/dist/core/unified/schema.d.ts.map +1 -1
- package/dist/core/unified/schema.js +1 -34
- package/dist/core/unified/schema.js.map +1 -1
- package/dist/headless/headlessApp.d.ts.map +1 -1
- package/dist/headless/headlessApp.js +0 -3
- package/dist/headless/headlessApp.js.map +1 -1
- package/dist/providers/anthropicProvider.d.ts.map +1 -1
- package/dist/providers/anthropicProvider.js +1 -4
- package/dist/providers/anthropicProvider.js.map +1 -1
- package/dist/shell/bracketedPasteManager.d.ts.map +1 -1
- package/dist/shell/bracketedPasteManager.js +3 -5
- package/dist/shell/bracketedPasteManager.js.map +1 -1
- package/dist/shell/composableMessage.js +2 -2
- package/dist/shell/composableMessage.js.map +1 -1
- package/dist/shell/interactiveShell.d.ts +1 -6
- package/dist/shell/interactiveShell.d.ts.map +1 -1
- package/dist/shell/interactiveShell.js +95 -421
- package/dist/shell/interactiveShell.js.map +1 -1
- package/dist/shell/shellApp.d.ts.map +1 -1
- package/dist/shell/shellApp.js +0 -7
- package/dist/shell/shellApp.js.map +1 -1
- package/dist/shell/systemPrompt.js +5 -5
- package/dist/shell/systemPrompt.js.map +1 -1
- package/dist/tools/bashTools.d.ts +0 -8
- package/dist/tools/bashTools.d.ts.map +1 -1
- package/dist/tools/bashTools.js +5 -80
- package/dist/tools/bashTools.js.map +1 -1
- package/dist/tools/diffUtils.d.ts.map +1 -1
- package/dist/tools/diffUtils.js +8 -12
- package/dist/tools/diffUtils.js.map +1 -1
- package/dist/tools/editTools.d.ts.map +1 -1
- package/dist/tools/editTools.js +36 -386
- package/dist/tools/editTools.js.map +1 -1
- package/dist/tools/fileTools.d.ts.map +1 -1
- package/dist/tools/fileTools.js +130 -25
- package/dist/tools/fileTools.js.map +1 -1
- package/dist/ui/ShellUIAdapter.d.ts +0 -5
- package/dist/ui/ShellUIAdapter.d.ts.map +1 -1
- package/dist/ui/ShellUIAdapter.js +0 -21
- package/dist/ui/ShellUIAdapter.js.map +1 -1
- package/dist/ui/persistentPrompt.d.ts +10 -83
- package/dist/ui/persistentPrompt.d.ts.map +1 -1
- package/dist/ui/persistentPrompt.js +119 -521
- package/dist/ui/persistentPrompt.js.map +1 -1
- package/dist/ui/richText.js +4 -4
- package/dist/ui/richText.js.map +1 -1
- package/dist/ui/shortcutsHelp.d.ts.map +1 -1
- package/dist/ui/shortcutsHelp.js +13 -31
- package/dist/ui/shortcutsHelp.js.map +1 -1
- package/package.json +1 -4
- package/dist/ui/EnhancedPinnedChatBox.d.ts +0 -93
- package/dist/ui/EnhancedPinnedChatBox.d.ts.map +0 -1
- package/dist/ui/EnhancedPinnedChatBox.js +0 -309
- package/dist/ui/EnhancedPinnedChatBox.js.map +0 -1
- package/dist/ui/PinnedChatBoxEnhancer.d.ts +0 -88
- package/dist/ui/PinnedChatBoxEnhancer.d.ts.map +0 -1
- package/dist/ui/PinnedChatBoxEnhancer.js +0 -205
- package/dist/ui/PinnedChatBoxEnhancer.js.map +0 -1
|
@@ -215,8 +215,8 @@ const ANSI = {
|
|
|
215
215
|
export class PinnedChatBox {
|
|
216
216
|
writeStream;
|
|
217
217
|
state;
|
|
218
|
+
reservedLines = 2; // Lines reserved at bottom for status bar (readline handles input)
|
|
218
219
|
_lastRenderedHeight = 0;
|
|
219
|
-
maxInputLines = 5; // Max lines to show before truncating
|
|
220
220
|
inputBuffer = '';
|
|
221
221
|
cursorPosition = 0;
|
|
222
222
|
commandIdCounter = 0;
|
|
@@ -234,8 +234,6 @@ export class PinnedChatBox {
|
|
|
234
234
|
maxStatusMessageLength = 200;
|
|
235
235
|
// Scroll region management for persistent bottom input
|
|
236
236
|
scrollRegionActive = false;
|
|
237
|
-
// Track the row position we should return to after rendering status (bottom of scroll region)
|
|
238
|
-
scrollRegionBottomRow = 0;
|
|
239
237
|
// Input history for up/down navigation
|
|
240
238
|
inputHistory = [];
|
|
241
239
|
historyIndex = -1;
|
|
@@ -246,13 +244,6 @@ export class PinnedChatBox {
|
|
|
246
244
|
pastedFullContent = '';
|
|
247
245
|
/** Cleanup function for output interceptor registration */
|
|
248
246
|
outputInterceptorCleanup;
|
|
249
|
-
// Performance: Track if we're in the middle of a rapid input sequence
|
|
250
|
-
inputSequenceTimer = null;
|
|
251
|
-
// Visual state: Track if cursor should blink
|
|
252
|
-
showCursor = true;
|
|
253
|
-
cursorBlinkTimer = null;
|
|
254
|
-
// Session tracking for elapsed time display
|
|
255
|
-
sessionStartTime = Date.now();
|
|
256
247
|
constructor(writeStream, _promptText = '> ', // Unused - readline handles input display
|
|
257
248
|
options = {}) {
|
|
258
249
|
this.writeStream = writeStream;
|
|
@@ -262,7 +253,6 @@ export class PinnedChatBox {
|
|
|
262
253
|
this.maxQueueSize = options.maxQueueSize ?? 100;
|
|
263
254
|
this.state = {
|
|
264
255
|
isProcessing: false,
|
|
265
|
-
isReasoningModel: false,
|
|
266
256
|
queuedCommands: [],
|
|
267
257
|
currentInput: '',
|
|
268
258
|
contextUsage: 0,
|
|
@@ -276,11 +266,6 @@ export class PinnedChatBox {
|
|
|
276
266
|
* Register with the display's output interceptor system.
|
|
277
267
|
* With scroll regions, output flows in the scrollable area while
|
|
278
268
|
* the input stays pinned at the bottom.
|
|
279
|
-
*
|
|
280
|
-
* NOTE: We no longer re-render the persistent input on every afterWrite
|
|
281
|
-
* because this caused the input to appear multiple times in the scroll
|
|
282
|
-
* output (the chat box was being "logged" to the terminal history).
|
|
283
|
-
* Instead, we only render when state actually changes (via scheduleRender).
|
|
284
269
|
*/
|
|
285
270
|
registerOutputInterceptor(display) {
|
|
286
271
|
if (this.outputInterceptorCleanup) {
|
|
@@ -292,333 +277,109 @@ export class PinnedChatBox {
|
|
|
292
277
|
// No need to clear - terminal handles it
|
|
293
278
|
},
|
|
294
279
|
afterWrite: () => {
|
|
295
|
-
//
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
280
|
+
// Debounced re-render of persistent input after streaming output
|
|
281
|
+
if (this.scrollRegionActive && this.state.isProcessing) {
|
|
282
|
+
if (this.pendingAfterWriteRender) {
|
|
283
|
+
clearTimeout(this.pendingAfterWriteRender);
|
|
284
|
+
}
|
|
285
|
+
// Debounce to ~30fps to avoid excessive rendering during fast streaming
|
|
286
|
+
this.pendingAfterWriteRender = setTimeout(() => {
|
|
287
|
+
this.pendingAfterWriteRender = null;
|
|
288
|
+
if (!this.isDisposed && this.scrollRegionActive) {
|
|
289
|
+
this.renderPersistentInput();
|
|
290
|
+
}
|
|
291
|
+
}, 33);
|
|
292
|
+
}
|
|
301
293
|
},
|
|
302
294
|
});
|
|
303
295
|
}
|
|
304
296
|
/**
|
|
305
297
|
* Enable scroll region to keep bottom lines reserved for input.
|
|
306
298
|
* Content scrolls in the upper region, input stays pinned at bottom.
|
|
307
|
-
*
|
|
308
|
-
* Optimized for stability:
|
|
309
|
-
* - Validates terminal dimensions before setting region
|
|
310
|
-
* - Uses atomic cursor save/restore operations
|
|
311
|
-
* - Ensures clean state transition
|
|
312
299
|
*/
|
|
313
300
|
enableScrollRegion() {
|
|
314
301
|
if (!this.supportsRendering() || this.scrollRegionActive)
|
|
315
302
|
return;
|
|
316
303
|
const rows = this.writeStream.rows || 24;
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
// Track the bottom row of the scroll region for reference
|
|
323
|
-
this.scrollRegionBottomRow = rows - reservedHeight;
|
|
324
|
-
// Hide cursor during setup
|
|
325
|
-
this.safeWrite(ANSI.HIDE_CURSOR);
|
|
326
|
-
// First, scroll terminal content up to make room for the chat box
|
|
327
|
-
// This ensures prompt and content are visible above the reserved area
|
|
328
|
-
// Move cursor to where chat box will be, then scroll up
|
|
329
|
-
this.safeWrite(`\u001b[${rows};1H`); // Move to bottom
|
|
330
|
-
for (let i = 0; i < reservedHeight; i++) {
|
|
331
|
-
this.safeWrite('\n'); // Scroll content up
|
|
332
|
-
}
|
|
333
|
-
// Set scroll region to exclude bottom lines (1 to scrollRegionBottomRow)
|
|
334
|
-
this.safeWrite(ANSI.SET_SCROLL_REGION(1, this.scrollRegionBottomRow));
|
|
304
|
+
const reservedHeight = this.reservedLines;
|
|
305
|
+
// Save cursor position so streaming continues from current location
|
|
306
|
+
this.safeWrite(ANSI.SAVE_CURSOR);
|
|
307
|
+
// Set scroll region to exclude bottom lines
|
|
308
|
+
this.safeWrite(ANSI.SET_SCROLL_REGION(1, rows - reservedHeight));
|
|
335
309
|
this.scrollRegionActive = true;
|
|
336
|
-
// Force immediate render to ensure persistent chat box is visible
|
|
337
|
-
this.forceRender();
|
|
338
310
|
// Render the persistent input area at the bottom (outside scroll region)
|
|
339
|
-
this.
|
|
340
|
-
//
|
|
341
|
-
//
|
|
342
|
-
this.safeWrite(
|
|
343
|
-
// Keep terminal cursor hidden during streaming - we show a visual cursor in the chat box instead
|
|
344
|
-
// This prevents the cursor from appearing in the stream output area
|
|
345
|
-
// The visual cursor (inverted block) in renderPersistentInput() provides user feedback
|
|
346
|
-
// Start cursor blink for visual feedback that input is active
|
|
347
|
-
this.startCursorBlink();
|
|
348
|
-
}
|
|
349
|
-
/**
|
|
350
|
-
* Render only the status bar at bottom without affecting cursor position.
|
|
351
|
-
* Used during initial scroll region setup.
|
|
352
|
-
*/
|
|
353
|
-
renderStatusBarOnly() {
|
|
354
|
-
if (!this.supportsRendering())
|
|
355
|
-
return;
|
|
356
|
-
const rows = this.writeStream.rows || 24;
|
|
357
|
-
const cols = Math.max(this.writeStream.columns || 80, 40);
|
|
358
|
-
const borderWidth = cols - 2;
|
|
359
|
-
// Calculate starting row (4 lines: top border + input + bottom border + status)
|
|
360
|
-
let currentRow = rows - 3;
|
|
361
|
-
// Build status line
|
|
362
|
-
const queueCount = this.state.queuedCommands.length;
|
|
363
|
-
let statusText = '';
|
|
364
|
-
if (this.state.isProcessing) {
|
|
365
|
-
const icon = this.state.isReasoningModel ? '🧠' : '⏳';
|
|
366
|
-
const queuePart = queueCount > 0 ? ` +${queueCount}` : '';
|
|
367
|
-
statusText = `${icon}${queuePart} [Enter: queue]`;
|
|
368
|
-
}
|
|
369
|
-
// Use absolute row positioning to prevent scroll history leakage
|
|
370
|
-
// Top border with status on right
|
|
371
|
-
const statusSuffix = statusText ? ` ${statusText}` : '';
|
|
372
|
-
const topBorderWidth = Math.max(10, borderWidth - statusSuffix.length);
|
|
373
|
-
this.safeWrite(`\u001b[${currentRow};1H${ANSI.CLEAR_LINE}`);
|
|
374
|
-
this.safeWrite(theme.ui.border('─'.repeat(topBorderWidth)) + theme.ui.muted(statusSuffix));
|
|
375
|
-
currentRow++;
|
|
376
|
-
// Input line with prompt
|
|
377
|
-
this.safeWrite(`\u001b[${currentRow};1H${ANSI.CLEAR_LINE}`);
|
|
378
|
-
this.safeWrite(theme.ui.muted('> '));
|
|
379
|
-
currentRow++;
|
|
380
|
-
// Bottom border
|
|
381
|
-
this.safeWrite(`\u001b[${currentRow};1H${ANSI.CLEAR_LINE}`);
|
|
382
|
-
this.safeWrite(theme.ui.border('─'.repeat(borderWidth)));
|
|
383
|
-
currentRow++;
|
|
384
|
-
// Status line (context + time)
|
|
385
|
-
const contextTimeLine = this.buildContextTimeStatus(cols);
|
|
386
|
-
if (contextTimeLine) {
|
|
387
|
-
this.safeWrite(`\u001b[${currentRow};1H${ANSI.CLEAR_LINE}`);
|
|
388
|
-
this.safeWrite(contextTimeLine);
|
|
389
|
-
}
|
|
311
|
+
this.renderPersistentInput();
|
|
312
|
+
// Restore cursor to original position so streaming output appears
|
|
313
|
+
// right below the user's prompt, not at the bottom of the screen
|
|
314
|
+
this.safeWrite(ANSI.RESTORE_CURSOR);
|
|
390
315
|
}
|
|
391
316
|
/**
|
|
392
317
|
* Disable scroll region and restore normal terminal behavior.
|
|
393
|
-
* Ensures clean state restoration.
|
|
394
318
|
*/
|
|
395
319
|
disableScrollRegion() {
|
|
396
320
|
if (!this.supportsRendering() || !this.scrollRegionActive)
|
|
397
321
|
return;
|
|
398
|
-
// Stop cursor blink
|
|
399
|
-
this.stopCursorBlink();
|
|
400
|
-
// Atomic operation: hide cursor, reset region, show cursor
|
|
401
|
-
this.safeWrite(ANSI.HIDE_CURSOR);
|
|
402
322
|
// Reset scroll region to full terminal
|
|
403
323
|
this.safeWrite(ANSI.RESET_SCROLL_REGION);
|
|
404
324
|
this.scrollRegionActive = false;
|
|
405
|
-
this.safeWrite(ANSI.SHOW_CURSOR);
|
|
406
|
-
}
|
|
407
|
-
/**
|
|
408
|
-
* Start cursor blink animation for visual feedback during typing
|
|
409
|
-
*/
|
|
410
|
-
startCursorBlink() {
|
|
411
|
-
if (this.cursorBlinkTimer)
|
|
412
|
-
return;
|
|
413
|
-
this.showCursor = true;
|
|
414
|
-
this.cursorBlinkTimer = setInterval(() => {
|
|
415
|
-
if (!this.scrollRegionActive || this.isDisposed) {
|
|
416
|
-
this.stopCursorBlink();
|
|
417
|
-
return;
|
|
418
|
-
}
|
|
419
|
-
this.showCursor = !this.showCursor;
|
|
420
|
-
// Only re-render if we're actually showing the input line
|
|
421
|
-
if (this.state.isProcessing && this.inputBuffer.length > 0) {
|
|
422
|
-
this.renderPersistentInput();
|
|
423
|
-
}
|
|
424
|
-
}, 530); // Standard cursor blink rate
|
|
425
|
-
}
|
|
426
|
-
/**
|
|
427
|
-
* Stop cursor blink animation
|
|
428
|
-
*/
|
|
429
|
-
stopCursorBlink() {
|
|
430
|
-
if (this.cursorBlinkTimer) {
|
|
431
|
-
clearInterval(this.cursorBlinkTimer);
|
|
432
|
-
this.cursorBlinkTimer = null;
|
|
433
|
-
}
|
|
434
|
-
this.showCursor = true;
|
|
435
|
-
}
|
|
436
|
-
/**
|
|
437
|
-
* Calculate how many lines the current input needs for display
|
|
438
|
-
*/
|
|
439
|
-
calculateInputLines(cols) {
|
|
440
|
-
if (!this.inputBuffer)
|
|
441
|
-
return 1;
|
|
442
|
-
const contentWidth = cols - 4; // Account for "> " prompt and padding
|
|
443
|
-
const lines = this.inputBuffer.split('\n');
|
|
444
|
-
let totalLines = 0;
|
|
445
|
-
for (const line of lines) {
|
|
446
|
-
// Each line wraps based on terminal width
|
|
447
|
-
totalLines += Math.max(1, Math.ceil(line.length / contentWidth));
|
|
448
|
-
}
|
|
449
|
-
return Math.min(totalLines, this.maxInputLines);
|
|
450
|
-
}
|
|
451
|
-
/**
|
|
452
|
-
* Get the current reserved line count based on input content
|
|
453
|
-
*/
|
|
454
|
-
getReservedLines() {
|
|
455
|
-
const cols = this.writeStream.columns || 80;
|
|
456
|
-
const inputLines = this.calculateInputLines(cols);
|
|
457
|
-
return inputLines + 3; // top border + input lines + bottom border + status line
|
|
458
325
|
}
|
|
459
326
|
/**
|
|
460
327
|
* Render the persistent input area at the bottom of the terminal.
|
|
461
328
|
* Called when scroll region is active to keep input visible during streaming.
|
|
462
|
-
*
|
|
463
|
-
* Layout:
|
|
464
|
-
* ─────────────────────────────────────────────────── ⏳ +2 [Enter: queue]
|
|
465
|
-
* > input text here
|
|
466
|
-
* continued on next line if needed
|
|
467
|
-
* ───────────────────────────────────────────────────────────────────────
|
|
329
|
+
* Shows actual input line so users can see what they're typing.
|
|
468
330
|
*/
|
|
469
331
|
renderPersistentInput() {
|
|
470
332
|
if (!this.supportsRendering())
|
|
471
333
|
return;
|
|
472
|
-
// IMPORTANT: Only render when scroll region is active
|
|
473
|
-
// This prevents the chat box from leaking into the scroll history
|
|
474
|
-
if (!this.scrollRegionActive)
|
|
475
|
-
return;
|
|
476
334
|
const rows = this.writeStream.rows || 24;
|
|
477
335
|
const cols = Math.max(this.writeStream.columns || 80, 40);
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
//
|
|
483
|
-
const startRow = rows - totalHeight + 1;
|
|
484
|
-
let currentRow = startRow;
|
|
485
|
-
// Build the entire output as a single string for atomic write
|
|
486
|
-
const output = [];
|
|
487
|
-
// CRITICAL FIX FOR UI LEAK:
|
|
488
|
-
// 1. Save cursor position (so streaming can continue from where it was)
|
|
489
|
-
// 2. Use absolute row positioning (no newlines that add to scroll buffer)
|
|
490
|
-
// 3. Restore cursor position (streaming continues seamlessly)
|
|
491
|
-
output.push('\u001b7'); // DECSC - save cursor position
|
|
492
|
-
// Build status indicators for top border
|
|
336
|
+
// Save cursor position
|
|
337
|
+
this.safeWrite(ANSI.SAVE_CURSOR);
|
|
338
|
+
// Move to the reserved bottom area (outside scroll region)
|
|
339
|
+
this.safeWrite(ANSI.CURSOR_TO_BOTTOM(rows - 1));
|
|
340
|
+
// Build status line
|
|
493
341
|
const queueCount = this.state.queuedCommands.length;
|
|
494
342
|
let statusText = '';
|
|
495
343
|
if (this.state.isProcessing) {
|
|
496
|
-
const
|
|
497
|
-
|
|
498
|
-
statusText = `${icon}${queuePart} [Enter: queue]`;
|
|
344
|
+
const queuePart = queueCount > 0 ? ` (${queueCount} queued)` : '';
|
|
345
|
+
statusText = `⏳ Processing...${queuePart} [Esc: cancel · Enter: queue]`;
|
|
499
346
|
}
|
|
500
|
-
else if (
|
|
501
|
-
statusText =
|
|
347
|
+
else if (this.state.statusMessage) {
|
|
348
|
+
statusText = this.state.statusMessage;
|
|
502
349
|
}
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
//
|
|
350
|
+
else if (queueCount > 0) {
|
|
351
|
+
statusText = `${queueCount} follow-up${queueCount === 1 ? '' : 's'} queued`;
|
|
352
|
+
}
|
|
353
|
+
// Render separator and status on bottom lines
|
|
354
|
+
const separatorWidth = Math.min(cols - 2, 72);
|
|
355
|
+
const separator = theme.ui.border('─'.repeat(separatorWidth));
|
|
356
|
+
// Line rows-1: separator
|
|
357
|
+
this.safeWrite(ANSI.CLEAR_LINE);
|
|
358
|
+
this.safeWrite(separator);
|
|
359
|
+
// Line rows: input line with prompt or status
|
|
360
|
+
this.safeWrite(`\n${ANSI.CLEAR_LINE}`);
|
|
361
|
+
// Show actual input line during processing so user can see what they're typing
|
|
362
|
+
const promptPrefix = theme.ui.muted('> ');
|
|
510
363
|
const currentInput = this.inputBuffer;
|
|
511
|
-
const
|
|
512
|
-
if
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
const lineContent = displayLines[i] ?? '';
|
|
527
|
-
const isFirstLine = i === 0;
|
|
528
|
-
const prompt = isFirstLine ? theme.ui.muted('> ') : ' ';
|
|
529
|
-
output.push(prompt);
|
|
530
|
-
// Check if cursor is on this line
|
|
531
|
-
const cursorOnThisLine = this.isCursorOnDisplayLine(i, displayLines, contentWidth);
|
|
532
|
-
if (cursorOnThisLine !== false && this.showCursor) {
|
|
533
|
-
// Render with cursor
|
|
534
|
-
const cursorCol = cursorOnThisLine;
|
|
535
|
-
const before = lineContent.slice(0, cursorCol);
|
|
536
|
-
const at = lineContent[cursorCol] || ' ';
|
|
537
|
-
const after = lineContent.slice(cursorCol + 1);
|
|
538
|
-
output.push(before);
|
|
539
|
-
output.push(`\u001b[7m${at}\u001b[27m`); // Inverted for cursor
|
|
540
|
-
output.push(after);
|
|
541
|
-
}
|
|
542
|
-
else {
|
|
543
|
-
output.push(lineContent);
|
|
544
|
-
}
|
|
545
|
-
currentRow++;
|
|
546
|
-
}
|
|
547
|
-
// Show truncation indicator if content exceeds max lines
|
|
548
|
-
if (this.inputBuffer.split('\n').length > this.maxInputLines ||
|
|
549
|
-
this.inputBuffer.length > contentWidth * this.maxInputLines) {
|
|
550
|
-
const truncatedLines = this.inputBuffer.split('\n').length - this.maxInputLines;
|
|
551
|
-
if (truncatedLines > 0) {
|
|
552
|
-
output.push(theme.ui.muted(` (+${truncatedLines} more lines)`));
|
|
553
|
-
}
|
|
364
|
+
const maxInputDisplay = cols - 4; // Reserve space for prompt and cursor
|
|
365
|
+
// Truncate input if too long, showing end of input
|
|
366
|
+
let displayInput = currentInput;
|
|
367
|
+
if (displayInput.length > maxInputDisplay) {
|
|
368
|
+
displayInput = '…' + displayInput.slice(-(maxInputDisplay - 1));
|
|
369
|
+
}
|
|
370
|
+
// Render prompt + input
|
|
371
|
+
this.safeWrite(promptPrefix);
|
|
372
|
+
this.safeWrite(displayInput);
|
|
373
|
+
// Show status hint on the right if there's room
|
|
374
|
+
if (statusText && currentInput.length < maxInputDisplay - statusText.length - 5) {
|
|
375
|
+
const padding = cols - 3 - currentInput.length - statusText.length - 3;
|
|
376
|
+
if (padding > 3) {
|
|
377
|
+
this.safeWrite(' '.repeat(padding));
|
|
378
|
+
this.safeWrite(theme.ui.muted(statusText.slice(0, cols - currentInput.length - 6)));
|
|
554
379
|
}
|
|
555
380
|
}
|
|
556
|
-
//
|
|
557
|
-
|
|
558
|
-
currentRow++;
|
|
559
|
-
// Status line below: Context usage and session elapsed time
|
|
560
|
-
const statusLine = this.buildContextTimeStatus(cols);
|
|
561
|
-
if (statusLine) {
|
|
562
|
-
output.push(`\u001b[${currentRow};1H${ANSI.CLEAR_LINE}${statusLine}`);
|
|
563
|
-
}
|
|
564
|
-
// Restore cursor position so streaming continues from where it left off
|
|
565
|
-
// This is critical - without it, streaming would jump to a fixed position
|
|
566
|
-
output.push('\u001b8'); // DECRC - restore cursor position
|
|
567
|
-
// Ensure terminal cursor stays hidden in the stream area
|
|
568
|
-
// We show a visual cursor (inverted block) in the chat box instead
|
|
569
|
-
output.push(ANSI.HIDE_CURSOR);
|
|
570
|
-
// Single atomic write for smoothness
|
|
571
|
-
this.safeWrite(output.join(''));
|
|
572
|
-
this._lastRenderedHeight = totalHeight;
|
|
573
|
-
}
|
|
574
|
-
/**
|
|
575
|
-
* Wrap input text into display lines for rendering
|
|
576
|
-
*/
|
|
577
|
-
wrapInputForDisplay(input, maxWidth, maxLines) {
|
|
578
|
-
const lines = [];
|
|
579
|
-
const inputLines = input.split('\n');
|
|
580
|
-
for (const line of inputLines) {
|
|
581
|
-
if (lines.length >= maxLines)
|
|
582
|
-
break;
|
|
583
|
-
if (line.length <= maxWidth) {
|
|
584
|
-
lines.push(line);
|
|
585
|
-
}
|
|
586
|
-
else {
|
|
587
|
-
// Wrap long lines
|
|
588
|
-
for (let i = 0; i < line.length && lines.length < maxLines; i += maxWidth) {
|
|
589
|
-
lines.push(line.slice(i, i + maxWidth));
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
|
-
return lines;
|
|
594
|
-
}
|
|
595
|
-
/**
|
|
596
|
-
* Determine if cursor should appear on a given display line
|
|
597
|
-
* Returns the column position if yes, false if no
|
|
598
|
-
*/
|
|
599
|
-
isCursorOnDisplayLine(lineIndex, displayLines, _maxWidth) {
|
|
600
|
-
// Calculate which display line the cursor is on
|
|
601
|
-
let charCount = 0;
|
|
602
|
-
for (let i = 0; i < displayLines.length; i++) {
|
|
603
|
-
const line = displayLines[i] ?? '';
|
|
604
|
-
const lineLength = line.length;
|
|
605
|
-
const lineEnd = charCount + lineLength;
|
|
606
|
-
// Account for newlines in original input
|
|
607
|
-
if (i > 0)
|
|
608
|
-
charCount++; // +1 for the newline character
|
|
609
|
-
if (this.cursorPosition >= charCount && this.cursorPosition <= lineEnd) {
|
|
610
|
-
if (i === lineIndex) {
|
|
611
|
-
return this.cursorPosition - charCount;
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
|
-
charCount = lineEnd;
|
|
615
|
-
}
|
|
616
|
-
// Cursor at end of last line
|
|
617
|
-
if (lineIndex === displayLines.length - 1 && this.cursorPosition >= charCount) {
|
|
618
|
-
const lastLine = displayLines[lineIndex] ?? '';
|
|
619
|
-
return lastLine.length;
|
|
620
|
-
}
|
|
621
|
-
return false;
|
|
381
|
+
// Restore cursor to scroll region
|
|
382
|
+
this.safeWrite(ANSI.RESTORE_CURSOR);
|
|
622
383
|
}
|
|
623
384
|
/**
|
|
624
385
|
* Update the persistent input during streaming.
|
|
@@ -639,53 +400,6 @@ export class PinnedChatBox {
|
|
|
639
400
|
typeof this.writeStream.write === 'function' &&
|
|
640
401
|
this.writeStream.writable !== false);
|
|
641
402
|
}
|
|
642
|
-
/**
|
|
643
|
-
* Build status line showing context usage and session elapsed time
|
|
644
|
-
*/
|
|
645
|
-
buildContextTimeStatus(cols) {
|
|
646
|
-
const parts = [];
|
|
647
|
-
// Context usage with visual bar
|
|
648
|
-
const contextPct = this.state.contextUsage;
|
|
649
|
-
if (contextPct > 0) {
|
|
650
|
-
const barWidth = 10;
|
|
651
|
-
const filled = Math.round((contextPct / 100) * barWidth);
|
|
652
|
-
const empty = barWidth - filled;
|
|
653
|
-
const bar = `${'█'.repeat(filled)}${'░'.repeat(empty)}`;
|
|
654
|
-
// Color based on usage level
|
|
655
|
-
let contextColor = theme.success;
|
|
656
|
-
if (contextPct >= 80)
|
|
657
|
-
contextColor = theme.error;
|
|
658
|
-
else if (contextPct >= 60)
|
|
659
|
-
contextColor = theme.warning;
|
|
660
|
-
parts.push(`${theme.ui.muted('Context')} ${contextColor(bar)} ${contextColor(`${contextPct}%`)}`);
|
|
661
|
-
}
|
|
662
|
-
// Session elapsed time
|
|
663
|
-
const elapsedMs = Date.now() - this.sessionStartTime;
|
|
664
|
-
const elapsedSecs = Math.floor(elapsedMs / 1000);
|
|
665
|
-
const elapsedStr = this.formatElapsedTime(elapsedSecs);
|
|
666
|
-
parts.push(`${theme.ui.muted('Session')} ${theme.info(elapsedStr)}`);
|
|
667
|
-
if (parts.length === 0)
|
|
668
|
-
return '';
|
|
669
|
-
// Center the status line
|
|
670
|
-
const statusText = parts.join(theme.ui.muted(' │ '));
|
|
671
|
-
const visibleLen = this.stripAnsi(statusText).length;
|
|
672
|
-
const padding = Math.max(0, Math.floor((cols - visibleLen) / 2));
|
|
673
|
-
return ' '.repeat(padding) + statusText;
|
|
674
|
-
}
|
|
675
|
-
/**
|
|
676
|
-
* Format elapsed seconds as human-readable time
|
|
677
|
-
*/
|
|
678
|
-
formatElapsedTime(seconds) {
|
|
679
|
-
if (seconds < 60)
|
|
680
|
-
return `${seconds}s`;
|
|
681
|
-
const mins = Math.floor(seconds / 60);
|
|
682
|
-
const secs = seconds % 60;
|
|
683
|
-
if (mins < 60)
|
|
684
|
-
return secs > 0 ? `${mins}m ${secs}s` : `${mins}m`;
|
|
685
|
-
const hours = Math.floor(mins / 60);
|
|
686
|
-
const remainMins = mins % 60;
|
|
687
|
-
return `${hours}h ${remainMins}m`;
|
|
688
|
-
}
|
|
689
403
|
/**
|
|
690
404
|
* Strip ANSI escape codes
|
|
691
405
|
*/
|
|
@@ -751,15 +465,6 @@ export class PinnedChatBox {
|
|
|
751
465
|
this.scheduleRender();
|
|
752
466
|
}
|
|
753
467
|
}
|
|
754
|
-
/**
|
|
755
|
-
* Set whether current model is a reasoning model (o1, deepseek-reasoner).
|
|
756
|
-
* Reasoning models don't stream text - they think internally then respond.
|
|
757
|
-
*/
|
|
758
|
-
setReasoningModel(isReasoning) {
|
|
759
|
-
if (this.isDisposed)
|
|
760
|
-
return;
|
|
761
|
-
this.state.isReasoningModel = isReasoning;
|
|
762
|
-
}
|
|
763
468
|
/**
|
|
764
469
|
* Set processing state
|
|
765
470
|
* Enables scroll region during processing to keep input visible while streaming
|
|
@@ -881,19 +586,14 @@ export class PinnedChatBox {
|
|
|
881
586
|
/**
|
|
882
587
|
* Handle character input with validation
|
|
883
588
|
* Detects multiline paste and stores full content while showing summary
|
|
884
|
-
*
|
|
885
|
-
* Optimized for fast typing:
|
|
886
|
-
* - Batches rapid keystrokes
|
|
887
|
-
* - Immediate visual feedback
|
|
888
|
-
* - Efficient state updates
|
|
889
589
|
*/
|
|
890
590
|
handleInput(char) {
|
|
891
591
|
if (!this.isEnabled || this.isDisposed)
|
|
892
592
|
return;
|
|
893
593
|
if (typeof char !== 'string')
|
|
894
594
|
return;
|
|
895
|
-
// Detect multiline paste (content with newlines
|
|
896
|
-
if (char.includes('\n')
|
|
595
|
+
// Detect multiline paste (content with newlines)
|
|
596
|
+
if (char.includes('\n')) {
|
|
897
597
|
this.handleMultilinePaste(char);
|
|
898
598
|
return;
|
|
899
599
|
}
|
|
@@ -914,122 +614,63 @@ export class PinnedChatBox {
|
|
|
914
614
|
this.inputBuffer.slice(this.cursorPosition);
|
|
915
615
|
this.cursorPosition = Math.min(this.cursorPosition + chunk.length, this.inputBuffer.length);
|
|
916
616
|
this.state.currentInput = this.inputBuffer;
|
|
917
|
-
|
|
918
|
-
this.showCursor = true;
|
|
919
|
-
// Fast render for single keystrokes during streaming
|
|
920
|
-
if (this.scrollRegionActive && this.state.isProcessing) {
|
|
921
|
-
// During streaming: immediate render for responsiveness
|
|
922
|
-
this.renderPersistentInput();
|
|
923
|
-
}
|
|
924
|
-
else {
|
|
925
|
-
// Normal mode: use standard scheduling
|
|
926
|
-
this.scheduleRender();
|
|
927
|
-
}
|
|
617
|
+
this.scheduleRender();
|
|
928
618
|
}
|
|
929
619
|
/**
|
|
930
|
-
* Handle multiline paste -
|
|
931
|
-
* Claude Code style: show all lines unless very long, then show first/last lines with ellipsis
|
|
932
|
-
* CRITICAL: Must work correctly in all circumstances:
|
|
933
|
-
* - During streaming (scroll region active)
|
|
934
|
-
* - During idle (normal readline mode)
|
|
935
|
-
* - Small/medium pastes (≤15 lines) - show full content
|
|
936
|
-
* - Large pastes (>15 lines) - show block with first/last lines
|
|
620
|
+
* Handle multiline paste - store full content but display summary
|
|
937
621
|
*/
|
|
938
622
|
handleMultilinePaste(content) {
|
|
939
623
|
const lines = content.split('\n');
|
|
940
624
|
const lineCount = lines.length;
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
this.cursorPosition = content.length;
|
|
950
|
-
this.state.currentInput = content;
|
|
951
|
-
}
|
|
952
|
-
else {
|
|
953
|
-
// Show summarized block for large pastes
|
|
954
|
-
this.pastedFullContent = content;
|
|
955
|
-
this.isPastedBlock = true;
|
|
956
|
-
const summary = this.generatePasteSummary(content, lineCount);
|
|
957
|
-
this.inputBuffer = summary;
|
|
958
|
-
this.cursorPosition = summary.length;
|
|
959
|
-
this.state.currentInput = summary;
|
|
960
|
-
}
|
|
961
|
-
// Use consistent rendering approach as handleInput:
|
|
962
|
-
// During streaming with scroll region active, render immediately
|
|
963
|
-
// Otherwise use scheduled render
|
|
964
|
-
if (this.scrollRegionActive && this.state.isProcessing) {
|
|
965
|
-
this.renderPersistentInput();
|
|
966
|
-
}
|
|
967
|
-
else {
|
|
968
|
-
this.scheduleRender();
|
|
969
|
-
}
|
|
625
|
+
this.pastedFullContent = content;
|
|
626
|
+
this.isPastedBlock = true;
|
|
627
|
+
// Generate a summary for display
|
|
628
|
+
const summary = this.generatePasteSummary(content, lineCount);
|
|
629
|
+
this.inputBuffer = summary;
|
|
630
|
+
this.cursorPosition = summary.length;
|
|
631
|
+
this.state.currentInput = summary;
|
|
632
|
+
this.scheduleRender();
|
|
970
633
|
}
|
|
971
634
|
/**
|
|
972
|
-
* Generate a
|
|
973
|
-
* Shows first few lines, ellipsis with count, and last line
|
|
635
|
+
* Generate a short summary of pasted content for display
|
|
974
636
|
*/
|
|
975
637
|
generatePasteSummary(content, lineCount) {
|
|
976
638
|
const lines = content.split('\n');
|
|
639
|
+
const firstLine = lines[0]?.trim() || '';
|
|
977
640
|
const charCount = content.length;
|
|
978
|
-
// Detect content type
|
|
979
|
-
|
|
980
|
-
let
|
|
981
|
-
const
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
}
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
const
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
for (let i = 0; i < Math.min(showFirstLines, lines.length); i++) {
|
|
1012
|
-
let line = lines[i] || '';
|
|
1013
|
-
if (line.length > maxLineWidth) {
|
|
1014
|
-
line = line.slice(0, maxLineWidth - 1) + '…';
|
|
1015
|
-
}
|
|
1016
|
-
displayLines.push(` ${line}`);
|
|
1017
|
-
}
|
|
1018
|
-
// Ellipsis with hidden count (if there are hidden lines)
|
|
1019
|
-
if (hiddenLines > 0) {
|
|
1020
|
-
displayLines.push(` ... (${hiddenLines} more lines)`);
|
|
1021
|
-
// Last N lines
|
|
1022
|
-
for (let i = lineCount - showLastLines; i < lineCount; i++) {
|
|
1023
|
-
if (i >= showFirstLines) { // Avoid duplicates
|
|
1024
|
-
let line = lines[i] || '';
|
|
1025
|
-
if (line.length > maxLineWidth) {
|
|
1026
|
-
line = line.slice(0, maxLineWidth - 1) + '…';
|
|
1027
|
-
}
|
|
1028
|
-
displayLines.push(` ${line}`);
|
|
1029
|
-
}
|
|
1030
|
-
}
|
|
1031
|
-
}
|
|
1032
|
-
return displayLines.join('\n');
|
|
641
|
+
// Detect content type and get appropriate icon/label
|
|
642
|
+
let typeLabel = 'Pasted';
|
|
643
|
+
let typeIcon = '📋';
|
|
644
|
+
if (firstLine.match(/^(import|export|const|let|var|function|class|def |async |from |interface |type )/)) {
|
|
645
|
+
typeIcon = '📝';
|
|
646
|
+
typeLabel = 'Code';
|
|
647
|
+
}
|
|
648
|
+
else if (firstLine.match(/^[{[\]]/)) {
|
|
649
|
+
typeIcon = '📊';
|
|
650
|
+
typeLabel = 'JSON';
|
|
651
|
+
}
|
|
652
|
+
else if (firstLine.match(/^<[!?]?[a-zA-Z]/)) {
|
|
653
|
+
typeIcon = '📄';
|
|
654
|
+
typeLabel = 'XML/HTML';
|
|
655
|
+
}
|
|
656
|
+
else if (firstLine.match(/^#|^\/\/|^\/\*|^\*|^--/)) {
|
|
657
|
+
typeIcon = '💬';
|
|
658
|
+
typeLabel = 'Text';
|
|
659
|
+
}
|
|
660
|
+
else if (firstLine.match(/^```|^~~~|^\s{4}/)) {
|
|
661
|
+
typeIcon = '📖';
|
|
662
|
+
typeLabel = 'Markdown';
|
|
663
|
+
}
|
|
664
|
+
// Create a compact preview
|
|
665
|
+
const maxPreviewLen = 25;
|
|
666
|
+
const preview = firstLine.length > maxPreviewLen
|
|
667
|
+
? firstLine.slice(0, maxPreviewLen - 1) + '…'
|
|
668
|
+
: firstLine || '(empty)';
|
|
669
|
+
// Format size info compactly
|
|
670
|
+
const sizeInfo = charCount > 1000
|
|
671
|
+
? `${(charCount / 1000).toFixed(1)}k`
|
|
672
|
+
: `${charCount}`;
|
|
673
|
+
return `${typeIcon} [${typeLabel}: ${lineCount}L/${sizeInfo}c] ${preview}`;
|
|
1033
674
|
}
|
|
1034
675
|
/**
|
|
1035
676
|
* Check if current input is a pasted block
|
|
@@ -1060,7 +701,7 @@ export class PinnedChatBox {
|
|
|
1060
701
|
this.state.currentInput = '';
|
|
1061
702
|
}
|
|
1062
703
|
/**
|
|
1063
|
-
* Handle backspace
|
|
704
|
+
* Handle backspace
|
|
1064
705
|
*/
|
|
1065
706
|
handleBackspace() {
|
|
1066
707
|
if (!this.isEnabled || this.isDisposed)
|
|
@@ -1068,28 +709,6 @@ export class PinnedChatBox {
|
|
|
1068
709
|
this.cursorPosition = Math.min(this.cursorPosition, this.inputBuffer.length);
|
|
1069
710
|
if (this.cursorPosition === 0)
|
|
1070
711
|
return;
|
|
1071
|
-
// Check if we're at the end of a paste chip (character before cursor is ']')
|
|
1072
|
-
const charBefore = this.inputBuffer[this.cursorPosition - 1];
|
|
1073
|
-
if (charBefore === ']') {
|
|
1074
|
-
// Find the matching '[' to get the full chip
|
|
1075
|
-
const beforeCursor = this.inputBuffer.slice(0, this.cursorPosition);
|
|
1076
|
-
const chipStart = beforeCursor.lastIndexOf('[');
|
|
1077
|
-
if (chipStart !== -1) {
|
|
1078
|
-
// Verify this looks like a paste chip (contains emoji)
|
|
1079
|
-
const potentialChip = beforeCursor.slice(chipStart);
|
|
1080
|
-
if (potentialChip.match(/^\[📋|^\[📝|^\[📊|^\[📄/)) {
|
|
1081
|
-
// Delete the entire chip
|
|
1082
|
-
this.inputBuffer =
|
|
1083
|
-
this.inputBuffer.slice(0, chipStart) +
|
|
1084
|
-
this.inputBuffer.slice(this.cursorPosition);
|
|
1085
|
-
this.cursorPosition = chipStart;
|
|
1086
|
-
this.state.currentInput = this.inputBuffer;
|
|
1087
|
-
this.scheduleRender();
|
|
1088
|
-
return;
|
|
1089
|
-
}
|
|
1090
|
-
}
|
|
1091
|
-
}
|
|
1092
|
-
// Normal single character delete
|
|
1093
712
|
this.inputBuffer =
|
|
1094
713
|
this.inputBuffer.slice(0, this.cursorPosition - 1) +
|
|
1095
714
|
this.inputBuffer.slice(this.cursorPosition);
|
|
@@ -1098,7 +717,7 @@ export class PinnedChatBox {
|
|
|
1098
717
|
this.scheduleRender();
|
|
1099
718
|
}
|
|
1100
719
|
/**
|
|
1101
|
-
* Handle delete key
|
|
720
|
+
* Handle delete key
|
|
1102
721
|
*/
|
|
1103
722
|
handleDelete() {
|
|
1104
723
|
if (!this.isEnabled || this.isDisposed)
|
|
@@ -1106,23 +725,6 @@ export class PinnedChatBox {
|
|
|
1106
725
|
this.cursorPosition = Math.min(this.cursorPosition, this.inputBuffer.length);
|
|
1107
726
|
if (this.cursorPosition >= this.inputBuffer.length)
|
|
1108
727
|
return;
|
|
1109
|
-
// Check if we're at the start of a paste chip (character at cursor is '[')
|
|
1110
|
-
const charAtCursor = this.inputBuffer[this.cursorPosition];
|
|
1111
|
-
if (charAtCursor === '[') {
|
|
1112
|
-
// Check if this looks like a paste chip
|
|
1113
|
-
const afterCursor = this.inputBuffer.slice(this.cursorPosition);
|
|
1114
|
-
const chipMatch = afterCursor.match(/^\[(?:📋|📝|📊|📄)[^\]]*\]/);
|
|
1115
|
-
if (chipMatch) {
|
|
1116
|
-
// Delete the entire chip
|
|
1117
|
-
this.inputBuffer =
|
|
1118
|
-
this.inputBuffer.slice(0, this.cursorPosition) +
|
|
1119
|
-
this.inputBuffer.slice(this.cursorPosition + chipMatch[0].length);
|
|
1120
|
-
this.state.currentInput = this.inputBuffer;
|
|
1121
|
-
this.scheduleRender();
|
|
1122
|
-
return;
|
|
1123
|
-
}
|
|
1124
|
-
}
|
|
1125
|
-
// Normal single character delete
|
|
1126
728
|
this.inputBuffer =
|
|
1127
729
|
this.inputBuffer.slice(0, this.cursorPosition) +
|
|
1128
730
|
this.inputBuffer.slice(this.cursorPosition + 1);
|
|
@@ -1524,6 +1126,12 @@ export class PinnedChatBox {
|
|
|
1524
1126
|
handleResize() {
|
|
1525
1127
|
this.scheduleRender();
|
|
1526
1128
|
}
|
|
1129
|
+
/**
|
|
1130
|
+
* Get number of reserved lines at bottom
|
|
1131
|
+
*/
|
|
1132
|
+
getReservedLines() {
|
|
1133
|
+
return this.reservedLines;
|
|
1134
|
+
}
|
|
1527
1135
|
/**
|
|
1528
1136
|
* Get last rendered height (for layout calculations)
|
|
1529
1137
|
*/
|
|
@@ -1536,25 +1144,16 @@ export class PinnedChatBox {
|
|
|
1536
1144
|
dispose() {
|
|
1537
1145
|
if (this.isDisposed)
|
|
1538
1146
|
return;
|
|
1539
|
-
// Clean up
|
|
1540
|
-
this.stopCursorBlink();
|
|
1147
|
+
// Clean up pending render timeout
|
|
1541
1148
|
if (this.pendingAfterWriteRender) {
|
|
1542
1149
|
clearTimeout(this.pendingAfterWriteRender);
|
|
1543
1150
|
this.pendingAfterWriteRender = null;
|
|
1544
1151
|
}
|
|
1545
|
-
if (this.inputSequenceTimer) {
|
|
1546
|
-
clearTimeout(this.inputSequenceTimer);
|
|
1547
|
-
this.inputSequenceTimer = null;
|
|
1548
|
-
}
|
|
1549
1152
|
// Clean up output interceptor registration
|
|
1550
1153
|
if (this.outputInterceptorCleanup) {
|
|
1551
1154
|
this.outputInterceptorCleanup();
|
|
1552
1155
|
this.outputInterceptorCleanup = undefined;
|
|
1553
1156
|
}
|
|
1554
|
-
// Disable scroll region before clearing
|
|
1555
|
-
if (this.scrollRegionActive) {
|
|
1556
|
-
this.disableScrollRegion();
|
|
1557
|
-
}
|
|
1558
1157
|
try {
|
|
1559
1158
|
this.clear();
|
|
1560
1159
|
}
|
|
@@ -1594,7 +1193,6 @@ export class PinnedChatBox {
|
|
|
1594
1193
|
this.cursorPosition = 0;
|
|
1595
1194
|
this.state = {
|
|
1596
1195
|
isProcessing: false,
|
|
1597
|
-
isReasoningModel: false,
|
|
1598
1196
|
queuedCommands: [],
|
|
1599
1197
|
currentInput: '',
|
|
1600
1198
|
contextUsage: 0,
|