deepseek-coder-agent-cli 1.0.39 → 1.0.41

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 (55) hide show
  1. package/dist/bin/deepseek.d.ts +0 -3
  2. package/dist/bin/deepseek.d.ts.map +1 -1
  3. package/dist/bin/deepseek.js +6 -6
  4. package/dist/bin/deepseek.js.map +1 -1
  5. package/dist/bin/lean.js +5 -4
  6. package/dist/bin/lean.js.map +1 -1
  7. package/dist/core/episodicMemory.d.ts.map +1 -1
  8. package/dist/core/episodicMemory.js +3 -2
  9. package/dist/core/episodicMemory.js.map +1 -1
  10. package/dist/core/flowProtection.d.ts.map +1 -1
  11. package/dist/core/flowProtection.js +2 -1
  12. package/dist/core/flowProtection.js.map +1 -1
  13. package/dist/core/guardrails.d.ts +0 -4
  14. package/dist/core/guardrails.d.ts.map +1 -1
  15. package/dist/core/guardrails.js +2 -1
  16. package/dist/core/guardrails.js.map +1 -1
  17. package/dist/core/inputProtection.d.ts.map +1 -1
  18. package/dist/core/inputProtection.js +2 -1
  19. package/dist/core/inputProtection.js.map +1 -1
  20. package/dist/core/revenueEnvValidator.d.ts.map +1 -1
  21. package/dist/core/revenueEnvValidator.js +8 -5
  22. package/dist/core/revenueEnvValidator.js.map +1 -1
  23. package/dist/core/shutdown.d.ts.map +1 -1
  24. package/dist/core/shutdown.js +3 -2
  25. package/dist/core/shutdown.js.map +1 -1
  26. package/dist/core/types/utilityTypes.d.ts +0 -9
  27. package/dist/core/types/utilityTypes.d.ts.map +1 -1
  28. package/dist/core/types/utilityTypes.js +2 -1
  29. package/dist/core/types/utilityTypes.js.map +1 -1
  30. package/dist/headless/interactiveShell.d.ts.map +1 -1
  31. package/dist/headless/interactiveShell.js +89 -43
  32. package/dist/headless/interactiveShell.js.map +1 -1
  33. package/dist/headless/quickMode.d.ts.map +1 -1
  34. package/dist/headless/quickMode.js +5 -4
  35. package/dist/headless/quickMode.js.map +1 -1
  36. package/dist/tools/emailTools.d.ts.map +1 -1
  37. package/dist/tools/emailTools.js +3 -2
  38. package/dist/tools/emailTools.js.map +1 -1
  39. package/dist/ui/PromptController.js +8 -2
  40. package/dist/ui/PromptController.js.map +1 -1
  41. package/dist/ui/UnifiedUIRenderer.d.ts +11 -16
  42. package/dist/ui/UnifiedUIRenderer.d.ts.map +1 -1
  43. package/dist/ui/UnifiedUIRenderer.js +116 -267
  44. package/dist/ui/UnifiedUIRenderer.js.map +1 -1
  45. package/dist/ui/overlay/OverlayManager.d.ts.map +1 -1
  46. package/dist/ui/overlay/OverlayManager.js +38 -25
  47. package/dist/ui/overlay/OverlayManager.js.map +1 -1
  48. package/dist/utils/statusReporter.d.ts +6 -0
  49. package/dist/utils/statusReporter.d.ts.map +1 -0
  50. package/dist/utils/statusReporter.js +26 -0
  51. package/dist/utils/statusReporter.js.map +1 -0
  52. package/dist/workspace.d.ts.map +1 -1
  53. package/dist/workspace.js +4 -3
  54. package/dist/workspace.js.map +1 -1
  55. package/package.json +1 -1
@@ -18,6 +18,7 @@ import { ContextMeter, disposeAnimations } from './animatedStatus.js';
18
18
  import { clampPercentage, getContextColor, } from './uiConstants.js';
19
19
  import { formatInlineText } from './richText.js';
20
20
  import { initializeInputProtection } from '../core/inputProtection.js';
21
+ import { reportStatus, reportStatusError } from '../utils/statusReporter.js';
21
22
  // UI helper methods for security components
