@deepseekdev/coder 1.0.84 → 1.0.86

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.
@@ -2517,11 +2517,16 @@ export class UnifiedUIRenderer extends EventEmitter {
2517
2517
  if (event.rawType === 'banner' && this.initializing) {
2518
2518
  this.initializing = false;
2519
2519
  }
2520
- // Clear live terminal box when tool completes, results arrive, or errors occur
2521
- if (this.liveTerminalActive &&
2522
- (event.type === 'tool-result' || event.rawType === 'tool-result' ||
2523
- event.rawType === 'error')) {
2524
- this.clearLiveTerminal();
2520
+ // Finalize or clear live terminal box when tool completes
2521
+ if (this.liveTerminalActive) {
2522
+ if (event.type === 'tool-result' || event.rawType === 'tool-result') {
2523
+ // Tool completed - finalize terminal (persist in scrollback)
2524
+ this.finalizeLiveTerminal();
2525
+ }
2526
+ else if (event.rawType === 'error') {
2527
+ // Error occurred - clear terminal
2528
+ this.clearLiveTerminal();
2529
+ }
2525
2530
  }
2526
2531
  if (this.plainMode) {
2527
2532
  const formattedPlain = this.formatContent(event);
@@ -2696,7 +2701,91 @@ export class UnifiedUIRenderer extends EventEmitter {
2696
2701
  }
2697
2702
  }
2698
2703
  /**
2699
- * Clear the live terminal box from display and reset state.
2704
+ * Finalize the live terminal - render final state and persist in scrollback.
2705
+ * The terminal box stays visible after command completion.
2706
+ */
2707
+ finalizeLiveTerminal() {
2708
+ if (!this.liveTerminalActive)
2709
+ return;
2710
+ // Cancel any pending render
2711
+ if (this.liveTerminalRenderTimer) {
2712
+ clearTimeout(this.liveTerminalRenderTimer);
2713
+ this.liveTerminalRenderTimer = null;
2714
+ }
2715
+ // Clear prompt overlay first
2716
+ const hadOverlay = this.output.isTTY && this.interactive && (this.promptHeight > 0 || this.lastOverlay);
2717
+ if (hadOverlay) {
2718
+ this.clearPromptArea();
2719
+ }
2720
+ // Erase the current (animated) render
2721
+ if (this.liveTerminalHeight > 0 && this.output.isTTY && !this.disposed) {
2722
+ this.output.write(`\x1b[${this.liveTerminalHeight}A`);
2723
+ for (let i = 0; i < this.liveTerminalHeight; i++) {
2724
+ this.output.write('\x1b[2K\n');
2725
+ }
2726
+ this.output.write(`\x1b[${this.liveTerminalHeight}A`);
2727
+ }
2728
+ // Render final "completed" version
2729
+ this.renderFinalTerminal();
2730
+ // Reset state for next command
2731
+ this.liveTerminalLines = [];
2732
+ this.liveTerminalActive = false;
2733
+ this.liveTerminalHeight = 0;
2734
+ this.lastLiveTerminalRender = 0;
2735
+ this.liveTerminalCommand = '';
2736
+ }
2737
+ /**
2738
+ * Render the final (completed) terminal box that persists in scrollback.
2739
+ */
2740
+ renderFinalTerminal() {
2741
+ if (this.liveTerminalLines.length === 0)
2742
+ return;
2743
+ if (this.disposed || !this.output || this.output.destroyed)
2744
+ return;
2745
+ const cols = this.cols || 80;
2746
+ const visibleLines = this.liveTerminalLines.slice(-this.LIVE_TERMINAL_MAX_LINES);
2747
+ const boxWidth = Math.min(cols, 80);
2748
+ const innerWidth = boxWidth - 2;
2749
+ // Create header with checkmark (completed) indicator
2750
+ const lineCount = this.liveTerminalLines.length;
2751
+ const headerText = this.liveTerminalCommand
2752
+ ? `$ ${this.liveTerminalCommand.slice(0, innerWidth - 12)}`.slice(0, innerWidth - 6)
2753
+ : 'Output';
2754
+ const headerPadding = Math.max(0, innerWidth - headerText.length - 6);
2755
+ const lineCountText = ` [${lineCount}]`;
2756
+ // Completed indicator (checkmark) instead of running dot
2757
+ const completedIndicator = chalk.green('✓');
2758
+ const topBorder = chalk.dim('┌─') + completedIndicator + chalk.dim('─') +
2759
+ chalk.dim(headerText) +
2760
+ chalk.dim('─'.repeat(Math.max(0, headerPadding - lineCountText.length))) +
2761
+ chalk.dim(lineCountText) +
2762
+ chalk.dim('─┐');
2763
+ const bottomBorder = chalk.dim('└' + '─'.repeat(innerWidth) + '┘');
2764
+ const lines = [topBorder];
2765
+ for (const rawLine of visibleLines) {
2766
+ const stripped = rawLine.replace(/\x1b\[[0-9;]*m/g, '');
2767
+ const truncated = stripped.length > innerWidth - 2
2768
+ ? rawLine.slice(0, innerWidth - 5) + chalk.dim('...')
2769
+ : rawLine;
2770
+ const paddedLen = stripped.length > innerWidth - 2 ? innerWidth - 2 : stripped.length;
2771
+ const padding = ' '.repeat(Math.max(0, innerWidth - 2 - paddedLen));
2772
+ // Color stderr lines red
2773
+ const isStderr = rawLine.startsWith('stderr:');
2774
+ const displayLine = isStderr ? rawLine.replace('stderr: ', '') : truncated;
2775
+ const lineColor = isStderr ? chalk.red : chalk.dim;
2776
+ lines.push(chalk.dim('│') + ' ' + lineColor(displayLine) + padding + ' ' + chalk.dim('│'));
2777
+ }
2778
+ lines.push(bottomBorder);
2779
+ // Write to output (this persists in scrollback)
2780
+ this.output.write(lines.join('\n') + '\n');
2781
+ this.lastOutputEndedWithNewline = true;
2782
+ // Restore prompt overlay
2783
+ if (this.output.isTTY && this.interactive && !this.disposed) {
2784
+ this.renderPromptOverlay(true);
2785
+ }
2786
+ }
2787
+ /**
2788
+ * Clear the live terminal box from display and reset state (used for errors/cancellation).
2700
2789
  */
2701
2790
  clearLiveTerminal() {
2702
2791
  if (!this.liveTerminalActive)