erosolar-cli 1.7.226 → 1.7.227

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 (59) hide show
  1. package/README.md +22 -148
  2. package/dist/mcp/sseClient.d.ts.map +1 -1
  3. package/dist/mcp/sseClient.js +9 -18
  4. package/dist/mcp/sseClient.js.map +1 -1
  5. package/dist/plugins/tools/build/buildPlugin.d.ts +0 -6
  6. package/dist/plugins/tools/build/buildPlugin.d.ts.map +1 -1
  7. package/dist/plugins/tools/build/buildPlugin.js +4 -10
  8. package/dist/plugins/tools/build/buildPlugin.js.map +1 -1
  9. package/dist/shell/interactiveShell.d.ts +2 -2
  10. package/dist/shell/interactiveShell.d.ts.map +1 -1
  11. package/dist/shell/interactiveShell.js +12 -12
  12. package/dist/shell/interactiveShell.js.map +1 -1
  13. package/dist/shell/terminalInput.d.ts +18 -44
  14. package/dist/shell/terminalInput.d.ts.map +1 -1
  15. package/dist/shell/terminalInput.js +106 -254
  16. package/dist/shell/terminalInput.js.map +1 -1
  17. package/dist/shell/terminalInputAdapter.d.ts +6 -8
  18. package/dist/shell/terminalInputAdapter.d.ts.map +1 -1
  19. package/dist/shell/terminalInputAdapter.js +6 -12
  20. package/dist/shell/terminalInputAdapter.js.map +1 -1
  21. package/dist/ui/theme.d.ts.map +1 -1
  22. package/dist/ui/theme.js +6 -8
  23. package/dist/ui/theme.js.map +1 -1
  24. package/dist/ui/unified/layout.d.ts.map +1 -1
  25. package/dist/ui/unified/layout.js +13 -26
  26. package/dist/ui/unified/layout.js.map +1 -1
  27. package/package.json +1 -1
  28. package/dist/core/aiFlowOptimizer.d.ts +0 -26
  29. package/dist/core/aiFlowOptimizer.d.ts.map +0 -1
  30. package/dist/core/aiFlowOptimizer.js +0 -31
  31. package/dist/core/aiFlowOptimizer.js.map +0 -1
  32. package/dist/core/aiOptimizationEngine.d.ts +0 -158
  33. package/dist/core/aiOptimizationEngine.d.ts.map +0 -1
  34. package/dist/core/aiOptimizationEngine.js +0 -428
  35. package/dist/core/aiOptimizationEngine.js.map +0 -1
  36. package/dist/core/aiOptimizationIntegration.d.ts +0 -93
  37. package/dist/core/aiOptimizationIntegration.d.ts.map +0 -1
  38. package/dist/core/aiOptimizationIntegration.js +0 -250
  39. package/dist/core/aiOptimizationIntegration.js.map +0 -1
  40. package/dist/core/enhancedErrorRecovery.d.ts +0 -100
  41. package/dist/core/enhancedErrorRecovery.d.ts.map +0 -1
  42. package/dist/core/enhancedErrorRecovery.js +0 -345
  43. package/dist/core/enhancedErrorRecovery.js.map +0 -1
  44. package/dist/shell/claudeCodeStreamHandler.d.ts +0 -145
  45. package/dist/shell/claudeCodeStreamHandler.d.ts.map +0 -1
  46. package/dist/shell/claudeCodeStreamHandler.js +0 -322
  47. package/dist/shell/claudeCodeStreamHandler.js.map +0 -1
  48. package/dist/shell/inputQueueManager.d.ts +0 -144
  49. package/dist/shell/inputQueueManager.d.ts.map +0 -1
  50. package/dist/shell/inputQueueManager.js +0 -290
  51. package/dist/shell/inputQueueManager.js.map +0 -1
  52. package/dist/shell/streamingOutputManager.d.ts +0 -115
  53. package/dist/shell/streamingOutputManager.d.ts.map +0 -1
  54. package/dist/shell/streamingOutputManager.js +0 -225
  55. package/dist/shell/streamingOutputManager.js.map +0 -1
  56. package/dist/ui/persistentPrompt.d.ts +0 -50
  57. package/dist/ui/persistentPrompt.d.ts.map +0 -1
  58. package/dist/ui/persistentPrompt.js +0 -92
  59. package/dist/ui/persistentPrompt.js.map +0 -1