22
23
  export function createBanner(title, subtitle) {
23
24
  const lines = [];
@@ -208,8 +209,7 @@ export class UnifiedUIRenderer extends EventEmitter {
208
209
  suggestionIndex = -1;
209
210
  availableCommands = [];
210
211
  hotkeysInToggleLine = new Set();
211
- // Paste region tracking for condensed display of large pastes
212
- pasteRegions = [];
212
+ // Note: collapsed paste functionality removed - pasted content is displayed directly
213
213
  mode = 'idle';
214
214
  /** Queue for input received during streaming - processed after streaming ends */
215
215
  streamingInputQueue = [];
@@ -356,125 +356,6 @@ export class UnifiedUIRenderer extends EventEmitter {
356
356
  sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '');
357
357
  return sanitized;
358
358
  }
359
- /** Detect likely programming language from pasted content */
360
- detectPasteLanguage(text) {
361
- const patterns = {
362
- javascript: /\b(const|let|var|function|async|await|import|export|=>)\b/,
363
- typescript: /\b(interface|type|enum|namespace|declare|as const)\b/,
364
- python: /\b(def|class|import|from|if __name__|async def)\b/,
365
- json: /^\s*(?:\{|\[)[\s\S]*(?:\}|\])\s*$/,
366
- html: /^<[a-z]/i,
367
- css: /^\s*[.#[]?[\w-]+\s*\{/,
368
- sql: /\b(SELECT|FROM|WHERE|INSERT|UPDATE|DELETE|CREATE|TABLE)\b/i,
369
- bash: /^#!/,
370
- yaml: /^[\w-]+:\s*\S/m,
371
- };
372
- for (const [lang, pattern] of Object.entries(patterns)) {
373
- if (pattern.test(text)) {
374
- return lang;
375
- }
376
- }
377
- return undefined;
378
- }
379
- /** Minimum chars/lines to trigger condensed display */
380
- pasteCondenseMinChars = 200;
381
- pasteCondenseMinLines = 4;
382
- /** Record a paste region for condensed display */
383
- recordPasteRegion(insertPos, content) {
384
- const lines = content.split('\n');
385
- const nonEmptyLines = lines.filter(line => line.trim().length > 0);
386
- const charCount = content.length;
387
- const lineCount = nonEmptyLines.length;
388
- // Only condense if paste is large enough
389
- if (charCount < this.pasteCondenseMinChars && lineCount < this.pasteCondenseMinLines) {
390
- return;
391
- }
392
- // Create preview: first line truncated to 50 chars
393
- let preview = nonEmptyLines[0]?.substring(0, 50) || '';
394
- if ((nonEmptyLines[0]?.length ?? 0) > 50) {
395
- preview += '…';
396
- }
397
- const language = this.detectPasteLanguage(content);
398
- // Shift existing regions that come after the insertion point
399
- for (const region of this.pasteRegions) {
400
- if (region.start >= insertPos) {
401
- region.start += content.length;
402
- region.end += content.length;
403
- }
404
- else if (region.end > insertPos) {
405
- // Insertion in the middle of a region - extend it
406
- region.end += content.length;
407
- }
408
- }
409
- // Add new region
410
- this.pasteRegions.push({
411
- start: insertPos,
412
- end: insertPos + content.length,
413
- lineCount,
414
- charCount,
415
- preview,
416
- language,
417
- });
418
- }
419
- /** Clear paste regions (called when buffer is cleared) */
420
- clearPasteRegions() {
421
- this.pasteRegions = [];
422
- }
423
- /** Adjust paste regions when characters are deleted from the buffer */
424
- adjustPasteRegionsForDeletion(deleteStart, deleteEnd) {
425
- const deleteLen = deleteEnd - deleteStart;
426
- if (deleteLen <= 0)
427
- return;
428
- // Filter out regions that are fully deleted, and adjust others
429
- this.pasteRegions = this.pasteRegions.filter(region => {
430
- // Region is fully before deletion - no change
431
- if (region.end <= deleteStart) {
432
- return true;
433
- }
434
- // Region is fully after deletion - shift left
435
- if (region.start >= deleteEnd) {
436
- region.start -= deleteLen;
437
- region.end -= deleteLen;
438
- return true;
439
- }
440
- // Region is fully within deletion - remove it
441
- if (region.start >= deleteStart && region.end <= deleteEnd) {
442
- return false;
443
- }
444
- // Partial overlap - adjust bounds
445
- if (region.start < deleteStart && region.end > deleteEnd) {
446
- // Deletion is inside region - shrink region
447
- region.end -= deleteLen;
448
- return true;
449
- }
450
- if (region.start < deleteStart) {
451
- // Deletion overlaps end of region
452
- region.end = deleteStart;
453
- return region.end > region.start;
454
- }
455
- // Deletion overlaps start of region
456
- region.start = deleteStart;
457
- region.end -= (deleteEnd - region.start);
458
- return region.end > region.start;
459
- });
460
- }
461
- /** Adjust paste regions when characters are inserted (not paste - regular typing) */
462
- adjustPasteRegionsForInsertion(insertPos, insertLen) {
463
- if (insertLen <= 0)
464
- return;
465
- for (const region of this.pasteRegions) {
466
- if (region.start >= insertPos) {
467
- // Region is after insertion point - shift right
468
- region.start += insertLen;
469
- region.end += insertLen;
470
- }
471
- else if (region.end > insertPos) {
472
- // Insertion is inside region - extend the region
473
- region.end += insertLen;
474
- }
475
- // Regions before insertion point are unaffected
476
- }
477
- }
478
359
  formatHotkey(combo) {
479
360
  if (!combo?.trim())
480
361
  return null;
@@ -554,7 +435,7 @@ export class UnifiedUIRenderer extends EventEmitter {
554
435
  // Set up error handling for EventEmitter
555
436
  this.on('error', (err) => {
556
437
  // Log errors but don't crash
557
- console.error('[UnifiedUIRenderer] Error:', err);
438
+ reportStatusError(err, '[UnifiedUIRenderer] Error');
558
439
  });
559
440
  this.output = output;
560
441
  this.input = input;
@@ -584,7 +465,7 @@ export class UnifiedUIRenderer extends EventEmitter {
584
465
  }
585
466
  catch (err) {
586
467
  // If readline creation fails, create a minimal fallback
587
- console.error('[UnifiedUIRenderer] Failed to create readline interface:', err);
468
+ reportStatusError(err, '[UnifiedUIRenderer] Failed to create readline interface');
588
469
  this.rl = readline.createInterface({
589
470
  input: process.stdin,
590
471
  output: process.stdout,
@@ -637,7 +518,6 @@ export class UnifiedUIRenderer extends EventEmitter {
637
518
  this.buffer = '';
638
519
  this.cursor = 0;
639
520
  this.inputRenderOffset = 0;
640
- this.clearPasteRegions();
641
521
  this.pasteBuffer = '';
642
522
  this.pasteBufferOverflow = false;
643
523
  this.inPlainPaste = false;
@@ -1091,19 +971,19 @@ export class UnifiedUIRenderer extends EventEmitter {
1091
971
  }
1092
972
  switch (letter) {
1093
973
  case 'a':
1094
- this.emit('toggle-critical-approval');
974
+ this.safeEmit('toggle-critical-approval');
1095
975
  forceRender();
1096
976
  break;
1097
977
  case 'g':
1098
- this.emit('toggle-auto-continue');
978
+ this.safeEmit('toggle-auto-continue');
1099
979
  forceRender();
1100
980
  break;
1101
981
  case 'd':
1102
- this.emit('toggle-alphazero');
982
+ this.safeEmit('toggle-alphazero');
1103
983
  forceRender();
1104
984
  break;
1105
985
  case 't':
1106
- this.emit('toggle-thinking');
986
+ this.safeEmit('toggle-thinking');
1107
987
  forceRender();
1108
988
  break;
1109
989
  case 'v':
@@ -1133,13 +1013,12 @@ export class UnifiedUIRenderer extends EventEmitter {
1133
1013
  this.buffer = '';
1134
1014
  this.cursor = 0;
1135
1015
  this.inputRenderOffset = 0;
1136
- this.clearPasteRegions();
1137
1016
  this.resetSuggestions();
1138
1017
  this.renderPrompt();
1139
1018
  this.emitInputChange();
1140
1019
  }
1141
1020
  // Emit ctrlc event with buffer state so shell can handle appropriately
1142
- this.emit('ctrlc', { hadBuffer });
1021
+ this.safeEmit('ctrlc', { hadBuffer });
1143
1022
  }
1144
1023
  /**
1145
1024
  * Handle Ctrl+D detected at raw data level.
@@ -1157,7 +1036,7 @@ export class UnifiedUIRenderer extends EventEmitter {
1157
1036
  }
1158
1037
  // Ctrl+D: interrupt if buffer is empty
1159
1038
  if (this.buffer.length === 0) {
1160
- this.emit('interrupt');
1039
+ this.safeEmit('interrupt');
1161
1040
  }
1162
1041
  }
1163
1042
  /**
@@ -1189,12 +1068,9 @@ export class UnifiedUIRenderer extends EventEmitter {
1189
1068
  // Insert paste content directly into buffer (supports multi-line)
1190
1069
  // Insert at cursor position
1191
1070
  this.clampCursor();
1192
- const insertPos = this.cursor;
1193
1071
  this.buffer = this.buffer.slice(0, this.cursor) + sanitized + this.buffer.slice(this.cursor);
1194
1072
  this.cursor += sanitized.length;
1195
1073
  this.clampCursor();
1196
- // Record paste region for condensed display
1197
- this.recordPasteRegion(insertPos, sanitized);
1198
1074
  this.updateSuggestions();
1199
1075
  this.resetPlainPasteBurst();
1200
1076
  this.forceNextRender = true;
@@ -1225,12 +1101,9 @@ export class UnifiedUIRenderer extends EventEmitter {
1225
1101
  // Insert paste content directly into buffer (supports multi-line)
1226
1102
  // Insert at cursor position
1227
1103
  this.clampCursor();
1228
- const insertPos = this.cursor;
1229
1104
  this.buffer = this.buffer.slice(0, this.cursor) + sanitized + this.buffer.slice(this.cursor);
1230
1105
  this.cursor += sanitized.length;
1231
1106
  this.clampCursor();
1232
- // Record paste region for condensed display
1233
- this.recordPasteRegion(insertPos, sanitized);
1234
1107
  this.updateSuggestions();
1235
1108
  this.resetPlainPasteBurst();
1236
1109
  this.forceNextRender = true;
@@ -1242,7 +1115,7 @@ export class UnifiedUIRenderer extends EventEmitter {
1242
1115
  text: this.buffer,
1243
1116
  cursor: this.cursor,
1244
1117
  };
1245
- this.emit('change', payload);
1118
+ this.safeEmit('change', payload);
1246
1119
  }
1247
1120
  handleKeypress(str, key) {
1248
1121
  // Skip processing if a raw-level toggle was already handled for this keypress
@@ -1303,19 +1176,18 @@ export class UnifiedUIRenderer extends EventEmitter {
1303
1176
  this.buffer = '';
1304
1177
  this.cursor = 0;
1305
1178
  this.inputRenderOffset = 0;
1306
- this.clearPasteRegions();
1307
1179
  this.resetSuggestions();
1308
1180
  this.renderPrompt();
1309
1181
  this.emitInputChange();
1310
1182
  }
1311
1183
  // Emit ctrlc event with buffer state so shell can handle appropriately
1312
- this.emit('ctrlc', { hadBuffer });
1184
+ this.safeEmit('ctrlc', { hadBuffer });
1313
1185
  return;
1314
1186
  }
1315
1187
  if (normalizedKey.name === 'd') {
1316
1188
  // Ctrl+D: interrupt if buffer is empty
1317
1189
  if (this.buffer.length === 0) {
1318
- this.emit('interrupt');
1190
+ this.safeEmit('interrupt');
1319
1191
  }
1320
1192
  return;
1321
1193
  }
@@ -1392,16 +1264,16 @@ export class UnifiedUIRenderer extends EventEmitter {
1392
1264
  // Ensure no buffered chars leak into the prompt
1393
1265
  this.pendingInsertBuffer = '';
1394
1266
  if (letter === 'a') {
1395
- this.emit('toggle-critical-approval');
1267
+ this.safeEmit('toggle-critical-approval');
1396
1268
  }
1397
1269
  else if (letter === 'g') {
1398
- this.emit('toggle-auto-continue');
1270
+ this.safeEmit('toggle-auto-continue');
1399
1271
  }
1400
1272
  else if (letter === 'd') {
1401
- this.emit('toggle-alphazero');
1273
+ this.safeEmit('toggle-alphazero');
1402
1274
  }
1403
1275
  else if (letter === 't') {
1404
- this.emit('toggle-thinking');
1276
+ this.safeEmit('toggle-thinking');
1405
1277
  }
1406
1278
  // Force immediate render during streaming to ensure toggle state is visible
1407
1279
  if (this.mode === 'streaming') {
@@ -1481,11 +1353,8 @@ export class UnifiedUIRenderer extends EventEmitter {
1481
1353
  // Skip word characters
1482
1354
  while (pos > 0 && this.buffer[pos - 1] !== ' ')
1483
1355
  pos--;
1484
- const deleteStart = pos;
1485
- const deleteEnd = this.cursor;
1486
- this.buffer = this.buffer.slice(0, deleteStart) + this.buffer.slice(deleteEnd);
1356
+ this.buffer = this.buffer.slice(0, pos) + this.buffer.slice(this.cursor);
1487
1357
  this.cursor = pos;
1488
- this.adjustPasteRegionsForDeletion(deleteStart, deleteEnd);
1489
1358
  this.updateSuggestions();
1490
1359
  this.renderPrompt();
1491
1360
  this.emitInputChange();
@@ -1495,10 +1364,7 @@ export class UnifiedUIRenderer extends EventEmitter {
1495
1364
  // Ctrl+K: Delete from cursor to end of line
1496
1365
  if (key.ctrl && key.name === 'k') {
1497
1366
  if (this.cursor < this.buffer.length) {
1498
- const deleteStart = this.cursor;
1499
- const deleteEnd = this.buffer.length;
1500
1367
  this.buffer = this.buffer.slice(0, this.cursor);
1501
- this.adjustPasteRegionsForDeletion(deleteStart, deleteEnd);
1502
1368
  this.updateSuggestions();
1503
1369
  this.renderPrompt();
1504
1370
  this.emitInputChange();
@@ -1567,11 +1433,8 @@ export class UnifiedUIRenderer extends EventEmitter {
1567
1433
  pos--;
1568
1434
  while (pos > 0 && this.buffer[pos - 1] !== ' ')
1569
1435
  pos--;
1570
- const deleteStart = pos;
1571
- const deleteEnd = this.cursor;
1572
- this.buffer = this.buffer.slice(0, deleteStart) + this.buffer.slice(deleteEnd);
1436
+ this.buffer = this.buffer.slice(0, pos) + this.buffer.slice(this.cursor);
1573
1437
  this.cursor = pos;
1574
- this.adjustPasteRegionsForDeletion(deleteStart, deleteEnd);
1575
1438
  this.updateSuggestions();
1576
1439
  this.renderPrompt();
1577
1440
  this.emitInputChange();
@@ -1610,11 +1473,8 @@ export class UnifiedUIRenderer extends EventEmitter {
1610
1473
  this.resetPlainPasteBurst();
1611
1474
  this.cancelPlainPasteCapture();
1612
1475
  if (this.cursor > 0) {
1613
- const deleteStart = this.cursor - 1;
1614
- const deleteEnd = this.cursor;
1615
- this.buffer = this.buffer.slice(0, deleteStart) + this.buffer.slice(deleteEnd);
1476
+ this.buffer = this.buffer.slice(0, this.cursor - 1) + this.buffer.slice(this.cursor);
1616
1477
  this.cursor--;
1617
- this.adjustPasteRegionsForDeletion(deleteStart, deleteEnd);
1618
1478
  this.updateSuggestions();
1619
1479
  this.forceNextRender = true;
1620
1480
  this.renderPrompt();
@@ -1627,10 +1487,7 @@ export class UnifiedUIRenderer extends EventEmitter {
1627
1487
  this.resetPlainPasteBurst();
1628
1488
  this.cancelPlainPasteCapture();
1629
1489
  if (this.cursor < this.buffer.length) {
1630
- const deleteStart = this.cursor;
1631
- const deleteEnd = this.cursor + 1;
1632
- this.buffer = this.buffer.slice(0, deleteStart) + this.buffer.slice(deleteEnd);
1633
- this.adjustPasteRegionsForDeletion(deleteStart, deleteEnd);
1490
+ this.buffer = this.buffer.slice(0, this.cursor) + this.buffer.slice(this.cursor + 1);
1634
1491
  this.updateSuggestions();
1635
1492
  this.forceNextRender = true;
1636
1493
  this.renderPrompt();
@@ -1707,7 +1564,6 @@ export class UnifiedUIRenderer extends EventEmitter {
1707
1564
  }
1708
1565
  this.cursor = this.buffer.length;
1709
1566
  this.inputRenderOffset = 0; // Reset render offset for new buffer
1710
- this.clearPasteRegions(); // History entries don't have paste regions
1711
1567
  this.updateSuggestions();
1712
1568
  this.renderPrompt();
1713
1569
  this.emitInputChange();
@@ -1734,7 +1590,6 @@ export class UnifiedUIRenderer extends EventEmitter {
1734
1590
  }
1735
1591
  this.cursor = this.buffer.length;
1736
1592
  this.inputRenderOffset = 0; // Reset render offset for new buffer
1737
- this.clearPasteRegions(); // History entries don't have paste regions
1738
1593
  this.updateSuggestions();
1739
1594
  this.renderPrompt();
1740
1595
  this.emitInputChange();
@@ -1974,12 +1829,9 @@ export class UnifiedUIRenderer extends EventEmitter {
1974
1829
  // Insert paste content directly into buffer (supports multi-line)
1975
1830
  // Insert at cursor position
1976
1831
  this.clampCursor();
1977
- const insertPos = this.cursor;
1978
1832
  this.buffer = this.buffer.slice(0, this.cursor) + content + this.buffer.slice(this.cursor);
1979
1833
  this.cursor += content.length;
1980
1834
  this.clampCursor();
1981
- // Record paste region for condensed display
1982
- this.recordPasteRegion(insertPos, content);
1983
1835
  this.updateSuggestions();
1984
1836
  this.forceNextRender = true;
1985
1837
  this.renderPrompt();
@@ -2156,12 +2008,9 @@ export class UnifiedUIRenderer extends EventEmitter {
2156
2008
  // Insert paste content directly into buffer (supports multi-line)
2157
2009
  // Insert at cursor position
2158
2010
  this.clampCursor();
2159
- const insertPos = this.cursor;
2160
2011
  this.buffer = this.buffer.slice(0, this.cursor) + content + this.buffer.slice(this.cursor);
2161
2012
  this.cursor += content.length;
2162
2013
  this.clampCursor();
2163
- // Record paste region for condensed display
2164
- this.recordPasteRegion(insertPos, content);
2165
2014
  this.updateSuggestions();
2166
2015
  this.forceNextRender = true;
2167
2016
  this.renderPrompt();
@@ -2225,12 +2074,9 @@ export class UnifiedUIRenderer extends EventEmitter {
2225
2074
  return;
2226
2075
  // Ensure cursor is valid before slicing
2227
2076
  this.clampCursor();
2228
- const insertPos = this.cursor;
2229
2077
  this.buffer = this.buffer.slice(0, this.cursor) + text + this.buffer.slice(this.cursor);
2230
2078
  this.cursor += text.length;
2231
2079
  this.clampCursor(); // Ensure cursor remains valid after modification
2232
- // Adjust paste regions for the insertion
2233
- this.adjustPasteRegionsForInsertion(insertPos, text.length);
2234
2080
  this.updateSuggestions();
2235
2081
  // Suppress render during paste detection to prevent visual leak
2236
2082
  const inEmitPaste = this.emitPasteBuffer.length > 0 || this.emitPasteTimer !== null;
@@ -2258,7 +2104,6 @@ export class UnifiedUIRenderer extends EventEmitter {
2258
2104
  this.buffer = '';
2259
2105
  this.cursor = 0;
2260
2106
  this.inputRenderOffset = 0;
2261
- this.clearPasteRegions();
2262
2107
  this.resetSuggestions();
2263
2108
  this.renderPrompt();
2264
2109
  this.emitInputChange();
@@ -2277,7 +2122,6 @@ export class UnifiedUIRenderer extends EventEmitter {
2277
2122
  // Prompt blocked - don't submit
2278
2123
  this.buffer = '';
2279
2124
  this.cursor = 0;
2280
- this.clearPasteRegions();
2281
2125
  this.renderPrompt();
2282
2126
  return;
2283
2127
  }
@@ -2295,14 +2139,13 @@ export class UnifiedUIRenderer extends EventEmitter {
2295
2139
  this.historyIndex = -1;
2296
2140
  }
2297
2141
  if (this.mode === 'streaming') {
2298
- this.emit('queue', normalized);
2142
+ this.safeEmit('queue', normalized);
2299
2143
  }
2300
2144
  else {
2301
- this.emit('submit', normalized);
2145
+ this.safeEmit('submit', normalized);
2302
2146
  }
2303
2147
  this.buffer = '';
2304
2148
  this.cursor = 0;
2305
- this.clearPasteRegions();
2306
2149
  this.resetSuggestions();
2307
2150
  this.renderPrompt();
2308
2151
  this.emitInputChange();
@@ -2398,6 +2241,10 @@ export class UnifiedUIRenderer extends EventEmitter {
2398
2241
  addEvent(type, content) {
2399
2242
  if (!content)
2400
2243
  return;
2244
+ if (type === 'error') {
2245
+ reportStatus(content);
2246
+ return;
2247
+ }
2401
2248
  const normalized = this.normalizeEventType(type);
2402
2249
  if (!normalized)
2403
2250
  return;
@@ -2435,16 +2282,21 @@ export class UnifiedUIRenderer extends EventEmitter {
2435
2282
  this.lastToolResult = content;
2436
2283
  }
2437
2284
  if (this.plainMode) {
2438
- const formatted = this.formatContent({
2439
- type: normalized,
2440
- rawType: type,
2441
- content,
2442
- timestamp: Date.now(),
2443
- });
2444
- if (formatted) {
2445
- const text = formatted.endsWith('\n') ? formatted : `${formatted}\n`;
2446
- this.output.write(text);
2447
- this.lastOutputEndedWithNewline = text.endsWith('\n');
2285
+ try {
2286
+ const formatted = this.formatContent({
2287
+ type: normalized,
2288
+ rawType: type,
2289
+ content,
2290
+ timestamp: Date.now(),
2291
+ });
2292
+ if (formatted) {
2293
+ const text = formatted.endsWith('\n') ? formatted : `${formatted}\n`;
2294
+ this.safeWrite(text);
2295
+ this.lastOutputEndedWithNewline = text.endsWith('\n');
2296
+ }
2297
+ }
2298
+ catch {
2299
+ // Non-blocking: format errors shouldn't crash the UI
2448
2300
  }
2449
2301
  return;
2450
2302
  }
@@ -4489,6 +4341,15 @@ export class UnifiedUIRenderer extends EventEmitter {
4489
4341
  // Don't render if disposed or still initializing (before banner shown)
4490
4342
  if (this.disposed || this.initializing)
4491
4343
  return;
4344
+ try {
4345
+ this.renderPromptImmediateInternal();
4346
+ }
4347
+ catch (error) {
4348
+ // Non-blocking: log but don't crash on render errors
4349
+ reportStatusError(error, '[UI] Render error');
4350
+ }
4351
+ }
4352
+ renderPromptImmediateInternal() {
4492
4353
  // Performance optimization: Skip render if nothing changed (during idle mode only)
4493
4354
  // During streaming mode, we need to update animations and status
4494
4355
  // Also check if terminal width changed to handle resize properly
@@ -5166,11 +5027,9 @@ export class UnifiedUIRenderer extends EventEmitter {
5166
5027
  const maxWidth = this.safeWidth();
5167
5028
  const continuationIndent = ' '; // 2 spaces for continuation lines
5168
5029
  const continuationWidth = continuationIndent.length;
5169
- // Build display buffer with paste regions condensed
5170
- const { displayBuffer, cursorDisplayPos } = this.buildCondensedDisplayBuffer();
5171
5030
  // Handle multi-line input - split buffer on newlines first
5172
5031
  // In secret mode, mask all characters with bullets
5173
- const rawBuffer = displayBuffer.replace(/\r/g, '\n');
5032
+ const rawBuffer = this.buffer.replace(/\r/g, '\n');
5174
5033
  const normalized = this.secretMode ? '•'.repeat(rawBuffer.length) : rawBuffer;
5175
5034
  const bufferLines = normalized.split('\n');
5176
5035
  // Wrap each logical line to fit terminal width, expanding vertically
@@ -5201,13 +5060,13 @@ export class UnifiedUIRenderer extends EventEmitter {
5201
5060
  else {
5202
5061
  displayLine = `${continuationIndent}${chunk}`;
5203
5062
  }
5204
- // Track cursor position (using mapped display position)
5063
+ // Track cursor position
5205
5064
  if (!foundCursor) {
5206
5065
  const chunkStart = lineStartChar + (line.length - remaining.length - chunk.length);
5207
5066
  const chunkEnd = chunkStart + chunk.length;
5208
- if (cursorDisplayPos >= chunkStart && cursorDisplayPos <= chunkEnd) {
5067
+ if (this.cursor >= chunkStart && this.cursor <= chunkEnd) {
5209
5068
  cursorLine = result.length;
5210
- const offsetInChunk = cursorDisplayPos - chunkStart;
5069
+ const offsetInChunk = this.cursor - chunkStart;
5211
5070
  cursorCol = ((isFirstLogicalLine && isFirstDisplayLine) ? promptWidth : continuationWidth) + offsetInChunk;
5212
5071
  foundCursor = true;
5213
5072
  }
@@ -5244,50 +5103,6 @@ export class UnifiedUIRenderer extends EventEmitter {
5244
5103
  this.cursorVisibleColumn = cursorCol + 1;
5245
5104
  return result.join('\n');
5246
5105
  }
5247
- /** Build a display buffer with paste regions condensed to summaries */
5248
- buildCondensedDisplayBuffer() {
5249
- // If no paste regions, return original buffer
5250
- if (this.pasteRegions.length === 0) {
5251
- return { displayBuffer: this.buffer, cursorDisplayPos: this.cursor };
5252
- }
5253
- // Sort regions by start position
5254
- const sortedRegions = [...this.pasteRegions].sort((a, b) => a.start - b.start);
5255
- let displayBuffer = '';
5256
- let bufferPos = 0;
5257
- let cursorDisplayPos = this.cursor;
5258
- let displayOffset = 0; // Track cumulative offset between buffer and display positions
5259
- for (const region of sortedRegions) {
5260
- // Add text before this region
5261
- if (region.start > bufferPos) {
5262
- displayBuffer += this.buffer.slice(bufferPos, region.start);
5263
- }
5264
- // Build condensed summary for this paste region
5265
- const langLabel = region.language ? `${region.language.toUpperCase()} ` : '';
5266
- const summary = `[📋 ${langLabel}${region.lineCount} lines · ${region.charCount} chars: ${region.preview}]`;
5267
- // Update cursor position if it's within or after this region
5268
- if (this.cursor >= region.start && this.cursor < region.end) {
5269
- // Cursor is inside paste region - position at start of summary
5270
- cursorDisplayPos = displayBuffer.length;
5271
- }
5272
- else if (this.cursor >= region.end) {
5273
- // Cursor is after this region - adjust by the difference
5274
- const regionLength = region.end - region.start;
5275
- const summaryLength = summary.length;
5276
- displayOffset += regionLength - summaryLength;
5277
- }
5278
- displayBuffer += summary;
5279
- bufferPos = region.end;
5280
- }
5281
- // Add remaining text after last region
5282
- if (bufferPos < this.buffer.length) {
5283
- displayBuffer += this.buffer.slice(bufferPos);
5284
- }
5285
- // Adjust cursor position for regions that came before it
5286
- if (this.cursor >= bufferPos || displayOffset > 0) {
5287
- cursorDisplayPos = Math.max(0, this.cursor - displayOffset);
5288
- }
5289
- return { displayBuffer, cursorDisplayPos };
5290
- }
5291
5106
  buildInputWindow(available) {
5292
5107
  if (available <= 0) {
5293
5108
  return { text: '', cursor: 0 };
@@ -5340,7 +5155,6 @@ export class UnifiedUIRenderer extends EventEmitter {
5340
5155
  this.buffer = '';
5341
5156
  this.cursor = 0;
5342
5157
  this.inputRenderOffset = 0;
5343
- this.clearPasteRegions();
5344
5158
  this.resetSuggestions();
5345
5159
  this.renderPrompt();
5346
5160
  this.emitInputChange();
@@ -5454,7 +5268,6 @@ export class UnifiedUIRenderer extends EventEmitter {
5454
5268
  // Validate cursor position to prevent out-of-bounds issues
5455
5269
  const requestedCursor = cursorPos ?? text.length;
5456
5270
  this.cursor = Math.max(0, Math.min(requestedCursor, text.length));
5457
- this.clearPasteRegions(); // External buffer replacement clears paste tracking
5458
5271
  this.inputRenderOffset = 0;
5459
5272
  this.updateSuggestions();
5460
5273
  this.renderPrompt();
@@ -5469,7 +5282,6 @@ export class UnifiedUIRenderer extends EventEmitter {
5469
5282
  this.buffer = '';
5470
5283
  this.cursor = 0;
5471
5284
  this.inputRenderOffset = 0;
5472
- this.clearPasteRegions();
5473
5285
  this.suggestions = [];
5474
5286
  this.suggestionIndex = -1;
5475
5287
  this.renderPrompt();
@@ -5538,6 +5350,37 @@ export class UnifiedUIRenderer extends EventEmitter {
5538
5350
  write(data) {
5539
5351
  this.output.write(data);
5540
5352
  }
5353
+ /**
5354
+ * Safe write that catches errors to prevent UI crashes.
5355
+ * Use this for non-critical writes where failure shouldn't block the UI.
5356
+ */
5357
+ safeWrite(data) {
5358
+ try {
5359
+ if (!this.disposed && this.output && !this.output.destroyed) {
5360
+ this.output.write(data);
5361
+ return true;
5362
+ }
5363
+ }
5364
+ catch {
5365
+ // Swallow write errors - output stream may be closed
5366
+ }
5367
+ return false;
5368
+ }
5369
+ /**
5370
+ * Safe emit that catches listener errors to prevent UI crashes.
5371
+ * Use this for events where listener failures shouldn't block input handling.
5372
+ */
5373
+ safeEmit(event, ...args) {
5374
+ try {
5375
+ this.emit(event, ...args);
5376
+ return true;
5377
+ }
5378
+ catch (error) {
5379
+ // Log but don't propagate listener errors
5380
+ reportStatusError(error, `[UI] Event listener error for '${event}'`);
5381
+ return false;
5382
+ }
5383
+ }
5541
5384
  pushPromptEvent(text) {
5542
5385
  const normalized = text?.trim();
5543
5386
  if (!normalized) {
@@ -5554,34 +5397,40 @@ export class UnifiedUIRenderer extends EventEmitter {
5554
5397
  const height = this.lastOverlay?.lines.length ?? this.promptHeight ?? 0;
5555
5398
  if (height === 0)
5556
5399
  return;
5557
- // Use batched atomic write to prevent visual glitches from non-atomic escape sequences
5558
- const buffer = [];
5559
- // Hide cursor during clearing to prevent visual artifacts
5560
- buffer.push('\x1b[?25l');
5561
- // Cursor is at prompt row. Move up to top of overlay first.
5562
- if (this.lastOverlay) {
5563
- const linesToTop = this.lastOverlay.promptIndex;
5564
- if (linesToTop > 0) {
5565
- buffer.push(`\x1b[${linesToTop}A`);
5400
+ try {
5401
+ // Use batched atomic write to prevent visual glitches from non-atomic escape sequences
5402
+ const buffer = [];
5403
+ // Hide cursor during clearing to prevent visual artifacts
5404
+ buffer.push('\x1b[?25l');
5405
+ // Cursor is at prompt row. Move up to top of overlay first.
5406
+ if (this.lastOverlay) {
5407
+ const linesToTop = this.lastOverlay.promptIndex;
5408
+ if (linesToTop > 0) {
5409
+ buffer.push(`\x1b[${linesToTop}A`);
5410
+ }
5566
5411
  }
5567
- }
5568
- // Now at top, clear each line downward
5569
- for (let i = 0; i < height; i++) {
5570
- buffer.push('\r');
5571
- buffer.push(ESC.CLEAR_LINE);
5572
- if (i < height - 1) {
5573
- buffer.push('\x1b[B');
5412
+ // Now at top, clear each line downward
5413
+ for (let i = 0; i < height; i++) {
5414
+ buffer.push('\r');
5415
+ buffer.push(ESC.CLEAR_LINE);
5416
+ if (i < height - 1) {
5417
+ buffer.push('\x1b[B');
5418
+ }
5419
+ }
5420
+ // Move back to top (where content should continue from)
5421
+ if (height > 1) {
5422
+ buffer.push(`\x1b[${height - 1}A`);
5574
5423
  }
5424
+ buffer.push('\r');
5425
+ // Show cursor again
5426
+ buffer.push('\x1b[?25h');
5427
+ // Single atomic write
5428
+ this.safeWrite(buffer.join(''));
5575
5429
  }
5576
- // Move back to top (where content should continue from)
5577
- if (height > 1) {
5578
- buffer.push(`\x1b[${height - 1}A`);
5430
+ catch {
5431
+ // Ensure cursor is shown even if clearing fails
5432
+ this.safeWrite('\x1b[?25h');
5579
5433
  }
5580
- buffer.push('\r');
5581
- // Show cursor again
5582
- buffer.push('\x1b[?25h');
5583
- // Single atomic write
5584
- this.write(buffer.join(''));
5585
5434
  this.lastOverlay = null;
5586
5435
  this.promptHeight = 0;
5587
5436
  this.lastOverlayHeight = 0;