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.
- package/dist/bin/deepseek.d.ts +0 -3
- package/dist/bin/deepseek.d.ts.map +1 -1
- package/dist/bin/deepseek.js +6 -6
- package/dist/bin/deepseek.js.map +1 -1
- package/dist/bin/lean.js +5 -4
- package/dist/bin/lean.js.map +1 -1
- package/dist/core/episodicMemory.d.ts.map +1 -1
- package/dist/core/episodicMemory.js +3 -2
- package/dist/core/episodicMemory.js.map +1 -1
- package/dist/core/flowProtection.d.ts.map +1 -1
- package/dist/core/flowProtection.js +2 -1
- package/dist/core/flowProtection.js.map +1 -1
- package/dist/core/guardrails.d.ts +0 -4
- package/dist/core/guardrails.d.ts.map +1 -1
- package/dist/core/guardrails.js +2 -1
- package/dist/core/guardrails.js.map +1 -1
- package/dist/core/inputProtection.d.ts.map +1 -1
- package/dist/core/inputProtection.js +2 -1
- package/dist/core/inputProtection.js.map +1 -1
- package/dist/core/revenueEnvValidator.d.ts.map +1 -1
- package/dist/core/revenueEnvValidator.js +8 -5
- package/dist/core/revenueEnvValidator.js.map +1 -1
- package/dist/core/shutdown.d.ts.map +1 -1
- package/dist/core/shutdown.js +3 -2
- package/dist/core/shutdown.js.map +1 -1
- package/dist/core/types/utilityTypes.d.ts +0 -9
- package/dist/core/types/utilityTypes.d.ts.map +1 -1
- package/dist/core/types/utilityTypes.js +2 -1
- package/dist/core/types/utilityTypes.js.map +1 -1
- package/dist/headless/interactiveShell.d.ts.map +1 -1
- package/dist/headless/interactiveShell.js +89 -43
- package/dist/headless/interactiveShell.js.map +1 -1
- package/dist/headless/quickMode.d.ts.map +1 -1
- package/dist/headless/quickMode.js +5 -4
- package/dist/headless/quickMode.js.map +1 -1
- package/dist/tools/emailTools.d.ts.map +1 -1
- package/dist/tools/emailTools.js +3 -2
- package/dist/tools/emailTools.js.map +1 -1
- package/dist/ui/PromptController.js +8 -2
- package/dist/ui/PromptController.js.map +1 -1
- package/dist/ui/UnifiedUIRenderer.d.ts +11 -16
- package/dist/ui/UnifiedUIRenderer.d.ts.map +1 -1
- package/dist/ui/UnifiedUIRenderer.js +116 -267
- package/dist/ui/UnifiedUIRenderer.js.map +1 -1
- package/dist/ui/overlay/OverlayManager.d.ts.map +1 -1
- package/dist/ui/overlay/OverlayManager.js +38 -25
- package/dist/ui/overlay/OverlayManager.js.map +1 -1
- package/dist/utils/statusReporter.d.ts +6 -0
- package/dist/utils/statusReporter.d.ts.map +1 -0
- package/dist/utils/statusReporter.js +26 -0
- package/dist/utils/statusReporter.js.map +1 -0
- package/dist/workspace.d.ts.map +1 -1
- package/dist/workspace.js +4 -3
- package/dist/workspace.js.map +1 -1
- 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
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
974
|
+
this.safeEmit('toggle-critical-approval');
|
|
1095
975
|
forceRender();
|
|
1096
976
|
break;
|
|
1097
977
|
case 'g':
|
|
1098
|
-
this.
|
|
978
|
+
this.safeEmit('toggle-auto-continue');
|
|
1099
979
|
forceRender();
|
|
1100
980
|
break;
|
|
1101
981
|
case 'd':
|
|
1102
|
-
this.
|
|
982
|
+
this.safeEmit('toggle-alphazero');
|
|
1103
983
|
forceRender();
|
|
1104
984
|
break;
|
|
1105
985
|
case 't':
|
|
1106
|
-
this.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
1267
|
+
this.safeEmit('toggle-critical-approval');
|
|
1396
1268
|
}
|
|
1397
1269
|
else if (letter === 'g') {
|
|
1398
|
-
this.
|
|
1270
|
+
this.safeEmit('toggle-auto-continue');
|
|
1399
1271
|
}
|
|
1400
1272
|
else if (letter === 'd') {
|
|
1401
|
-
this.
|
|
1273
|
+
this.safeEmit('toggle-alphazero');
|
|
1402
1274
|
}
|
|
1403
1275
|
else if (letter === 't') {
|
|
1404
|
-
this.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
2142
|
+
this.safeEmit('queue', normalized);
|
|
2299
2143
|
}
|
|
2300
2144
|
else {
|
|
2301
|
-
this.
|
|
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
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
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 =
|
|
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
|
|
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 (
|
|
5067
|
+
if (this.cursor >= chunkStart && this.cursor <= chunkEnd) {
|
|
5209
5068
|
cursorLine = result.length;
|
|
5210
|
-
const offsetInChunk =
|
|
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
|
-
|
|
5558
|
-
|
|
5559
|
-
|
|
5560
|
-
|
|
5561
|
-
|
|
5562
|
-
|
|
5563
|
-
|
|
5564
|
-
|
|
5565
|
-
|
|
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
|
-
|
|
5569
|
-
|
|
5570
|
-
|
|
5571
|
-
|
|
5572
|
-
|
|
5573
|
-
|
|
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
|
-
|
|
5577
|
-
|
|
5578
|
-
|
|
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;
|