@@ -3,13 +3,14 @@
3
3
  *
4
4
  * Design principles:
5
5
  * - Single source of truth for input state
6
+ * - One bottom-pinned chat box for the entire session (no inline anchors)
6
7
  * - Native bracketed paste support (no heuristics)
7
8
  * - Clean cursor model with render-time wrapping
8
9
  * - State machine for different input modes
9
10
  * - No readline dependency for display
10
11
  */
11
12
  import { EventEmitter } from 'node:events';
12
- import { isMultilinePaste, generatePasteSummary } from '../core/multilinePasteHandler.js';
13
+ import { isMultilinePaste } from '../core/multilinePasteHandler.js';
13
14
  import { writeLock } from '../ui/writeLock.js';
14
15
  import { renderDivider, renderStatusLine } from '../ui/unified/layout.js';
15
16
  import { isStreamingMode } from '../ui/globalWriteLock.js';
@@ -67,6 +68,9 @@ export class TerminalInput extends EventEmitter {
67
68
  statusMessage = null;
68
69
  overrideStatusMessage = null; // Secondary status (warnings, etc.)
69
70
  streamingLabel = null; // Streaming progress indicator
71
+ metaElapsedSeconds = null; // Optional elapsed time for header line
72
+ metaTokensUsed = null; // Optional token usage
73
+ metaTokenLimit = null; // Optional token window
70
74
  reservedLines = 2;
71
75
  scrollRegionActive = false;
72
76
  lastRenderContent = '';
@@ -74,9 +78,6 @@ export class TerminalInput extends EventEmitter {
74
78
  renderDirty = false;
75
79
  isRendering = false;
76
80
  pinnedTopRows = 0;
77
- inlineAnchorRow = null;
78
- inlineLayout = false;
79
- anchorProvider = null;
80
81
  // Lifecycle
81
82
  disposed = false;
82
83
  enabled = true;
@@ -91,10 +92,6 @@ export class TerminalInput extends EventEmitter {
91
92
  // Streaming render throttle
92
93
  lastStreamingRender = 0;
93
94
  streamingRenderInterval = 250; // ms between renders during streaming
94
- // Metrics tracking for status bar
95
- streamingStartTime = null;
96
- tokensUsed = 0;
97
- thinkingEnabled = true;
98
95
  constructor(writeStream = process.stdout, config = {}) {
99
96
  super();
100
97
  this.out = writeStream;
@@ -182,11 +179,6 @@ export class TerminalInput extends EventEmitter {
182
179
  if (handled)
183
180
  return;
184
181
  }
185
- // Handle '?' for help hint (if buffer is empty)
186
- if (str === '?' && this.buffer.length === 0) {
187
- this.emit('showHelp');
188
- return;
189
- }
190
182
  // Insert printable characters
191
183
  if (str && !key?.ctrl && !key?.meta) {
192
184
  this.insertText(str);
@@ -195,56 +187,24 @@ export class TerminalInput extends EventEmitter {
195
187
  /**
196
188
  * Set the input mode
197
189
  *
198
- * Streaming uses scroll region to contain output:
199
- * - Scroll region: rows 1 to (total - reservedLines)
200
- * - Input area protected at bottom (outside scroll region)
201
- * - Cursor positioned inside scroll region so content scrolls there
190
+ * Streaming keeps the scroll region active so the prompt/status stay pinned
191
+ * below the streaming output. When streaming ends, we refresh the input area.
202
192
  */
203
193
  setMode(mode) {
204
194
  const prevMode = this.mode;
205
195
  this.mode = mode;
206
196
  if (mode === 'streaming' && prevMode !== 'streaming') {
207
- // Track streaming start time for elapsed display
208
- this.streamingStartTime = Date.now();
209
- const { rows } = this.getSize();
210
- // Ensure scroll region is active to protect input area
211
- if (!this.scrollRegionActive) {
212
- this.applyScrollRegion();
213
- this.scrollRegionActive = true;
214
- }
215
- // Position cursor at BOTTOM of scroll region (row = rows - reservedLines)
216
- // This is where new streaming content should appear and scroll up from
217
- const scrollBottom = Math.max(1, rows - this.reservedLines);
218
- this.write(ESC.TO(scrollBottom, 1));
197
+ // Keep scroll region active so status/prompt stay pinned while streaming
198
+ this.enableScrollRegion();
219
199
  this.renderDirty = true;
200
+ this.render();
220
201
  }
221
202
  else if (mode !== 'streaming' && prevMode === 'streaming') {
222
- // Reset streaming time
223
- this.streamingStartTime = null;
224
- // Re-render the input area (scroll region keeps it protected)
203
+ // Streaming ended - render the input area
204
+ this.enableScrollRegion();
225
205
  this.forceRender();
226
206
  }
227
207
  }
228
- /**
229
- * Update token count for metrics display
230
- */
231
- setTokensUsed(tokens) {
232
- this.tokensUsed = tokens;
233
- }
234
- /**
235
- * Toggle thinking/reasoning mode
236
- */
237
- toggleThinking() {
238
- this.thinkingEnabled = !this.thinkingEnabled;
239
- this.emit('thinkingToggle', this.thinkingEnabled);
240
- this.scheduleRender();
241
- }
242
- /**
243
- * Get thinking enabled state
244
- */
245
- isThinkingEnabled() {
246
- return this.thinkingEnabled;
247
- }
248
208
  /**
249
209
  * Keep the top N rows pinned outside the scroll region (used for the launch banner).
250
210
  */
@@ -257,42 +217,6 @@ export class TerminalInput extends EventEmitter {
257
217
  }
258
218
  }
259
219
  }
260
- /**
261
- * Anchor prompt rendering near a specific row (inline layout). Pass null to
262
- * restore the default bottom-aligned layout.
263
- */
264
- setInlineAnchor(row) {
265
- if (row === null || row === undefined) {
266
- this.inlineAnchorRow = null;
267
- this.inlineLayout = false;
268
- this.renderDirty = true;
269
- this.render();
270
- return;
271
- }
272
- const { rows } = this.getSize();
273
- const clamped = Math.max(1, Math.min(Math.floor(row), rows));
274
- this.inlineAnchorRow = clamped;
275
- this.inlineLayout = true;
276
- this.renderDirty = true;
277
- this.render();
278
- }
279
- /**
280
- * Provide a dynamic anchor callback. When set, the prompt will follow the
281
- * output by re-evaluating the anchor before each render.
282
- */
283
- setInlineAnchorProvider(provider) {
284
- this.anchorProvider = provider;
285
- if (!provider) {
286
- this.inlineLayout = false;
287
- this.inlineAnchorRow = null;
288
- this.renderDirty = true;
289
- this.render();
290
- return;
291
- }
292
- this.inlineLayout = true;
293
- this.renderDirty = true;
294
- this.render();
295
- }
296
220
  /**
297
221
  * Get current mode
298
222
  */
@@ -402,6 +326,29 @@ export class TerminalInput extends EventEmitter {
402
326
  this.streamingLabel = next;
403
327
  this.scheduleRender();
404
328
  }
329
+ /**
330
+ * Surface meta status just above the divider (e.g., elapsed time or token usage).
331
+ */
332
+ setMetaStatus(meta) {
333
+ const nextElapsed = typeof meta.elapsedSeconds === 'number' && Number.isFinite(meta.elapsedSeconds) && meta.elapsedSeconds >= 0
334
+ ? Math.floor(meta.elapsedSeconds)
335
+ : null;
336
+ const nextTokens = typeof meta.tokensUsed === 'number' && Number.isFinite(meta.tokensUsed) && meta.tokensUsed >= 0
337
+ ? Math.floor(meta.tokensUsed)
338
+ : null;
339
+ const nextLimit = typeof meta.tokenLimit === 'number' && Number.isFinite(meta.tokenLimit) && meta.tokenLimit > 0
340
+ ? Math.floor(meta.tokenLimit)
341
+ : null;
342
+ if (this.metaElapsedSeconds === nextElapsed &&
343
+ this.metaTokensUsed === nextTokens &&
344
+ this.metaTokenLimit === nextLimit) {
345
+ return;
346
+ }
347
+ this.metaElapsedSeconds = nextElapsed;
348
+ this.metaTokensUsed = nextTokens;
349
+ this.metaTokenLimit = nextLimit;
350
+ this.scheduleRender();
351
+ }
405
352
  /**
406
353
  * Keep mode toggles (verification/auto-continue) visible in the control bar.
407
354
  * Hotkey labels remain stable so the bar looks the same before/during streaming.
@@ -480,9 +427,9 @@ export class TerminalInput extends EventEmitter {
480
427
  const availableForContent = Math.max(1, rows - 3); // room for separator + input + controls
481
428
  const maxVisible = Math.max(1, Math.min(this.config.maxLines, availableForContent));
482
429
  const displayLines = Math.min(lines.length, maxVisible);
483
- // Reserved lines: separator(1) + controls(1) + input lines
484
- // Layout: [separator] [controls] [input...]
485
- this.updateReservedLines(displayLines + 2);
430
+ const metaLine = this.buildMetaLine(cols - 2);
431
+ // Reserved lines: optional meta(1) + separator(1) + input lines + controls(1)
432
+ this.updateReservedLines(displayLines + 2 + (metaLine ? 1 : 0));
486
433
  // Calculate display window (keep cursor visible)
487
434
  let startLine = 0;
488
435
  if (lines.length > displayLines) {
@@ -494,35 +441,26 @@ export class TerminalInput extends EventEmitter {
494
441
  // Render
495
442
  this.write(ESC.HIDE);
496
443
  this.write(ESC.RESET);
497
- // Enhanced layout from bottom to top:
498
- // Row N: Mode controls (shortcuts, thinking toggle)
499
- // Row N-1: Bottom separator
500
- // Row N-2: Input area
501
- // Row N-3: Top separator
502
- // Row N-4: Status bar (streaming status, elapsed time, tokens)
503
- // Calculate positions from absolute bottom
504
- const modeControlRow = rows;
505
- const bottomSepRow = rows - 1;
506
- const inputEndRow = rows - 2;
507
- const inputStartRow = inputEndRow - visibleLines.length + 1;
508
- const topSepRow = inputStartRow - 1;
509
- const statusBarRow = topSepRow - 1;
510
- // Reserved lines: status(1) + topSep(1) + input + bottomSep(1) + controls(1)
511
- this.updateReservedLines(visibleLines.length + 4);
512
- // Status bar (streaming status + metrics)
513
- this.write(ESC.TO(statusBarRow, 1));
514
- this.write(ESC.CLEAR_LINE);
515
- this.write(this.buildStatusBar(cols));
516
- // Top separator
517
- this.write(ESC.TO(topSepRow, 1));
444
+ const startRow = Math.max(1, rows - this.reservedLines + 1);
445
+ let currentRow = startRow;
446
+ // Meta/status header (elapsed, tokens/context)
447
+ if (metaLine) {
448
+ this.write(ESC.TO(currentRow, 1));
449
+ this.write(ESC.CLEAR_LINE);
450
+ this.write(metaLine);
451
+ currentRow += 1;
452
+ }
453
+ // Separator line
454
+ this.write(ESC.TO(currentRow, 1));
518
455
  this.write(ESC.CLEAR_LINE);
519
456
  const divider = renderDivider(cols - 2);
520
457
  this.write(divider);
458
+ currentRow += 1;
521
459
  // Render input lines
522
- let finalRow = inputStartRow;
460
+ let finalRow = currentRow;
523
461
  let finalCol = 3;
524
462
  for (let i = 0; i < visibleLines.length; i++) {
525
- const rowNum = inputStartRow + i;
463
+ const rowNum = currentRow + i;
526
464
  this.write(ESC.TO(rowNum, 1));
527
465
  this.write(ESC.CLEAR_LINE);
528
466
  const line = visibleLines[i] ?? '';
@@ -560,15 +498,12 @@ export class TerminalInput extends EventEmitter {
560
498
  this.write(' '.repeat(padding));
561
499
  this.write(ESC.RESET);
562
500
  }
563
- // Bottom separator
564
- this.write(ESC.TO(bottomSepRow, 1));
565
- this.write(ESC.CLEAR_LINE);
566
- this.write(divider);
567
- // Mode controls (shortcuts + thinking toggle)
568
- this.write(ESC.TO(modeControlRow, 1));
501
+ // Mode controls line (Claude Code style)
502
+ const controlRow = currentRow + visibleLines.length;
503
+ this.write(ESC.TO(controlRow, 1));
569
504
  this.write(ESC.CLEAR_LINE);
570
505
  this.write(this.buildModeControls(cols));
571
- // Position cursor in input area
506
+ // Position cursor
572
507
  this.write(ESC.TO(finalRow, Math.min(finalCol, cols)));
573
508
  this.write(ESC.SHOW);
574
509
  // Update state
@@ -581,86 +516,69 @@ export class TerminalInput extends EventEmitter {
581
516
  }
582
517
  }
583
518
  /**
584
- * Build status bar showing streaming status, elapsed time, and token count.
519
+ * Build a compact meta line above the divider (elapsed, context usage, queue size).
585
520
  */
586
- buildStatusBar(cols) {
521
+ buildMetaLine(width) {
587
522
  const parts = [];
588
- // Streaming status
589
- if (this.mode === 'streaming') {
590
- parts.push({ text: '● Streaming', tone: 'success' });
591
- // Elapsed time
592
- if (this.streamingStartTime) {
593
- const elapsed = Math.floor((Date.now() - this.streamingStartTime) / 1000);
594
- const mins = Math.floor(elapsed / 60);
595
- const secs = elapsed % 60;
596
- const timeStr = mins > 0 ? `${mins}m ${secs}s` : `${secs}s`;
597
- parts.push({ text: `⏱ ${timeStr}`, tone: 'muted' });
598
- }
523
+ if (this.metaElapsedSeconds !== null) {
524
+ parts.push({ text: `elapsed ${this.metaElapsedSeconds}s`, tone: 'muted' });
599
525
  }
600
- else {
601
- parts.push({ text: '○ Ready', tone: 'muted' });
526
+ if (this.metaTokensUsed !== null) {
527
+ const limitText = this.metaTokenLimit ? `/${this.metaTokenLimit}` : '';
528
+ parts.push({ text: `tokens ${this.metaTokensUsed}${limitText}`, tone: 'muted' });
602
529
  }
603
- // Token count
604
- if (this.tokensUsed > 0) {
605
- const tokenStr = this.tokensUsed >= 1000
606
- ? `${(this.tokensUsed / 1000).toFixed(1)}k`
607
- : `${this.tokensUsed}`;
608
- parts.push({ text: `◈ ${tokenStr} tokens`, tone: 'info' });
530
+ if (this.contextUsage !== null) {
531
+ const tone = this.contextUsage >= 75 ? 'warn' : 'muted';
532
+ parts.push({ text: `used ${this.contextUsage}%`, tone });
609
533
  }
610
- // Thinking mode
611
- parts.push({
612
- text: this.thinkingEnabled ? '💭 Thinking on' : '💭 Thinking off',
613
- tone: this.thinkingEnabled ? 'info' : 'muted',
614
- });
615
- // Paste block count if any
616
- if (this.pastePlaceholders.length > 0) {
617
- const totalLines = this.pastePlaceholders.reduce((sum, p) => sum + p.lineCount, 0);
618
- parts.push({
619
- text: `📋 ${this.pastePlaceholders.length} paste(s), ${totalLines} lines`,
620
- tone: 'info',
621
- });
534
+ if (this.mode === 'streaming' && this.queue.length > 0) {
535
+ parts.push({ text: `queued ${this.queue.length}`, tone: 'info' });
622
536
  }
623
- return renderStatusLine(parts, cols - 2);
537
+ return parts.length ? renderStatusLine(parts, width) : '';
624
538
  }
625
539
  /**
626
540
  * Build Claude Code style mode controls line.
627
- * Shows keyboard shortcuts and toggle states.
541
+ * Combines streaming label + override status + main status for simultaneous display.
628
542
  */
629
543
  buildModeControls(cols) {
630
544
  const parts = [];
631
- // Keyboard shortcuts hint
632
- parts.push({ text: '? help', tone: 'muted' });
633
- parts.push({ text: 'esc interrupt', tone: 'muted' });
634
- // Streaming status messages
635
545
  if (this.streamingLabel) {
636
546
  parts.push({ text: `◐ ${this.streamingLabel}`, tone: 'info' });
637
547
  }
638
548
  if (this.overrideStatusMessage) {
639
549
  parts.push({ text: `⚠ ${this.overrideStatusMessage}`, tone: 'warn' });
640
550
  }
641
- // Mode toggles
642
- const verifyLabel = this.verificationEnabled ? 'verify on' : 'verify off';
551
+ if (this.statusMessage) {
552
+ parts.push({ text: this.statusMessage, tone: this.streamingLabel ? 'muted' : 'info' });
553
+ }
554
+ const verifyLabel = this.verificationEnabled ? 'verify+build on' : 'verify+build off';
643
555
  parts.push({
644
556
  text: `${verifyLabel} (${this.verificationHotkey.toLowerCase()})`,
645
557
  tone: this.verificationEnabled ? 'success' : 'muted',
646
558
  });
559
+ const continueLabel = this.autoContinueEnabled ? 'auto-continue on' : 'auto-continue off';
560
+ parts.push({
561
+ text: `${continueLabel} (${this.autoContinueHotkey.toLowerCase()})`,
562
+ tone: this.autoContinueEnabled ? 'info' : 'muted',
563
+ });
647
564
  const editLabel = this.editMode === 'display-edits' ? 'auto-edit' : 'ask first';
648
565
  parts.push({ text: `${editLabel} (⇧⇥)`, tone: 'muted' });
649
- // Context usage
650
566
  if (this.contextUsage !== null) {
651
567
  const pct = Math.max(0, 100 - this.contextUsage);
652
568
  const tone = pct < 25 ? 'warn' : 'muted';
653
- parts.push({ text: `ctx ${pct}%`, tone });
569
+ parts.push({ text: `context ${pct}%`, tone });
654
570
  }
655
- // Queue during streaming
656
- if (this.mode === 'streaming' && this.queue.length > 0) {
657
- parts.push({ text: `queued ${this.queue.length}`, tone: 'info' });
658
- }
659
- // Multi-line indicator
660
571
  if (this.buffer.includes('\n')) {
661
572
  const lineCount = this.buffer.split('\n').length;
662
573
  parts.push({ text: `${lineCount} lines`, tone: 'muted' });
663
574
  }
575
+ if (this.pastePlaceholders.length > 0) {
576
+ const latest = this.pastePlaceholders[this.pastePlaceholders.length - 1];
577
+ parts.push({
578
+ text: `paste #${latest.id} +${latest.lineCount} lines (⌫ to drop)`,
579
+ tone: 'info',
580
+ });
581
+ }
664
582
  return renderStatusLine(parts, cols - 2);
665
583
  }
666
584
  /**
@@ -696,23 +614,13 @@ export class TerminalInput extends EventEmitter {
696
614
  * Register with display's output interceptor to position cursor correctly.
697
615
  * When scroll region is active, output needs to go to the scroll region,
698
616
  * not the protected bottom area where the input is rendered.
699
- *
700
- * NOTE: With scroll region properly set, content naturally stays within
701
- * the region boundaries - no cursor manipulation needed per-write.
702
617
  */
703
618
  registerOutputInterceptor(display) {
704
619
  if (this.outputInterceptorCleanup) {
705
620
  this.outputInterceptorCleanup();
706
621
  }
707
- this.outputInterceptorCleanup = display.registerOutputInterceptor({
708
- beforeWrite: () => {
709
- // Scroll region handles content containment automatically
710
- // No per-write cursor manipulation needed
711
- },
712
- afterWrite: () => {
713
- // No cursor manipulation needed
714
- },
715
- });
622
+ // Scroll region handles containment now; interceptor is a no-op placeholder to keep API stable.
623
+ this.outputInterceptorCleanup = display.registerOutputInterceptor({});
716
624
  }
717
625
  /**
718
626
  * Dispose and clean up
@@ -832,22 +740,7 @@ export class TerminalInput extends EventEmitter {
832
740
  this.toggleEditMode();
833
741
  return true;
834
742
  }
835
- // Tab: toggle paste expansion if in placeholder, otherwise toggle thinking
836
- if (this.findPlaceholderAt(this.cursor)) {
837
- this.togglePasteExpansion();
838
- }
839
- else {
840
- this.toggleThinking();
841
- }
842
- return true;
843
- case 'escape':
844
- // Esc: interrupt if streaming, otherwise clear buffer
845
- if (this.mode === 'streaming') {
846
- this.emit('interrupt');
847
- }
848
- else if (this.buffer.length > 0) {
849
- this.clear();
850
- }
743
+ this.insertText(' ');
851
744
  return true;
852
745
  }
853
746
  return false;
@@ -1138,7 +1031,9 @@ export class TerminalInput extends EventEmitter {
1138
1031
  if (available <= 0)
1139
1032
  return;
1140
1033
  const chunk = clean.slice(0, available);
1141
- if (isMultilinePaste(chunk)) {
1034
+ const isMultiline = isMultilinePaste(chunk);
1035
+ const isShortMultiline = isMultiline && this.shouldInlineMultiline(chunk);
1036
+ if (isMultiline && !isShortMultiline) {
1142
1037
  this.insertPastePlaceholder(chunk);
1143
1038
  }
1144
1039
  else {
@@ -1158,6 +1053,7 @@ export class TerminalInput extends EventEmitter {
1158
1053
  return;
1159
1054
  this.applyScrollRegion();
1160
1055
  this.scrollRegionActive = true;
1056
+ this.forceRender();
1161
1057
  }
1162
1058
  disableScrollRegion() {
1163
1059
  if (!this.scrollRegionActive)
@@ -1308,17 +1204,19 @@ export class TerminalInput extends EventEmitter {
1308
1204
  this.shiftPlaceholders(position, text.length);
1309
1205
  this.buffer = this.buffer.slice(0, position) + text + this.buffer.slice(position);
1310
1206
  }
1207
+ shouldInlineMultiline(content) {
1208
+ const lines = content.split('\n').length;
1209
+ const maxInlineLines = 4;
1210
+ const maxInlineChars = 240;
1211
+ return lines <= maxInlineLines && content.length <= maxInlineChars;
1212
+ }
1311
1213
  findPlaceholderAt(position) {
1312
1214
  return this.pastePlaceholders.find((ph) => position >= ph.start && position < ph.end) ?? null;
1313
1215
  }
1314
- buildPlaceholder(summary) {
1216
+ buildPlaceholder(lineCount) {
1315
1217
  const id = ++this.pasteCounter;
1316
- const lang = summary.language ? ` ${summary.language.toUpperCase()}` : '';
1317
- // Show first line preview (truncated)
1318
- const preview = summary.preview.length > 30
1319
- ? `${summary.preview.slice(0, 30)}...`
1320
- : summary.preview;
1321
- const placeholder = `[📋 #${id}${lang} ${summary.lineCount}L] "${preview}"`;
1218
+ const plural = lineCount === 1 ? '' : 's';
1219
+ const placeholder = `[Pasted text #${id} +${lineCount} line${plural}]`;
1322
1220
  return { id, placeholder };
1323
1221
  }
1324
1222
  insertPastePlaceholder(content) {
@@ -1326,67 +1224,21 @@ export class TerminalInput extends EventEmitter {
1326
1224
  if (available <= 0)
1327
1225
  return;
1328
1226
  const cleanContent = content.slice(0, available);
1329
- const summary = generatePasteSummary(cleanContent);
1330
- // For short pastes (< 5 lines), show full content instead of placeholder
1331
- if (summary.lineCount < 5) {
1332
- const placeholder = this.findPlaceholderAt(this.cursor);
1333
- const insertPos = placeholder && this.cursor > placeholder.start ? placeholder.end : this.cursor;
1334
- this.insertPlainText(cleanContent, insertPos);
1335
- this.cursor = insertPos + cleanContent.length;
1336
- return;
1337
- }
1338
- const { id, placeholder } = this.buildPlaceholder(summary);
1227
+ const lineCount = cleanContent.split('\n').length;
1228
+ const { id, placeholder } = this.buildPlaceholder(lineCount);
1339
1229
  const insertPos = this.cursor;
1340
1230
  this.shiftPlaceholders(insertPos, placeholder.length);
1341
1231
  this.pastePlaceholders.push({
1342
1232
  id,
1343
1233
  content: cleanContent,
1344
- lineCount: summary.lineCount,
1234
+ lineCount,
1345
1235
  placeholder,
1346
1236
  start: insertPos,
1347
1237
  end: insertPos + placeholder.length,
1348
- summary,
1349
- expanded: false,
1350
1238
  });
1351
1239
  this.buffer = this.buffer.slice(0, insertPos) + placeholder + this.buffer.slice(insertPos);
1352
1240
  this.cursor = insertPos + placeholder.length;
1353
1241
  }
1354
- /**
1355
- * Toggle expansion of a paste placeholder at the current cursor position.
1356
- * When expanded, shows first 3 and last 2 lines of the content.
1357
- */
1358
- togglePasteExpansion() {
1359
- const placeholder = this.findPlaceholderAt(this.cursor);
1360
- if (!placeholder)
1361
- return false;
1362
- placeholder.expanded = !placeholder.expanded;
1363
- // Update the placeholder text in buffer
1364
- const newPlaceholder = placeholder.expanded
1365
- ? this.buildExpandedPlaceholder(placeholder)
1366
- : this.buildPlaceholder(placeholder.summary).placeholder;
1367
- const lengthDiff = newPlaceholder.length - placeholder.placeholder.length;
1368
- // Update buffer
1369
- this.buffer =
1370
- this.buffer.slice(0, placeholder.start) +
1371
- newPlaceholder +
1372
- this.buffer.slice(placeholder.end);
1373
- // Update placeholder tracking
1374
- placeholder.placeholder = newPlaceholder;
1375
- placeholder.end = placeholder.start + newPlaceholder.length;
1376
- // Shift other placeholders
1377
- this.shiftPlaceholders(placeholder.end, lengthDiff, placeholder.id);
1378
- this.scheduleRender();
1379
- return true;
1380
- }
1381
- buildExpandedPlaceholder(ph) {
1382
- const lines = ph.content.split('\n');
1383
- const firstLines = lines.slice(0, 3).map(l => l.slice(0, 60)).join('\n');
1384
- const lastLines = lines.length > 5
1385
- ? '\n...\n' + lines.slice(-2).map(l => l.slice(0, 60)).join('\n')
1386
- : '';
1387
- const lang = ph.summary.language ? ` ${ph.summary.language.toUpperCase()}` : '';
1388
- return `[📋 #${ph.id}${lang} ${ph.lineCount}L ▼]\n${firstLines}${lastLines}\n[/📋 #${ph.id}]`;
1389
- }
1390
1242
  deletePlaceholder(placeholder) {
1391
1243
  const length = placeholder.end - placeholder.start;
1392
1244
  this.buffer = this.buffer.slice(0, placeholder.start) + this.buffer.slice(placeholder.end);